Files
Sdf1_login/api/recharge_orders_api.php
T

699 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 充值订单API(recharge_orders_api.php)
* 提供充值订单查询、统计、筛选功能
*/
error_reporting(E_ALL);
ini_set('display_errors', 0);
ob_start();
require_once __DIR__ . '/../core.php';
ob_end_clean();
// 加载支付密钥(含MySQL凭据)
$secretsFile = __DIR__ . '/pay_secrets.php';
if (@is_file($secretsFile) && @is_readable($secretsFile)) {
@require_once $secretsFile;
}
// ★ 订单前缀读配置(2026-10-06 多服隔离):与 pay.php 创单端共用同一份 PAY_ORDER_PREFIX
require_once __DIR__ . '/pay_config.php';
/**
* 获取平台MySQL数据库连接(集中凭据管理)
* 注意:poller_online.php 也有同名函数,此处用不同名称避免 redeclare 冲突
*/
function getAdminPlatformDB() {
$host = defined('PAY_MYSQL_HOST') ? PAY_MYSQL_HOST : '127.0.0.1';
$dbname = defined('PAY_MYSQL_DBNAME') ? PAY_MYSQL_DBNAME : 'caihong';
$user = defined('PAY_MYSQL_USER') ? PAY_MYSQL_USER : 'hbye3AezRNk4r7YA';
$pass = defined('PAY_MYSQL_PASS') ? PAY_MYSQL_PASS : '5HtD7Rn3seRAn2BE';
$pdo = new PDO(
"mysql:host=$host;dbname=$dbname;charset=utf8mb4",
$user,
$pass,
[PDO::ATTR_TIMEOUT => 15, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
return $pdo;
}
/**
* 确保充值商店配置表存在
*/
function ensureShopConfigsTable($db) {
$db->exec("CREATE TABLE IF NOT EXISTS shop_configs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_name TEXT NOT NULL DEFAULT '',
stock INTEGER NOT NULL DEFAULT -1,
temporary_offer TEXT NOT NULL DEFAULT '',
offer_expire TEXT NOT NULL DEFAULT '',
price REAL NOT NULL DEFAULT 0,
discount_price REAL NOT NULL DEFAULT 0,
bond_reward TEXT NOT NULL DEFAULT '1-1',
is_active INTEGER NOT NULL DEFAULT 1,
created_at INTEGER NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL DEFAULT 0
)");
// 兼容旧表:检查并添加discount_price字段
$hasCol = false;
$cols = $db->query("PRAGMA table_info(shop_configs)");
while ($col = $cols->fetchArray(SQLITE3_ASSOC)) {
if ($col['name'] === 'discount_price') { $hasCol = true; break; }
}
if (!$hasCol) {
$db->exec("ALTER TABLE shop_configs ADD COLUMN discount_price REAL NOT NULL DEFAULT 0");
}
}
// 检查管理员登录
if (!isAdminLoggedIn()) {
error('无权限访问', 403);
}
$action = getParam('action', '');
switch ($action) {
case 'list':
handleOrderList();
break;
case 'detail':
handleOrderDetail();
break;
case 'shop_config':
handleShopConfig();
break;
case 'reconcile':
handleReconcile();
break;
case 'sync_platform':
handleSyncFromPlatform();
break;
default:
error('未知操作');
}
/**
* 获取订单列表
*/
function handleOrderList() {
// ★ 使用独立订单库,不和Java共享web.db
$db = getOrdersDB();
ensurePayOrdersTable($db);
// ★ 增量迁移:从 web.db 同步缺失的 pay_orders 到 orders.db(处理历史数据)
try {
$webDb = getDB();
$tableCheck = $webDb->query("SELECT name FROM sqlite_master WHERE type='table' AND name='pay_orders'");
if ($tableCheck->fetchArray()) {
$migrateData = $webDb->query("SELECT * FROM pay_orders");
$ins = $db->prepare("INSERT OR IGNORE INTO pay_orders (out_trade_no, trade_no, player_name, tier_id, money, bond_amount, status, name, platform_sign, submit_params, created_at, paid_at) VALUES (:no, :tn, :p, :ti, :m, :b, :s, :n, :ps, :sp, :ca, :pa)");
$migrated = 0;
while ($row = $migrateData->fetchArray(SQLITE3_ASSOC)) {
$ins->bindValue(':no', $row['out_trade_no'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':tn', $row['trade_no'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':p', $row['player_name'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':ti', $row['tier_id'] ?? 0, SQLITE3_INTEGER);
$ins->bindValue(':m', $row['money'] ?? '0', SQLITE3_TEXT);
$ins->bindValue(':b', $row['bond_amount'] ?? 0, SQLITE3_INTEGER);
$ins->bindValue(':s', $row['status'] ?? 'created', SQLITE3_TEXT);
$ins->bindValue(':n', $row['name'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':ps', $row['platform_sign'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':sp', $row['submit_params'] ?? '', SQLITE3_TEXT);
$ins->bindValue(':ca', $row['created_at'] ?? 0, SQLITE3_INTEGER);
$ins->bindValue(':pa', $row['paid_at'] ?? 0, SQLITE3_INTEGER);
$ins->execute();
if ($db->changes() > 0) $migrated++;
}
if ($migrated > 0) {
debugLog('[handleOrderList] 增量迁移web.db→orders.db', ['migrated' => $migrated]);
}
}
} catch (Exception $e) {
debugLog('[handleOrderList] 迁移失败', ['error' => $e->getMessage()]);
}
$player = getParam('player', '');
$orderNo = getParam('order_no', '');
$status = getParam('status', '');
$limit = min(200, max(1, (int)getParam('limit', 50)));
// 构建查询
$conditions = [];
$params = [];
if ($player) {
$conditions[] = "player_name LIKE :player";
$params[':player'] = '%' . $player . '%';
}
if ($orderNo) {
$conditions[] = "out_trade_no LIKE :order_no";
$params[':order_no'] = '%' . $orderNo . '%';
}
if ($status) {
$conditions[] = "status = :status";
$params[':status'] = $status;
}
$where = '';
if ($conditions) {
$where = 'WHERE ' . implode(' AND ', $conditions);
}
// 查询订单
$sql = "SELECT * FROM pay_orders $where ORDER BY created_at DESC LIMIT :limit";
$stmt = $db->prepare($sql);
foreach ($params as $k => $v) {
$stmt->bindValue($k, $v, SQLITE3_TEXT);
}
$stmt->bindValue(':limit', $limit, SQLITE3_INTEGER);
$orders = [];
$result = $stmt->execute();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$orders[] = $row;
}
// 统计数据
$statsSql = "SELECT
COUNT(*) as total,
SUM(CASE WHEN status='paid' THEN 1 ELSE 0 END) as paid,
SUM(CASE WHEN status='created' THEN 1 ELSE 0 END) as created,
SUM(CASE WHEN status='paid' THEN CAST(money AS REAL) ELSE 0 END) as totalMoney
FROM pay_orders";
$statsResult = $db->querySingle($statsSql, true);
$stats = [
'total' => $statsResult['total'] ?? 0,
'paid' => $statsResult['paid'] ?? 0,
'created' => $statsResult['created'] ?? 0,
'totalMoney' => number_format($statsResult['totalMoney'] ?? 0, 2),
];
success(['orders' => $orders, 'stats' => $stats]);
}
/**
* 获取单个订单详情
*/
function handleOrderDetail() {
$outTradeNo = getParam('out_trade_no');
if (!$outTradeNo) {
error('缺少订单号');
}
// ★ 使用独立订单库
$db = getOrdersDB();
ensurePayOrdersTable($db);
$stmt = $db->prepare("SELECT * FROM pay_orders WHERE out_trade_no = :no");
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
if (!$row) {
error('订单不存在');
}
success(['order' => $row]);
}
/**
* 手动触发对账(调用 poller_online.php)
*/
function handleReconcile() {
// 直接包含 poller_online.php 并调用 pollPaidOrders
define('POLLER_NO_AUTO_RUN', true);
// ★ 清空所有已有缓冲区(防止任何HTML/警告污染JSON)
while (ob_get_level() > 0) { ob_end_clean(); }
// 包含 poller_online.php(定义函数,不执行)
require_once __DIR__ . '/poller_online.php';
// 记录对账开始
if (function_exists('debugLog')) {
debugLog('[reconcile] 开始对账', [
'admin' => $_SESSION['admin_user'] ?? 'unknown',
'time' => date('Y-m-d H:i:s'),
]);
}
// 开始对账 - 捕获所有输出
ob_start();
try {
pollPaidOrders();
} catch (\Throwable $e) {
// 捕获异常,输出JSON错误
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'poller_exception', 'detail' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
exit;
}
$output = ob_get_clean();
// 记录原始输出
if (function_exists('debugLog')) {
debugLog('[reconcile] pollPaidOrders输出', [
'output_len' => strlen($output),
'output' => substr($output, 0, 500),
]);
}
// 解析结果
$result = json_decode($output, true);
if (!$result || !is_array($result)) {
if (function_exists('debugLog')) {
debugLog('[reconcile] JSON解析失败', ['output' => substr($output, 0, 200)]);
}
// 确保输出干净的JSON
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => 'poller_output_invalid', 'raw' => substr($output, 0, 100)], JSON_UNESCAPED_UNICODE);
exit;
}
// 记录解析结果
if (function_exists('debugLog')) {
debugLog('[reconcile] 解析结果', [
'result' => $result,
]);
}
// 直接输出结果
while (ob_get_level() > 0) { ob_end_clean(); }
header('Content-Type: application/json; charset=utf-8');
echo json_encode($result, JSON_UNESCAPED_UNICODE);
exit;
}
/**
* 从平台MySQL同步所有已支付订单到本地
*/
function handleSyncFromPlatform() {
// ★ 使用独立订单库,不和Java共享web.db(Java高频写入会锁死SQLite)
$db = getOrdersDB();
ensurePayOrdersTable($db);
// 注意:web_transactions 表只在 web.db 中(Java读取),不在 orders.db 中
try {
$pdo = getAdminPlatformDB();
// ★ 2026-10-02 只查后端自己的订单:平台 MySQL 是多商户共用库,整表拉 200 条
// 会把其他商户/项目的单(纯数字单号)也同步成本地充值单,凭空造出
// 玩家=unknown 的垃圾订单。本方单号前缀取自 config.php 的 PAY_ORDER_PREFIX
// (pay.php createOrder 用同一个值生成),平台侧据此过滤,PHP 侧再兜底校验;
// 平台其他订单一概不碰。
// ★ 2026-10-06:前缀改为读配置(原来写死 'RE',测试服改前缀后这里不跟着改
// 就会把生产服的单同步进来)。
// ★ 2026-10-07:前缀第一来源改为站点根 pay.md「前缀码」;扫描改为「当前前缀+历史前缀」
// 双条件(payOrderPrefixLikeAny),改过前缀码后在途老单仍能同步、不丢单。
$stmt = $pdo->prepare("SELECT out_trade_no, trade_no, uid, money, status, param, addtime FROM `pay_order` WHERE (" . payOrderPrefixLikeAny() . ") AND status IN (1, 2) ORDER BY addtime DESC LIMIT 200");
$stmt->execute();
$platformOrders = $stmt->fetchAll(PDO::FETCH_ASSOC);
$synced = 0;
$alreadyPaid = 0;
$foreign = 0;
$errors = [];
$now = time();
foreach ($platformOrders as $po) {
$outTradeNo = $po['out_trade_no'];
// ★ 兜底校验:与创单端读同一份 PAY_ORDER_PREFIX
if (!payOrderIsOurs($outTradeNo)) {
$foreign++; // 非本方订单:不建单、不写流水
continue;
}
$player = $po['param'] ?? 'unknown';
$tradeNo = $po['trade_no'] ?? '';
$money = (string)$po['money'];
// ★ 债券换算保持原始行为(2026-10-06 用户指示:代码不干预换算比例,
// 金额→券数由后台「面板-充值商店配置」决定,运维自行调整)。
// 本行仅在本地无档位记录时作兜底估算,公式与改造前完全一致。
$bonds = ($money >= '1.0') ? (int)($money * 10) : max(1, (int)($money * 100));
// 查本地是否已有
$check = $db->prepare("SELECT id, status FROM pay_orders WHERE out_trade_no = :no");
$check->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$localRow = $check->execute()->fetchArray(SQLITE3_ASSOC);
if ($localRow && $localRow['status'] === 'paid') {
$alreadyPaid++;
continue;
}
if ($localRow) {
// 已有记录但未标记paid → 更新
$upd = $db->prepare("UPDATE pay_orders SET status='paid', trade_no=:tn, paid_at=:t WHERE out_trade_no=:no AND status != 'paid'");
$upd->bindValue(':tn', $tradeNo, SQLITE3_TEXT);
$upd->bindValue(':t', $now, SQLITE3_INTEGER);
$upd->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$upd->execute();
} else {
// 本地无记录 → 创建
$ins = $db->prepare("INSERT OR IGNORE INTO pay_orders (out_trade_no, trade_no, player_name, money, bond_amount, status, name, submit_params, created_at, paid_at) VALUES (:no, :tn, :p, :m, :b, 'paid', :n, '', :t, :t)");
$ins->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$ins->bindValue(':tn', $tradeNo, SQLITE3_TEXT);
$ins->bindValue(':p', $player, SQLITE3_TEXT);
$ins->bindValue(':m', $money, SQLITE3_TEXT);
$ins->bindValue(':b', $bonds, SQLITE3_INTEGER);
$ins->bindValue(':n', '债券充值', SQLITE3_TEXT);
$ins->bindValue(':t', $now, SQLITE3_INTEGER);
$ins->execute();
}
// 写 web_transactions(幂等)— 注意:web_transactions 在 web.db 中(Java读取),不在 orders.db
try {
$webDb = getDB(); // 获取 web.db 连接
ensureWebTransactionsTable($webDb);
$txCheck = $webDb->prepare("SELECT id FROM web_transactions WHERE detail = :d AND type = 'recharge' LIMIT 1");
$txCheck->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
$txRow = $txCheck->execute()->fetchArray(SQLITE3_ASSOC);
if (!$txRow) {
$txIns = $webDb->prepare("INSERT OR IGNORE INTO web_transactions (player_name, type, amount, operator, reason, detail, status, created_at) VALUES (:p, 'recharge', :a, '管理员同步', '在线充值(支付宝)', :d, 'pending', :t)");
$txIns->bindValue(':p', $player, SQLITE3_TEXT);
$txIns->bindValue(':a', $bonds, SQLITE3_INTEGER);
$txIns->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
$txIns->bindValue(':t', $now, SQLITE3_INTEGER);
$txIns->execute();
}
} catch (Exception $e) {
// web_transactions 写入失败不影响主流程(poller 会兜底)
debugLog('[sync_platform] web_transactions写入失败', ['out_trade_no' => $outTradeNo, 'error' => $e->getMessage()]);
}
$synced++;
}
success([
'platform_total' => count($platformOrders),
'synced' => $synced,
'already_paid' => $alreadyPaid,
'foreign_skipped' => $foreign,
], "同步完成:平台{$synced}笔已同步,{$alreadyPaid}笔已存在,{$foreign}笔非本方订单已忽略");
} catch (\Throwable $e) {
error('同步失败: ' . $e->getMessage());
}
}
/**
* 确保 web_transactions 表存在
*/
function ensureWebTransactionsTable($db) {
$db->exec("CREATE TABLE IF NOT EXISTS web_transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_name TEXT NOT NULL,
type TEXT,
amount INTEGER DEFAULT 0,
operator TEXT,
reason TEXT,
detail TEXT,
status TEXT DEFAULT 'pending',
created_at INTEGER
)");
@$db->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_web_tx_detail_type ON web_transactions(detail, type)");
}
function ensurePayOrdersTable($db) {
$db->exec("CREATE TABLE IF NOT EXISTS pay_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
out_trade_no TEXT UNIQUE,
trade_no TEXT,
player_name TEXT NOT NULL,
tier_id INTEGER DEFAULT 0,
money TEXT,
bond_amount INTEGER DEFAULT 0,
status TEXT DEFAULT 'created',
name TEXT,
platform_sign TEXT,
submit_params TEXT,
created_at INTEGER,
paid_at INTEGER
)");
// 检查并添加缺失的列
$hasCol = false;
$res = $db->query("PRAGMA table_info(pay_orders)");
while ($col = $res->fetchArray(SQLITE3_ASSOC)) {
if ($col['name'] === 'submit_params') { $hasCol = true; break; }
}
if (!$hasCol) {
$db->exec("ALTER TABLE pay_orders ADD COLUMN submit_params TEXT");
}
}
/**
* 处理充值商店配置
*/
function handleShopConfig() {
$db = getOrdersDB();
ensureShopConfigsTable($db);
$method = getParam('method', 'list');
switch ($method) {
case 'list':
handleShopConfigList($db);
break;
case 'save':
handleShopConfigSave($db);
break;
case 'delete':
handleShopConfigDelete($db);
break;
case 'toggle':
handleShopConfigToggle($db);
break;
default:
error('未知操作');
}
}
/**
* 获取商店配置列表
*/
function handleShopConfigList($db) {
$stmt = $db->query("SELECT * FROM shop_configs ORDER BY id DESC");
$configs = [];
while ($row = $stmt->fetchArray(SQLITE3_ASSOC)) {
// 解析有效期
$expireInfo = parseExpireDate($row['offer_expire']);
$row['expire_valid'] = $expireInfo['valid'];
$row['expire_timestamp'] = $expireInfo['timestamp'];
$row['expire_display'] = $expireInfo['display'];
$configs[] = $row;
}
success($configs);
}
/**
* 保存商店配置(添加或编辑)
*/
function handleShopConfigSave($db) {
$id = intval(getParam('id', 0));
$itemName = getParam('item_name', '');
$stock = intval(getParam('stock', -1));
$temporaryOffer = getParam('temporary_offer', '');
$offerExpire = getParam('offer_expire', '');
$price = floatval(getParam('price', 0));
$discountPriceRaw = getParam('discount_price', '0');
$bondReward = getParam('bond_reward', '1-1');
$isActive = intval(getParam('is_active', 1));
// 验证库存
if ($stock < -1) {
error('库存不能小于-1');
}
// 验证售价
if ($price <= 0) {
error('售价必须大于0');
}
// 解析优惠后价格
// 支持两种格式:
// 1. 直接价格(如 1、2、1.5)→ 直接使用
// 2. 折扣(如 0.9、0.8)→ 计算 原价 × 折扣
// 硬锁规则:
// - 0.01元以下的商品不支持折扣
// - 优惠后价格必须小于原价
$discountPrice = 0;
$discountPriceRaw = trim($discountPriceRaw);
// 0.01元以下的商品不支持折扣
if ($price < 0.01) {
if ($discountPriceRaw !== '' && $discountPriceRaw !== '0') {
error('售价低于0.01元的商品不支持折扣');
}
} elseif ($discountPriceRaw !== '' && $discountPriceRaw !== '0') {
$discountValue = floatval($discountPriceRaw);
if ($discountValue <= 0) {
error('优惠后价格必须大于0');
}
// 判断是直接价格还是折扣(<1的数认为是折扣)
if ($discountValue < 1) {
// 折扣:原价 × 折扣
$discountPrice = round($price * $discountValue, 2);
} else {
// 直接价格
$discountPrice = round($discountValue, 2);
}
// 硬锁:优惠后价格必须小于原价
if ($discountPrice >= $price) {
error('优惠后价格必须小于原价(当前原价: ' . number_format($price, 2) . '元)');
}
// 硬锁:优惠后价格不能低于0.01元
if ($discountPrice < 0.01) {
error('优惠后价格不能低于0.01元');
}
}
// 验证债券范围格式(支持盲盒范围,如"1-10")
if (!preg_match('/^\d+(-\d+)?$/', $bondReward)) {
error('债券范围格式错误,应为数字或范围如"1-10"');
}
// 解析并验证有效期
$expireInfo = parseExpireDate($offerExpire);
if ($offerExpire && !$expireInfo['valid']) {
error('有效期格式错误,请使用yyyy-MM-dd或中文格式如"2026年7月16日"');
}
$now = time();
if ($id > 0) {
// 编辑
$stmt = $db->prepare("UPDATE shop_configs SET
item_name = :name,
stock = :stock,
temporary_offer = :offer,
offer_expire = :expire,
price = :price,
discount_price = :discount_price,
bond_reward = :bond,
is_active = :active,
updated_at = :updated
WHERE id = :id");
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
} else {
// 添加
$stmt = $db->prepare("INSERT INTO shop_configs
(item_name, stock, temporary_offer, offer_expire, price, discount_price, bond_reward, is_active, created_at, updated_at)
VALUES (:name, :stock, :offer, :expire, :price, :discount_price, :bond, :active, :created, :updated)");
$stmt->bindValue(':created', $now, SQLITE3_INTEGER);
}
$stmt->bindValue(':name', $itemName, SQLITE3_TEXT);
$stmt->bindValue(':stock', $stock, SQLITE3_INTEGER);
$stmt->bindValue(':offer', $temporaryOffer, SQLITE3_TEXT);
$stmt->bindValue(':expire', $offerExpire, SQLITE3_TEXT);
$stmt->bindValue(':price', $price, SQLITE3_FLOAT);
$stmt->bindValue(':discount_price', $discountPrice, SQLITE3_FLOAT);
$stmt->bindValue(':bond', $bondReward, SQLITE3_TEXT);
$stmt->bindValue(':active', $isActive, SQLITE3_INTEGER);
$stmt->bindValue(':updated', $now, SQLITE3_INTEGER);
$stmt->execute();
$savedId = $id > 0 ? $id : $db->lastInsertRowID();
success(['id' => $savedId, 'discount_price' => $discountPrice], $id > 0 ? '配置已更新' : '配置已添加');
}
/**
* 删除商店配置
*/
function handleShopConfigDelete($db) {
$id = intval(getParam('id', 0));
if ($id <= 0) {
error('无效的配置ID');
}
$stmt = $db->prepare("DELETE FROM shop_configs WHERE id = :id");
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
$stmt->execute();
if ($db->changes() > 0) {
success([], '配置已删除');
} else {
error('配置不存在');
}
}
/**
* 切换商店配置上下架状态
*/
function handleShopConfigToggle($db) {
$id = intval(getParam('id', 0));
if ($id <= 0) {
error('无效的配置ID');
}
$stmt = $db->prepare("UPDATE shop_configs SET is_active = CASE WHEN is_active = 1 THEN 0 ELSE 1 END, updated_at = :updated WHERE id = :id");
$stmt->bindValue(':id', $id, SQLITE3_INTEGER);
$stmt->bindValue(':updated', time(), SQLITE3_INTEGER);
$stmt->execute();
if ($db->changes() > 0) {
// 获取新状态
$check = $db->prepare("SELECT is_active FROM shop_configs WHERE id = :id");
$check->bindValue(':id', $id, SQLITE3_INTEGER);
$row = $check->execute()->fetchArray(SQLITE3_ASSOC);
$newStatus = $row ? $row['is_active'] : 0;
success(['is_active' => $newStatus], $newStatus ? '已上架' : '已下架');
} else {
error('配置不存在');
}
}
/**
* 解析有效期日期(支持yyyy-MM-dd和中文格式如"2026年7月16日")
*/
function parseExpireDate($expireStr) {
if (empty($expireStr)) {
return ['valid' => true, 'timestamp' => 0, 'display' => '长期有效'];
}
$expireStr = trim($expireStr);
// 尝试解析yyyy-MM-dd格式
if (preg_match('/^\d{4}-\d{1,2}-\d{1,2}$/', $expireStr)) {
$time = strtotime($expireStr . ' 23:59:59');
if ($time !== false) {
return [
'valid' => true,
'timestamp' => $time,
'display' => date('Y-m-d', $time) . ' 到期'
];
}
}
// 尝试解析中文格式(如"2026年7月16日")
if (preg_match('/(\d{4})年(\d{1,2})月(\d{1,2})日/', $expireStr, $matches)) {
$year = $matches[1];
$month = $matches[2];
$day = $matches[3];
$time = mktime(23, 59, 59, $month, $day, $year);
if ($time !== false) {
return [
'valid' => true,
'timestamp' => $time,
'display' => "{$year}-{$month}-{$day} 到期"
];
}
}
// 尝试使用strtotime解析其他格式
$time = strtotime($expireStr);
if ($time !== false && $time > time()) {
return [
'valid' => true,
'timestamp' => $time,
'display' => date('Y-m-d', $time) . ' 到期'
];
}
return ['valid' => false, 'timestamp' => 0, 'display' => '无效日期'];
}