vr-shopxo-plugin/reviews/BackendArchitect-on-phase2.md

229 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 完成*