PHP 怎么PWA PHP

wen PHP项目 3

本文目录导读:

PHP 怎么PWA PHP

  1. PWA核心组成部分
  2. 测试和部署

我将为您详细介绍如何将PHP网站改造成PWA(渐进式Web应用)。

PWA核心组成部分

创建Manifest文件

创建 manifest.json

{
  "name": "我的PHP应用",
  "short_name": "PHPApp",
  "description": "这是一个PHP开发的PWA应用",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#4A90D9",
  "orientation": "portrait",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

创建Service Worker

创建 sw.js

const CACHE_NAME = 'php-pwa-v1';
const urlsToCache = [
  '/',
  '/manifest.json',
  '/css/styles.css',
  '/js/main.js',
  '/offline.html'
];
// 安装Service Worker
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('缓存已打开');
        return cache.addAll(urlsToCache);
      })
      .then(() => self.skipWaiting())
  );
});
// 激活Service Worker
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== CACHE_NAME) {
            console.log('删除旧缓存', cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});
// 处理API请求缓存策略
const cacheStrategies = {
  // 缓存优先,用于静态资源
  cacheFirst: async (request) => {
    const cachedResponse = await caches.match(request);
    if (cachedResponse) {
      return cachedResponse;
    }
    try {
      const networkResponse = await fetch(request);
      if (networkResponse.status === 200) {
        const cache = await caches.open(CACHE_NAME);
        await cache.put(request, networkResponse.clone());
      }
      return networkResponse;
    } catch (error) {
      return caches.match('/offline.html');
    }
  },
  // 网络优先,用于API接口
  networkFirst: async (request) => {
    try {
      const networkResponse = await fetch(request);
      if (networkResponse.status === 200) {
        const cache = await caches.open(CACHE_NAME);
        await cache.put(request, networkResponse.clone());
      }
      return networkResponse;
    } catch (error) {
      const cachedResponse = await caches.match(request);
      return cachedResponse || caches.match('/offline.html');
    }
  },
  // 仅网络
  networkOnly: async (request) => {
    return fetch(request);
  },
  // 仅缓存
  cacheOnly: async (request) => {
    return caches.match(request);
  }
};
// 拦截请求
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  // 根据URL类型选择缓存策略
  if (event.request.url.includes('/api/')) {
    // API请求使用网络优先
    event.respondWith(cacheStrategies.networkFirst(event.request));
  } else if (event.request.mode === 'navigate') {
    // 页面导航使用网络优先
    event.respondWith(cacheStrategies.networkFirst(event.request));
  } else if (event.request.url.includes('.php')) {
    // PHP动态页面使用网络优先
    event.respondWith(cacheStrategies.networkFirst(event.request));
  } else {
    // 静态资源使用缓存优先
    event.respondWith(cacheStrategies.cacheFirst(event.request));
  }
});
// 处理推送通知
self.addEventListener('push', event => {
  const data = event.data.json();
  const options = {
    body: data.message,
    icon: '/icons/icon-192x192.png',
    badge: '/icons/icon-72x72.png',
    vibrate: [200, 100, 200],
    data: {
      url: data.url || '/'
    }
  };
  event.waitUntil(
    self.registration.showNotification(data.title, options)
  );
});
// 处理通知点击事件
self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(
    clients.matchAll({type: 'window'}).then(windowClients => {
      // 检查是否有已打开的窗口
      for (const client of windowClients) {
        if (client.url === event.notification.data.url && 'focus' in client) {
          return client.focus();
        }
      }
      // 如果没有,则打开新窗口
      if (clients.openWindow) {
        return clients.openWindow(event.notification.data.url);
      }
    })
  );
});
// 后台同步
self.addEventListener('sync', event => {
  if (event.tag === 'sync-data') {
    event.waitUntil(syncPendingData());
  }
});
async function syncPendingData() {
  // 在这里实现数据同步逻辑
  console.log('同步后台数据');
}

PHP主页面修改

修改您的PHP主页面,添加PWA支持:

<?php
session_start();
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- PWA配置 -->
    <link rel="manifest" href="/manifest.json">
    <meta name="theme-color" content="#4A90D9">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="PHP App">
    <!-- iOS相关 -->
    <link rel="apple-touch-icon" href="/icons/icon-192x192.png">
    PHP PWA应用</title>
    <!-- 添加favicon -->
    <link rel="icon" href="/icons/icon-96x96.png" type="image/png">
    <!-- 引入样式和脚本 -->
    <link rel="stylesheet" href="/css/styles.css">
    <script src="/js/main.js" defer></script>
</head>
<body>
    <header>
        <!-- 导航部分 -->
    </header>
    <main>
        <h1>欢迎使用PHP PWA</h1>
        <p>这是一个支持离线访问的PHP应用</p>
        <!-- 内容区域 -->
        <div id="content">
            <?php
            // PHP内容输出
            echo "<p>当前时间: " . date('Y-m-d H:i:s') . "</p>";
            ?>
        </div>
    </main>
    <footer>
        <?php // 页脚内容 ?>
    </footer>
    <script>
        // 注册Service Worker
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', function() {
                navigator.serviceWorker.register('/sw.js')
                    .then(function(registration) {
                        console.log('Service Worker注册成功:', registration.scope);
                    })
                    .catch(function(error) {
                        console.log('Service Worker注册失败:', error);
                    });
            });
        }
    </script>
