header('X-Token') ?: request()->header('Authorization', ''); if (!empty($token)) { $token = trim(str_replace('Bearer ', '', $token)); } // 方式1.5:UniApp 以 query 参数传入 token(&token=xxx) if (empty($token)) { $token = input('token', '', null); } if (!empty($token)) { // 优先用 vrt_user_platform.token 查 DB(App 登录场景) $user = \app\service\UserService::UserTokenData($token); if (!empty($user) && !empty($user['id'])) { return intval($user['id']); } // 如果没查到,说明是 web 登录 token(存在 user_info cookie 里,不在 vrt_user_platform) // 尝试从 user_info cookie 直接解码(cookie 内容 = 用户 JSON) $userInfoCookie = request()->cookie('user_info'); if (!empty($userInfoCookie)) { $decoded = urldecode($userInfoCookie); $userData = json_decode($decoded, true); if (!empty($userData) && !empty($userData['id'])) { return intval($userData['id']); } } } // 方式2:ShopXO 标准方式(session / cookie,适用于页面直接访问场景) $user = \app\service\UserService::LoginUserInfo(); if (!empty($user) && !empty($user['id'])) { return intval($user['id']); } return null; } /** * 返回未登录错误 * * @return Json */ private static function unauthorized(string $msg = '请先登录') { return [ 'code' => 401, 'msg' => $msg, 'data' => [], ]; } /** * 返回成功响应 * * @param mixed $data * @param string $msg * @return Json */ private static function success($data = [], string $msg = 'success') { return [ 'code' => 0, 'msg' => $msg, 'data' => $data, ]; } /** * 返回错误响应 * * @param string $msg * @param int $code * @return Json */ private static function error(string $msg = '请求失败', int $code = -1) { return [ 'code' => $code, 'msg' => $msg, 'data' => [], ]; } /** * 获取用户票列表 * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=list * * 支持两种模式: * * 【模式 1:传统分页】 * - page: 页码,默认 1 * - page_size: 每页数量,默认 20,最大 100 * * 【模式 2:瀑布流】(last_id > 0 或传入 limit 时启用) * - last_id: 游标 ID,0 表示首次加载 * - limit: 每次拉取数量,默认 20,最大 100 * - order_by: 排序方向,desc(降序,历史) / asc(升序,新数据),默认 desc * * 通用筛选参数: * - order_id: 按单个订单ID筛选 * - order_ids: 按多个订单ID批量筛选,逗号分隔,如 41,42,43 * - goods_id: 按商品ID筛选 * - status: 按核销状态筛选 (0=未核销, 1=已核销, 2=已退款) * * @return Json */ public function list() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } // 解析筛选参数 $orderId = input('order_id', 0, 'intval'); $orderIdsStr = input('order_ids', '', 'trim'); $orderIds = !empty($orderIdsStr) ? array_filter(array_map('intval', explode(',', $orderIdsStr))) : null; $goodsId = input('goods_id', 0, 'intval'); $status = input('status', null, 'intval'); // 判断模式:瀑布流 vs 分页 $lastId = input('last_id', 0, 'intval'); $hasLimitParam = input('limit') !== null && input('limit') !== ''; if ($lastId > 0 || $hasLimitParam) { // 瀑布流模式 $limit = min(100, max(1, intval(input('limit', 20)))); $orderByInput = strtolower(trim(input('order_by', 'desc'))); $orderBy = in_array($orderByInput, ['asc', 'desc']) ? $orderByInput : 'desc'; try { $result = WalletService::getUserTicketsWaterfall( $userId, $lastId, $orderBy, $limit, $orderId, $orderIds, $goodsId, $status ); return self::success([ 'tickets' => $result['list'], 'has_more' => $result['has_more'], 'last_id' => $result['last_id'], 'count' => $result['count'], ]); } catch (\Exception $e) { return self::error('获取票列表失败: ' . $e->getMessage()); } } else { // 传统分页模式(保持兼容) $page = max(1, intval(input('page', 1))); $pageSize = min(100, max(1, intval(input('page_size', 20)))); try { $result = WalletService::getUserTicketsPaginated( $userId, $orderId, $orderIds, $goodsId, $status, $page, $pageSize ); return self::success([ 'tickets' => $result['list'], 'total' => $result['total'], 'page' => $result['page'], 'page_size' => $result['page_size'], 'pages' => $result['pages'], ]); } catch (\Exception $e) { return self::error('获取票列表失败: ' . $e->getMessage()); } } } /** * 获取用户票列表(tickets 别名,兼容文档格式) * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=tickets * * @return Json */ public function tickets() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } try { $tickets = WalletService::getUserTickets($userId); return self::success([ 'tickets' => $tickets, 'count' => count($tickets), ]); } catch (\Exception $e) { return self::error('获取票列表失败: ' . $e->getMessage()); } } /** * 获取票详情(含 QR payload) * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=detail&id=X * * @return Json */ public function detail() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } $ticketId = input('id', 0, 'intval'); if ($ticketId <= 0) { return self::error('参数错误:票ID无效'); } try { $ticket = WalletService::getTicketDetail($ticketId, $userId); if (empty($ticket)) { return self::error('票不存在或无权访问', -404); } return self::success([ 'ticket' => $ticket, ]); } catch (\Exception $e) { return self::error('获取票详情失败: ' . $e->getMessage()); } } /** * 强制刷新 QR payload * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=refreshQr&id=X * * @return Json */ public function refreshQr() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } $ticketId = input('id', 0, 'intval'); if ($ticketId <= 0) { return self::error('参数错误:票ID无效'); } try { $ticket = WalletService::refreshQrPayload($ticketId, $userId); if (empty($ticket)) { return self::error('票不存在或无权访问', -404); } return self::success([ 'ticket' => $ticket, ]); } catch (\Exception $e) { return self::error('刷新QR失败: ' . $e->getMessage()); } } /** * 扫码核销票 * * POST /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=verify * Body: { ticket_code: "AB12..." } * * 鉴权:用户必须是 vr_verifiers 表中 status=1 的记录 * @return Json */ public function verify() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } // 鉴权:检查该 C 端用户是否是授权核销员 $verifier = \think\facade\Db::name('vr_verifiers') ->where('user_id', $userId) ->where('status', 1) ->find(); if (empty($verifier)) { return self::error('你不是授权核销员,无权核销', -403); } $verifier_id = $verifier['id']; $ticket_code = input('ticket_code', '', null, 'trim'); if (empty($ticket_code)) { return self::error('票码不能为空'); } // 自动识别短码 vs UUID:短码长度 < 20 且不含连字符 $is_short_code = (strlen($ticket_code) < 20 && strpos($ticket_code, '-') === false); if ($is_short_code) { $result = \app\plugins\vr_ticket\service\TicketService::verifyByShortCode($ticket_code, $verifier_id); } else { $result = \app\plugins\vr_ticket\service\TicketService::verifyTicket($ticket_code, $verifier_id); } return [ 'code' => $result['code'], 'msg' => $result['msg'], 'data' => $result['data'] ?? [], ]; } /** * 我的核销记录 * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=myVerifications * * @return Json */ public function myVerifications() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } // 鉴权:检查该 C 端用户是否是授权核销员 $verifier = \think\facade\Db::name('vr_verifiers') ->where('user_id', $userId) ->where('status', 1) ->find(); if (empty($verifier)) { return self::error('你不是授权核销员', -403); } $verifier_id = $verifier['id']; // 分页参数 $page = max(1, intval(input('page', 1))); $page_size = min(100, max(10, intval(input('page_size', 20)))); $where = ['verifier_id' => $verifier_id]; $list = \think\facade\Db::name('vr_verifications') ->where($where) ->order('id', 'desc') ->page($page, $page_size) ->select() ->toArray(); $total = \think\facade\Db::name('vr_verifications') ->where($where) ->count(); // 关联票和商品信息 $ticket_ids = array_filter(array_column($list, 'ticket_id')); $tickets_map = []; $user_ids = []; if (!empty($ticket_ids)) { $tickets_raw = \think\facade\Db::name('vr_tickets') ->where('id', 'in', $ticket_ids) ->select() ->toArray(); foreach ($tickets_raw as $t) { $tickets_map[$t['id']] = $t; if (!empty($t['user_id'])) { $user_ids[] = $t['user_id']; } } } $users_map = []; if (!empty($user_ids)) { $users_raw = \think\facade\Db::name('User') ->where('id', 'in', array_unique($user_ids)) ->select() ->toArray(); foreach ($users_raw as $u) { $users_map[$u['id']] = !empty($u['nickname']) ? $u['nickname'] : (!empty($u['username']) ? $u['username'] : '用户' . $u['id']); } } $goods_ids = array_filter(array_unique(array_column($list, 'goods_id'))); $goods_map = []; if (!empty($goods_ids)) { $goods_list = \think\facade\Db::name('Goods') ->where('id', 'in', $goods_ids) ->column('title', 'id'); $goods_map = $goods_list; } $result = []; foreach ($list as $v) { $ticket = $tickets_map[$v['ticket_id']] ?? []; // 提取短码(从 qr_data 的前半部分) $short_code = ''; if (!empty($ticket['qr_data']) && strpos($ticket['qr_data'], '|') !== false) { $short_code = explode('|', $ticket['qr_data'], 2)[0]; } else { $short_code = !empty($ticket['ticket_code']) ? substr($ticket['ticket_code'], 0, 8) : ''; } $buyer_name = $users_map[$ticket['user_id'] ?? 0] ?? ''; $result[] = [ 'id' => $v['id'], 'ticket_id' => $v['ticket_id'], 'ticket_code' => $v['ticket_code'], 'short_code' => $short_code, 'seat_info' => $ticket['seat_info'] ?? '', 'real_name' => $ticket['real_name'] ?? '', 'phone' => $ticket['phone'] ?? '', 'order_no' => $ticket['order_no'] ?? '', 'buyer_name' => $buyer_name, 'goods_title' => $goods_map[$v['goods_id']] ?? '已下架商品', 'created_at' => $v['created_at'], ]; } return self::success([ 'list' => $result, 'total' => $total, 'page' => $page, 'page_size' => $page_size, 'pages' => ceil($total / $page_size), ]); } /** * 检测当前登录用户是否为授权核销员 * * GET /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=checkVerifier * * 轻量接口,用于前端快速判断是否展示核销员入口 * @return Json */ public function checkVerifier() { $userId = self::getUserId(); if (empty($userId)) { return self::unauthorized(); } $verifier = \think\facade\Db::name('vr_verifiers') ->where('user_id', $userId) ->where('status', 1) ->find(); return self::success([ 'is_verifier' => !empty($verifier), 'verifier_id' => $verifier['id'] ?? 0, 'verifier_name' => $verifier['name'] ?? '', ]); } }