vr-shopxo-plugin/docs/PHASE_4_3_IMPLEMENTATION_PL...

6.0 KiB
Raw Permalink Blame History

Phase 4.3 实现计划C端票夹

创建日期2026-04-23 状态:待实现 依赖Phase 4.1 (HMAC-XOR) + Phase 4.2 (issueTicket) 已完成


一、文件清单

步骤 文件 类型 说明
1 api/Ticket.php 新建 C端 API 控制器
2 service/WalletService.php 新建 票夹核心服务
3 view/goods/ticket_card.html 新建 票卡片共享组件
4 view/goods/ticket_wallet.html 新建 票夹列表页
5 Hook.php 修改 注册 C 端挂载点钩子

二、API 设计

2.1 获取用户票列表

GET ?s=api/plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=list

Headers:
  X-Token: {user_token}

Response:
{
  "code": 0,
  "msg": "success",
  "data": {
    "tickets": [
      {
        "id": 1,
        "goods_id": 118,
        "goods_title": "周杰伦演唱会",
        "seat_info": "A区-3排-15座",
        "session_time": "2026-06-01 20:00",
        "venue_name": "国家体育馆",
        "real_name": "张三",
        "verify_status": 0,
        "issued_at": 1745286000,
        "short_code": "003a2hgmgety"
      }
    ],
    "count": 1
  }
}

2.2 获取票详情(含 QR payload

GET ?s=api/plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=detail&id={ticket_id}

Headers:
  X-Token: {user_token}

Response:
{
  "code": 0,
  "msg": "success",
  "data": {
    "ticket": {
      "id": 1,
      "goods_id": 118,
      "goods_title": "周杰伦演唱会",
      "seat_info": "A区-3排-15座",
      "session_time": "2026-06-01 20:00",
      "venue_name": "国家体育馆",
      "real_name": "张三",
      "phone": "138****1234",
      "verify_status": 0,
      "short_code": "003a2hgmgety",
      "qr_payload": "eyJpZCI6MSwiZyI6MTE4LCJpYXQiOjE3NDUyODY2MDAsImV4cCI6MTc0NTI4NzIwMCwic2lnIjoiQTNGOUIyQzEifQ==",
      "qr_expires_at": 1745287200,
      "qr_expires_in": 1800
    }
  }
}

2.3 刷新 QR payload

GET ?s=api/plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=refreshQr&id={ticket_id}

Headers:
  X-Token: {user_token}

Response: 同 2.2

三、数据流程

用户访问票夹页
    ↓
Hook 注入票夹入口(或直接在商品详情页显示)
    ↓
ticket_wallet.html 加载
    ↓
JS 调用 /ticket/list API 获取票列表
    ↓
渲染 ticket_card 列表
    ↓
点击单个票 → 调用 /ticket/detail API → 展示 QR + 短码

四、实现步骤

Step 1: WalletService.php

// service/WalletService.php
namespace app\plugins\vr_ticket\service;

class WalletService extends BaseService
{
    /**
     * 获取用户所有票
     */
    public static function getUserTickets(int $userId): array

    /**
     * 获取单个票详情
     */
    public static function getTicketDetail(int $ticketId, int $userId): ?array

    /**
     * 生成 QR payload含缓存逻辑
     * 
     * 缓存策略:
     * - QR 有效期 30 分钟
     * - 剩余有效期 > 15 分钟:返回缓存
     * - 剩余有效期 ≤ 15 分钟:刷新
     */
    public static function getQrPayload(int $ticketId): array
}

Step 2: api/Ticket.php

// api/Ticket.php
namespace app\plugins\vr_ticket\api;

class Ticket
{
    /**
     * 获取用户票列表
     * GET ?s=api/plugins/...&pluginsaction=list
     */
    public function list(): Json

    /**
     * 获取票详情
     * GET ?s=api/plugins/...&pluginsaction=detail&id=X
     */
    public function detail(): Json

    /**
     * 刷新 QR payload
     * GET ?s=api/plugins/...&pluginsaction=refreshQr&id=X
     */
    public function refreshQr(): Json
}

路由验证

  • pluginsname=vr_ticketvr_ticket 目录
  • pluginscontrol=ticketapi/Ticket.php (ucfirst('ticket') = 'Ticket')
  • pluginsaction=listTicket::list()

Step 3: ticket_card.html

票卡片组件,包含:

  • 商品信息(标题、场次、座位)
  • 观演人信息
  • QR 码展示区(懒加载)
  • 短码展示
  • 核销状态标识

Step 4: ticket_wallet.html

票夹页面:

  • 加载 JS/CSSqrcode.js、jQuery
  • 调用 /ticket/list 获取票列表
  • 渲染票卡片列表
  • 空状态提示

Step 5: Hook.php 修改

注册 C 端挂载点钩子:

// 在 handle() 方法中添加
case 'plugins_service_order_detail_page_info':
    $this->InjectTicketCard($params);
    break;

五、QR 码展示逻辑

// ticket_card.html 伪代码
function loadQrCode(ticketId) {
    // 1. 检查 localStorage 缓存
    const cached = localStorage.getItem('vr_qr_' + ticketId);
    if (cached) {
        const data = JSON.parse(cached);
        if (data.expires_at > Date.now() / 1000) {
            // 缓存有效,剩余 > 15 分钟则直接展示
            const remaining = data.expires_at - Date.now() / 1000;
            if (remaining > 900) {
                renderQr(data.payload);
                return;
            }
        }
    }

    // 2. 调用 API 获取新 QR
    $.get('/api/plugins/...&pluginsaction=detail&id=' + ticketId, function(res) {
        if (res.code === 0) {
            // 3. 缓存到 localStorage
            localStorage.setItem('vr_qr_' + ticketId, JSON.stringify({
                payload: res.data.ticket.qr_payload,
                expires_at: res.data.ticket.qr_expires_at
            }));
            // 4. 渲染 QR
            renderQr(res.data.ticket.qr_payload);
        }
    });
}

function renderQr(base64Payload) {
    const payload = atob(base64Payload);
    $('#qrcode').qrcode({ text: payload });
}

六、测试用例

用例 预期结果
未登录访问 返回 401
无票用户 返回空列表
有票用户 返回票列表
点击票卡片 展示 QR + 短码
QR 过期前刷新 获取新 QR
核销后展示 显示已核销状态

七、进度记录

  • Step 1: WalletService.php
  • Step 2: api/Ticket.php
  • Step 3: ticket_card.html
  • Step 4: ticket_wallet.html
  • Step 5: Hook.php
  • Step 6: 联调测试