vr-shopxo-plugin/docs/REPORT-KICKOFF.md

570 lines
20 KiB
Markdown
Raw Permalink Normal View History

# vr-shopxo-plugin 项目启动报告
> 生成时间2026-04-15 00:16 CST
> 生成方式:西莉娅 Council 协助模型路由opusMiniMax 路由)
> 关联 Issue#TBD创建后填入
---
## 一、当前状态确认Status Report
### 1.1 代码 vs 文档差异
| 项目 | 文档状态ARCHITECTURE.md v2.2 | 实际代码 |
|------|---|---|
| 核心架构 | spec = 场次venue_data 精简 | ✅ 一致 |
| 插件表 | vr_seat_templates / vr_tickets / vr_verifiers / vr_verifications | ✅ 一致 |
| plugin.json | 应删除 event/session 管理菜单 | ❌ 仍为旧版菜单vr_events/vr_sessions |
| plan.md | 应删除 vr_events/vr_sessions 表 | ❌ 仍为旧 phase 结构 |
| PHP 代码 | 应有完整骨架service/EventListener.php | ❌ 零 PHP 代码 |
| 前端代码 | shopxo-uniapp 定制页面 | ❌ 零 Vue 代码 |
| 数据库迁移 | 应有 001-004 迁移文件 | ❌ 无 SQL 文件 |
### 1.2 待清理的旧内容
- [ ] `plan.md` 中的 Phase 1旧 vr_events/vr_sessions 表结构)
- [ ] `docs/04_IMPLEMENTATION_ROADMAP.md` 中的旧 phase旧表名
- [ ] `plugin.json` 中的 `活动管理/场次管理` 菜单 → 改为 `座位模板/电子票/核销记录`
### 1.3 环境现状
| 组件 | 状态 | 地址 |
|------|------|------|
| ShopXO PHP 后端 | ✅ 运行中 | http://localhost:10000/ |
| ShopXO MySQL | ✅ 运行中 | localhost:10001 |
| shopxo-uniapp | ❌ 未安装 | — |
| 插件目录 | ✅ 存在 | `{SHOPXO_SRC}/app/plugins/` |
| vr_ticket 插件目录 | ❌ 为空 | — |
> `SHOPXO_SRC` = `/Users/bigemon/.openclaw/workspace/council-research/shopxo-eval/.worktrees/shopxo-evaluator/shopxo-src`
---
## 二、实施路线图Revised v2.2
> 基于 ARCHITECTURE.md v2.2,删除 vr_events/vr_sessions/vr_venues 全部内容
### 精简后的 Phase 列表
| Phase | 内容 | 交付物 | 预估 | 可并行 |
|-------|------|--------|------|--------|
| **Phase 0** | 环境搭建 + 插件骨架 | 可安装的插件目录结构 | 1天 | — |
| **Phase 1** | 数据库迁移 | 4个 SQL 迁移文件 + 执行 | 0.5天 | ✅ Phase 0 |
| **Phase 2** | 后台 CRUD + API | SeatTemplate + Ticket 后台 + API | 3天 | ✅ Phase 1 |
| **Phase 3** | 前端选座 UI | 座位地图组件 + 选座页 | 3天 | ✅ Phase 2 |
| **Phase 4** | 订单钩子 + 观演人 | `plugins_service_buy_order_*` 钩子 | 2天 | ✅ Phase 3 |
| **Phase 5** | 支付回调 + QR 票 | `order.paid` 监听 → QR 生成 | 1天 | ✅ Phase 4 |
| **Phase 6** | B 端核销页 | 扫码核销页面 + API | 2天 | ✅ Phase 5 |
| **Phase 7** | 票夹 + C 端 | 用户票夹页 + 订单列表 | 2天 | ✅ Phase 6 |
| **Phase 8** | 联调 + 测试 + 部署 | 微信小程序审核上线 | 2天 | 串行 |
**预估**Agent 集群并行 → **Phase 0-7 共约 7 天**Phase 8 收尾共 9 天
### 核心变更说明vs 旧版 roadmap
1. **删除 vr_events 表**:活动信息直接用 ShopXO 商品替代(商品名称 = 活动名称)
2. **删除 vr_sessions 表**:场次 = ShopXO spec_value每个规格值 = 一个演出时间)
3. **删除 vr_venues 表**:场馆/座位配置合并到 `vr_seat_templates.venue_data` JSON
4. **Phase 2 简化**:不需要独立的 Event/Session CRUD商家直接用 ShopXO 商品管理
5. **Phase 5 新增**QR 票生成 + 支付回调分离Phase 4 只处理下单钩子Phase 5 处理支付成功)
---
## 三、任务分配方案Task Allocation
### Agent 分工建议
| Phase | 主要负责 | 辅助 | 说明 |
|-------|---------|------|------|
| Phase 0 | **大头(手动)** | AI 生成清单 | 环境需要本地操作AI 生成步骤清单 |
| Phase 1 | 妮可 | — | 数据库迁移脚本CRUD 模式 |
| Phase 2 | 李狗蛋 | 妮可 | 后台 CRUD + API可并行 |
| Phase 3 | 李狗蛋 | 大头(验收) | 前端选座组件shopxo-uniapp |
| Phase 4 | 李狗蛋 | — | 订单钩子 + 观演人表单 |
| Phase 5 | 西莉娅 | — | 支付回调 + QR 生成(熟悉 ShopXO Hook |
| Phase 6 | 西莉娅 | — | B 端核销页(参考 realstore/check.vue |
| Phase 7 | 西莉娅 | 大头(验收) | 票夹 + C 端 |
| Phase 8 | 大头 | 全员 | 联调 + 微信审核上线 |
### 大头Bigemon职责
- **Phase 0**:本地操作(安装 shopxo-uniapp、配置 HBuilderX、安装插件到 ShopXO
- **Phase 3/7 验收**:前端 UI/UX 体验把关
- **Phase 8 主协调**:联调问题定位 + 微信审核沟通
- **全局**:架构决策拍板、技术债务审查
### 优先级排序(先做什么价值最大)
```
P0立即可做
└── Phase 0环境 + 插件骨架 → 无此则后续无法运行
P1骨架完成后立即启动
├── Phase 1数据库迁移 → 提供所有表的 SQL
└── Phase 2后台 API → 其他 phase 依赖
P2Phase 2 完成后并行)
├── Phase 3前端选座 → 用户核心体验
├── Phase 4订单钩子 → 核心购票流程
└── Phase 5QR 票 → 购票交付物
P3后半程
├── Phase 6B 端核销 → 商家核心功能
└── Phase 7票夹 → 用户核心功能
```
---
## 四、Phase 0 详细实施计划
### 环境信息
```
ShopXO 后台http://localhost:10000/admin
ShopXO 前台http://localhost:10000/
MySQLlocalhost:10001root/ShopXO@2026
SHOPXO_SRC/Users/bigemon/.openclaw/workspace/council-research/shopxo-eval/.worktrees/shopxo-evaluator/shopxo-src
插件目录:{SHOPXO_SRC}/app/plugins/vr_ticket/
shopxo-uniapp需新建目录 TBD建议放在 shopxo-env/ 下)
```
### Step by Step
- [ ] **Step 0.1**:验证 ShopXO 后台可访问 → http://localhost:10000/admin
- [ ] **Step 0.2**:下载 shopxo-uniappHBuilderX → Git 克隆 or 下载 zip
- [ ] **Step 0.3**:配置 `shopxo-uniapp/common/config.js``request_url` = `http://localhost:10000/`
- [ ] **Step 0.4**HBuilderX 导入 shopxo-uniapp → 本地 H5 预览验证
- [ ] **Step 0.5**:创建插件目录结构(见下方目录树)
- [ ] **Step 0.6**:写入 `plugin.json`(更新版,对齐 v2.2
- [ ] **Step 0.7**:写入 `EventListener.php` + `service/BaseService.php`(骨架)
- [ ] **Step 0.8**:后台 → 插件管理 → 找到 vr_ticket → 点击安装
- [ ] **Step 0.9**后台左侧菜单是否出现「VR票务」菜单
### AI 可参与度
| Step | AI 可做 | 需要大头手动 |
|------|---------|------------|
| 0.1 | ✅ | 验证 URL |
| 0.2 | ✅(生成 git clone 命令) | HBuilderX 操作 |
| 0.3 | ✅(生成配置代码) | 打开 HBuilderX |
| 0.4 | ❌ | HBuilderX 预览 |
| 0.5 | ✅(生成 mkdir 命令) | 执行命令 |
| 0.6 | ✅(生成完整 JSON | 上传到服务器 |
| 0.7 | ✅(生成完整 PHP | 上传到服务器 |
| 0.8 | ❌ | 浏览器操作 |
| 0.9 | ✅ | 人工验收 |
### 验收标准
1. 后台左侧菜单出现「VR票务」→「座位模板/电子票/核销记录」
2. 点击「座位模板」不报错(可为空列表)
3. 访问 `/plugins/vr_ticket/admin/seat_template/list` 返回有效 JSON
---
## 五、Phase 1 — 数据库迁移文件
### `database/migrations/001_vr_seat_templates.sql`
```sql
-- =====================================================
-- VR票务插件 - 座位模板表
-- 座位模板通过分类ID绑定到 ShopXO 商品
-- AI 参与度100%(标准建表语句)
-- =====================================================
CREATE TABLE IF NOT EXISTS `vr_seat_templates` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '模板ID',
`name` VARCHAR(180) NOT NULL COMMENT '模板名称(如:鸟巢-A区',
`category_id` BIGINT UNSIGNED NOT NULL COMMENT '绑定的 ShopXO 分类ID',
`seat_map` LONGTEXT NOT NULL COMMENT '座位地图JSON见 venue_data 结构)',
`spec_base_id_map` LONGTEXT DEFAULT NULL COMMENT '座位ID→spec_base_id 映射JSON',
`status` TINYINT UNSIGNED DEFAULT 1 COMMENT '状态0禁用 1启用',
`add_time` INT UNSIGNED DEFAULT 0 COMMENT '创建时间戳',
`upd_time` INT UNSIGNED DEFAULT 0 COMMENT '更新时间戳',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='VR演唱会座位模板';
-- seat_map JSON 结构示例:
-- {
-- "map": ["aaaaaaaaaaaa", "aaaaaaaaaaaa", "bbbbbb__bb"],
-- "row_labels": ["A", "B", "C"],
-- "seats": {
-- "a": { "price": 599, "label": "VIP区", "classes": "seat-vip" },
-- "b": { "price": 399, "label": "普通区", "classes": "seat-normal" },
-- "_": null
-- },
-- "sections": [
-- { "name": "VIP区", "color": "#FF6B6B", "rows": [0, 1] },
-- { "name": "普通区", "color": "#4ECDC4", "rows": [2, 3] }
-- ]
-- }
```
### `database/migrations/002_vr_tickets.sql`
```sql
-- =====================================================
-- VR票务插件 - 电子票表
-- 支付成功后由 TicketService 写入
-- AI 参与度100%(标准建表语句)
-- =====================================================
CREATE TABLE IF NOT EXISTS `vr_tickets` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '票ID',
`order_id` BIGINT UNSIGNED NOT NULL COMMENT 'ShopXO 订单ID',
`order_no` CHAR(60) NOT NULL COMMENT '订单号',
`goods_id` BIGINT UNSIGNED NOT NULL COMMENT '商品ID',
`goods_snapshot` TEXT DEFAULT NULL COMMENT '商品快照JSON规格名/场次名)',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`ticket_code` CHAR(36) NOT NULL COMMENT 'UUID 票码',
`qr_data` TEXT DEFAULT NULL COMMENT '加密QR内容',
`seat_info` VARCHAR(255) DEFAULT NULL COMMENT '座位信息(如 A区-3排-5座',
`spec_base_id` BIGINT UNSIGNED DEFAULT 0 COMMENT 'spec_base_id关联ShopXO规格',
`real_name` VARCHAR(60) DEFAULT NULL COMMENT '观演人姓名',
`phone` CHAR(15) DEFAULT NULL COMMENT '观演人手机',
`id_card` CHAR(18) DEFAULT NULL COMMENT '观演人身份证(选填)',
`verify_status` TINYINT UNSIGNED DEFAULT 0 COMMENT '核销状态0未核销 1已核销 2已退款',
`verify_time` INT UNSIGNED DEFAULT 0 COMMENT '核销时间戳',
`verifier_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '核销员IDvr_verifiers.id',
`issued_at` INT UNSIGNED DEFAULT 0 COMMENT '票发放时间戳(支付成功后)',
`created_at` INT UNSIGNED DEFAULT 0 COMMENT '记录创建时间',
`updated_at` INT UNSIGNED DEFAULT 0 COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_ticket_code` (`ticket_code`),
KEY `idx_order_id` (`order_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_goods_id` (`goods_id`),
KEY `idx_verify_status` (`verify_status`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='VR演唱会电子票';
```
### `database/migrations/003_vr_verifiers.sql`
```sql
-- =====================================================
-- VR票务插件 - 核销员表
-- AI 参与度100%(标准建表语句)
-- =====================================================
CREATE TABLE IF NOT EXISTS `vr_verifiers` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '核销员ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '对应的 ShopXO 用户ID',
`name` VARCHAR(60) NOT NULL COMMENT '核销员名称',
`status` TINYINT UNSIGNED DEFAULT 1 COMMENT '状态0禁用 1启用',
`created_at` INT UNSIGNED DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='VR票务核销员';
```
### `database/migrations/004_vr_verifications.sql`
```sql
-- =====================================================
-- VR票务插件 - 核销记录表
-- AI 参与度100%(标准建表语句)
-- =====================================================
CREATE TABLE IF NOT EXISTS `vr_verifications` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '核销记录ID',
`ticket_id` BIGINT UNSIGNED NOT NULL COMMENT '票IDvr_tickets.id',
`ticket_code` CHAR(36) NOT NULL COMMENT '票码(冗余存储)',
`verifier_id` BIGINT UNSIGNED NOT NULL COMMENT '核销员ID',
`verifier_name` VARCHAR(60) DEFAULT NULL COMMENT '核销员名称(冗余)',
`goods_id` BIGINT UNSIGNED DEFAULT 0 COMMENT '商品ID',
`created_at` INT UNSIGNED DEFAULT 0 COMMENT '核销时间',
PRIMARY KEY (`id`),
KEY `idx_ticket_id` (`ticket_id`),
KEY `idx_verifier_id` (`verifier_id`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
COMMENT='VR票务核销记录';
```
---
## 六、插件骨架代码
### `plugin.json`(更新版,对齐 v2.2
```json
{
"name": "vr-ticket",
"title": "VR票务",
"description": "为ShopXO添加VR演唱会票务功能座位模板、观演人收集、QR电子票、扫码核销",
"version": "1.0.0",
"author": "sileya-ai",
"author_url": "http://xmhome.ow-my.com:3000/sileya-ai/vr-shopxo-plugin",
"shopxo_version": ">=1.0.0",
"dependencies": [],
"menus": [
{
"title": "VR票务",
"icon": "icon icon-ticket",
"submenus": [
{ "title": "座位模板", "url": "/plugins/vr-ticket/admin/seat_template/list" },
{ "title": "电子票管理", "url": "/plugins/vr-ticket/admin/ticket/list" },
{ "title": "核销记录", "url": "/plugins/vr-ticket/admin/verification/list" }
]
}
],
"listen_events": [
"order.paid"
],
"hooks": [
"onOrderPaid"
]
}
```
### `EventListener.php`
```php
<?php
/**
* VR票务插件 - 事件监听器
*
* ShopXO 事件监听入口
* 监听 order.paid 事件 → 触发 QR 票生成
*
* @package vr-ticket
*/
/**
* 订单支付成功事件处理
*
* @param array $params 事件参数,含 order_id / order_no / user_id
* @return bool
*/
function vr_ticket_on_order_paid($params = [])
{
// 引入 TicketService
use_app_service('vr_ticket/TicketService');
try {
$order_id = $params['order_id'] ?? 0;
if (empty($order_id)) {
return false;
}
// 调用票服务:生成并发放 QR 票
$result = \app\plugins\vr_ticket\service\TicketService::OnOrderPaid($order_id, $params);
return $result !== false;
} catch (\Exception $e) {
// 记录错误,不阻塞订单流程
log_info('[vr-ticket] OrderPaid Error: ' . $e->getMessage(), $params);
return false;
}
}
```
### `service/BaseService.php`
```php
<?php
/**
* VR票务插件 - 基础服务
*
* 提供通用工具方法:加密/解密、日志、时间戳等
*
* @package vr-ticket/service
*/
namespace app\plugins\vr_ticket\service;
class BaseService
{
/**
* 获取插件根目录
* @return string
*/
public static function getPluginPath()
{
return ROOT_PATH . 'app' . DS . 'plugins' . DS . 'vr_ticket' . DS;
}
/**
* 获取插件静态资源URL
* @param string $path 相对于 static/ 目录的路径
* @return string
*/
public static function getStaticUrl($path = '')
{
return ROOT_URL . 'app/plugins/vr_ticket/static/' . ltrim($path, '/');
}
/**
* 当前时间戳
* @return int
*/
public static function now()
{
return time();
}
/**
* 生成 UUID v4 票码
* @return string
*/
public static function generateUuid()
{
$data = random_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
/**
* AES-256-CBC 加密票务数据
*
* @param array $data 待加密数据
* @param int $expire 过期时间戳默认30天
* @return string base64 编码的密文
*/
public static function encryptTicketData($data, $expire = null)
{
$secret = self::getTicketSecret();
$expire = $expire ?? (time() + 86400 * 30);
$payload = json_encode(array_merge($data, [
'exp' => $expire,
'iat' => time(),
]), JSON_UNESCAPED_UNICODE);
$iv = random_bytes(16);
$encrypted = openssl_encrypt($payload, 'AES-256-CBC', $secret, OPENSSL_RAW_DATA, $iv);
$combined = $iv . $encrypted;
return base64_encode($combined);
}
/**
* 解密票务数据
*
* @param string $encoded base64 编码的密文
* @return array|null 解密失败返回 null
*/
public static function decryptTicketData($encoded)
{
$secret = self::getTicketSecret();
$combined = base64_decode($encoded);
if (strlen($combined) < 16) {
return null;
}
$iv = substr($combined, 0, 16);
$encrypted = substr($combined, 16);
$decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $secret, OPENSSL_RAW_DATA, $iv);
if ($decrypted === false) {
return null;
}
$data = json_decode($decrypted, true);
// 检查过期
if (isset($data['exp']) && $data['exp'] < time()) {
return null;
}
return $data;
}
/**
* 获取票务加密密钥
* 优先从环境变量读取,否则用 ShopXO app_secret
* @return string
*/
private static function getTicketSecret()
{
$secret = env('VR_TICKET_SECRET', '');
if (!empty($secret)) {
return $secret;
}
// 回退:使用 ShopXO 应用密钥
return config('shopxo.app_key', 'shopxo_default_secret_change_me');
}
/**
* 安全的日志写入
*
* @param string $message 日志信息
* @param array $context 上下文数据
* @param string $level info|error|warning
*/
public static function log($message, $context = [], $level = 'info')
{
$tag = '[vr-ticket]';
$ctx = empty($context) ? '' : ' ' . json_encode($context, JSON_UNESCAPED_UNICODE);
log_{$level}($tag . $message . $ctx);
}
/**
* 判断商品是否为票务商品
*
* @param int $goods_id 商品ID
* @return bool
*/
public static function isTicketGoods($goods_id)
{
$goods = \app\model\Goods::find($goods_id);
if (empty($goods)) {
return false;
}
return !empty($goods['venue_data']);
}
/**
* 获取商品 venue_data
*
* @param int $goods_id
* @return array
*/
public static function getGoodsVenueData($goods_id)
{
$goods = \app\model\Goods::find($goods_id);
if (empty($goods) || empty($goods['venue_data'])) {
return [];
}
return is_string($goods['venue_data']) ? json_decode($goods['venue_data'], true) : $goods['venue_data'];
}
}
```
---
## 七、立即可执行的下一步
1. **大头手动**:安装 shopxo-uniappHBuilderX → Git 克隆 or 下载)
2. **西莉娅生成**Phase 0 插件骨架 → 上传到 ShopXO 插件目录
3. **大头验收**:后台安装插件 → 验证菜单出现
4. **妮可生成**Phase 1 数据库迁移 SQL001-004
5. **李狗蛋**待大头搭好环境后Phase 2 后台 CRUD
---
## 八、分工确认(待大头回复)
请确认以下分工是否符合预期:
| Phase | 主要负责人 | 预计开始 |
|-------|----------|---------|
| Phase 0 | 大头(手动操作) | **立即** |
| Phase 1 | 妮可SQL 迁移) | Phase 0 完成后 |
| Phase 2 | 李狗蛋(后台 CRUD | Phase 1 完成后 |
| Phase 3 | 李狗蛋 + 大头(验收) | Phase 2 完成后 |
| Phase 4 | 李狗蛋(订单钩子) | Phase 3 中 |
| Phase 5 | 西莉娅QR 生成) | Phase 4 中 |
| Phase 6 | 西莉娅B端核销 | Phase 5 中 |
| Phase 7 | 西莉娅 + 大头(票夹) | Phase 6 中 |
| Phase 8 | 大头(联调上线) | Phase 7 完成后 |