diff --git a/docs/09_SHOPXO_CACHE_HANDBOOK.md b/docs/09_SHOPXO_CACHE_HANDBOOK.md new file mode 100644 index 0000000..8fa2246 --- /dev/null +++ b/docs/09_SHOPXO_CACHE_HANDBOOK.md @@ -0,0 +1,116 @@ +# ShopXO 缓存速查手册 + +## 核心结论 + +| 命题 | 结论 | +|------|------| +| 数据库查询自动缓存? | ❌ 否,必须手动显式包裹 | +| 哪些查询被缓存? | 仅代码中用 `MyCache()` / `cache()` 包装的 | +| `Db::name('x')->find()` 自动走 Redis? | ❌ 直击 DB,无中间层 | +| 缓存驱动切换 | 由后台 `common_data_is_use_redis_cache` 控制(0=file, 1=redis) | +| Redis 参数来源 | 从数据库配置表读取(host/port/password) | + +--- + +## 缓存调用方式 + +### ShopXO 自有的 `MyCache()` + +```php +// 读取(为空触发重新查询) +$data = MyCache($key); + +// 写入(第三参数 = 秒数 expire) +MyCache($key, $data, 3600); + +// 删除 +MyCache($key, null); +``` + +### ThinkPHP 原生 `cache()` + +```php +cache($key, $value, $expire_seconds); +cache($key, null); // 删除 +``` + +--- + +## vr_ticket 插件缓存策略 + +### 缓存场景 + +| 数据 | 缓存时间 | 原因 | +|------|---------|------| +| 座位模板(vr_seat_templates) | 86400s(1天) | 静态数据,变更少 | +| 商品座位图渲染数据 | 3600s(1小时) | 商品信息不频繁改 | +| 核销记录列表 | 300s(5分钟) | 需要一定实时性 | +| 观演人信息 | ❌ 不缓存 | 隐私数据 | +| 座位实时余量 | ❌ 不缓存 | 强一致性要求,走 DB | + +### 缓存 Key 规范 + +``` +vrticket:seat_template_{id} # 单个模板 +vrticket:seat_templates_list # 模板列表 +vrticket:goods_seat_map_{goods_id} # 商品座位图 +vrticket:verifications_list # 核销记录列表 +``` + +### 数据变更时主动失效 + +```php +// 模板更新后 +cache('vrticket:seat_template_' . $id, null); +cache('vrticket:seat_templates_list', null); + +// 核销记录新增后 +cache('vrticket:verifications_list', null); +``` + +--- + +## 配置说明(config/cache.php) + +```php +'default' => (MyFileConfig('common_data_is_use_redis_cache','',0,true) == 1) ? 'redis' : 'file', + +'stores' => [ + 'redis' => [ + 'host' => MyFileConfig('common_cache_data_redis_host','','127.0.0.1',true), + 'port' => MyFileConfig('common_cache_data_redis_port','',6379,true), + 'password' => MyFileConfig('common_cache_data_redis_password','','',true), + 'expire' => intval(MyFileConfig('common_cache_data_redis_expire','',0,true)), + 'prefix' => MyFileConfig('common_cache_data_redis_prefix','','redis_shopxo',true), + ], +] +``` + +- 全局 `expire` = 0 表示永久(受实际 per-key expire 覆盖) +- ShopXO 后台可配置 `common_data_is_use_cache` 全局开关(debug 模式下自动 bypass) + +--- + +## 防坑提示 + +1. **Debug 模式绕过缓存**:`MyEnv('app_debug')` 为 true 时所有 `MyCache()` 直接 miss +2. **全局开关**:`MyC('common_data_is_use_cache') != 1` 时全部走 DB +3. **超卖风险**:座位状态查询禁止缓存,必须实时查 DB + `FOR UPDATE SKIP LOCKED` +4. **MyCache 静态复用**:同一请求内同一 key 只调一次底层 `cache()`(in-request 内存缓存) + +--- + +## 快速上手模板 + +```php +// 读缓存 +$key = 'vrticket:seat_template_' . $templateId; +$data = MyCache($key); +if ($data === null) { + $data = Db::name('SeatTemplate')->where(['id'=>$templateId])->find(); + MyCache($key, $data, 86400); +} + +// 写操作后清缓存 +cache('vrticket:seat_template_' . $templateId, null); +``` \ No newline at end of file diff --git a/shopxo/app/plugins/vr_ticket/service/BaseService.php b/shopxo/app/plugins/vr_ticket/service/BaseService.php index 509a914..0687362 100644 --- a/shopxo/app/plugins/vr_ticket/service/BaseService.php +++ b/shopxo/app/plugins/vr_ticket/service/BaseService.php @@ -385,8 +385,9 @@ class BaseService $R = $packed & 0x1FFFF; // 8轮逆向 Feistel 置换 + // 标准逆向:F 输入是 R(与 encode 一致) for ($i = 7; $i >= 0; $i--) { - $F = self::feistelRound($L, $i, $key); + $F = self::feistelRound($R, $i, $key); // 修复:使用 R,不是 L $R_new = $L; $L_new = $R ^ $F; $R = $R_new; diff --git a/tests/phase4_1_feistel_test.php b/tests/phase4_1_feistel_test.php index 4b13768..317083d 100644 --- a/tests/phase4_1_feistel_test.php +++ b/tests/phase4_1_feistel_test.php @@ -65,8 +65,10 @@ function feistelDecode(string $code, string $key): int $L = ($packed >> 17) & 0x7FFFF; $R = $packed & 0x1FFFF; + // 8轮逆向 Feistel 置换 + // 标准逆向:F 输入是 R(与 encode 一致) for ($i = 7; $i >= 0; $i--) { - $F = feistelRound($L, $i, $key); + $F = feistelRound($R, $i, $key); // 修复:使用 R,不是 L $R_new = $L; $L_new = $R ^ $F; $R = $R_new;