本文目录导读:

- 方案一:Service Worker + Cache API(推荐,PWA方案)
- 方案二:本地存储(localStorage)+ PHP动态生成
- 方案三:IndexedDB + PHP API
- 方案四:PHP生成静态缓存文件
- 最佳实践建议
- 注意事项
在PHP项目中实现离线缓存,通常需要结合 Service Worker(PWA技术) 和 本地存储 来实现,PHP本身是服务端语言,所以离线缓存的关键在于前端。
以下是几种主流的实现方案,按推荐程度排序:
Service Worker + Cache API(推荐,PWA方案)
这是目前最成熟、功能最强大的离线缓存方案,适用于PHP构建的Web应用。
实现步骤:
在PHP页面中注册Service Worker
<!-- index.php -->
<!DOCTYPE html>
<html>
<head>离线缓存示例</title>
</head>
<body>
<h1>欢迎访问我们的网站</h1>
<p><?php echo date('Y-m-d H:i:s'); ?></p>
<script>
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('Service Worker 注册成功');
})
.catch(function(error) {
console.log('Service Worker 注册失败: ', error);
});
}
</script>
</body>
</html>
创建 Service Worker 文件(sw.js)
// sw.js - 必须放在网站根目录或指定路径
const CACHE_NAME = 'my-cache-v1';
// 需要缓存的资源列表
const urlsToCache = [
'/',
'/index.php',
'/css/main.css',
'/js/main.js',
'/images/logo.png',
'/api/data.json' // PHP API接口
];
// 安装阶段 - 缓存资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('打开缓存');
return cache.addAll(urlsToCache);
})
.then(() => self.skipWaiting())
);
});
// 激活阶段 - 清理旧缓存
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
// 拦截请求 - 从缓存获取或网络请求
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果缓存中有,直接返回
if (response) {
return response;
}
// 缓存中没有,发起网络请求
return fetch(event.request)
.then(response => {
// 检查是否是有效响应
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// 克隆响应(因为流只能使用一次)
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(() => {
// 网络请求失败,返回离线页面
return caches.match('/offline.php');
});
})
);
});
PHP离线页面
<!-- offline.php -->
<!DOCTYPE html>
<html>
<head>离线模式</title>
<style>
body { font-family: Arial; text-align: center; padding: 50px; }
.offline-icon { font-size: 80px; margin-bottom: 20px; }
</style>
</head>
<body>
<div class="offline-icon">📡</div>
<h1>您已离线</h1>
<p>当前网络不可用,部分内容可能无法显示。</p>
<p>缓存的页面和资源仍然可以访问。</p>
<button onclick="location.reload()">重新连接</button>
</body>
</html>
缓存策略选择:
// 1. 缓存优先(适合静态资源)
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
// 2. 网络优先(适合实时数据)
event.respondWith(
fetch(event.request)
.then(response => {
// 更新缓存
const responseClone = response.clone();
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => caches.match(event.request))
);
// 3. 网络和缓存同时(适合动态内容)
event.respondWith(
Promise.race([
fetch(event.request),
new Promise(resolve => {
setTimeout(() => resolve(caches.match(event.request)), 300);
})
])
);
本地存储(localStorage)+ PHP动态生成
适用于小规模数据缓存,比如用户设置、配置信息等。
<?php
// php/generate_config.php
header('Content-Type: application/json');
$config = [
'theme' => 'dark',
'language' => 'zh-CN',
'api_endpoint' => '/api/v1/',
'debug_mode' => false,
'updated_at' => date('Y-m-d H:i:s')
];
echo json_encode($config);
?>
// JavaScript端
// 从PHP获取配置并缓存
function loadConfig() {
const cachedConfig = localStorage.getItem('app_config');
const cachedTime = localStorage.getItem('config_time');
// 检查缓存是否过期(例如24小时)
if (cachedConfig && cachedTime) {
const now = new Date().getTime();
const twentyFourHours = 24 * 60 * 60 * 1000;
if (now - parseInt(cachedTime) < twentyFourHours) {
return JSON.parse(cachedConfig);
}
}
// 从服务器获取最新配置
return fetch('/php/generate_config.php')
.then(response => response.json())
.then(config => {
localStorage.setItem('app_config', JSON.stringify(config));
localStorage.setItem('config_time', new Date().getTime().toString());
return config;
});
}
// 使用缓存的配置
loadConfig().then(config => {
console.log('使用配置:', config);
document.body.className = config.theme;
});
IndexedDB + PHP API
适用于大量结构化数据的离线缓存。
<?php
// api/products.php
header('Content-Type: application/json');
// 模拟数据库查询
$products = [
['id' => 1, 'name' => '产品A', 'price' => 99.90],
['id' => 2, 'name' => '产品B', 'price' => 149.90],
['id' => 3, 'name' => '产品C', 'price' => 199.90],
];
echo json_encode($products);
?>
// 使用 IndexedDB 缓存
class ProductCache {
constructor() {
this.dbName = 'productDB';
this.dbVersion = 1;
this.db = null;
}
async openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('products')) {
const store = db.createObjectStore('products', { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => {
reject('数据库打开失败');
};
});
}
async cacheProducts(products) {
await this.openDB();
const transaction = this.db.transaction(['products'], 'readwrite');
const store = transaction.objectStore('products');
products.forEach(product => {
store.put(product);
});
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onerror = () => reject('缓存失败');
});
}
async getCachedProducts() {
await this.openDB();
const transaction = this.db.transaction(['products'], 'readonly');
const store = transaction.objectStore('products');
const request = store.getAll();
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject('读取失败');
});
}
}
// 使用示例
const productCache = new ProductCache();
// 缓存数据
function fetchAndCacheProducts() {
fetch('/api/products.php')
.then(response => response.json())
.then(products => {
productCache.cacheProducts(products);
});
}
// 优先使用缓存,再更新
async function getProducts() {
let products = await productCache.getCachedProducts();
if (products.length === 0) {
const response = await fetch('/api/products.php');
products = await response.json();
await productCache.cacheProducts(products);
}
return products;
}
PHP生成静态缓存文件
不经常变化的PHP页面。
<?php
// cache_manager.php
class StaticCache {
private $cacheDir = './cache/';
private $cacheTime = 3600; // 1小时
public function getCache($key) {
$cacheFile = $this->cacheDir . md5($key) . '.cache';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $this->cacheTime) {
return file_get_contents($cacheFile);
}
return false;
}
public function setCache($key, $content) {
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
$cacheFile = $this->cacheDir . md5($key) . '.cache';
file_put_contents($cacheFile, $content);
}
public function generatePageCache($pageUrl) {
$cache = new StaticCache();
// 检查是否有缓存
$cachedContent = $cache->getCache($pageUrl);
if ($cachedContent !== false) {
echo $cachedContent;
exit;
}
// 开始输出缓冲
ob_start();
// 页面PHP代码...
echo "<h1>动态生成的页面</h1>";
echo "<p>生成时间: " . date('Y-m-d H:i:s') . "</p>";
// 获取页面内容并缓存
$content = ob_get_clean();
$cache->setCache($pageUrl, $content);
echo $content;
}
}
// 使用
$pageCache = new StaticCache();
$pageCache->generatePageCache($_SERVER['REQUEST_URI']);
?>
最佳实践建议
-
选择合适的缓存策略:
- 静态资源(CSS/JS/图片):
Cache First - API数据:
Network First或Stale While Revalidate - 配置信息:使用
localStorage缓存
- 静态资源(CSS/JS/图片):
-
定期更新缓存:
// 在sw.js中定期检查更新 setInterval(() => { caches.open(CACHE_NAME).then(cache => { // 检查并更新所有缓存资源 }); }, 3600000); // 每小时更新一次 -
处理缓存版本:
// 使用版本号控制缓存 const CACHE_NAME = 'my-cache-v' + new Date().getTime();
-
结合PHP生成Service Worker:
<?php // sw.php - 动态生成Service Worker header('Service-Worker-Allowed: /'); header('Content-Type: application/javascript'); header('Cache-Control: no-cache'); $version = '1.0.' . date('Ymd'); ?> const CACHE_NAME = 'my-cache-<?php echo $version; ?>'; // ... 其他配置
注意事项
- Service Worker 必须通过 HTTPS 或 localhost 运行
- 注意浏览器兼容性(大部分现代浏览器都支持)
- 合理控制缓存大小,避免占用过多存储空间
- 提供用户手动清除缓存的选项
- 测试离线功能时,使用 Chrome DevTools 的 Network 面板模拟离线
建议根据项目规模和需求选择合适的方案,对于重要的Web应用,推荐使用 Service Worker + Cache API 方案,这是目前最专业、功能最完整的离线缓存实现方式。