$expire, 'iat' => time(), ]), JSON_UNESCAPED_UNICODE); $iv = random_bytes(16); $encrypted = openssl_encrypt($payload, 'AES-256-CBC', $secret, OPENSSL_RAW_DATA, $iv); return base64_encode($iv . $encrypted); } /** * 解密 QR 数据 * * @param string $encoded base64 编码密文 * @return array|null */ public static function decryptQrData($encoded) { $secret = self::getQrSecret(); $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; } /** * 获取 QR 加密密钥 */ private static function getQrSecret() { // 优先从环境变量读取 $secret = env('VR_TICKET_QR_SECRET', ''); if (!empty($secret)) { return $secret; } // 回退:使用 ShopXO 应用密钥 return config('shopxo.app_key', 'shopxo_default_secret_change_me'); } /** * 判断商品是否为票务商品 * * @param int $goods_id * @return bool */ public static function isTicketGoods($goods_id) { $goods = \Db::name('Goods')->find($goods_id); if (empty($goods)) { return false; } return !empty($goods['venue_data']) || ($goods['item_type'] ?? '') === 'ticket'; } /** * 获取商品座位模板 * * @param int $goods_id * @return array|null */ public static function getSeatTemplateByGoods($goods_id) { $goods = \Db::name('Goods')->find($goods_id); if (empty($goods) || empty($goods['category_id'])) { return null; } return \Db::name(self::table('seat_templates')) ->where('category_id', $goods['category_id']) ->where('status', 1) ->find(); } /** * 安全日志 */ public static function log($message, $context = [], $level = 'info') { $tag = '[vr_ticket]'; $ctx = empty($context) ? '' : ' ' . json_encode($context, JSON_UNESCAPED_UNICODE); $log_func = "log_{$level}"; if (function_exists($log_func)) { $log_func($tag . $message . $ctx); } } }