</body>
</html>

PHP后端代码优化

创建一个专门的PHP文件处理API请求:

// api.php
<?php
header('Content-Type: application/json');
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
// 处理CORS
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    exit(0);
}
// API逻辑
$action = $_GET['action'] ?? '';
switch ($action) {
    case 'data':
        $data = ['status' => 'success', 'message' => 'API数据', 'time' => date('Y-m-d H:i:s')];
        echo json_encode($data);
        break;
    case 'user':
        // 处理用户请求
        $userData = [
            'id' => 1,
            'name' => '测试用户',
            'email' => 'user@example.com'
        ];
        echo json_encode($userData);
        break;
    default:
        http_response_code(404);
        echo json_encode(['status' => 'error', 'message' => '接口不存在']);
}

离线页面

创建 offline.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">离线页面</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
            background: #f5f5f5;
            color: #333;
        }
        .offline-icon {
            font-size: 100px;
            margin-bottom: 20px;
        }
        .retry-button {
            background: #4A90D9;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }
    </style>
</head>
<body>
    <div class="offline-icon">📡</div>
    <h1>您似乎处于离线状态</h1>
    <p>请检查您的网络连接</p>
    <button class="retry-button" onclick="window.location.reload()">重试</button>
</body>
</html>

推送通知功能

创建PHP推送通知结合Web Push:

// push_notification.php
<?php
require_once 'vendor/autoload.php'; // 假设使用web-push-php库
use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
$auth = [
    'VAPID' => [
        'subject' => 'mailto:your-email@example.com',
        'publicKey' => 'your-public-key',
        'privateKey' => 'your-private-key'
    ]
];
$webPush = new WebPush($auth);
// 发送推送通知
function sendPushNotification($endpoint, $title, $message, $url) {
    global $webPush;
    $subscription = Subscription::create([
        'endpoint' => $endpoint,
        'keys' => [
            'p256dh' => 'user-p256dh-key',
            'auth' => 'user-auth-key'
        ]
    ]);
    $payload = json_encode([
        'title' => $title,
        'message' => $message,
        'url' => $url
    ]);
    $webPush->queueNotification($subscription, $payload);
    foreach ($webPush->flush() as $report) {
        if (!$report->isSuccess()) {
            // 处理失败
            error_log("推送通知失败: " . $report->getReason());
        }
    }
}
// 接收订阅信息
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['subscribe'])) {
    $subscriptionData = json_decode($_POST['subscription'], true);
    // 保存订阅信息到数据库
    saveSubscriptionToDatabase($subscriptionData);
}
function saveSubscriptionToDatabase($data) {
    // 实现数据库保存逻辑
}
// 示例:发送测试通知
if (isset($_GET['test'])) {
    sendPushNotification(
        'user-endpoint',
        '测试通知',
        '这是一条测试推送通知',
        '/'
    );
}

前端推送通知支持

前端JavaScript处理推送:

// 处理推送通知
async function setupPushNotifications() {
    // 检查浏览器是否支持
    if (!('Notification' in window)) {
        console.log('此浏览器不支持通知');
        return;
    }
    // 检查是否支持Service Worker
    if (!('serviceWorker' in navigator)) {
        return;
    }
    // 请求权限
    const permission = await Notification.requestPermission();
    if (permission === 'granted') {
        const registration = await navigator.serviceWorker.ready;
        // 获取订阅
        const subscription = await registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: urlBase64ToUint8Array('your-vapid-public-key')
        });
        // 发送到服务器
        fetch('/push_notification.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: `subscribe=1&subscription=${JSON.stringify(subscription)}`
        });
    }
}
function urlBase64ToUint8Array(base64String) {
    const padding = '='.repeat((4 - base64String.length % 4) % 4);
    const base64 = (base64String + padding)
        .replace(/\-/g, '+')
        .replace(/_/g, '/');
    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);
    for (let i = 0; i < rawData.length; ++i) {
        outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
}
// 页面加载时设置
document.addEventListener('DOMContentLoaded', function() {
    setupPushNotifications();
});

测试和部署

测试清单

  1. 本地测试:使用HTTPS或在localhost测试
  2. 检查Lighthouse:使用Chrome DevTools的Lighthouse工具
  3. 响应式测试:确保在不同设备上正常运行

性能优化建议

// 在PHP中添加缓存头
<?php
// 静态资源缓存
header('Cache-Control: public, max-age=31536000');
// 动态页面控制缓存
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
?>
```
### 安全注意事项
- 始终使用HTTPS
- 定期更新Service Worker
- 处理用户数据的隐私问题
- 合理管理缓存清理策略
这样,您就完成了一个完整的PHP PWA应用,PWA的核心是提供app-like的用户体验,同时保持Web的开放性和可访问性。

抱歉,评论功能暂时关闭!