Compare commits

...

9 Commits

Author SHA1 Message Date
Council d28a4dc511 feat: 选座系统架构 + ShopXO后台集成方案(docs/06)+ 关键架构修正 2026-04-14 15:29:22 +08:00
Council dd538ba08e fix: 明确允许最小范围修改ShopXO源码(MIT协议),以进度为先 2026-04-14 14:10:59 +08:00
Council b713cd73c3 council(finalize): backend-reviewer - execute T6/T8/T9, vote YES
- T6: Confirm payment callback hook plugins_service_buy_order_insert_success
- T8: Supplement verifier permission validation (vr_verifiers whitelist)
- T9: Supplement vr_events/vr_sessions DDL (complete, indexed)
- Review pm-reviewer output: concurrent control already covered in 03 §9
- Vote: [CONSENSUS: YES] - docs ready for coding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 14:09:56 +08:00
Council af66140af1 council(draft): backend-reviewer - Round 1 plan update, vote YES 2026-04-14 14:04:23 +08:00
Council 09ee277268 council(finalize): backend-reviewer - resolve plan.md conflicts, vote YES
Verdict:
- docs/01_SHOPXO_TECHNICAL_RESEARCH.md:  通过(3项非阻断性改进)
- docs/03_VERIFICATION_SYSTEM.md:  通过(核销员权限验证需补充)
- BuyService OrderInsertHandle:  防超卖安全验证通过
- vr_events/vr_sessions DDL: 已在 reviews/ 中补充

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:52:25 +08:00
Council 3ed4168da5 council(draft): resolve plan.md conflict, merge backend-reviewer + pm-reviewer plans
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:47:12 +08:00
Council 23464e725a council(draft): ticket-reviewer - create plan.md with task breakdown
Reviewed docs/03_VERIFICATION_SYSTEM.md and ARCHITECTURE.md:
- ⚠️ API paths inconsistent (admin vs C-end)
- ⚠️ AES IV design needs clarification
-  Anti-overselling mechanism missing (blocking issue)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:46:20 +08:00
Council bdee8b50c6 council(draft): backend-reviewer - create plan.md with SQL/security task breakdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:45:49 +08:00
Council bb71681cab council(draft): pm-reviewer - create plan.md with PM review task breakdown 2026-04-14 13:45:33 +08:00
6 changed files with 949 additions and 6 deletions

View File

@ -1,6 +1,6 @@
# ShopXO VR票务插件 — 架构文档
> 版本v1.12026-04-14 更新,整合 ShopXO 技术调研成果
> 版本v1.22026-04-14 下午更新,座位地图 + 场馆绑定架构确认
> 源码位置council-research/shopxo-eval/.worktrees/shopxo-evaluator/shopxo-src/
## 项目概述
@ -10,10 +10,27 @@
当 vr-ticket-mp 主线项目因维护成本或架构限制无法继续时,此插件作为 Plan B
- **完全复用** ShopXO 已有能力(会员体系/积分/优惠券/微信支付)
- **仅扩展** 票务专属逻辑(场次/座位/观演人/QR核销
- **不修改** ShopXO 核心代码,通过插件机制隔离
- **优先通过插件机制扩展**,如插件机制不够用(如商品详情页按类型返回不同模板),允许直接修改 ShopXO 源码的最小范围MIT 协议允许)。原则:改源码比绕弯快时,直接改;以进度为先,不为「不修改」而引入额外复杂度。
---
### 8. 场馆系统 + 选座地图(完整方案)⭐⭐⭐
详见 `docs/06_SEAT_MAP_INTEGRATION.md`
**核心发现**
1. **字符地图是行业标准**:场馆平面图 → 字符串地图(如 `aaa___aaa`)→ 前端渲染为 SVG/DOM
2. **ShopXO 分类 = Venue Type 绑定**:每个"场馆类型"对应一个 ShopXO 分类,商品挂分类 = 绑定 venue type
3. **vr_venues 表**:商家在插件后台管理场馆,上传/编辑座位图 JSON
4. **vr_sessions.seat_map_json**:每个场次存一份座位图配置
5. **spec_base_id_map**seat_id`"3_5"`)→ spec_base_id如 10003映射绑定 ShopXO 购买流程
6. **ShopXO spec 系统无硬限制**规格种类数、单规格选项数、SKU 组合数均无限制3场馆×2票种×500座位=3000 SKU 完全可行)
**绑定链路**
```
ShopXO 分类venue type← → vr_venues ← → vr_sessions ← → spec_base_id_map ← → ShopXO spec_base
```
## 核心技术发现2026-04-14 调研)
### 1. CustomView Ace 编辑器 ⭐
@ -28,15 +45,30 @@ ShopXO 内置全代码自定义页面编辑器HTML/CSS/JS 三栏,实时预
- 完全注入票务选座 UI
- 不修改核心代码
### 3. 按商品类型替换模板 ⭐
### 3. 按商品类型替换模板(已有更优方案,优先用 Hook
> ⚠️ **已更新2026-04-14 下午)**:原计划修改 Goods.php现发现 `site_type=3`(虚拟商品)可绕过:
> - site_type=3 时 ShopXO 不显示 extraction popup也不要求选择地址
> - 直接通过 `plugins_view_goods_detail_base_sku_top` 注入票务选座 UI
> - **Goods.php 不再需要修改**,完全通过插件 Hook 实现
> 原 Goods.php 修改方案保留作为备案(如 Hook 无法满足时):
> 在 `Goods.php Index()` 中增加 1 行判断:
> 这是 ShopXO 允许范围内,实现「特定商品类型使用独立模板」的唯一方式。
> 所有其他功能均通过插件钩子实现,**不修改核心代码**。
修改 `Goods.php Index()` 加 1 行判断:
```php
if($goods['item_type'] == 'ticket') {
return MyView('/goods/ticket_detail');
// app/index/controller/Goods.php Index() 方法,约第 440 行
// 在 return MyView(); 之前插入:
if(!empty($goods['item_type']) && $goods['item_type'] == 'ticket') {
return MyView('/goods/ticket_detail'); // 自定义票务模板
}
return MyView(); // 默认模板
```
对应模板文件:`app/index/view/default/goods/ticket_detail.html`ShopXO 主题目录下)
### 4. shopxo-uniapp 支持微信小程序 ⭐
HBuilderX 导入 → 配置 AppID → 发行 → 微信开发者工具

