fix(phase4.1): 修复 Feistel-8 decode 往返失败 P0 bug
根因:feistelDecode 中 F 函数输入错误 - 错误:F = feistelRound($L, ...) - 正确:F = feistelRound($R, ...) 标准 Feistel 解码原理: - 编码: L_new=R, R_new=L^F(R) - 解码: L_new=R^F(L), R_new=L(这里 L 是编码后的 L,即当前 L) - 因此 F 输入应该是 R(编码时的输入),不是 Lfeat/phase4-ticket-wallet
parent
4c1192d491
commit
2e9f3182ee
|
|
@ -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);
|
||||||
|
```
|
||||||
|
|
@ -385,8 +385,9 @@ class BaseService
|
||||||
$R = $packed & 0x1FFFF;
|
$R = $packed & 0x1FFFF;
|
||||||
|
|
||||||
// 8轮逆向 Feistel 置换
|
// 8轮逆向 Feistel 置换
|
||||||
|
// 标准逆向:F 输入是 R(与 encode 一致)
|
||||||
for ($i = 7; $i >= 0; $i--) {
|
for ($i = 7; $i >= 0; $i--) {
|
||||||
$F = self::feistelRound($L, $i, $key);
|
$F = self::feistelRound($R, $i, $key); // 修复:使用 R,不是 L
|
||||||
$R_new = $L;
|
$R_new = $L;
|
||||||
$L_new = $R ^ $F;
|
$L_new = $R ^ $F;
|
||||||
$R = $R_new;
|
$R = $R_new;
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,10 @@ function feistelDecode(string $code, string $key): int
|
||||||
$L = ($packed >> 17) & 0x7FFFF;
|
$L = ($packed >> 17) & 0x7FFFF;
|
||||||
$R = $packed & 0x1FFFF;
|
$R = $packed & 0x1FFFF;
|
||||||
|
|
||||||
|
// 8轮逆向 Feistel 置换
|
||||||
|
// 标准逆向:F 输入是 R(与 encode 一致)
|
||||||
for ($i = 7; $i >= 0; $i--) {
|
for ($i = 7; $i >= 0; $i--) {
|
||||||
$F = feistelRound($L, $i, $key);
|
$F = feistelRound($R, $i, $key); // 修复:使用 R,不是 L
|
||||||
$R_new = $L;
|
$R_new = $L;
|
||||||
$L_new = $R ^ $F;
|
$L_new = $R ^ $F;
|
||||||
$R = $R_new;
|
$R = $R_new;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue