Compare commits

...

3 Commits

Author SHA1 Message Date
Council 470ffdeec0 council(draft): BackendArchitect - Phase 2 technical assessment findings + merged council report
Key findings:
- Buy::Index() POST/GET dual-path: submit() uses GET (fails) instead of POST
- Field name mismatch: goods_params vs goods_data
- ShopXO spec matches by type:value, not spec_base_id direct
- Recommend: hidden form POST, plugin API for sold-seats

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:41:26 +08:00
Council e5736338bd council(draft): BackendArchitect - plan for Phase 2 technical assessment
- Add plan for VR ticket P0-P2 issue evaluation
- Tasks: B1-B5 (BackendArchitect), F1-F4 (FrontendDev), P1-P3 (FirstPrinciples)
- Output: reviews/council-phase2-assessment.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 08:32:25 +08:00
Council bed933e8df council(draft): BackendArchitect - plan for frontend template research Q1-Q4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 23:10:43 +08:00
3 changed files with 503 additions and 40 deletions

96
plan.md
View File

@ -1,57 +1,73 @@
# Plan — 场馆删除后规格重复(幽灵 spec根因调研
# Plan — VR 演唱会票务小程序 Phase 2 技术评估
> 版本v1.0 | 日期2026-04-20 | Agentcouncil/BackendArchitect
> 任务:调研「场馆删除后编辑商品出现规格重复错误」问题
> 版本v1.0 | 日期2026-04-21 | Agentcouncil/BackendArchitect
> 任务:评估 4 个已知问题(购物车格式 P0、舞台缩放 P1、spec 加载 P1、商品详情 P2
---
## BackendArchitectTask B1-B6
## Phase 2 评估任务分配
- [x] [Done: council/BackendArchitect] **Task B1**: AdminGoodsSaveHandle.php 全链路追踪 — vr_goods_config 读取/解析/snapshot 重建
- [x] [Done: council/BackendArchitect] **Task B2**: spec_base_id_map 如何被转换成规格项(已验证:存储在模板表,与幽灵 spec 无关)
- [x] [Done: council/BackendArchitect] **Task B3**: SeatSkuService GetGoodsViewData 模板不存在时的 fallback单模板处理多模板有缺陷
- [x] [Done: council/BackendArchitect] **Task B4**: 幽灵 spec 产生环节 + 清理时机(保存时未清理,写回 DB
- [x] [Done: council/BackendArchitect] **Task B5**: 商品保存规格去重逻辑GoodsService.php:1859
- [x] [Done: council/BackendArchitect] **Task B6**: 根因分析报告(含行号)→ `reviews/council-ghost-spec-BackendArchitect.md`
### BackendArchitect
| Task | 内容 | 状态 |
|------|------|------|
| B1 | 分析 GoodsCartService::Save 的真实 API 契约(期望参数格式) | [Done: council/BackendArchitect] |
| B2 | 验证 ticket_detail.html submit() 的 params 构造是否符合规范 | [Done: council/BackendArchitect] |
| B3 | ShopXO spec 加载标准端点调研GoodsSpecDetail 等) | [Done: council/BackendArchitect] |
| B4 | 在 ticket_detail.html 中优雅加载规格/库存的方案设计 | [Done: council/BackendArchitect] |
| B5 | 整合 BackendArchitect findings → `reviews/BackendArchitect-on-phase2.md` | [Done: council/BackendArchitect] |
### FrontendDev
| Task | 内容 | 状态 |
|------|------|------|
| F1 | 修复 .vr-stage 缩放问题方案 | [To Claim: council/FrontendDev] |
| F2 | submit() 多座位串行提交逻辑优化分析 | [To Claim: council/FrontendDev] |
| F3 | goods_spec_data 与前端价格联动方案 | [To Claim: council/FrontendDev] |
| F4 | 商品图片/详情内容加载现状评估 | [To Claim: council/FrontendDev] |
### FirstPrinciples
| Task | 内容 | 状态 |
|------|------|------|
| P1 | 多座位提交 API 设计正交性分析 | [To Claim: council/FirstPrinciples] |
| P2 | spec_base_id_map 设计复杂度评审 | [To Claim: council/FirstPrinciples] |
| P3 | 票务场景是否需要走购物车流程?关键问题识别 | [To Claim: council/FirstPrinciples] |
---
## 根因结论
## 阶段划分
| 优先级 | 根因 | 文件:行号 |
|--------|------|-----------|
| **P1** | 无效 config 块未从数组移除,`continue` 后脏数据写回 DB | AdminGoodsSaveHandle.php:88-89 + 148-150 |
| **P2** | GetGoodsViewData 单模板模式,多模板时覆盖有效块 | SeatSkuService.php:368 + 386-388 |
| **P3** | BatchGenerate 对无效 template_id 返回 code=-2阻断保存 | AdminGoodsSaveHandle.php:164-170 |
| **P4** | 前端过滤后 configs 为空时用户无声失去配置 | AdminGoodsSave.php:196-229 |
| **P5** | loadSoldSeats 未实现TODO 注释) | ticket_detail.html:375-383 |
| Round | 内容 | 产出 |
|-------|------|------|
| Round 1当前 | 各自调研BackendArchitect 读关键代码 | plan.md 更新 + 各自 findings 文件 |
| Round 2 | 合并 findings交叉 Review | `reviews/council-phase2-assessment.md` 草稿 |
| Round 3 | 收敛共识,形成修复方案建议 | 完整 assessment 文档 + 可执行 todo |
| Round 4 | 最终 review + 投票 | `[CONSENSUS: YES]` |
---
## 关键文件
## BackendArchitect 调研计划
| 文件 | 关注点 |
|------|--------|
| `shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php` | P1 根因continue 不删除脏 config |
| `shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` | GetGoodsViewDataP2 根因,多模板处理缺陷 |
| `shopxo/app/plugins/vr_ticket/hook/AdminGoodsSave.php` | 前端过滤逻辑P4 体验问题 |
| `shopxo/app/plugins/vr_ticket/admin/Admin.php` | VenueDelete硬删除逻辑第 888 行) |
| `shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html` | loadSoldSeats 未实现P5 |
| `shopxo/app/service/GoodsService.php` | 规格列值去重检测(第 1859 行) |
**关键文件列表**(按优先级):
1. `shopxo/app/service/GoodsCartService.php` — Save() 方法签名 + 参数契约
2. `shopxo/app/service/GoodsService.php` — GoodsSpecDetail() 规格加载
3. `shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html` — submit() 函数
4. `shopxo/app/plugins/vr_ticket/service/SeatSkuService.php` — GetGoodsViewData()
5. `docs/VR_GOODS_CONFIG_SPEC.md` — v3.0 配置格式
6. `docs/PHASE2_PLAN.md` — Phase 2 计划文档
**调研目标**
- B1: 找到 GoodsCartService::Save 的参数期望goods_id, spec_base_id, stock 等字段)
- B2: 对比 ticket_detail.html submit() 当前构造的 params
- B3: 找到 ShopXO spec 数据加载的入口(是否有 API endpoint 可被前端直接调用?)
- B4: 设计 ticket_detail.html 中加载真实库存的最小方案
---
## 修复方案
## 输出目标
### P1 Fix立即实施
1. AdminGoodsSaveHandle.php:88 — `continue` 改为 `unset($configs[$i])`
2. AdminGoodsSaveHandle.php:145 后 — 添加 `$configs = array_values($configs);`
3. AdminGoodsSaveHandle.php:148 — 写回前加 `if (!empty($configs))`
4. AdminGoodsSaveHandle.php:158-173 — BatchGenerate 前增加模板存在性显式校验
### P2 Fix高优先级
1. SeatSkuService.php GetGoodsViewData — 遍历所有有效配置块,不只处理 `$vrGoodsConfig[0]`
2. 修改 DB 写回逻辑为写回 `validConfigs` 而非 `[$config]`
### P3 Fix中优先级
1. AdminGoodsSave.php — configs 为空时提示用户重新选择场馆
- BackendArchitect findings: `reviews/BackendArchitect-on-phase2.md`
- 最终合并文档: `reviews/council-phase2-assessment.md`
- 每个问题的根因分析 + 前后端修复方案 + API 设计建议 + 第一性原则关键提醒