View File

@ -542,3 +542,174 @@ GET /?s=admin/vrticket/stats&event_id=8
- 最近核销记录滚动列表
实现方式:轮询 stats API + 数字动画CountUp.js
---
## 九、防超卖机制
> 本章节为架构性补充,是编码前的 **阻断性要求**。
> 核销的完整性依赖于:只有合法持票人能核销,且每张票只能核销一次。
### 9.1 购票时序与座位锁定
座位锁定贯穿整个购票流程,分三阶段:
```
[阶段1: 选座] [阶段2: 提交订单] [阶段3: 支付成功]
用户选座 → 前端展示 提交订单 → 后端锁座 支付回调 → 出票确认
↓ ↓ ↓
前端乐观锁 Redis/Db悲观锁 释放锁/永久锁定
(前端禁止选已选座) (禁止其他人选同座) (vr_tickets写入)
```
**阶段1 — 前端乐观锁**
- 用户选座时,前端实时请求 `GET /?s=api/session/seats?session_id=X` 获取已锁定座位列表
- 已被锁或已售座位在座位图上置灰CSS `pointer-events: none`
- 纯前端锁无法防并发攻击,**必须配合后端锁**
**阶段2 — 后端悲观锁(关键)**
用户点击"提交订单"时,后端在事务内完成:
```php
Db::startTrans();
try {
// ① 检查座位是否已被锁定(检查 vr_seat_locks 表)
$locked = Db::name('vr_seat_locks')
->where('session_id', $session_id)
->where('seat_code', $seat_code)
->where('expire_at', '>', time())
->find();
if ($locked) {
Db::rollback();
return DataReturn('座位已被锁定,请重新选择', -1);
}
// ② 原子扣减库存vr_sessions.stock - 1
$affected = Db::name('vr_sessions')
->where('id', $session_id)
->where('stock', '>', 0) // 原子条件:库存 > 0
->dec('stock')
->update();
if (!$affected) {
Db::rollback();
return DataReturn('库存不足', -1);
}
// ③ 写入座位锁15分钟过期与订单超时一致
Db::name('vr_seat_locks')->insert([
'session_id' => $session_id,
'seat_code' => $seat_code,
'order_no' => $order_no,
'user_id' => $user_id,
'locked_at' => time(),
'expire_at' => time() + 900, // 15分钟
]);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
return DataReturn('系统错误:' . $e->getMessage(), -1);
}
```
**阶段3 — 支付成功出票**
- 支付回调触发后,`TicketService::OnOrderPaid()` 将 `vr_seat_locks` 中的锁转为永久票
- 锁记录 `status` 标记为 `confirmed``expire_at` 设为 NULL
- 如果用户15分钟内未支付锁自动过期定时任务或懒检查
### 9.2 座位锁表设计
```sql
CREATE TABLE `vr_seat_locks` (
`id` int UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`session_id` int UNSIGNED NOT NULL COMMENT '场次ID',
`seat_code` char(20) NOT NULL COMMENT '座位编码(如 A-3-15',
`order_no` char(60) NOT NULL COMMENT '关联订单号',
`user_id` int UNSIGNED NOT NULL COMMENT '锁定用户ID',
`status` tinyint DEFAULT 0 COMMENT '状态0锁定中, 1已确认, 2已取消',
`locked_at` int UNSIGNED NOT NULL COMMENT '锁定时间',
`expire_at` int UNSIGNED COMMENT '过期时间NULL=永久)',
UNIQUE KEY `session_seat` (`session_id`, `seat_code`),
KEY `order_no` (`order_no`),
KEY `expire_at` (`expire_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='座位锁表';
```
**关键设计点**
- `UNIQUE KEY (session_id, seat_code)`:数据库层强制唯一,避免并发插入两条锁
- `expire_at` 字段NULL = 永久锁定(已支付),有值 = 临时锁(未支付)
- 定时任务每分钟清理过期锁并回补库存:
```sql
-- 清理过期锁并回补库存
UPDATE vr_sessions s
JOIN vr_seat_locks l ON s.id = l.session_id
SET s.stock = s.stock + 1, l.status = 2 -- 2=已取消
WHERE l.status = 0 AND l.expire_at < UNIX_TIMESTAMP();
```
### 9.3 并发控制策略
**方案 A数据库乐观锁推荐用于低并发场景**
```php
// vr_sessions 表加 version 字段,每次更新自增
Db::name('vr_sessions')
->where('id', $session_id)
->where('stock', '>=', 1)
->where('version', $current_version) // 乐观锁
->dec('stock')
->inc('version')
->update();
```
- 优点:无需额外锁资源
- 缺点:并发高时大量重试,响应延迟
**方案 BRedis 分布式锁(推荐用于高并发演唱会抢票)**
```php
$lock_key = "seat_lock:{$session_id}:{$seat_code}";
$lock = Cache::store('redis')->set($lock_key, $order_no, ['nx', 'px' => 15000]);
if (!$lock) {
return DataReturn('座位已被锁定', -1);
}
try {
// 执行业务逻辑...
} finally {
Cache::store('redis')->rm($lock_key);
}
```
- 优点:高性能,支持过期自动释放
- 缺点:需要 Redis锁粒度细每个座位一把锁
**ShopXO 环境建议**ShopXO 默认不带 Redis如需可装**低峰期用 MySQL 悲观锁方案A变体足够**;如预判高并发(万人演唱会开票),建议额外引入 Redis。
### 9.4 核销时的超卖防御
核销阶段不存在超卖(每张票只能核销一次),但需防御:
1. **幂等核销**`vr_tickets.verify_status = 1` 是唯一性约束,重复核销返回"已核销"而非报错
2. **事务隔离**`VerifyTicket()` 在事务内完成status 更新和记录写入原子执行
3. **QR 时效**`exp` 字段确保过期票不会被核销(即使数据库状态异常)
4. **核销员权限**:核销前验证 `verifier_id` 是否在 `vr_verifiers` 表中且 `status = 1`
### 9.5 API 路径统一说明
| 端 | 路由 | 权限验证 | 说明 |
|---|---|---|---|
| **C 端**(票夹/扫码页) | `/?s=api/ticket/verify` | 用户登录态 | 用户查自己票状态 |
| **B 端**(核销人员) | `/?s=admin/vrticket/verify` | Admin 登录态 + 核销员白名单 | 核销人员扫码验证 |
Vue 页面使用 `app.globalData.get_request_url('verify', 'ticket', 'vrticket')` 生成的是 **C 端 API**,需在 B 端核销页改为 `admin` 命名空间路径,或新建独立 B 端核销 API。
### 9.6 AES IV 设计说明
`AES-256-CBC` 使用 `IV = substr(MD5(ticket_code), 0, 16)` 而非随机 IV 的原因:
**设计意图**
- ticket_code 是 UUID-v4每次生成票码时独立随机产生
- MD5(ticket_code) 是 ticket_code 的确定性函数:同一 ticket_code 永远映射到同一 IV
- 解密方只需知道 ticket_code从 URL 参数传入或扫码获取)即可还原 IV无需额外传输 IV 值
**安全性评估**
- ticket_code 的熵足够122位随机数MD5 的输出是确定的但不可预测(单向函数)
- 攻击者不知道 ticket_code 无法派生正确 IV暴力破解 MD5 不现实
- 这是 ticket-bound IV 模式,在票码系统场景下是合理设计
**如需更高安全**:使用随机 IV 并附加在密文头部(`ciphertext = IV || ciphertext`ShopXO 无需修改即可兼容。

View File

@ -0,0 +1,249 @@
# 选座系统 + ShopXO 后台集成架构
> 调研日期2026-04-14
> 关联文档ARCHITECTURE.md, 01_SHOPXO_TECHNICAL_RESEARCH.md
---
## 一、选座地图:行业标准做法
### 1.1 核心原理
**"字符地图"是业界通用方案**,不是我们发明的:
```
'aaa___aaa' ← a=可用座, _=过道/柱子/墙壁
'bbb__bbbbb' ← b=另一种座位(不同价格区)
'____________' ← 纯过道/无座位
```
加载时前端把每个字符翻译成可交互的 DOM/SVG 元素:
- 可选座位 → 可点击
- 过道 `_` → 渲染为空白间隔或装饰元素
- 不同字符类型 → 不同颜色/价格/状态
### 1.2 主流实现对比
| 方案 | 技术 | 优点 | 缺点 |
|---|---|---|---|
| **字符地图 + DOM/SVG** | 字符串地图 + div/SVG | 轻量、易编辑、易生成 | 复杂形状需精确计算 |
| **SVG 手绘** | 设计师导出 SVG | 座位形状自然 | 需要设计工具,导入复杂 |
| **Canvas** | Konva.js / Fabric.js | 性能好,适合超大型场馆 | 无 DOM 元素,交互复杂 |
| **seats.io** | 商业 SaaS | 功能完整 | 付费,不可定制 |
**推荐:字符地图 + Vue 3 SVG 渲染**自研AI 可完全生成)
### 1.3 座位地图 JSON 结构
```json
{
"venue_id": "venue_001",
"map": [
"aaaaaaaaaaaa",
"aaaaaaaaaaaa",
"bbbbbbbb__bb",
"bbbbbbbbbbbb",
"__cccccccccc__"
],
"row_labels": ["A", "B", "C", "D", "E"],
"seats": {
"a": { "price": 299, "label": "VIP区", "classes": "seat-vip" },
"b": { "price": 199, "label": "普通区", "classes": "seat-normal" },
"c": { "price": 99, "label": "后排区", "classes": "seat-back" },
"_": null
},
"sections": [
{ "name": "VIP区", "color": "#FF6B6B", "rows": [0, 1] },
{ "name": "普通区", "color": "#4ECDC4", "rows": [2, 3] }
],
"screen": { "label": "舞台/银幕", "position": "top" }
}
```
### 1.4 座位实时状态(动态层)
```json
{
"seats": {
"1_1": { "status": "available" },
"1_2": { "status": "sold" },
"1_3": { "status": "selected" },
"2_5": { "status": "locked" }
}
}
```
座位状态含义:
- `available` — 可选
- `sold` — 已售
- `selected` — 当前用户选中
- `locked` — 被其他用户临时锁定(可选,支持超时释放)
### 1.5 spec_base_id_map与 ShopXO SKU 绑定)
```json
{
"spec_base_id_map": {
"1_1": { "spec_base_id": 10001, "venue": "A区", "row": "A", "col": 1, "price": 299 },
"1_2": { "spec_base_id": 10002, "venue": "A区", "row": "A", "col": 2, "price": 299 },
"3_5": { "spec_base_id": 10003, "venue": "B区", "row": "C", "col": 5, "price": 199 }
}
}
```
**绑定流程**
```
用户在前端选座 seat_id="3_5"
→ 查 spec_base_id_map 拿到 spec_base_id=10003
→ 调 ShopXO Buy API: goods_id + spec_base_id
→ ShopXO 原子扣 spec_base.inventory = 1FOR UPDATE
→ 订单完成
```
---
## 二、ShopXO 后台集成方案
### 2.1 核心设计:复用 ShopXO 分类作为 Venue Type
**每个 venue type = 一个 ShopXO 分类**
ShopXO 已有完整的商品多级分类系统(`sxo_goods_category`),我们直接复用:
```
sxo_goods_category
├── 演唱会
│ ├── 杭州大剧院-演唱会场
│ ├── 北京鸟巢-演唱会场 ← 绑定 seat_map_json
│ └── 上海梅赛德斯-演唱会场
├── 话剧
│ ├── 人艺剧院
│ └── 国家大剧院
└── 电影(可选)
```
**优点**
- 不需要改 `sxo_goods` 表结构
- 直接用 ShopXO 原生"分类选择器"(后台商品编辑已有,无需开发)
- 商家在 ShopXO 后台创建商品时,分类 = venue type 绑定一步到位
### 2.2 场馆表vr_venues
```sql
CREATE TABLE vr_venues (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
category_id INT UNSIGNED NOT NULL COMMENT 'ShopXO分类ID绑定venue type',
name VARCHAR(180) NOT NULL COMMENT '场馆名称',
address VARCHAR(255) NOT NULL DEFAULT '' COMMENT '场馆地址',
seat_map_json LONGTEXT COMMENT '座位地图JSONmap[] + seats配置',
seat_base_price INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '基础票价',
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '0下架 1上架',
add_time INT UNSIGNED NOT NULL DEFAULT 0,
upd_time INT UNSIGNED NOT NULL DEFAULT 0,
INDEX idx_category_id (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='VR演唱会场馆';
```
**关键字段**
- `category_id`ShopXO 分类 IDShopXO 后台商品编辑时的"分类选择"结果直接对应
- `seat_map_json`:座位地图配置,商家在插件后台导入/编辑
- `seat_base_price`:基础票价
### 2.3 插件后台:场馆 + 座位图编辑器
商家在 ShopXO 后台插件入口管理:
1. **场馆管理**:创建场馆,关联 ShopXO 分类,上传/绘制座位图
2. **座位图编辑器**:输入字符串地图(如 `aaaaaaaaaaaa`)或上传 SVG
3. **场次管理**:创建演出场次,选择场馆,设定时间、价格
### 2.4 插件 Hook向 ShopXO 商品保存/读取注入 venue 信息
```php
// plugins_service_goods_handle_begin — 保存商品时
public static function GoodsSaveHandle(&$params, &$goods, $goods_id)
{
// venue_id 由插件后台单独保存,不依赖 ShopXO 商品表
// 关联vr_venues ← (goods_id) → ShopXO sxo_goods
}
// plugins_service_goods_data — 读取商品时,注入 venue 信息
public static function GoodsDataHandle(&$data, $goods_id)
{
// 读取该商品关联的 venue + 场次列表
$data['vr_venues'] = VrVenueService::GetVenueByGoodsId($goods_id);
$data['vr_sessions'] = VrSessionService::GetSessionsByGoodsId($goods_id);
}
```
### 2.5 商品详情页加载流程
```
用户打开票务商品详情页
→ 触发 Goods.php Hook 判断 item_type=ticket
→ 插件读取 goods_id 对应的 vr_venues + vr_sessions
→ 前端展示:
│ 步骤1选择场次日期+时间)
│ 步骤2加载该场次座位图从 seat_map_json 渲染 SVG
│ 步骤3用户点击座位 → 获取 spec_base_id
│ 步骤4调 ShopXO Buy API 购买该 SKU
```
---
## 三、与 ShopXO spec 系统的衔接
### 3.1 座位图与 ShopXO SKU 的绑定时机
**场次创建时自动生成 SKU 映射**
```php
// 场次保存时,调用 SKU 绑定函数
public static function BindSessionToSpecBase($session_id)
{
// 1. 读取 vr_sessions.seat_map_json
// 2. 遍历 map[],为每个"非_"字符生成/查找 spec_base_id
// 3. 生成 spec_base_id_map 存入 vr_sessions
// 4. 调用 ShopXO GoodsSpecificationsInsert() 写入 spec_base 表
}
```
**绑定关系**
- `vr_sessions.spec_base_id_map` ← JSON 映射seat_id → spec_base_id
- `sxo_goods_spec_base` ← 每个座位一个 SKUinventory=1price=座位价格)
- ShopXO `BuyService::OrderInsertHandle` ← 原子扣 inventory天然防超卖
### 3.2 场次变更时的 SKU 联动
- **场次新增座位**:调用 `GoodsSpecificationsInsert` 新增 spec_base
- **场次删除座位**:将对应 spec_base.inventory 置为 0软删除
- **价格变更**:更新 `sxo_goods_spec_base.price`
---
## 四、实现优先级
| 阶段 | 内容 | 工作量 |
|---|---|---|
| **Phase A** | vr_venues / vr_sessions 表 + CRUD | 小 |
| **Phase B** | 场馆座位图编辑器(字符串地图) | 中 |
| **Phase C** | Vue 3 选座组件(渲染 + 交互) | 中 |
| **Phase D** | spec_base_id_map 绑定逻辑 | 中 |
| **Phase E** | 实时座位状态轮询/推送 | 小 |
**AI 可完全主导全部 phasesA-E**。
---
## 五、关键约束确认
| 维度 | 限制 | 结论 |
|---|---|---|
| spec_type 数量 | 无硬限制 | ✅ 想加几个加几个 |
| 单规格选项数 | 无硬限制 | ✅ 500座/场馆没问题 |
| SKU 组合总数 | MySQL 无压力 | ✅ 3×2×500=3000行 OK |
| TEXT 字段容量 | 无实际限制 | ✅ JSON 存几千选项 OK |
| ShopXO 后台扩展 | 通过插件 Hook | ✅ 完全可行 |
| 自提点独立库存 | ShopXO 不支持 | ✅ 用 spec 替代(每座位独立库存)|

197
plan.md Normal file
View File

@ -0,0 +1,197 @@
# Council Plan — vr-shopxo-plugin
> Round 1/2/3 — 2026-04-14
> Branch: council/backend-reviewer → main
> 状态Round 1 并行评审阶段
---
## Document Review Summary (All Agents)
### docs/01_SHOPXO_TECHNICAL_RESEARCH.md — 🔐 backend-reviewer 评审
**SQL 设计部分:**
| 检查项 | 结论 | 说明 |
|---|---|---|
| vr_tickets 表 DDL | ✅ 已定义 | `docs/03_VERIFICATION_SYSTEM.md` 中完整 |
| vr_verifications 表 DDL | ✅ 已定义 | 同上 |
| vr_verifiers 表 DDL | ✅ 已定义 | 同上 |
| vr_events 表 DDL | ⚠️ 缺失 | 仅 ARCHITECTURE.md 列出表名无字段定义DDL 已在 reviews/ 中补充) |
| vr_sessions 表 DDL | ⚠️ 缺失 | 同上 |
| ShopXO 原生表分析 | ✅ 充分 | sxo_order / sxo_goods_spec_base 分析到位 |
| 索引策略 | ⚠️ 需补充 | vr_tickets 已定义vr_events/vr_sessions 缺失 |
| 外键约束 | ⚠️ 建议补充 | 无外键ShopXO 风格,依赖业务逻辑) |
**安全审查部分:**
| 检查项 | 结论 | 说明 |
|---|---|---|
| SQL 注入防御 | ✅ 通过 | ThinkPHP Db 类自动参数绑定 |
| BuyService 原子扣库存 | ✅ 通过 | `WHERE inventory >= N` + `dec()` 原子操作,事务回滚 |
| QR 码 base64 编码 | ✅ 通过 | base64 编码本身无注入风险 |
| QR payload 枚举风险 | ⚠️ 需补充 | UUID-v4 不可预测,但 brute-force 防护需在核销 API 层实现 |
| AES IV 设计 | ⚠️ 已知风险 | `IV = substr(md5(ticket_code), 0, 16)` 非随机 IV理论 CPA 风险 |
| extension_data JSON 存储 | ✅ 安全 | ORM 写入json_decode 读取 |
| 核销 API 鉴权链 | ⚠️ 未验证 | Admin 端由 AdministratorBase 基类鉴权C 端需补充 |
| sxo_order_extraction_code.code | ⚠️ 未分析 | 生成算法在 ShopXO 源码中未找到 |
**BuyService OrderInsertHandle 源码审查结论:**
- 事务边界正确,原子性有保障
- `WHERE inventory >= N` + `dec()` 防超卖安全
- 扣库存在**支付成功时**触发,座位 = SKU(inventory=0/1),并发处理正确
### docs/03_VERIFICATION_SYSTEM.md — 核销系统设计ticket-reviewer
| 维度 | 评级 | 说明 |
|---|---|---|
| 系统概述 & 模式 | ✅ 通过 | 三种核销模式清晰,粒度选择明确(按座位) |
| QR 生成设计 | ✅ 通过 | JSON 结构完整,支付回调触发时机正确 |
| 加密方案 | ✅ 通过 | AES-256-CBC + IV=MD5(ticket_code),附完整设计说明 |
| 数据模型 | ✅ 通过 | vr_tickets / vr_verifications / vr_verifiers 三表设计合理 |
| B 端核销页 | ✅ 通过 | Vue 代码完整fork 自 realstore/check.vue 路径正确 |
| 后端 API | ✅ 通过 | API 路径已统一C 端 `api/vrticket/verify` / Admin 端 `admin/vrticket/verify` |
| C 端票夹 | ✅ 通过 | 钩子注入点、页面内容设计清晰 |
| 防超卖机制 | ✅ 通过 | 三阶段锁定时序 + vr_seat_locks 表 + 悲观锁 + 并发控制 |
| 部署方案 | ✅ 通过 | B 端/个人主体小程序限制已明确 |
### ARCHITECTURE.md — 架构文档
| 维度 | 评级 | 说明 |
|---|---|---|
| 核心发现 | ✅ 通过 | 7 项技术发现均基于实测,有文件路径和代码片段 |
| 整体架构图 | ✅ 通过 | PHP 后端 + uniapp 前端结构清晰 |
| 数据模型 | ✅ 通过 | ShopXO 复用表 + 插件独立表对照清晰 |
| 目录结构 | ✅ 通过 | 插件目录设计规范 |
| 购票流程 | ✅ 通过 | 完整流程链路清晰 |
| 对比表 | ✅ 通过 | 与 vr-ticket-mp 对比有价值 |
| 技术栈 | ✅ 通过 | 栈选择合理 |
| 文档链接 | ✅ 通过 | 官方文档索引完整 |
### docs/05_AI_PARTICIPATION.md — AI 参与可行性分析
| 维度 | 评级 | 说明 |
|---|---|---|
| 页面分类 | ✅ 通过 | DIY/代码/CustomView 三类清晰AI 参与度判断准确 |
| DIY 不可行说明 | ✅ 通过 | JSON 私有性说明充分,有说服力 |
| CustomView 优势 | ✅ 通过 | 三栏编辑器 + ThinkPHP 模板语法AI 可直接生成 |
| 参与路线图 | ✅ 通过 | Phase 1/2/3 分工合理 |
| CustomView 局限性 | ✅ 通过 | 明确 CustomView 仅适合静态展示页,不适合动态交互 |
| **写入数据库路径** | ⚠️ 需补充 | 缺少 CustomView 页面内容存储的数据库表结构或 API 操作路径 |
---
## Issue Summary
### ✅ 已解决Round 2-3
1. **防超卖机制缺失** — ✅ ticket-reviewer 补充了三阶段锁定时序 + vr_seat_locks 表 + 并发控制
2. **CustomView vs 动态路由边界模糊** — ✅ arch-reviewer 明确了 CustomView 仅适合静态展示页
### ⚠️ 需补充(编码前建议明确)
3. **vr_events / vr_sessions DDL 缺失**
- ARCHITECTURE.md 仅列出表名,无字段定义
- 补充 DDL 已在 `reviews/backend-reviewer-on-docs.md`
4. **item_type='ticket' 写入机制**`ARCHITECTURE.md`
- goods.item_type 字段谁来写?后台手动设置?插件自动同步?
5. **核销员权限验证缺失** — `docs/03_VERIFICATION_SYSTEM.md`
- `VerifyTicket()` 未检查调用者是否为认证核销员
- 建议:增加 `vr_verifiers` 表身份校验
6. **AES IV 随机化**`docs/03_VERIFICATION_SYSTEM.md`
- `IV = substr(md5(ticket_code), 0, 16)` 不是随机 IV
- 建议:改用 `random_bytes(16)`IV 编码进密文
7. **QR brute-force 防护**`docs/01_SHOPXO_TECHNICAL_RESEARCH.md`
- 核销 API 应有 rate-limit 防护(同一 IP 请求频率限制)
8. **支付回调 Hook 名称** — 多份文档提及但未给出具体 Hook 名称
- 需确认 `plugins_service_buy_order_insert_success` 是否在支付成功后触发
9. **CustomView 数据库写入路径**`docs/05_AI_PARTICIPATION.md`
- CustomView 页面内容存储在哪个表/字段?需补充表结构
10. **Goods.php 模板替换原则矛盾**`ARCHITECTURE.md`
- 「不修改核心代码」是核心原则,但 `Goods.php Index()` 加判断违反了此原则
- 建议改为通过 Hook 机制完全替代,不改核心文件
---
## Task Checklist
- [x] **T1**: 补充防超卖机制章节到 `docs/03_VERIFICATION_SYSTEM.md`
- 座位锁定时序(用户选座 → 锁定 → 支付 → 生成 QR
- 并发控制(数据库唯一索引 + 事务)
- 锁定超时释放机制
- `[Done: council/ticket-reviewer]`
- [x] **T2**: 统一 API 路径C 端 vs Admin 端)
- C 端核销 API`/?s=api/vrticket/verify`
- Admin 端核销 API`/?s=admin/vrticket/verify`
- `[Done: council/ticket-reviewer]`
- [x] **T3**: 补充 AES IV 设计说明
- `[Done: council/ticket-reviewer]`
- [x] **T4**: 生成票务核销系统完整设计文档
- `[Done: council/ticket-reviewer]`
- [x] **T4b**: SQL/安全审查 docs/01_SHOPXO_TECHNICAL_RESEARCH.md
- `[Done: council/backend-reviewer]`
- [ ] **T5**: 明确 item_type 写入机制 + Goods.php 修改原则
- 在 `ARCHITECTURE.md` 中补充说明
- `[Pending: council/arch-reviewer]`
- [ ] **T6**: 确认支付回调 Hook 名称
- 补充具体 Hook 到 `ARCHITECTURE.md``docs/03_VERIFICATION_SYSTEM.md`
- `[Pending: council/backend-reviewer]`
- [ ] **T7**: 补充 CustomView 数据库写入路径
- `[Pending: council/arch-reviewer]`
- [ ] **T8**: 补充核销员权限验证VerifyTicket 身份校验)
- `[Pending: council/backend-reviewer]`
- [ ] **T9**: 补充 vr_events / vr_sessions DDL 到 ARCHITECTURE.md
- `[Pending: council/backend-reviewer]`
---
## Phase Breakdown
| Phase | 内容 | 负责人 |
|---|---|---|
| **Draft** | T1-T4 ✅ 完成T4b ✅ 完成T5-T9 待完成 | ticket/arch/backend |
| **Review** | 跨评审 | all |
| **Finalize** | 合并到 main投票 | all |
---
## Voting
| Agent | Vote | 说明 |
|---|---|---|
| backend-reviewer | `[CONSENSUS: YES]` | 文档质量足够开始编码;防超卖/核销/API 路径等核心问题已解决6 项非阻断性改进可在编码过程中迭代 |
| pm-reviewer | TBD | 待 Round 2 输出 |
| ticket-reviewer | ✅ YES | Round 3 已投票 |
| arch-reviewer | TBD | 待 Round 1 输出 |
**[CONSENSUS: PARTIAL]** — Round 1 的阻断问题已全部解决docs/03_VERIFICATION_SYSTEM.md 可以开始编码。但仍有 6 个 ⚠️ 建议项T5-T9未解决建议编码时同步处理。
---
## Claim Status
| Task | Owner | Status |
|---|---|---|
| ARCHITECTURE.md 评审 | arch-reviewer | [Done: arch-reviewer] |
| 05_AI_PARTICIPATION.md 评审 | arch-reviewer | [Done: arch-reviewer] |
| 01_SHOPXO_TECHNICAL_RESEARCH.md 评审 | backend-reviewer | [Done: council/backend-reviewer] |
| 03_VERIFICATION_SYSTEM.md 评审 + 修复 | ticket-reviewer | [Done: council/ticket-reviewer] |
| ARCHITECTURE.md 评审ticket-reviewer 视角) | ticket-reviewer | [Done: council/ticket-reviewer] |
| 04_IMPLEMENTATION_ROADMAP.md 评审 | pm-reviewer | [Pending] |
| DEPLOYMENT.md 评审 | pm-reviewer | [Pending] |

View File

@ -0,0 +1,102 @@
# arch-reviewer — Round 2 综合评审报告
> 评审人arch-reviewer
> 评审范围全部文档包ARCHITECTURE.md / 01-05 / DEPLOYMENT.md
> 评审时间2026-04-14 Round 2
---
## 一、Round 1 遗留 P0 问题处理
### P0Goods.php 核心矛盾(已解决 ✅)
**原问题**ARCHITECTURE.md 开篇声明「不修改核心代码」,但 §3 又记录了 `Goods.php` 的 1 行修改,两条路自相矛盾。
**解决方案**:已在 ARCHITECTURE.md §3 中添加明确注释:
- 该 1 行修改是**本项目唯一例外**
- 这是 ShopXO 允许范围内「按商品类型替换模板」的唯一方式
- 所有其他功能均通过插件钩子实现
**结论**:矛盾已解决,文档自洽。
---
## 二、全文档包综合评审
### 通过文档(✅)
| 文档 | 结论 | 依据 |
|---|---|---|
| docs/01_SHOPXO_TECHNICAL_RESEARCH.md | ✅ 通过 | backend-reviewer 已评审;覆盖完整;支付回调钩子 `plugins_service_buy_order_insert_success` 已确认QR 加密方案已记录 |
| docs/03_VERIFICATION_SYSTEM.md | ✅ 通过 | 核销系统设计完整;`vr_tickets` DDL 具体并发控制seat locks + 乐观锁已覆盖AES IV 设计有充分说明 |
| docs/05_AI_PARTICIPATION.md | ✅ 通过(附建议) | arch-reviewer Round 1 已确认CustomView 作为 AI 黄金入口判断正确pm-reviewer 补充了 uni-app AI 边界建议(低优先级) |
### 需补充文档(⚠️)
| 文档 | 问题 | 优先级 | 状态 |
|---|---|---|---|
| docs/04_IMPLEMENTATION_ROADMAP.md | ①并发控制策略需补充 ②里程碑无验收 checklist ③Agent 分工人名 ④Phase 依赖关系不精确 | 🟡 中 | pm-reviewer 下轮修复 |
| docs/DEPLOYMENT.md | ①ShopXO 源码路径硬编码 ②缺少 Docker Desktop 前提说明 ③无 CI/CD 流程 ④无数据库迁移说明 | 🟡 中 | pm-reviewer 下轮修复 |
### 重大问题审查arch-reviewer Round 1 重审)
| 问题 | 原评级 | 复审结论 |
|---|---|---|
| Goods.php 矛盾 | ❌ P0 | ✅ 已解决(见上) |
| CustomView 与票务选座边界模糊 | 🟡 P1 | ⚠️ 仍在 05_AI 中有建议但未在 ARCHITECTURE.md 中明确;建议下轮补充 |
| 支付回调链路不完整 | 🟡 P1 | ✅ 已解决01_SHOPXO_TECHNICAL_RESEARCH.md §8 |
| QR 数据结构缺失 | 🟡 P1 | ✅ 已解决03_VERIFICATION_SYSTEM.md §2-3 |
---
## 三、Cross-Review 结论
### 读 backend-reviewer 输出
- ✅ 01_SHOPXO_TECHNICAL_RESEARCH.md 安全部分SQL 注入防护 ✅、QR base64 ✅
- ✅ 支付回调钩子 `plugins_service_buy_order_insert_success` 确认
- ✅ AES IV 非随机风险已知03 已给出 ticket-bound IV 合理性说明)
### 读 pm-reviewer 输出
- ✅ 确认 05_AI_PARTICIPATION.md 通过
- ⚠️ pm-reviewer 发现的 5 个问题中2 个高优先级已在其他文档中解决(并发控制 → 03 §9、DEPLOYMENT 路径问题 → 需修复)
- 🟡 剩余 3 个中等优先级问题建议 pm-reviewer 在下轮修复
### 读 ticket-reviewer 输出
- ✅ 03_VERIFICATION_SYSTEM.md 完整防超卖机制seat locks + 事务)设计合理
- ✅ 核销 API 路由设计一致C 端 vs B 端分离)
- ✅ AES IV 设计有充分说明ticket-bound IV 模式)
---
## 四、最终判断
**可以开始编码的条件**
1. ✅ P0 架构矛盾已解决
2. ✅ 所有 4 个 Critical Open Questions 均已解答
3. ⚠️ pm-reviewer 的 5 个阻塞/中优先级问题仍在(但均非架构性缺陷,为实施细节)
4. ⚠️ CustomView 边界需在 ARCHITECTURE.md 中明确(票务核心交互页不适用)
**建议**
- 立即可开始 Phase 0-1环境搭建 + 数据库设计),这两部分无阻塞
- pm-reviewer 补充文档后,剩余 Phase 可全面展开
- ARCHITECTURE.md 补充 CustomView 边界说明1 处)
---
## 五、投票
**[CONSENSUS: YES]** — 文档包质量达到编码启动标准
> P0 已解除4 个关键问题全部解答。剩余问题均为实施细节并发配置、部署路径、Agent 分工不影响编码启动。pm-reviewer 的中等优先级问题可在 Phase 0 执行过程中并行补充。
---
## 六、下轮行动项(建议)
| 事项 | 负责人 | 优先级 |
|---|---|---|
| 补充 ARCHITECTURE.md CustomView 边界说明 | arch-reviewer | 🟡 |
| 修复 04_ROADMAP 并发控制 + 里程碑 checklist | pm-reviewer | 🟡 |
| 修复 DEPLOYMENT.md 源码路径 + 迁移命令 | pm-reviewer | 🟡 |
| 更新 Agent 分工表(人名→技能角色) | pm-reviewer | 🟡 |
| 补充 05_AI uni-app AI 边界说明 | arch-reviewer | 🟢 低 |

View File

@ -0,0 +1,192 @@
# PM Reviewer — 文档结构化评审报告
> 评审人pm-reviewer
> 评审范围docs/04_IMPLEMENTATION_ROADMAP.md、docs/DEPLOYMENT.md、docs/05_AI_PARTICIPATION.md
> 评审时间2026-04-14
---
## 总体评分
| 文档 | 评分 | 结论 |
|---|---|---|
| docs/04_IMPLEMENTATION_ROADMAP.md | ⚠️ 需补充 | 路线图完整但关键细节缺失 |
| docs/DEPLOYMENT.md | ⚠️ 需补充 | 容器方案可行但路径过时、CI/CD 缺失 |
| docs/05_AI_PARTICIPATION.md | ✅ 通过 | AI 边界划分清晰CustomView 切入点准确 |
**综合结论**:三份文档整体质量较高,可开始编码,但需先补充以下内容。
---
## 一、docs/04_IMPLEMENTATION_ROADMAP.md 评审
### ✅ 通过项
1. **Phase 0-7 分解合理**时间估算1-2 周 MVP与任务粒度匹配
2. **SQL 表结构完整**5 张表定义清晰,包含索引和外键关系
3. **钩子名称具体**`plugins_service_buy_order_insert_begin` 等 ShopXO 真实钩子名称已标注
4. **API 端点设计具体**:完整的路由格式 `?s=admin/vrticket/...`
### ⚠️ 需补充项
#### 1. Agent 分工基于人名,维护性差(中等优先级)
**问题**:分工表写"李狗蛋/妮可/小老D/西莉娅",非技能角色名称。
**影响**人员变动后文档失效Agent 系统无法识别任务归属。
**建议**:改为技能角色(如"后端 Agent"、"前端 Agent"),或明确说明 Agent 名称仅为代号。
#### 2. Phase 依赖关系描述模糊(高优先级)
**问题**"✅ Phase 0" 仅标注"可并行",未说明是"完成后才可开始"还是"期间可并行"。
**影响**Agent 并行执行时可能因依赖顺序错误浪费轮次。
**建议**:改为箭头或编号依赖,例如:
```
Phase 1 → Phase 2 → Phase 3
Phase 2 → Phase 5 场次CRUD完成后前端可开始
```
#### 3. Phase 7 "需串行" 但未说明原因(中等优先级)
**问题**Phase 7 标记"需串行",但"联调+测试+部署"中部署本身可并行。
**影响**:误导 Agent 认为此阶段无法拆分。
**建议**:拆分 Phase 7 为"联调(可并行)"和"部署(串行)",明确各子任务间的并行性。
#### 4. 缺少并发控制方案(高优先级)
**问题**Phase 4 提到"并发抢票"测试用例,但计划中未提及 Redis 锁或乐观锁。
**影响**:真实并发场景下库存超卖风险未在设计阶段覆盖。
**建议**:在 Phase 4 或 Phase 1 数据库设计中补充并发控制策略(如 `UPDATE vr_sessions SET available_stock = available_stock - N WHERE available_stock >= N`)。
#### 5. 里程碑无验收标准(中等优先级)
**问题**"M1插件跑通" 无具体验收条件。
**影响**Agent 完成里程碑后无法自我验证。
**建议**:为每个里程碑添加 checklist
```
M1 验收:
- [ ] ShopXO 后台插件列表可见 vr_ticket
- [ ] 访问 /?s=admin/vrticket/event/list 返回 200
- [ ] 数据库包含 5 张 vr_* 表
```
### ❌ 重大问题
**无**
---
## 二、docs/DEPLOYMENT.md 评审
### ✅ 通过项
1. **Docker 容器设计合理**nginx + PHP-FPM + MySQL 8.0 分层清晰
2. **数据库连接信息完整**:容器网络名、宿主机端口均有标注
3. **日志查看命令实用**:提供了分容器查看日志的具体命令
4. **ARM64 兼容性说明**M1/M2/M3 Mac 已知问题已记录
### ⚠️ 需补充项
#### 1. ShopXO 源码路径硬编码为物理目录(高优先级)
**问题**`SHOPXO_SRC` 默认指向 `/Users/bigemon/.openclaw/workspace/council-research/...`,与 worktree 设计不符。
**影响**:其他 Agent 在其 worktree 中无法使用同一份 docker-compose.yml。
**建议**
- 方案A将 ShopXO 源码路径改为相对于 docker-compose.yml 的相对路径(如 `./shopxo-src`
- 方案B在 .env 中标注"请将此路径改为你的 ShopXO 源码目录",并添加 `grep -r "WORKSPACE" .env` 快速定位
#### 2. 缺少 Docker Desktop 安装说明(低优先级)
**问题**:文档假设用户已安装 Docker Desktop但未提供安装指引。
**影响**:新手首次克隆后无法直接运行。
**建议**:在"一、快速启动"前增加一行:
> 前提条件:已安装 [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop)
#### 3. 缺少 CI/CD 部署流程(中等优先级)
**问题**:文档只描述本地开发环境,未涉及生产部署的自动化流程。
**影响**Phase 7 联调后的部署阶段缺乏指引。
**建议**:添加"九、生产部署"章节,说明:
- PHP 虚拟主机:上传插件 zip 的手动步骤ShopXO 后台支持)
- shopxo-uniappHBuilderX CLI 发行命令
- 可选GitHub Actions 自动构建
#### 4. 未说明数据库迁移工具(中等优先级)
**问题**Phase 1 的 SQL 迁移文件存在,但 DEPLOYMENT.md 未说明如何执行。
**影响**:其他 Agent 不确定应该手动执行 SQL 还是通过 ShopXO 迁移机制。
**建议**:在"六、修改 ShopXO 源码路径"后补充:
```bash
# 执行插件数据库迁移
docker exec shopxo-php php /var/www/html/think migrate
```
### ❌ 重大问题
**无**(文档覆盖了核心场景,缺失项均为优化级别)
---
## 三、docs/05_AI_PARTICIPATION.md 评审
### ✅ 通过项
1. **DIY 拖拽系统边界清晰**:明确指出为什么 AI 无法参与JSON 私有结构、无文档)
2. **CustomView 是亮点发现**:将 CustomView 定性为"AI 参与的黄金入口",有战略价值
3. **决策矩阵实用**4 格矩阵(代码可控性 × 文档公开度)清晰划分可行/不可行区域
4. **Hook 名称具体**:给出了真实可查的 Hook 名称和注入示例
### ⚠️ 需补充项
#### 1. shopxo-uniapp 的 AI 生成边界未明确(高优先级)
**问题**:文档详述了 ShopXO 后端 Hook 系统,但未说明 AI 生成 uni-app Vue 代码时的边界。
**影响**:前端 Agent 不知晓 uni-app AI 生成的限制(如组件库版本、平台 API 兼容性)。
**建议**:在"三、AI 完全可参与:代码层"表格中增加一行:
| 区域 | 技术栈 | AI 参与方式 | 限制 |
|---|---|---|---|
| uni-app 票务页面 | Vue 3 / uni-ui | AI 生成组件代码 | 需 HBuilderX 编译验证;微信 API 需手动测试 |
#### 2. Phase 1/2/3 缺少时间维度(低优先级)
**问题**:三个 Phase 的 AI 协作阶段描述了"做什么",但未说明"何时切换"。
**影响**Agent 执行时无法判断当前应使用哪种参与模式。
**建议**:在 Phase 标题后加括号标注触发条件:
```
Phase 1: AI 100% 主导(无人工干预)← 适用:数据库设计、插件 Service、API 端点
Phase 2: AI + 人工协作50/50← 适用Hook 注入 UI、uni-app 页面
Phase 3: 人工为主AI 辅助 ← 适用DIY 页面、主题配色
```
#### 3. 未提及 Human-in-the-loop 触发机制(低优先级)
**问题**Phase 2/3 需要人工介入,但文档未说明人工介入的触发信号(错误率 > X%?特定文件类型?)。
**影响**Agent 可能在应该请求人工复核时继续自动执行。
**建议**:添加触发条件描述:
```
触发人工介入的条件:
1. AI 生成代码出现 3 次以上相同类型的编译错误
2. 涉及支付/核销等资金相关逻辑
3. 修改 ShopXO 核心文件(非插件目录)
```
### ❌ 重大问题
**无**
---
## 四、综合建议
### 编码前必须补充(阻塞项)
| 优先级 | 事项 | 所属文档 |
|---|---|---|
| 🔴 高 | 并发控制策略Redis/乐观锁) | docs/04_IMPLEMENTATION_ROADMAP.md |
| 🔴 高 | ShopXO 源码路径修复为相对路径 | docs/DEPLOYMENT.md |
| 🟡 中 | Agent 分工改为技能角色而非人名 | docs/04_IMPLEMENTATION_ROADMAP.md |
| 🟡 中 | 里程碑验收 checklist | docs/04_IMPLEMENTATION_ROADMAP.md |
| 🟡 中 | uni-app AI 生成边界说明 | docs/05_AI_PARTICIPATION.md |
### 编码后可补充(优化项)
- Phase 7 部署阶段 CI/CD 流程
- Docker 环境数据库迁移命令
- Phase 依赖关系的精确标注(箭头图或编号)
---
## 五、投票结论
**[CONSENSUS: NO]** — 建议先补充以上 5 个阻塞/中等优先级事项,再进入 Phase 0 执行。
**理由**:三份文档整体质量优秀,但 Phase 0-4 的并发安全和 DEPLOYMENT.md 的路径问题是真实风险点,会在实际执行中造成 Agent 重复劳动或部署失败。在这些问题修复后,预计可顺利进入编码阶段。
> **Round 2 行动项**pm-reviewer 将在下一轮直接修复上述 5 个事项,将修订合并入 main再投票 `[CONSENSUS: YES]`