From 098bcfe78099b5901ea22d25767eb50d1ae8cb11 Mon Sep 17 00:00:00 2001 From: Council Date: Wed, 15 Apr 2026 16:59:22 +0800 Subject: [PATCH] fix(P0): P0-1 idempotent ticket issuance, P0-3 XSS, P0-4 QR secret exception P0-1: issueTicket() now checks for existing tickets by (order_id, spec_base_id) before inserting. Prevents duplicate tickets on HTTP retry/multi-instance. P0-3: Removed |raw from simple_desc and content in ticket_detail.html. Prevents stored XSS via malicious admin content injection. P0-4: getQrSecret() now throws exception if VR_TICKET_QR_SECRET is unset, instead of falling back to insecure default key. --- .../app/plugins/vr_ticket/service/BaseService.php | 8 +++----- .../app/plugins/vr_ticket/service/TicketService.php | 13 +++++++++++++ .../plugins/vr_ticket/view/goods/ticket_detail.html | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/shopxo/app/plugins/vr_ticket/service/BaseService.php b/shopxo/app/plugins/vr_ticket/service/BaseService.php index d8f6697..adabe68 100644 --- a/shopxo/app/plugins/vr_ticket/service/BaseService.php +++ b/shopxo/app/plugins/vr_ticket/service/BaseService.php @@ -97,13 +97,11 @@ class BaseService */ private static function getQrSecret() { - // 优先从环境变量读取 $secret = env('VR_TICKET_QR_SECRET', ''); - if (!empty($secret)) { - return $secret; + if (empty($secret)) { + throw new \Exception('[vr_ticket] VR_TICKET_QR_SECRET 环境变量未配置,QR加密密钥不能为空。请在.env中设置VR_TICKET_QR_SECRET=<随机64字符字符串>'); } - // 回退:使用 ShopXO 应用密钥 - return config('shopxo.app_key', 'shopxo_default_secret_change_me'); + return $secret; } /** diff --git a/shopxo/app/plugins/vr_ticket/service/TicketService.php b/shopxo/app/plugins/vr_ticket/service/TicketService.php index ba29fba..f4034a4 100644 --- a/shopxo/app/plugins/vr_ticket/service/TicketService.php +++ b/shopxo/app/plugins/vr_ticket/service/TicketService.php @@ -76,6 +76,19 @@ class TicketService extends BaseService */ public static function issueTicket($order, $order_goods) { + // P0-1 幂等保护:同一订单+同一规格只发一张票 + $existing = \Db::name(BaseService::table('tickets')) + ->where('order_id', $order['id']) + ->where('spec_base_id', $order_goods['spec_base_id'] ?? 0) + ->find(); + if (!empty($existing)) { + BaseService::log('issueTicket: idempotent_skip', [ + 'order_id' => $order['id'], + 'spec_base_id'=> $order_goods['spec_base_id'] ?? 0, + ], 'info'); + return $existing['id']; + } + $ticket_code = BaseService::generateUuid(); // 构建 QR 数据 diff --git a/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html b/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html index 2d81c8f..85ff9c6 100644 --- a/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html +++ b/shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html @@ -122,7 +122,7 @@
{$goods.title|default='VR演唱会'}
-
{$goods.simple_desc|default=''|raw}
+
{$goods.simple_desc|default=''}
@@ -161,7 +161,7 @@ {if !empty($goods.content)}
演出详情
-
{$goods.content|raw}
+
{$goods.content}
{/if}