本文目录导读:

我将为您详细介绍如何将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();
});
测试和部署
测试清单
- 本地测试:使用HTTPS或在localhost测试
- 检查Lighthouse:使用Chrome DevTools的Lighthouse工具
- 响应式测试:确保在不同设备上正常运行
性能优化建议
// 在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的开放性和可访问性。