vr-shopxo-plugin/app/plugins/vr_ticket/service/BaseService.php

211 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* VR票务插件 - 基础服务
*
* @package vr_ticket\service
*/
namespace app\plugins\vr_ticket\service;
class BaseService
{
/**
* 获取插件表前缀
*/
public static function table($name)
{
return 'plugins_vr_' . $name;
}
/**
* 获取当前时间戳
*/
public static function now()
{
return time();
}
/**
* 生成 UUID v4 票码
*/
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 加密 QR 数据
*
* @param array $data 待加密数据
* @param int|null $expire 过期时间戳默认30天
* @return string base64 编码密文
*/
public static function encryptQrData($data, $expire = null)
{
$secret = self::getQrSecret();
$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);
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);
}
}
/**
* 插件后台权限菜单
*
* ShopXO 通过 PluginsService::PluginsAdminPowerMenu() 调用此方法
* 返回格式:二维数组,每项代表一个菜单分组
*
* @return array
*/
public static function AdminPowerMenu()
{
return [
// 座位模板
[
'name' => '座位模板',
'control' => 'seat_template',
'action' => 'list',
'item' => [
['name' => '座位模板', 'action' => 'list'],
['name' => '添加模板', 'action' => 'save'],
],
],
// 电子票
[
'name' => '电子票',
'control' => 'ticket',
'action' => 'list',
'item' => [
['name' => '电子票列表', 'action' => 'list'],
['name' => '票详情', 'action' => 'detail'],
['name' => '手动核销', 'action' => 'verify'],
['name' => '导出票', 'action' => 'export'],
],
],
// 核销员
[
'name' => '核销员',
'control' => 'verifier',
'action' => 'list',
'item' => [
['name' => '核销员列表', 'action' => 'list'],
['name' => '添加核销员', 'action' => 'save'],
],
],
// 核销记录
[
'name' => '核销记录',
'control' => 'verification',
'action' => 'list',
'item' => [
['name' => '核销记录', 'action' => 'list'],
],
],
];
}
}