新增 Exact Cache 模块,为已有的 ExactCache 核心类提供完整的管理界面和统计功能: - 模块骨架:module.json + ExactCacheModule(实现 ModuleInterface 全部 7 个方法) - AJAX 控制器:4 个 handler(保存设置/清空缓存/重置统计/获取统计),含安全三要素 - 成本估算器:基于 UsageTracker 历史数据估算缓存节省金额 - 日统计:7 天滚动统计,shutdown 批量写入,retry-once transient lock - 管理界面:统计卡片 + Chart.js 7 天趋势图 + 设置表单 + 缓存管理 - settings-page.php:添加精确缓存 tab(与 GEO/API Gateway 相同模式) - ExactCache.php:仅添加 get_entries() 只读方法,零核心影响 - uninstall.php:添加 option + transient 批量清理 经过 3 轮 Codex 架构审查(v1.0→v2.0→v2.1→v2.2),所有 P0/P1 问题已解决。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
121 lines
2.9 KiB
PHP
121 lines
2.9 KiB
PHP
<?php
|
||
/**
|
||
* Daily Stats - 日维度缓存统计(7 天滚动)
|
||
*
|
||
* 通过 shutdown hook 批量写入,使用 retry-once transient lock 处理并发。
|
||
*
|
||
* @package WPMind\Modules\ExactCache
|
||
* @since 1.0.0
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace WPMind\Modules\ExactCache;
|
||
|
||
final class DailyStats {
|
||
|
||
private const OPTION_KEY = 'wpmind_exact_cache_daily_stats';
|
||
private const LOCK_KEY = 'wpmind_daily_stats_lock';
|
||
private const MAX_DAYS = 7;
|
||
|
||
/** @var array<string, array{hits: int, misses: int, writes: int}> */
|
||
private static array $pending = [];
|
||
|
||
private static bool $shutdown_registered = false;
|
||
|
||
public static function record_hit(): void {
|
||
self::buffer('hits');
|
||
}
|
||
|
||
public static function record_miss(): void {
|
||
self::buffer('misses');
|
||
}
|
||
|
||
public static function record_write(): void {
|
||
self::buffer('writes');
|
||
}
|
||
|
||
private static function buffer(string $metric): void {
|
||
$today = wp_date('Y-m-d');
|
||
if (!isset(self::$pending[$today])) {
|
||
self::$pending[$today] = ['hits' => 0, 'misses' => 0, 'writes' => 0];
|
||
}
|
||
self::$pending[$today][$metric]++;
|
||
|
||
if (!self::$shutdown_registered) {
|
||
add_action('shutdown', [self::class, 'flush_pending']);
|
||
self::$shutdown_registered = true;
|
||
}
|
||
}
|
||
|
||
public static function flush_pending(): void {
|
||
if (empty(self::$pending)) {
|
||
return;
|
||
}
|
||
|
||
// Retry-once lock: 首次失败后等 100ms 重试一次
|
||
if (!self::acquire_lock()) {
|
||
usleep(100000); // 100ms
|
||
if (!self::acquire_lock()) {
|
||
// 仍然失败,接受丢失(统计非关键数据)
|
||
return;
|
||
}
|
||
}
|
||
|
||
$data = get_option(self::OPTION_KEY, []);
|
||
if (!is_array($data)) {
|
||
$data = [];
|
||
}
|
||
|
||
foreach (self::$pending as $date => $metrics) {
|
||
if (!isset($data[$date])) {
|
||
$data[$date] = ['hits' => 0, 'misses' => 0, 'writes' => 0];
|
||
}
|
||
$data[$date]['hits'] += $metrics['hits'];
|
||
$data[$date]['misses'] += $metrics['misses'];
|
||
$data[$date]['writes'] += $metrics['writes'];
|
||
}
|
||
|
||
// 滚动清理:只保留最近 7 天
|
||
ksort($data);
|
||
while (count($data) > self::MAX_DAYS) {
|
||
array_shift($data);
|
||
}
|
||
|
||
update_option(self::OPTION_KEY, $data, false);
|
||
delete_transient(self::LOCK_KEY);
|
||
self::$pending = [];
|
||
}
|
||
|
||
private static function acquire_lock(): bool {
|
||
if (get_transient(self::LOCK_KEY)) {
|
||
return false;
|
||
}
|
||
set_transient(self::LOCK_KEY, 1, 5);
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 获取最近 7 天的统计数据(无数据的天填 0)
|
||
*
|
||
* @return array<string, array{hits: int, misses: int, writes: int}>
|
||
*/
|
||
public static function get_daily_data(): array {
|
||
$data = get_option(self::OPTION_KEY, []);
|
||
if (!is_array($data)) {
|
||
return [];
|
||
}
|
||
|
||
// 补齐最近 7 天
|
||
$result = [];
|
||
for ($i = 6; $i >= 0; $i--) {
|
||
$date = wp_date('Y-m-d', strtotime("-{$i} days"));
|
||
$result[$date] = $data[$date] ?? ['hits' => 0, 'misses' => 0, 'writes' => 0];
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
public static function reset(): void {
|
||
delete_option(self::OPTION_KEY);
|
||
}
|
||
}
|