View File

@ -0,0 +1,229 @@
# BackendArchitect Phase 2 技术评估 Findings
> Agent: council/BackendArchitect | Date: 2026-04-21 | Round 2
---
## B1: GoodsCartService::Save API 契约分析
### 结论:`Save()` 方法未找到,但找到了真正的下单入口
**关键发现**ShopXO 的票务下单流程 **不经过购物车**,而是直接 POST 到 `index/buy/index`
```php
// Buy.php Index() — 真正的入口
public function Index()
{
if($this->data_post)
{
// 将数据存储到缓存,以 user_id 为 key
BuyService::BuyDataStorage($this->user['id'], $this->data_post);
return MyRedirect(MyUrl('index/buy/index'));
}
// 读取缓存,展示订单确认页
$buy_data = BuyService::BuyDataRead($this->user['id']);
}
```
**真正接收的数据结构**(来自 `BuyService::BuyInitialize`
```php
// BuyService.php ~line 51 — 参数契约
$p = [
[
'checked_type' => 'empty',
'key_name' => 'goods_data', // ← 核心字段
'error_msg' => MyLang('common_service.buy.buy_goods_data_error_tips'),
],
];
// goods_data 格式:
// [{goods_id, spec, stock, ...}]
// 或从 base64 解码json_decode(base64_decode(urldecode($params['goods_data'])), true)
```
### BuyService::BuyInitialize 处理流程
```php
foreach($params['goods_data'] as $v) {
// 1. 规格解析 — GoodsSpecificationsHandle()
// 期望: {goods_id, spec: [{type, value}], stock, extension_data?, ...}
$goods['spec'] = self::GoodsSpecificationsHandle($v);
// 2. 调用 GoodsService::GoodsSpecDetail(spec: [{type, value}])
// ← 关键:这里通过 spec.value 匹配 GoodsSpecValue 表,而不是 spec_base_id
// 如果 spec 为空但商品有多规格,必须报错
// 如果 spec 不为空但商品无规格,也必须报错
// 3. 从返回的 spec_base 获取 inventory, price, spec_base_id
$goods['inventory'] = $goods_base['data']['spec_base']['inventory'];
$goods['price'] = $goods_base['data']['spec_base']['price'];
$goods['spec_base_id'] = $goods_base['data']['spec_base']['id'];
}
```
### 结论B1
**ShopXO 的 spec 匹配机制是 `type:value` 匹配,不是 `spec_base_id` 直接传递。**
`GoodsSpecDetail` 内部逻辑:
1. 从 `params['spec']` 提取 `value` 数组 → `spec = array_column($params['spec'], 'value')`
2. `WHERE goods_id=X AND value IN (...)` 查询 `GoodsSpecValue` 表 → 得到 `goods_spec_base_id`
3. 从 `GoodsSpecBase` 读取最终规格记录
---
## B2: ticket_detail.html submit() 参数校验
### 当前代码submit 函数)
```javascript
var goodsParamsList = this.selectedSeats.map(function(seat, i) {
var specBaseId = self.specBaseIdMap[seat.seatKey] || self.sessionSpecId;
return {
goods_id: self.goodsId,
spec_base_id: parseInt(specBaseId) || 0, // ← 直接传 ID
stock: 1,
extension_data: JSON.stringify({...})
};
});
// 重定向到 checkoutUrl:
var checkoutUrl = this.requestUrl + '?s=index/buy/index' +
'&goods_params=' + encodeURIComponent(goodsParams); // ← 拼到 URL
```
**问题 1严重**`goods_params` 是 URL 参数,而不是 POST body。
- ShopXO `Buy::Index()` 通过 `$this->data_post` 判断是否 POST
- URL 参数在 `$_GET`,不在 `$_POST`,所以 `$this->data_post` 可能是 `false`
- 应该用 `<form method="POST">` 提交,或者用 JS `fetch('/?s=index/buy/index', {method:'POST', body: JSON.stringify(...)})`
- 当前的重定向方式 `$location.href = checkoutUrl` → GET 请求,无法触发 POST 分支
**问题 2中等**`BuyService::BuyInitialize` 期望的字段是 `goods_data`,不是 `goods_params`
**问题 3严重**`BuyInitialize` 期望的 `spec` 格式是 `[{type, value}]`,不是 `spec_base_id`
- 当前代码直接传 `spec_base_id`,不经过 ShopXO 的规格匹配逻辑
- ShopXO 会调用 `GoodsSpecDetail({id, spec: [{type, value}]})`,通过 `value` 匹配规格
- 如果 `specBaseIdMap` 存储的是规格值而非 `{type, value}` 对象,则不兼容
**问题 4中等**`extension_data` 不是标准字段ShopXO 的订单系统不会处理。
---
## B3: ShopXO spec 加载标准端点
### 关键端点GoodsService::GoodsSpecDetail
**参数**
```php
[
'id' => goods_id,
'spec' => [ // ← 必须是 [{type, value}] 格式
['type' => '场次', 'value' => '2026-04-21 19:00'],
['type' => '座位区', 'value' => 'A区'],
],
'stock' => 1 // 可选,数量
]
```
**返回**
```json
{
"code": 0,
"data": {
"spec_base": {
"id": 2001,
"price": 599.00,
"inventory": 50,
"original_price": 799.00,
"buy_min_number": 1,
"buy_max_number": 5
}
}
}
```
### spec 加载链路
1. **直接调用**(后端 PHP`GoodsService::GoodsSpecDetail(['id'=>X, 'spec'=>[...]])`
2. **前端 AJAX**ShopXO 有 API 端点 `api/goods/spec-detail`(需验证)
3. **ShopXO 标准流程**:用户选择规格 → 前端拼 `spec=[{type:'场次',value:'...'}]` → 提交 `goods_data`
### spec 数据来源
`$goods_spec_data``SeatSkuService::GetGoodsViewData()` 传入前端PHP 渲染):
```php
// ticket_detail.html 顶部
var specData = <?php echo json_encode($goods_spec_data ?? []); ?> || [];
// specData[0]: {spec_id, spec_name, price, ...}
```
---
## B4: ticket_detail.html 加载真实库存的方案
### 方案对比
| 方案 | 复杂度 | 实时性 | 风险 |
|------|--------|--------|------|
| A. 直接调用插件 APIAJAX | 低 | 高 | 需新增插件端点 `/api/vr-ticket/sold-seats` |
| B. ShopXO 标准 spec 加载流程 | 中 | 高 | 需理解 ShopXO 规格匹配机制 |
| C. PHP 后端预渲染(当前) | 低 | 低 | 页面加载时已固定 |
### 推荐方案(最小实现)
**插件新增 API 端点**
```
GET /?s=api/vr-ticket/sold-seats&goods_id=X&spec_base_id=Y
Response: {sold_seats: ["A_1", "A_2", "B_5"]}
```
前端在选中场次后调用此接口,标记已售座位。
### 关于 ShopXO spec 机制的说明
VR 票务的 `spec_base_id_map` 存储的是每个座位对应的 `GoodsSpecBase.id`。但 ShopXO 的 `GoodsSpecDetail` 是通过 `{type, value}` 匹配规格的,不是直接接受 `spec_base_id`
**这意味着**:如果 VR 票务已经生成了 `GoodsSpecBase` 记录,`GoodsSpecDetail` 可以通过 `spec=[{type:'座位', value:'A_1'}]` 来查询,但更直接的方式是让插件自己维护座位→规格的映射,并提供独立的 API。
---
## B5: 根因总结
### Issue 1P0— 购物车提交格式错误
**根因**submit() 把 `goods_params` 拼到 URLGET`Buy::Index()` 只在 `$this->data_post` 时处理数据 → POST 分支永远不触发。
**其次**`BuyService::BuyInitialize` 期望 `goods_data` 字段,且 `spec` 必须是 `[{type, value}]` 格式,而不是 `spec_base_id`
**修复方案(后端)**
1. 新增插件端点 `index/buy/index``api/vr-ticket/buy-direct`,专门处理 VR 票务的 POST 提交
2. 或者修改 submit() 为表单 POST 提交,但需处理 ShopXO 的 CSRF 保护
### Issue 2P1— 缩放时舞台不跟随
**根因**`.vr-stage` 在 `.vr-seat-rows` 容器外CSS `transform: scale()` 只作用于容器内子元素。
**修复方案(前端)**:将 `.vr-stage` 移入 `.vr-seat-rows` 容器,或创建共享的 zoom wrapper详见 FrontendDev findings
### Issue 3P1— spec 加载问题
**根因**ShopXO 的规格匹配通过 `spec.value` 字符串匹配,而非直接接受 `spec_base_id`。VR 票务场景下,每个座位对应独立的 `GoodsSpecBase`ShopXO 标准流程需要为每个座位生成 ShopXO 规格记录。
**修复方案**:插件需要维护座位→规格映射,并在选中场次后通过 AJAX 加载已售座位数据(新增插件 API
### Issue 4P2— 商品详情/图片加载
**根因**ShopXO 商品详情页通过 `Goods.php``Index()` 方法加载,`$goods['content_web']` 等字段由 ShopXO 处理。
**修复方案**:需要确认 ticket_detail.html 是否需要 ShopXO 的商品内容渲染,如果需要,应该在插件模板中引入 ShopXO 的商品内容组件。
---
## 推荐的修复优先级
1. **P0立即修复**Issue 1 — submit() 的 GET→POST 问题,导致下单无法工作
2. **P1**Issue 2 — 舞台缩放视觉问题
3. **P1**Issue 3 — spec 加载/已售座位显示
4. **P2**Issue 4 — 商品详情(可延后)
---
*BackendArchitect findings — Round 2 完成*

View File

@ -0,0 +1,218 @@
# Council Phase 2 技术评估报告
> 协作产出 | 日期2026-04-21
> 参与 AgentBackendArchitect、FrontendDev、FirstPrinciples
---
## 问题总览
| # | 问题 | 优先级 | 根因分类 |
|---|------|--------|---------|
| 1 | 购物车提交格式错误 | P0 | API 传递方式 + 参数契约不匹配 |
| 2 | 缩放时舞台元素不跟随 | P1 | DOM 结构导致 CSS transform 不共享 |
| 3 | spec 加载问题(已回滚) | P1 | ShopXO 规格匹配机制 + API 链路不明确 |
| 4 | 商品详情/图片加载 | P2 | 模板未引入 ShopXO 商品内容组件 |
---
## Issue 1P0购物车提交格式错误
### 根因分析(三层)
**第一层(致命)**`location.href` 产生 GET 请求,但 `Buy::Index()` 只在 `$this->data_post` 时处理下单逻辑。
```php
// Buy.php:58-61
public function Index()
{
if($this->data_post) {
BuyService::BuyDataStorage($this->user['id'], $this->data_post);
return MyRedirect(MyUrl('index/buy/index'));
} else {
// GET 分支:从 session 读取URL 参数从未被读取
$buy_data = BuyService::BuyDataRead($this->user['id']);
}
}
```
`goods_params` URL 参数从未被读取,`BuyDataStorage` 从未被调用,`BuyDataRead` 返回空 → "商品数据为空"错误。
**第二层(严重)**:字段名不匹配。
- 前端发送:`goods_params`JSON 数组)
- ShopXO 期望:`goods_data`JSON 数组)
**第三层(中等)**:规格匹配机制不兼容。
- 当前:`spec_base_id: parseInt(specBaseId)` — 直接传 ID
- ShopXO`spec: [{type, value}]` — 通过 type:value 字符串匹配 GoodsSpecValue 表
### 推荐修复(前后端)
**前端BackendArchitect + FrontendDev 联合)**
```javascript
// 方案 A隐藏表单 POST最小化变更
submit: function() {
var goods_data = this.selectedSeats.map(function(seat, i) {
return {
goods_id: self.goodsId,
spec: [{type: '座位', value: seat.seatKey}], // ← ShopXO 规格格式
stock: 1,
extension_data: JSON.stringify({attendee: attendeeData[i], seat: {...}})
};
});
var form = document.createElement('form');
form.method = 'POST';
form.action = MyUrl('index/buy/index');
var input = document.createElement('input');
input.name = 'goods_data';
input.value = JSON.stringify(goods_data);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
}
```
**后端BackendArchitect**
- 方案 B推荐在插件中新增 `VrTicketBuy` 控制器,复用 BuyService 链路,但绕过 ShopXO 的 spec 匹配(直接通过 spec_base_id 查询)
- 方案 C`BuyService::BuyInitialize` 中增加插件扩展点,允许 vr_ticket 插件注入自定义的规格处理逻辑
### API 设计建议
当前实现是「用 GET 做 POST 的事」。正确的做法是:
1. 隐藏表单 POST `goods_data``index/buy/index`ShopXO 原生)
2. 或者插件新增端点POST `goods_data``plugins/vr_ticket/buy-direct`
---
## Issue 2P1缩放时舞台元素不跟随
### 根因分析
```html
<div class="vr-seat-map-wrapper">
<div class="vr-stage">舞 台</div> <!-- 舞台wrapper 直接子元素 -->
<div class="vr-seat-rows" id="seatRows"></div> <!-- 座位行 -->
</div>
```
CSS `transform: scale()` 只作用于应用元素的子树。`.vr-stage` 和 `.vr-seat-rows` 是平级,没有共同的 transform 容器。
### 推荐修复FrontendDev
**方案:将舞台和座位行包裹在同一 zoom 容器内**
```html
<div class="vr-seat-map-wrapper">
<div class="vr-zoom-container" id="zoomContainer">
<div class="vr-stage">舞 台</div>
<div class="vr-seat-rows" id="seatRows"></div>
</div>
</div>
```
```css
.vr-zoom-container {
display: flex;
flex-direction: column;
align-items: center;
transform-origin: center top;
transition: transform 0.2s ease;
}
```
JS 缩放时,操作 `#zoomContainer``transform: scale(X)`,舞台和座位同步缩放。
**注意**:舞台的 `border-radius: 50% 50% 0 0 / 20px 20px 0 0` 弧形在缩放后可能变形,需要在 zoom 场景下调整。
---
## Issue 3P1spec 加载问题
### 根因分析
**问题 A**ShopXO 的 `GoodsSpecDetail` 通过 `spec.value` 字符串匹配规格,而非直接接受 `spec_base_id`
```php
// GoodsService.php:2749-2757
$spec = array_column($params['spec'], 'value'); // ['A_1', 'VIP']
$where['value'] = $spec;
$ids = Db::name('GoodsSpecValue')->where($where)->column('goods_spec_base_id');
```
VR 票务场景下,每个座位对应独立的 `GoodsSpecBase` 记录inventory=1。要通过 ShopXO 标准流程加载,需要为每个座位生成 `GoodsSpecValue` 记录type='座位', value='A_1')。
**问题 B**:插件 API 端点未建立,导致前端 `loadSoldSeats()` 是 TODO stub。
### 推荐修复
**后端BackendArchitect**:新增插件 API 端点
```
GET /?s=api/vr-ticket/sold-seats&goods_id=X&spec_base_id=Y
Response: {code: 0, data: {sold_seats: ["A_1", "A_2", "B_5"]}}
```
前端在选中场次后调用此接口,标记已售座位。
**关于 ShopXO spec 机制**:如果 VR 票务已经生成了 `GoodsSpecBase` 记录,最直接的方式是让插件维护座位→规格的映射,并提供独立的已售座位查询 API而不是依赖 ShopXO 的规格匹配流程。
---
## Issue 4P2商品详情/图片加载
### 根因分析
`ticket_detail.html` 是插件独立模板ShopXO 商品的 `content_web` 和图片数据由 `Goods.php Index()` 加载,但插件模板可能未正确引入这些数据。
### 推荐修复
确认 ticket_detail.html 是否需要 ShopXO 商品内容渲染。如果需要,应该在模板中引入 ShopXO 的商品内容组件:
- 商品详情:`$goods['content_web']` 由 GoodsService 处理
- 商品图册:通过 `ResourcesService` 获取
如果票务 UI 不需要 ShopXO 商品内容区(票务详情页有自己的布局),则此问题可降级为「确认不需要」。
---
## 第一性原则视角的关键提醒FirstPrinciples
1. **P0 的真正来源**submit() 的 URL 重定向方式错了,修复后 Buy 链路本身是可用的——不需要重构 spec 系统。
2. **spec_base_id_map 是性能缓存**:不是业务必需。如果 `onOrderPaid` 能通过 seatKey 查询到 spec_base_idmap 可以去掉。保留它是合理的优化,但需要确保同步机制。
3. **购物车对票务无价值**:当前实现已经在用 Buy 链路,不是 Cart 链路。说明直觉上的「绕过购物车」需求其实不存在——只是 submit() 的传递方式错了。
4. **已售座位展示是 P1不是 P0**:真正的 P0 是 `onOrderPaid` 防双售。前端是否实时显示已售状态,是体验优化,不是业务正确性的根本。
5. **多场次 bug**`GetGoodsViewData()` 只返回第一个配置的场次(取 `validConfigs[0]`)。如果一个商品有多个场次配置,只显示第一个——这是潜在的 bug。
6. **最小修复范围**:只需修复 submit() 的传递方式(隐藏表单 POST不需要重构 spec 系统,不需要引入实时已售座位更新(除非 spec 加载方案已实施)。
---
## 修复优先级与分工
| 优先级 | 问题 | 负责方 | 修复说明 |
|--------|------|--------|---------|
| P0 | Issue 1 submit() | BackendArchitect + FrontendDev | 改用隐藏表单 POST复用 Buy 链路 |
| P1 | Issue 2 舞台缩放 | FrontendDev | 新增 zoom wrapper 容器 |
| P1 | Issue 3 spec 加载 | BackendArchitect | 新增插件 API 端点 |
| P2 | Issue 4 商品详情 | 延后 | 确认是否需要 |
---
## 附录ShopXO Buy 链路关键代码索引
| 文件 | 行号 | 说明 |
|------|------|------|
| `Buy.php` | 56-62 | Index() — POST/GET 分支BuyDataStorage/BuyDataRead |
| `BuyService.php` | ~51 | BuyGoods — goods_data 参数校验 + base64 解码 |
| `BuyService.php` | ~173 | GoodsSpecificationsHandle — 规格解析 |
| `BuyService.php` | ~104-109 | GoodsSpecDetail 调用 — 通过 spec.value 匹配 |
| `GoodsService.php` | 2720-2795 | GoodsSpecDetail — type:value 查询 GoodsSpecValue |
| `BuyService.php` | 1932-1936 | BuyDataStorage — session 缓存21600s TTL |
---
*Council Phase 2 技术评估报告 — 由 BackendArchitect、FrontendDev、FirstPrinciples 协作完成*