在無 Redis/Memcached 環境下,基於文件系統的緩存是輕量級 PHP 應用的常見選擇。本文提供一個功能完整的 Cache 類,支持鍵值存取、過期時間、MD5 混淆文件名、目錄自動創建與遞歸清理。
['data' => value, 'time' => timestamp],使用 flock(LOCK_EX) 防併發寫入衝突。{key}_{md5(key . secret)},防止鍵名衝突與遍歷猜測。<?php
class Cache {
private $dir = 'data/cache/'; // 默認緩存目錄
private $key = 'c_a_sss'; // 文件名 MD5 加密密鑰
/**
* 設置緩存目錄並自動創建
* @param string $dirpath
*/
public function set_dir($dirpath) {
$this->dir = rtrim($dirpath, '/') . '/';
$this->make_dir($this->dir);
}
/**
* 讀取緩存
* @param string $key
* @param int $minutes 過期分鐘數
* @return mixed|false
*/
public function read($key, $minutes = 1) {
$filename = $this->get_filename($key);
if ($datas = @file_get_contents($filename)) {
$datas = unserialize($datas);
if (is_array($datas) && isset($datas['time'], $datas['data'])) {
if (time() - $datas['time'] < $minutes * 60) {
return $datas['data'];
}
}
}
return false;
}
/**
* 寫入緩存
* @param string $key
* @param mixed $data
* @return bool
*/
public function write($key, $data) {
$filename = $this->get_filename($key);
$datas = array('data' => $data, 'time' => time());
$serialized = serialize($datas);
if ($handle = fopen($filename, 'w+')) {
flock($handle, LOCK_EX);
$rs = fwrite($handle, $serialized);
flock($handle, LOCK_UN);
fclose($handle);
return $rs !== false;
}
return false;
}
/**
* 清空所有緩存
*/
public function clear_all() {
$this->del_file($this->dir);
}
// 生成帶密鑰的 MD5 文件名
private function get_filename($key) {
return $this->dir . $key . '_' . md5($key . $this->key);
}
// 遞歸創建目錄
private function make_dir($path) {
if (!file_exists($path)) {
$this->make_dir(dirname($path));
if (!mkdir($path, 0777)) {
die('無法創建緩存目錄: ' . $path);
}
}
}
// 遞歸刪除目錄下所有文件(保留目錄結構)
private function del_file($dir) {
if (is_dir($dir)) {
$dh = opendir($dir);
while (($file = readdir($dh)) !== false) {
if ($file !== '.' && $file !== '..') {
$fullpath = $dir . '/' . $file;
if (is_dir($fullpath)) {
$this->del_file($fullpath);
} else {
unlink($fullpath);
}
}
}
closedir($dh);
}
}
}
?>
<?php
require 'Cache.class.php';
$cache = new Cache();
$cache->set_dir(__DIR__ . '/data/cache_dir/'); // 設置絕對路徑
// 嘗試讀取緩存(1 分鐘過期)
$data = $cache->read('sys', 1);
if ($data === false) {
// 緩存未命中或過期,生成數據
$data = array(
'aa' => 1111,
'bb' => 2222,
'date' => date('Y-m-d H:i:s')
);
// 寫入緩存
$cache->write('sys', $data);
echo "數據來自數據庫/計算,已寫入緩存\n";
} else {
echo "數據來自緩存\n";
}
print_r($data);
?>
| 設計點 | 實現方式 | 優勢 |
|---|---|---|
| 併發安全 | flock(LOCK_EX) 獨佔鎖 | 防止多進程同時寫入導致文件損壞 |
| 文件名安全 | md5(key . secret) | 避免鍵名包含特殊字符、防止遍歷猜測真實鍵 |
| 過期機制 | 存儲時間戳,讀取時比較 | 無需額外掃描進程,讀取時惰性過期 |
| 目錄管理 | 遞歸 mkdir / 遞歸 unlink | 自適應多級目錄,清理徹底 |
| 序列化存儲 | serialize() | 支持任意 PHP 變量類型(數組、對象、標量) |
md5(key)[0..1] 分 256 個子目錄。var_export + include 或 igbinary 序列化,或直接升級 Redis。0755,文件 0644,避免 0777 風險。<?php
// 擴展思路:在緩存文件中存儲元數據
$meta = array(
'data' => $data,
'time' => time(),
'tags' => array('user', 'profile') // 標籤
);
// 清理時掃描文件解析 tags,匹配則刪除
?>
該類代碼精簡、依賴極少(僅需文件系統),適合中小型項目、微服務、無緩存中間件環境。掌握其原理有助於理解緩存系統的核心機制:序列化、原子寫入、過期判定、命名隔離。