vr-shopxo-plugin/reviews/BackendArchitect-on-Issue-1...

176 lines
6.4 KiB
Markdown
Raw Permalink 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.

# 评审报告Issue #13 "Undefined array key 'id'" 根因分析
> 评审人council/BackendArchitect | 日期2026-04-20
> 代码版本bbea35d83feat: 保存时自动填充 template_snapshot
---
## 一、"Undefined array key 'id'" 根因定位
### Primary Bug — 99% 是这行(第 77 行)
**文件**`shopxo/app/plugins/vr_ticket/hook/AdminGoodsSaveHandle.php`
**行号**:第 77 行(`array_filter` 回调内)
**代码**
```php
return in_array($r['id'], $config['selected_rooms'] ?? []);
```
**根因**:当 `$r`rooms 数组元素)缺少 `'id'` key 时,访问 `$r['id']` 直接抛出 `Undefined array key "id"`
**何时触发**:当 `vr_seat_templates.seat_map.rooms[]` 中存在任何一个没有 `id` 字段的房间对象时,在 `template_snapshot` 填充逻辑中崩溃。
**对比**`SeatSkuService::BatchGenerate` 第 100 行做了正确防护:
```php
// ✅ 安全写法
$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
```
`AdminGoodsSaveHandle` 第 77 行没有这个防护。
---
## 二、`Db::name('vr_seat_templates')` 表前缀问题
### 结论:两者等价,不存在前缀错误
**验证依据**`admin/Admin.php` 第 66 行):
```php
$prefix = \think\facade\Config::get('database.connections.mysql.prefix', 'vrt_');
$tableName = $prefix . 'vr_seat_templates'; // → vrt_vr_seat_templates
```
ShopXO 默认表前缀为 `vrt_`。因此:
- `Db::name('vr_seat_templates')``vrt_vr_seat_templates`
- `BaseService::table('seat_templates')``vr_seat_templates` + ShopXO 前缀 → `vrt_vr_seat_templates`
两者查询同一张表,**不是错误来源**。
> ⚠️ 但 `AdminGoodsSaveHandle` 使用裸 `Db::name()` 而非 `SeatSkuService` 使用的 `BaseService::table()`,风格不统一。建议统一。
---
## 三、`find()` 返回 null 的空安全问题
### Secondary Bug — 触发概率 5%(第 71 行)
**代码**
```php
$template = Db::name('vr_seat_templates')->find($templateId);
$seatMap = json_decode($template['seat_map'] ?? '{}', true); // ❌ $template 可能是 null
```
**根因**:若 `vr_seat_templates` 表中不存在 `id = $templateId` 的记录,`find()` 返回 `null`,访问 `$template['seat_map']` 抛出 `Undefined array key "seat_map"`(虽然报错信息不是 "id",但属于同类空安全问题)。
**对比**`SeatSkuService::BatchGenerate` 第 55-57 行做了正确防护:
```php
if (empty($template)) {
return ['code' => -2, 'msg' => "座位模板 {$seatTemplateId} 不存在"];
}
```
`AdminGoodsSaveHandle` 第 71 行没有等效检查。
---
## 四、`selected_rooms` 类型不匹配问题
### Tertiary Bug — 静默失败(第 77 行)
**代码**
```php
return in_array($r['id'], $config['selected_rooms'] ?? []);
```
**根因**`selected_rooms[]` 从前端传来是字符串(如 `"room_0"`),而 `$r['id']``vr_seat_templates.seat_map.rooms[]` 中可能是整数或字符串,取决于模板创建时的数据。
**影响**:类型不匹配时 `in_array()` 永远返回 `false`,导致 `selectedRoomIds` 永远为空数组,前端无法正确展示选中的房间。**但不会抛出 PHP 错误**,属于静默逻辑错误。
**修复建议**
```php
// 使用严格模式 (bool) 第三个参数
in_array($r['id'], $config['selected_rooms'] ?? [], true)
// 或统一为字符串比较
in_array((string)($r['id'] ?? ''), array_map('strval', $config['selected_rooms'] ?? []))
```
---
## 五、SeatSkuService::BatchGenerate 审计结论
### ✅ 无 "id" 访问问题
| 位置 | 代码 | 结论 |
|------|------|------|
| 第 100 行 | `$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx)` | ✅ 有 null-safe fallback |
| 第 103 行 | `in_array($roomId, $selectedRooms)` | ✅ 基于安全的 `$roomId` |
| 第 127-128 行 | `in_array($char, $selectedSections[$roomId])` | ✅ 先检查 `!empty()` |
| 第 278-280 行 | `json_decode($existingItems, true) ?: []` | ✅ 有 fallback |
| 第 283 行 | `array_column($existingItems, 'name')` | ⚠️ 若 `$existingItems` 不是数组,抛出 Warning |
---
## 六、`$data['item_type']` 访问安全分析
### ✅ 安全(第 59 行)
```php
if ($goodsId > 0 && ($data['item_type'] ?? '') === 'ticket') {
```
使用 `?? ''` 提供默认值,`'' === 'ticket'` 为 `false`,不会误入票务分支。
---
## 七、修复建议汇总
### 高优先级(必须修复)
| # | 位置 | 问题 | 修复方案 |
|---|------|------|----------|
| **P1** | AdminGoodsSaveHandle.php:77 | `$r['id']` 无空安全 | 参考 BatchGenerate 第 100 行:`(($r['id'] ?? null) ?: ('room_' . $rIdx))` |
| **P2** | AdminGoodsSaveHandle.php:71 | `$template` null 访问 | `find()` 后加 `if (empty($template)) { continue; }` |
| **P3** | AdminGoodsSaveHandle.php:77 | 类型不匹配静默失败 | 加严格类型比较或统一字符串化 |
### 建议优化(非必须)
| # | 位置 | 问题 | 建议 |
|---|------|------|------|
| S1 | AdminGoodsSaveHandle.php:70 | `Db::name()` 不统一 | 改用 `SeatSkuService``BaseService::table()` 风格一致 |
| S2 | AdminGoodsSaveHandle.php:91 | goods 表写回时机 | 确认 save_thing_end 时机 goods 已落表,可以直接 update |
---
## 八、最终根因结论
**"Undefined array key 'id'" 错误 99% 来自 AdminGoodsSaveHandle.php 第 77 行**
```php
return in_array($r['id'], $config['selected_rooms'] ?? []);
// ^^^^^^^^ 当 $r 无 'id' key 时崩溃
```
**触发条件**`vr_seat_templates.seat_map.rooms[]` 中存在至少一个没有 `id` 字段的房间对象(这在前端手动构造 seat_map 或某些旧模板数据中很可能发生)。
**修复后代码建议**
```php
$selectedRoomIds = array_column(
array_filter($allRooms, function ($r, $idx) use ($config) {
$roomId = !empty($r['id']) ? $r['id'] : ('room_' . $idx);
return in_array($roomId, array_map('strval', $config['selected_rooms'] ?? []));
}), null
);
```
---
## 九、审查结论
| 审查项 | 结论 |
|--------|------|
| 错误根因 | ✅ 已定位AdminGoodsSaveHandle.php:77 |
| 表前缀问题 | ✅ 确认无前缀错误,两者等价 |
| null 安全 | ❌ 存在两处 null 安全问题P1/P2 |
| 类型匹配 | ⚠️ 存在静默类型不匹配P3 |
| SeatSkuService | ✅ BatchGenerate 已正确处理 |
| 建议修复优先级 | P1 > P2 > P3 |
**[APPROVE] — 根因已确认,建议按 P1→P2→P3 顺序修复**