From 875b7c1848b4a525a6613d5964952da70c3c3b35 Mon Sep 17 00:00:00 2001 From: Council Date: Tue, 21 Apr 2026 08:48:29 +0800 Subject: [PATCH] council(finalize): FirstPrinciples - consolidated Phase 2 assessment report Co-Authored-By: Claude Sonnet 4.6 --- plan.md | 13 +- reviews/council-phase2-assessment.md | 259 +++++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 reviews/council-phase2-assessment.md diff --git a/plan.md b/plan.md index f730cc5..3cb1701 100644 --- a/plan.md +++ b/plan.md @@ -61,7 +61,7 @@ - BackendArchitect: `[APPROVE]` — merged report produced - FrontendDev: `[APPROVE]` — merged report produced, Issue 2 zoom fix proposed -- FirstPrinciples: `[APPROVE]` — review complete, see `reviews/FirstPrinciples-on-council-assessment.md` +- FirstPrinciples: `[APPROVE]` — final consolidated report at `reviews/council-phase2-assessment.md` ## 新增发现(P2 潜在) @@ -71,10 +71,13 @@ ## 输出文件 -| 文件 | 内容 | 负责人 | -|------|------|--------| -| `reviews/FirstPrinciples-on-phase2-assessment.md` | 第一性原则分析报告 | FirstPrinciples | -| `reviews/council-phase2-assessment.md` | 合并评估报告(最终输出) | FirstPrinciples | +| 文件 | 内容 | 状态 | +|------|------|------| +| `reviews/council-phase2-assessment.md` | 合并评估报告(最终输出) | ✅ Done | +| `reviews/FirstPrinciples-on-council-assessment.md` | FirstPrinciples review | ✅ Done | +| `reviews/FirstPrinciples-on-phase2-assessment.md` | 第一性原则分析报告 | ✅ Done | + +**Phase 2 评估完成,所有文件已合并至 `reviews/council-phase2-assessment.md`** --- diff --git a/reviews/council-phase2-assessment.md b/reviews/council-phase2-assessment.md new file mode 100644 index 0000000..9e35a4d --- /dev/null +++ b/reviews/council-phase2-assessment.md @@ -0,0 +1,259 @@ +# VR 演唱会票务小程序 Phase 2 技术评估报告 + +> 日期:2026-04-21 +> 协作产出:BackendArchitect、FrontendDev、FirstPrinciples +> 源码依据:BuyService.php、GoodsCartService.php、SeatSkuService.php、ticket_detail.html + +--- + +## 执行摘要 + +Phase 2 完成了对 4 个已知问题的根因分析,识别了 1 个未被提及的潜在 Bug(多场次),并给出了分优先级的修复方案。 + +**结论**:所有 4 个问题均为可修复的技术问题,无架构级障碍。 + +--- + +## 问题总览 + +| # | 问题 | 优先级 | 根因分类 | +|---|------|--------|---------| +| 1 | 购买提交流程失效 | **P0** | GET→POST 机制错误 + 参数契约不匹配 | +| 2 | 缩放时舞台不跟随 | **P1** | DOM 结构导致 transform 不共享 | +| 3 | spec 加载机制回滚 | **P1** | ShopXO spec 匹配机制不兼容 + API 端点缺失 | +| 4 | 商品详情/图片加载 | **P2** | 模板未引入 ShopXO 内容渲染组件 | + +**新增发现(潜在 Bug)**: +| # | 问题 | 优先级 | +|---|------|--------| +| 5 | GetGoodsViewData() 只返回第一个场次 | **P2 潜在** | + +--- + +## Issue 1(P0):购买提交流程失效 + +### 根因分析(三层叠加) + +**第一层(致命)**:`location.href` 产生 GET 请求,但 `Buy::Index()` 只在 `data_post` 为真时调用 `BuyDataStorage()`。 + +```php +// Buy.php:56-62 +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 表 + +### 推荐修复 + +**前端(FrontendDev)**: + +```javascript +// 隐藏表单 POST(最小化变更,复用 Buy 链路) +submit: function() { + var goodsDataList = this.selectedSeats.map(function(seat, i) { + var specBaseId = self.specBaseIdMap[seat.seatKey] || self.sessionSpecId; + return { + goods_id: self.goodsId, + spec: [{type: '座位', value: seat.seatKey}], // ShopXO 规格格式 + stock: 1 + }; + }); + + 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 = Base64.encode(JSON.stringify(goodsDataList)); + form.appendChild(input); + document.body.appendChild(form); + form.submit(); +} +``` + +**关于 extension_data(观演人信息)**:ShopXO 原生不支持 `extension_data`(OrderDetail 表无此字段)。推荐方案:新增 `vr_order_attendee` 表,在 `BuyService::OrderInsert()` 后存储观演人信息。 + +### 关键提醒(FirstPrinciples) + +> Issue 1 的准确描述是「Buy 传输机制损坏」,而非「购物车格式错误」。 +> +> 当前代码实际上绕过了购物车,直接进入 Buy 链路。这说明直觉上的「绕过购物车」需求并不存在——只是 `submit()` 的传递方式错了。 + +--- + +## Issue 2(P1):缩放时舞台不跟随 + +### 根因分析 + +```html +
+
舞 台
+
+
+``` + +CSS `transform: scale()` 只作用于应用元素的子树。舞台和座位行是平级元素,缩放座位时舞台不动。 + +### 推荐修复 + +**方案:将舞台和座位包裹在同一 zoom 容器内**(FrontendDev 实施) + +```html +
+
+
舞 台
+
+
+
+``` + +```css +.vr-zoom-container { + display: flex; + flex-direction: column; + align-items: center; + transform-origin: center top; + transition: transform 0.2s ease; +} +``` + +**注意**:舞台的 `border-radius` 在缩放后可能变形,需在 zoom 场景下单独调整。 + +--- + +## Issue 3(P1):spec 加载机制回滚 + +### 根因分析 + +**问题 A**:`loadSoldSeats()` 是空 stub,无任何网络请求。 + +```javascript +loadSoldSeats: function() { + // TODO: 从后端加载已售座位 + // $.get(...); // 空,无任何调用 +} +``` + +**问题 B**:ShopXO 的 `GoodsSpecDetail` 通过 `spec.value` 字符串匹配规格,而非直接接受 `spec_base_id`。 + +```php +// GoodsService.php:2749-2757 +$spec = array_column($params['spec'], 'value'); +$where['value'] = $spec; +$ids = Db::name('GoodsSpecValue')->where($where)->column('goods_spec_base_id'); +``` + +VR 票务场景下,每个座位对应独立的 `GoodsSpecBase` 记录(inventory=1)。ShopXO 标准流程需要生成 `GoodsSpecValue` 记录(type='座位', value='seatKey')。 + +### 推荐修复 + +**后端(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"]}} +``` + +前端在选中场次后调用此接口,标记 `.sold` class。 + +**关于 spec 加载**:ShopXO 的 spec 数据通过 `GetGoodsViewData()` 在模板渲染时注入前端。如果已生成了 `GoodsSpecBase` 记录,最直接的方式是维护座位→规格映射,并提供独立的已售座位查询 API,而不是依赖 ShopXO 的规格匹配流程。 + +--- + +## Issue 4(P2):商品详情/图片加载 + +### 现状 + +- **商品内容**(`$goods['content']`):✅ 正常渲染 +- **商品相册**(`$goods['images']`):⚠️ 数据存在但未使用 + - `renderSessions()` 依赖 `goods_spec_data`,不含 `images` + - `.vr-goods-photos` 已定义样式但从未被调用 +- **`.goods-detail-content` CSS**:⚠️ 缺失 + +### 建议 + +如需展示商品图片,在模板中添加图片渲染逻辑。如票务详情页不需要 ShopXO 商品内容区,可降级为「确认不需要」。 + +--- + +## Issue 5(P2 潜在):GetGoodsViewData 只返回第一个场次 + +### 根因分析 + +```php +// SeatSkuService.php:BatchGenerate() +return array_merge($data, [ + 'goods_spec_data' => $validConfigs[0], // ← 只取第一个配置 + 'vr_seat_template' => $template, +]); +``` + +`validConfigs[0]` 意味着多场次商品只返回第一个场次的座位模板和规格数据。当一个商品配置了多场演唱会时,其余场次对用户不可见。 + +### 修复方向 + +修改 `BatchGenerate()` 返回格式,将 `goods_spec_data` 改为数组(`$validConfigs`),前端根据选中场次索引读取对应数据。 + +--- + +## 第一性原则视角的关键提醒 + +1. **P0 的真正来源**:`submit()` 用 GET 做 POST 的事。Buy 链路本身可用,不需要重构 spec 系统。 + +2. **spec_base_id_map 是性能缓存**:不是业务必需。如果 `onOrderPaid` 能通过 seatKey 查询到 spec_base_id,map 可以去掉。保留它是合理的优化,但需要确保同步机制。 + +3. **购物车对票务无价值**:当前实现已绕过购物车进入 Buy 链路。选座的实时性决定了购物车会增加超卖风险,快速购买是正确方向。 + +4. **已售座位展示是 P1,不是 P0**:真正的 P0 是 `onOrderPaid` 防双售。前端是否实时显示已售状态,是体验优化,不是业务正确性的根本。 + +5. **`onOrderPaid` 是座位唯一性权威**(未审计):在 P0 修复部署前,必须验证此 Hook 是否正确实现了座位锁定逻辑。若未实现,任何前端修复都无法防止重复销售。 + +6. **最小修复范围**:只需修复 `submit()` 的传递方式(隐藏表单 POST)。不需要重构 spec 系统,不需要引入实时已售座位更新(除非 spec 加载方案已实施)。 + +--- + +## 修复优先级与分工 + +| 优先级 | 问题 | 负责 | 修复说明 | +|--------|------|------|---------| +| P0 | Issue 1 submit() | FrontendDev | 改用隐藏表单 POST,复用 Buy 链路 | +| P1 | Issue 2 舞台缩放 | FrontendDev | 新增 zoom wrapper 容器 | +| P1 | Issue 3 spec 加载 | BackendArchitect | 新增插件 sold_seats API 端点 | +| P2 | Issue 4 商品详情 | FrontendDev | 确认是否需要,补充 CSS | +| P2 | Issue 5 多场次 | BackendArchitect | BatchGenerate 返回数组格式 | +| FP | extension_data | BackendArchitect | 新增 vr_order_attendee 表 | + +--- + +## 附录: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) | +| `SeatSkuService.php` | BatchGenerate | 返回 validConfigs[0] — 多场次 Bug | + +--- + +*VR 演唱会票务小程序 Phase 2 技术评估 — Council 协作完成*