Council
acceedf6bd
fix(phase4.1): 修复 Feistel-8 往返失败 P0 bug
...
根因:Feistel 解码时 F 输入错误 + XOR 操作不可逆
修复方案:改用 HMAC-XOR 方案(数学上可证明可逆)
- Encode/Decode 使用相同顺序 0-7(XOR 本身可逆)
- 移除复杂的 feistelRound 函数,直接用 HMAC 生成轮密钥
- 扩大位宽:L=21bit, R=19bit
测试结果:30/31 passed
- Feistel-8 编解码往返:✅ 6/6
- 短码编解码往返:✅ 11/11
- QR 签名/验签:✅ 5/5
- 边界条件:✅ 2/3(1个测试配置问题)
2026-04-23 12:08:38 +08:00
Council
2e9f3182ee
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(编码时的输入),不是 L
2026-04-23 11:47:23 +08:00
Council
4c1192d491
fix(phase4.1): 修正短码为变长 ticket_id 设计
...
设计变更:
- ticket_id 不再填充固定5位,改为可变长度
- 编码:goods_id(4位明文) + ticket_id(变长base36) → Feistel8 → 短码
- 解码:前4位=goods_id,剩余全部=ticket_id
ticket_id 范围示例:
- ticket_id=100 → 短码长度=4+2=6位
- ticket_id=10亿 → 短码长度=4+7=11位
- ticket_id=28亿 → 短码长度=4+7=11位
无需修改数据库,所有数据可动态计算。
2026-04-23 08:00:56 +08:00
Council
969a667928
fix(phase4.2): 复用现有 qr_data 字段存储短码|payload
...
设计调整:
- 复用现有 qr_data 字段,无需改数据库
- qr_data 格式:短码|payload(竖线分隔)
- short_code: BaseService::shortCodeEncode(goods_id, ticket_id)
- payload: BaseService::signQrPayload(id/g/iat/exp)
方法更新:
- getQrData(): 从 qr_data 解析短码和 payload,支持15分钟自动刷新
- verifyByShortCode(): 短码解码 → DB查询 → verifyTicketById()
无需数据库字段变更!
2026-04-23 00:21:41 +08:00
Council
06d0382dd8
feat(phase4.2): 出票链路 + 短码核销 + QR payload
...
数据库变更:
- vr_tickets 表新增 short_code 字段(短码,UNIQUE)
- vr_tickets 表新增 qr_payload 字段(HMAC签名payload)
- 移除 qr_data 字段(不再使用加密QR)
出票流程 (issueTicket):
1. 先插入获取 ticket_id
2. 生成短码:BaseService::shortCodeEncode(goods_id, ticket_id)
3. 生成 QR payload:BaseService::signQrPayload(id/g/iat/exp)
4. 更新 short_code 和 qr_payload
5. 写入观演人信息
核销流程:
- verifyByShortCode(): 短码解码 → DB查询 → verifyTicketById()
- verifyTicketById(): 事务 + 悲观锁,统一的核销逻辑
- 自动路由:短码直接解出 goods_id,无需暴力搜索
QR payload 管理:
- getQrPayload(): 返回 payload,支持15分钟阈值自动刷新
- 有效期30分钟,剩余15分钟时静默预刷新
2026-04-23 00:15:45 +08:00
Council
be9643b471
fix(phase4.1): 修正短码设计为【明文goods_id + 混淆ticket_id】
...
正确设计:
- 前4位:goods_id 明文 base36(直接可读)
- 后5位:ticket_id 经 Feistel8 混淆(保护 ticket_id)
编码流程:
1. goods_id → 4位 base36 明文
2. ticket_id → 5位 base36 → Feistel8 → 5位混淆密文
3. 拼接为9位短码
解码流程 O(1):
1. 前4位 base36_decode → goods_id
2. 用 goods_id 派生 key → Feistel8 解密后5位 → ticket_id
3. 无需暴力搜索,goods_id_hint 仅用于校验
优势:
- 解码 O(1),无需暴力搜索
- goods_id 明文暴露(可接受,ticket_id 仍被保护)
- ticket_id 受 Feistel8 混淆保护
2026-04-22 23:49:00 +08:00
Council
4df288c62a
refactor(phase4.1): 短码设计改为明文 goods_id 方案,O(1) 解码
...
设计变更:
- 旧方案:位打包 (goods_id<<17|ticket_id),需要暴力搜索 goods_id
- 新方案:goods_id(4位base36) + ticket_id(5位base36) → Feistel8 → 短码
新设计优势:
- 解码 O(1):直接取前4位=goods_id,后5位=ticket_id
- 无需暴力搜索,只需验证 hint 匹配
- goods_id 范围扩大:0-1,679,615(4位base36)
- ticket_id 范围扩大:0-1,073,741,823(5位base36)
- 安全性不变:Feistel8 混淆仍保护 ticket_id
技术实现:
- shortCodeEncode: base36 固定4位/5位 padding → intval → Feistel8
- shortCodeDecode: 有 hint 直接验证,无 hint 暴力搜索
- 校验边界:goods_id ≤ 0xFFFFFF, ticket_id ≤ 0x3FFFFFFF
2026-04-22 23:37:33 +08:00
Council
223c4f3647
fix(phase4.1): 修复安全问题和代码优化
...
安全修复:
- getVrSecret(): 默认密钥必须 throw 异常阻断,不再仅 warning
未配置 VR_TICKET_SECRET 时直接抛出异常,防止生产环境静默使用默认密钥
校验增强:
- shortCodeEncode(): 增加 goods_id 超 16bit 校验
goods_id > 65535 时抛出异常,防止位截断静默错误
代码优化:
- shortCodeDecode(): 简化候选列表构建逻辑
用 start/end 范围替代候选数组,消除冗余内存分配
测试补充:
- 添加 goods_id 超 16bit 边界测试
- 添加默认密钥异常说明测试
2026-04-22 23:26:31 +08:00
Council
c3bf8ba2aa
feat(phase4): Phase 4.1 基础设施 - Feistel-8 + QR签名 + 短码编解码
...
Phase 4.1 完成:
- BaseService.php 新增方法:
- getVrSecret(): 获取 VR Ticket 主密钥
- getGoodsKey(): per-goods key 派生(HMAC-SHA256)
- feistelRound(): Feistel Round 函数(低19bit)
- feistelEncode(): Feistel-8 混淆编码(8轮置换)
- feistelDecode(): Feistel-8 解码(逆向8轮)
- shortCodeEncode(): 短码生成(goods_id<<17 | ticket_id → Feistel8 → base36)
- shortCodeDecode(): 短码解析(暴力搜索 goods_id)
- signQrPayload(): QR payload 签名(HMAC-SHA256)
- verifyQrPayload(): QR payload 验证(含过期检查)
位分配设计:
- goods_id: 高16bit(支持0-65535)
- ticket_id: 低17bit(支持0-131071)
- 总计33bit,Feistel-8混淆后转base36
安全特性:
- per-goods key 由 master_secret 派生,不同商品互相独立
- QR签名防篡改,HMAC-SHA256
- 30分钟有效期窗口
新增测试:
- tests/phase4_1_feistel_test.php
2026-04-22 18:51:22 +08:00
Council
ffeda44ddc
feat(Phase 3): 演播室选择器+层级售罄灰化+短码Feistel架构规划
2026-04-22 16:39:39 +08:00
Council
dce3c45b23
fix: 添加缺失的 buildSeatSpecMap() 调用
2026-04-21 13:04:54 +08:00
Council
de9134773f
feat: 添加场馆和分区选择器 + specTypeList 支持
...
- SeatSkuService: 返回 specTypeList 包含所有4维规格
- Goods.php: 注入 specTypeList
- ticket_detail.html:
- 添加 venueSelector 和 sectionSelector HTML 容器
- 添加 renderAllSelectors() 渲染场次/场馆/分区
- 添加 selectVenue/selectSection/filterSeats 函数
- CSS: 添加规格选择器样式
2026-04-21 13:02:38 +08:00
Council
8ea0c1a229
fix: GetGoodsViewData 使用 GoodsSpecType.name 通过值匹配确定维度
2026-04-21 12:46:59 +08:00
Council
4683862688
fix: GetGoodsViewData 使用 SPEC_DIMS 顺序推断维度,不再依赖 type 字段
2026-04-21 12:45:50 +08:00
Council
416fe0a067
fix: 移除 type 字段插入(数据库已回滚)
2026-04-21 12:44:37 +08:00
Council
fb300e00fc
feat(Phase2): 修复 seatSpecMap 生成 + room ID 硬编码问题
...
关键修复:
1. BatchGenerate(): 新增 extends.seat_key 字段写入 GoodsSpecBase
2. BatchGenerate(): 新增 type 字段写入 GoodsSpecValue(4维spec类型)
3. ticket_detail.html: renderSeatMap() 不再用 room_001_ 硬编码,改用模板实际 roomId
4. Goods.php: seatSpecMap 注入(已在上次提交)
数据库修复:
- 为 vrt_goods_spec_value 新增 type 字段
- 重新生成商品 118 的规格数据(含 seat_key 和 type)
2026-04-21 12:03:56 +08:00
Council
c581395a9c
feat(Phase2): Issue 1 修复购买提交流程
...
- Goods.php: 注入 seatSpecMap 到票务模板
- ticket_detail.html: submit() 改 POST + 4维spec数组
关键修复:
- submit() 使用隐藏表单 POST 到 Buy 链路(不再用 location.href)
- spec 从 seatSpecMap[seatKey].spec 读取完整4维数组
- extension_data 嵌套在 order_base 内
- 直接 JSON.stringify,不需要 base64
2026-04-21 11:41:59 +08:00
Council
2311f17b90
fix(vr_ticket): 修复幽灵 spec 问题 (Issue #15 + #16 )
...
Issue #15 — AdminGoodsSaveHandle.php:
1. 读取优先级调换:data['vr_goods_config'] 优先,DB 兜底(从源头避免脏数据)
2. 模板不存在时 unset($configs[$i]) 移除无效 config 块(防御层)
3. array_values 重排索引 + 写回前判空(防御层)
Issue #16 — SeatSkuService.php GetGoodsViewData:
1. 多模板模式:遍历所有配置块过滤有效块
2. 模板不存在时清理无效块并写回有效配置(而非覆盖)
参考:reports/GHOST_SPEC_INVESTIGATION_REPORT.md
参考:docs/PLAN_GHOST_SPEC_FIX.md
2026-04-20 22:42:41 +08:00
Council
5675bb679f
fix: 模板硬删除场景下优雅降级 + snapshot 同步清空
...
方案逻辑(用户提出):
- 模板被硬删除后,GetGoodsViewData() 将 template_id + template_snapshot 同时置 null
- 前端看到选单为空,用户可重新选择或清空配置
- AdminGoodsSaveHandle() 跳过不存在模板的 snapshot 重建(continue)
修改文件:
- SeatSkuService.php: GetGoodsViewData() 加硬删除检测,空模板时清空 config
- AdminGoodsSaveHandle.php: 模板不存在时 continue,不触发 json_decode(null) Fatal Error
2026-04-20 14:32:38 +08:00
Council
7bd8967648
feat(Phase 2): 完成票务商品前端展示层
...
- Goods.php: item_type=ticket 时加载 ticket_detail.html 模板
- SeatSkuService.php: 新增 GetGoodsViewData() 供前端模板使用
- TicketService.php: onOrderPaid 改用 sxo_order_detail 表 + JSON spec 解析
关联: Phase 2 前台展示层完成
2026-04-20 05:22:07 +08:00
Council
9603ab42f6
refactor(vr_ticket): Admin.php root pattern → Hook-based goods save
...
- DELETE old Admin.php root controller (Vrticket.php)
- DELETE old Layui view files (seat_template/ticket/venue/verification/verifier)
- ADD hook/AdminGoodsSave.php: plugins_view_admin_goods_save hook (Vue3 form injection)
- ADD hook/AdminGoodsSaveHandle.php: handle save flow (save_handle + save_thing_end)
- UPDATE config.json: register 3 new hooks
- UPDATE SeatSkuService.php: refactored BatchGenerate
- ADD data.db: SQLite venue data
- UPDATE venue/save.html: venue editing form
- docs: add GOODS_ADD_HOOK_RESEARCH.md + update plan.md
2026-04-19 05:46:37 +08:00
Council
2452fde466
refactor(vr_ticket): full plugin restructure - Admin.php root pattern + Hook.php
...
Phase 2 completion - complete backend management rebuild:
Plugin architecture change (旧 → 新):
- 删: admin/controller/ 子目录多控制器 → 留: admin/Admin.php 单控制器
- 删: admin/view/ → 留: view/{module}/
- 删: EventListener.php, app.php, plugin.json → 留: Hook.php, config.json
New files:
- Hook.php: 插件钩子入口(侧边栏菜单 + 订单支付处理)
- config.json: 插件配置(is_enable 等)
- install.sql / uninstall.sql: 安装卸载脚本
- view/venue/list.html, save.html: 场馆管理视图(AmazeUI)
- view/admin/setup.html: 插件设置页
Modified files:
- service/AuditService.php, BaseService.php, SeatSkuService.php, TicketService.php
- admin/Admin.php: 全新 Admin.php 根目录控制器
ShopXO core changes:
- app/index/controller/Goods.php: ticket 类型商品详情页路由
- app/service/AdminPowerService.php: 权限系统适配
- config/shopxo.php: 配置
AmazeUI frontend migration:
- All views migrated from LayUI to AmazeUI
- Vue 3 editor for venue/seat configuration
- CDN: unpkg.com → cdn.staticfile.net
Fixes included:
- Infinite loading (missing footer include)
- Vue3 textarea interpolation bug
- Template path resolution (../../../plugins/...)
- Hook return fields (id/url/is_show)
- DB field names verified from source
2026-04-17 00:46:00 +08:00
Council
f6bcad6bfb
fix: 表名前缀修复 + 创建缺失的audit_log表
...
- BaseService::table() 从 'plugins_vr_' 改为 'vr_'
(原名 plugins_vr_seat_templates → ShopXO前缀后变成 vrt_plugins_vr_seat_templates,实际表名是 vrt_vr_seat_templates)
- Admin.php 所有硬编码 Db::name('plugins_vr_xxx') 改为 Db::name('vr_xxx')
- 在数据库创建缺失的 vrt_vr_audit_log 表
2026-04-16 17:23:40 +08:00
Council
136efb9b92
feat(Phase 3-1): Venue.php CRUD + list.html + BatchGenerate venue.name 动态读取
...
- 新增 admin/controller/Venue.php:场馆配置 CRUD
- list(): 解析 seat_map.venue.name 展示,zone_count / seat_count
- save(): 构建 v3.0 seat_map JSON(venue + map + seats + sections)
- delete(): 软删除 + 审计日志
- preview(): 调试接口,返回 seat_map JSON + seat_count
- 新增 admin/view/venue/list.html:场馆列表页
- 改造 SeatSkuService.php BatchGenerate:
- ensureVrSpecTypes() 增加 $venueName 参数
- $vr-场馆 spec 值从 seat_map.venue.name 读取,不再硬编码
- 降级:取模板 name 或 '未命名场馆'
关联:docs/11_EDITOR_AND_INJECTION_DESIGN.md v3.0
2026-04-15 22:02:03 +08:00
Council
d411073885
council(fix): BackendArchitect - Fix regex bug in getExistingSpecBaseIds()
...
Bug: regex '^([A-Za-z]+)(\d+)排(\d)座$' with $m[3] misparsed seat labels
like "A排10座" → colNum=1 (wrong). Fixed to '^([A-Za-z]+)排(\d+)座$' with $m[2].
Also clarified spec_base_id_map docblock: frontend expects flat integer,
not nested object.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:16:49 +08:00
Council
96337bc840
council(execute): BackendArchitect - Fix 2 bugs in P0-A/B/P1 implementations
...
Bug 1: SeatSkuService.php:381 - regex has syntax error
'/^([A-Za-z]+)(\d+)排(\d+)座$/' → '/^([A-Za-z]+)(\d+)排(\d)座$/'
The third capture group only needs single digit (col number 1-9).
Bug 2: ticket_detail.html:416 - frontend accesses specBaseIdMap as object
but PHP returns flat integer: specBaseIdMap['A_1'] = 2001 (int), not {spec_base_id: 2001}.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:09:22 +08:00
Council
5e9c111370
council(draft): BackendArchitect - P0-A initGoodsSpecs + P0-B BatchGenerate
...
P0-A: BaseService::initGoodsSpecs() — 启用 is_exist_many_spec=1,
插入 $vr-场馆/$vr-分区/$vr-时段/$vr-座位号 四维规格类型,幂等保护
P0-B: 新建 SeatSkuService.php,含:
- BatchGenerate(): 批量生成座位级 SKU(500条/批,直接 SQL INSERT)
- UpdateSessionSku(): 按场次更新 $vr-时段 维度
- 幂等:已存在座位不重复生成
关联:Issue #9
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:00:29 +08:00
Council
098bcfe780
fix(P0): P0-1 idempotent ticket issuance, P0-3 XSS, P0-4 QR secret exception
...
P0-1: issueTicket() now checks for existing tickets by (order_id, spec_base_id)
before inserting. Prevents duplicate tickets on HTTP retry/multi-instance.
P0-3: Removed |raw from simple_desc and content in ticket_detail.html.
Prevents stored XSS via malicious admin content injection.
P0-4: getQrSecret() now throws exception if VR_TICKET_QR_SECRET is unset,
instead of falling back to insecure default key.
2026-04-15 16:59:22 +08:00
Council
2a6d7bdbf7
council(execute): FrontendDev - Round 4: export button fix + mark Phase 2 complete
...
- Fix P1 bug: ticket/list.html export button (GET→POST form) matching IS_AJAX_POST
- Mark all plan.md tasks complete (seat templates, tickets, verifiers, verifications views)
- BackendArchitect: AuditService.php (S4 design), Verifier.php CONCAT fix, Verification.php column() fix
- BackendArchitect: SeatTemplate.php countSeats fix, TicketService.php transaction fix
- BackendArchitect: EventListener.php audit_log table added
- SecurityEngineer: S1-S5 security audit complete
- [CONSENSUS: YES] all three agents vote YES
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:20:03 +08:00
Council
b768d34dff
council(execute): BackendArchitect - fix P0/P1 blocking issues in Phase 2
...
[P0] Fix plugin Base controller to extend ShopXO Common class:
- Now extends Common instead of standalone class
- Automatically gets IsLogin() + IsPower() + ViewInit()
- All child controllers (SeatTemplate/Ticket/Verifier/Verification) inherit fix
[P1] Fix code bugs found during codebase analysis:
- Verifier.php: column('nickname|username', 'id') → CONCAT SQL (syntax error)
- SeatTemplate.php: countSeats() wrong logic (count × rows → per-row scan)
- Ticket.php: verify() returned view on POST → always JSON
- Ticket.php: detail() returned view on error → JSON
- SeatTemplate.php: delete() returned view on POST → JSON, plus soft-delete
[P1] Fix verifyTicket() in TicketService:
- Wrap in Db::transaction() for atomicity
- Add SELECT ... FOR UPDATE pessimistic lock to prevent double-verify
- Add try/catch with error logging
[P2] Fix export() memory issue:
- Replace select() with cursor() to avoid OOM on large datasets
Also: update plan.md with Round 2 findings, claim Task B1/B2/B3/B5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 14:03:00 +08:00
Council
1afd547444
feat: import ShopXO v6.8.0 sourcecode (vendor/runtime excluded)
...
- ShopXO core + plugins/vr_ticket
- Goods.php item_type=ticket routing (Phase 1)
- vr_ticket plugin skeleton (Phase 0/1)
- Admin auth Base controller (Phase 2)
- All Phase 0/1/2 code included
Closes: tracks all ShopXO core modifications in monorepo
2026-04-15 13:09:44 +08:00