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

20 KiB
Raw Permalink Blame 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.jsrequest_url = http://localhost:10000/
  • Step 0.4HBuilderX 导入 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

-- =====================================================
-- 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

-- =====================================================
-- 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

-- =====================================================
-- 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

-- =====================================================
-- 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

{
    "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
/**
 * 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
/**
 * 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 完成后