feat: 债券在线充值联控(彩虹易支付RSA版) — pay.php + player.php

- api/pay.php: 下单/回调/查询 + RSA(SHA256)签名验签 + pay_orders表
- player.php: 在线充值UI + ?paid=1回跳 + 3s轮询补单
- 密钥仅从 git-ignored 的 pay_secrets.php / 密钥文件_git拉黑.txt 读取,不入库
- fix: 去除 open_basedir 限制的 /pay_keys 候选路径 + @抑制文件访问警告,避免污染响应
This commit is contained in:
ypsdf1
2026-07-14 21:03:28 +08:00
parent 5515d22d4c
commit 9a78c693bf
2 changed files with 511 additions and 1 deletions
+394
View File
@@ -0,0 +1,394 @@
<?php
/**
* 债券在线充值 · 支付平台联控(彩虹易支付 RSA 版)
*
* 动作:
* - create_order : 校验登录态 → 生成订单 → RSA签名 → 返回跳转支付URL
* - notify : 平台异步回调(GET)→ 平台公钥验签 → 金额校验 → 幂等 → 写 web_transactions(recharge)
* - query_order : 前端补单/轮询订单状态
*
* ★ 安全:本文件不含任何密钥。密钥从 pay_secrets.php(git-ignored,仅定义常量的 .php)或
* 本地 git-ignored 的「密钥文件_git拉黑.txt」读取;两者均不会进入版本库,也不会被部署脚本上传。
*/
// 防止任何前置输出污染 JSON / notify 纯文本响应
ob_start();
require_once __DIR__ . '/../core.php';
ob_end_clean();
// ===== 非机密常量(可安全提交) =====
// 彩虹易支付 下单网关
define('PAY_GATEWAY', 'https://zf.ypshidifu.cn/api/pay/submit');
// 异步通知地址(平台服务器回调,必须公网可达)
define('PAY_NOTIFY_URL', 'https://caoyuan.ypshidifu.cn/plugin/api/pay.php?action=notify');
// 同步跳转地址(玩家付款后浏览器跳回)
define('PAY_RETURN_URL', 'https://caoyuan.ypshidifu.cn/plugin/player.php?paid=1');
define('PAY_SIGN_TYPE', 'RSA');
// ===== 0.01 元测试配置(先跑通全链路,后续替换为 recharge_tiers 档位) =====
define('PAY_TEST_MONEY', '0.01'); // 元(字符串,与回调比对)
define('PAY_TEST_BONDS', 1); // 对应债券数量
define('PAY_TEST_NAME', '债券充值-测试0.01元');
// ====================================================================
// 密钥加载(双来源:本地 txt 优先,服务器 pay_secrets.php 兜底)
// ====================================================================
function loadPayKeys() {
// 来源 A:本地/部署仓库中的 git-ignored 明文文件(源真相,便于改密)
$txtCandidates = [
__DIR__ . '/../密钥文件_git拉黑.txt',
__DIR__ . '/密钥文件_git拉黑.txt',
];
foreach ($txtCandidates as $f) {
// @ 抑制:密钥文件可能不存在(正常情况),避免 open_basedir 等警告污染响应输出
if (@is_file($f) && @is_readable($f)) {
$parsed = parsePayKeyFile(@file_get_contents($f));
if (!empty($parsed['merchant_private']) && !empty($parsed['platform_public'])) {
return [
'pid' => $parsed['pid'],
'private_key' => wrapPem($parsed['merchant_private'], 'private'),
'public_key' => wrapPem($parsed['platform_public'], 'public'),
];
}
}
}
// 来源 B:服务器部署用的 pay_secrets.php(git-ignored,仅 define 常量,无输出,web 安全)
$secretsFile = __DIR__ . '/pay_secrets.php';
if (@is_file($secretsFile) && @is_readable($secretsFile)) {
@require_once $secretsFile;
if (defined('PAY_MCH_PRIVATE_KEY') && defined('PAY_PLATFORM_PUBLIC_KEY')) {
return [
'pid' => defined('PAY_PID') ? PAY_PID : '',
'private_key' => wrapPem(PAY_MCH_PRIVATE_KEY, 'private'),
'public_key' => wrapPem(PAY_PLATFORM_PUBLIC_KEY, 'public'),
];
}
}
return ['pid' => '', 'private_key' => '', 'public_key' => ''];
}
/**
* 解析「密钥文件_git拉黑.txt」格式:
* 商户ID:1001
* 平台公钥:<base64>
* 商户公钥:<base64>
* 商户私钥:<base64>
* MD5密钥:<base64>
*/
function parsePayKeyFile($content) {
$lines = preg_split('/\r\n|\r|\n/', (string)$content);
$blocks = [];
$cur = null;
foreach ($lines as $ln) {
if (preg_match('/^([\x{4e00}-\x{9fa5}A-Za-z0-9 ]+)\s*[::]\s*(.*)$/u', $ln, $m)) {
$cur = trim($m[1]);
$blocks[$cur] = $m[2];
} elseif ($cur !== null) {
$blocks[$cur] .= $ln;
}
}
$clean = function ($v) { return preg_replace('/\s+/', '', $v ?? ''); };
return [
'pid' => trim($blocks['商户ID'] ?? ''),
'platform_public' => $clean($blocks['平台公钥'] ?? ''),
'merchant_public' => $clean($blocks['商户公钥'] ?? ''),
'merchant_private'=> $clean($blocks['商户私钥'] ?? ''),
'md5' => trim($blocks['MD5密钥'] ?? ''),
];
}
/** 将裸 base64 包装为 PEM(PKCS#8 私钥 / SubjectPublicKeyInfo 公钥) */
function wrapPem($b64, $type) {
$b64 = preg_replace('/\s+/', '', $b64);
if ($b64 === '') return '';
$header = ($type === 'private') ? 'PRIVATE KEY' : 'PUBLIC KEY';
$body = trim(chunk_split($b64, 64, "\n"));
return "-----BEGIN $header-----\n$body\n-----END $header-----\n";
}
/**
* RSA(SHA256withRSA) 请求签名:取全部非空参数 → 剔除 sign/sign_type → 参数名 ASCII 升序 → 原始值 k=v& 拼接 → 私钥签名
*/
function buildSign($params, $privateKeyPem) {
$filtered = [];
foreach ($params as $k => $v) {
if ($k === 'sign' || $k === 'sign_type') continue;
if ($v === '' || $v === null) continue;
$filtered[$k] = $v;
}
ksort($filtered, SORT_STRING);
$parts = [];
foreach ($filtered as $k => $v) {
$parts[] = $k . '=' . $v;
}
$data = implode('&', $parts);
$key = openssl_pkey_get_private($privateKeyPem);
if ($key === false) {
throw new Exception('商户私钥加载失败: ' . openssl_error_string());
}
openssl_sign($data, $signature, $key, OPENSSL_ALGO_SHA256);
openssl_free_key($key);
return base64_encode($signature);
}
/**
* RSA(SHA256withRSA) 回调验签:用平台公钥验证(兼容平台新增的扩展字段——取回调全部非空参数参与)
*/
function verifyNotifySign($params, $publicKeyPem) {
if (empty($params['sign']) || empty($publicKeyPem)) return false;
$sign = $params['sign'];
$filtered = [];
foreach ($params as $k => $v) {
if ($k === 'sign' || $k === 'sign_type') continue;
if ($v === '' || $v === null) continue;
$filtered[$k] = $v;
}
ksort($filtered, SORT_STRING);
$parts = [];
foreach ($filtered as $k => $v) {
$parts[] = $k . '=' . $v;
}
$data = implode('&', $parts);
$key = openssl_pkey_get_public($publicKeyPem);
if ($key === false) return false;
$ok = openssl_verify($data, base64_decode($sign), $key, OPENSSL_ALGO_SHA256);
openssl_free_key($key);
return $ok === 1;
}
// ====================================================================
// 数据表
// ====================================================================
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,
created_at INTEGER,
paid_at INTEGER
)");
}
// ====================================================================
// 动作:create_order
// ====================================================================
function createOrder($token) {
$keys = loadPayKeys();
if (empty($keys['pid']) || empty($keys['private_key']) || empty($keys['public_key'])) {
error('支付密钥未配置,请联系管理员', 500);
}
$info = validateTokenSilent($token);
if (!$info || empty($info['player'])) {
error('登录状态无效,请重新登录', 401);
}
$player = $info['player'];
$db = getDB();
ensurePayOrdersTable($db);
// ===== 0.01 元测试档位(固定映射:0.01 元 → 1 债券) =====
$money = PAY_TEST_MONEY;
$bonds = PAY_TEST_BONDS;
$name = PAY_TEST_NAME;
$outTradeNo = 'RE' . date('YmdHis') . sprintf('%04d', mt_rand(0, 9999));
$stmt = $db->prepare("INSERT INTO pay_orders (out_trade_no, player_name, tier_id, money, bond_amount, status, name, created_at) VALUES (:no, :p, 0, :m, :b, 'created', :n, :t)");
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$stmt->bindValue(':p', $player, SQLITE3_TEXT);
$stmt->bindValue(':m', $money, SQLITE3_TEXT);
$stmt->bindValue(':b', $bonds, SQLITE3_INTEGER);
$stmt->bindValue(':n', $name, SQLITE3_TEXT);
$stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->execute();
// 构造提交参数(原始值,不做 urlencode;sign 在原始值上计算)
$params = [
'pid' => $keys['pid'],
'type' => 'alipay',
'out_trade_no'=> $outTradeNo,
'notify_url' => PAY_NOTIFY_URL,
'return_url' => PAY_RETURN_URL,
'name' => $name,
'money' => $money,
'param' => $player,
'timestamp' => (string)time(),
'sign_type' => PAY_SIGN_TYPE,
];
$params['sign'] = buildSign($params, $keys['private_key']);
// GET 跳转(浏览器跳转付款)。http_build_query 仅做传输层 urlencode,平台解码后还原为原始值参与验签。
$payUrl = PAY_GATEWAY . '?' . http_build_query($params);
success([
'pay_url' => $payUrl,
'out_trade_no' => $outTradeNo,
'money' => $money,
'bonds' => $bonds,
'player' => $player,
], '订单创建成功,正在跳转支付');
}
// ====================================================================
// 动作:notify(平台异步回调,必须输出纯文本 success)
// ====================================================================
function handleNotify() {
// 清空输出缓冲,确保最终只输出 success/fail
while (ob_get_level() > 0) { ob_end_clean(); }
$params = $_GET;
debugLog('[pay notify] 收到异步回调', $params);
$keys = loadPayKeys();
// 1) 验签(平台公钥)
if (!verifyNotifySign($params, $keys['public_key'])) {
debugLog('[pay notify] 验签失败,疑似伪造', ['out_trade_no' => $params['out_trade_no'] ?? '']);
echo 'fail';
exit;
}
// 2) 交易状态必须是成功
if (($params['trade_status'] ?? '') !== 'TRADE_SUCCESS') {
debugLog('[pay notify] 交易状态非成功: ' . ($params['trade_status'] ?? ''), ['out_trade_no' => $params['out_trade_no'] ?? '']);
// 非成功状态(如 WAIT_BUYER_PAY)返回 fail,让平台继续重试直到成功
echo 'fail';
exit;
}
$outTradeNo = $params['out_trade_no'] ?? '';
if ($outTradeNo === '') {
echo 'fail';
exit;
}
$db = getDB();
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) {
debugLog('[pay notify] 订单不存在: ' . $outTradeNo);
// 订单未知也返回 success,避免平台死循环重试无效通知
echo 'success';
exit;
}
// 3) 金额校验:回调 money 必须与订单完全一致(防篡改少付多充)
if (function_exists('bccomp')) {
$moneyOk = bccomp((string)$row['money'], (string)($params['money'] ?? ''), 2) === 0;
} else {
$moneyOk = ((string)$row['money'] === (string)($params['money'] ?? ''));
}
if (!$moneyOk) {
debugLog('[pay notify] 金额不匹配(疑似篡改)', ['order_money' => $row['money'], 'notify_money' => $params['money'] ?? '']);
echo 'fail';
exit;
}
// 4) 幂等:已支付则直接返回 success,绝不重复写 web_transactions
if ($row['status'] === 'paid') {
echo 'success';
exit;
}
// 5) 标记订单已支付
$stmt = $db->prepare("UPDATE pay_orders SET status='paid', trade_no=:tn, paid_at=:t WHERE out_trade_no=:no");
$stmt->bindValue(':tn', $params['trade_no'] ?? '', SQLITE3_TEXT);
$stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$stmt->execute();
// 6) 写 web_transactions(recharge) 供 Java 高频定时器拉取 → addBonds 到账
$stmt = $db->prepare("INSERT INTO web_transactions (player_name, type, amount, operator, reason, detail, status, created_at) VALUES (:p, 'recharge', :a, '支付平台', '在线充值(支付宝)', :d, 'pending', :t)");
$stmt->bindValue(':p', $row['player_name'], SQLITE3_TEXT);
$stmt->bindValue(':a', (int)$row['bond_amount'], SQLITE3_INTEGER);
$stmt->bindValue(':d', $outTradeNo, SQLITE3_TEXT);
$stmt->bindValue(':t', time(), SQLITE3_INTEGER);
$stmt->execute();
debugLog('[pay notify] 充值交易已写入', ['player' => $row['player_name'], 'bonds' => $row['bond_amount'], 'out_trade_no' => $outTradeNo]);
echo 'success';
exit;
}
// ====================================================================
// 动作:query_order(前端补单 / 轮询)
// ====================================================================
function queryOrder($token) {
$info = validateTokenSilent($token);
if (!$info || empty($info['player'])) {
error('登录状态无效', 401);
}
$outTradeNo = getParam('out_trade_no');
if (!$outTradeNo) {
error('缺少订单号');
}
$db = getDB();
ensurePayOrdersTable($db);
$stmt = $db->prepare("SELECT * FROM pay_orders WHERE out_trade_no=:no AND player_name=:p");
$stmt->bindValue(':no', $outTradeNo, SQLITE3_TEXT);
$stmt->bindValue(':p', $info['player'], SQLITE3_TEXT);
$row = $stmt->execute()->fetchArray(SQLITE3_ASSOC);
if (!$row) {
error('订单不存在');
}
$status = $row['status'];
$message = '订单创建中';
if ($status === 'paid') {
$message = '已支付,游戏内债券发放中(若未到账请稍候或联系管理员)';
}
success([
'out_trade_no' => $outTradeNo,
'status' => $status,
'money' => $row['money'],
'bonds' => (int)$row['bond_amount'],
'paid_at' => $row['paid_at'],
], $message);
}
// ====================================================================
// 入口分发
// ====================================================================
$action = getParam('action', '');
try {
switch ($action) {
case 'create_order':
createOrder(getParam('token'));
break;
case 'notify':
handleNotify(); // 内部已 exit
break;
case 'query_order':
queryOrder(getParam('token'));
break;
default:
error('未知操作: ' . $action);
}
// ★ WAL checkpoint:释放WAL锁,减少 database is locked 概率
try { walCheckpoint(); } catch (\Throwable $ignored) {}
} catch (\Throwable $e) {
if ($action === 'notify') {
// notify 异常时返回 fail,让平台重试
while (ob_get_level() > 0) { ob_end_clean(); }
echo 'fail';
exit;
}
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['error' => ['code' => 500, 'message' => 'Internal error: ' . $e->getMessage()]], JSON_UNESCAPED_UNICODE);
exit;
}
+117 -1
View File
@@ -276,6 +276,7 @@ if ($currentVersion !== $BUILD_VERSION) {
<div class="sidebar-item" data-page="lands" onclick="switchPage('lands')">🏡 领地管理</div> <div class="sidebar-item" data-page="lands" onclick="switchPage('lands')">🏡 领地管理</div>
<div class="sidebar-item" data-page="cdk" onclick="switchPage('cdk')">🎁 CDK兑换</div> <div class="sidebar-item" data-page="cdk" onclick="switchPage('cdk')">🎁 CDK兑换</div>
<div class="sidebar-item" data-page="balance" onclick="switchPage('balance')">💰 余额查询</div> <div class="sidebar-item" data-page="balance" onclick="switchPage('balance')">💰 余额查询</div>
<div class="sidebar-item" data-page="recharge" onclick="switchPage('recharge')">💳 在线充值</div>
<div class="sidebar-item" data-page="groups" onclick="switchPage('groups')">👥 用户组</div> <div class="sidebar-item" data-page="groups" onclick="switchPage('groups')">👥 用户组</div>
<div class="sidebar-item" data-page="ticket" onclick="switchPage('ticket')">📋 工单系统</div> <div class="sidebar-item" data-page="ticket" onclick="switchPage('ticket')">📋 工单系统</div>
</div> </div>
@@ -423,7 +424,12 @@ if ($currentVersion !== $BUILD_VERSION) {
// ★ 如果不需要密码,直接加载个人中心 // ★ 如果不需要密码,直接加载个人中心
if (typeof NEED_PASSWORD !== 'undefined' && !NEED_PASSWORD) { if (typeof NEED_PASSWORD !== 'undefined' && !NEED_PASSWORD) {
setTimeout(() => { setTimeout(() => {
switchPage('dashboard'); // 支付回跳 ?paid=1 直接进入充值页确认到账
if (new URLSearchParams(location.search).get('paid') === '1') {
switchPage('recharge');
} else {
switchPage('dashboard');
}
// ★ Token跟随Java端配置过期时间自动过期,不续费 // ★ Token跟随Java端配置过期时间自动过期,不续费
// startTokenPolling() 不再调用,仅保留变量声明 // startTokenPolling() 不再调用,仅保留变量声明
}, 100); }, 100);
@@ -1045,6 +1051,116 @@ if ($currentVersion !== $BUILD_VERSION) {
else if (page === 'groups') renderGroups(c); else if (page === 'groups') renderGroups(c);
else if (page === 'account') renderAccount(c); else if (page === 'account') renderAccount(c);
else if (page === 'ticket') renderTicket(c); else if (page === 'ticket') renderTicket(c);
else if (page === 'recharge') renderRecharge(c);
}
// ==================== 债券在线充值(测试:0.01元 → 1债券) ====================
function renderRecharge(el) {
el.innerHTML = `
<div class="card" style="max-width:520px;margin:0 auto">
<h2 style="margin:0 0 6px">💳 债券在线充值</h2>
<p style="color:var(--dim);font-size:13px;margin:0 0 18px">测试阶段:¥0.01 = 1 债券(仅支付宝)。支付成功后游戏内即时到账。</p>
<div style="border:1px solid var(--border);border-radius:14px;padding:18px;margin-bottom:16px;background:linear-gradient(135deg,rgba(63,185,80,0.08),rgba(88,166,255,0.06))">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<span style="font-size:14px;color:var(--fg)">测试充值档位</span>
<span style="font-size:12px;color:var(--green);border:1px solid var(--green);border-radius:999px;padding:2px 10px">测试</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:baseline">
<div><div style="font-size:28px;font-weight:800;color:var(--accent)">¥0.01</div><div style="font-size:12px;color:var(--dim)">支付金额</div></div>
<div style="font-size:22px;color:var(--green)">→ 1 债券</div>
</div>
</div>
<button id="rechargeBtn" class="btn btn-primary" style="width:100%;font-size:15px;padding:12px" onclick="startRecharge()">🅰️ 支付宝支付 ¥0.01</button>
<div id="rechargeStatus" style="margin-top:14px;font-size:13px;min-height:20px"></div>
<p style="color:var(--dim);font-size:12px;margin-top:14px;line-height:1.6">说明:点击后跳转至支付宝完成付款,浏览器会自动跳回本页并自动确认到账。若未自动到账,可点击下方按钮手动查询。</p>
<button id="rechargeQueryBtn" class="btn" style="width:100%;margin-top:8px;display:none" onclick="pollRechargeOrder(true)">🔄 查询到账状态</button>
</div>
`;
// 处理支付回跳 ?paid=1
const paid = new URLSearchParams(location.search).get('paid');
const lastNo = localStorage.getItem('sdf1_last_recharge_no');
if (paid === '1') {
const st = document.getElementById('rechargeStatus');
if (lastNo) {
st.innerHTML = '<span style="color:var(--accent)">检测到支付回跳,正在确认到账…</span>';
document.getElementById('rechargeQueryBtn').style.display = 'block';
pollRechargeOrder(false);
} else {
st.innerHTML = '<span style="color:var(--yellow)">已返回,但未找到本地订单记录。如已支付,请在「余额查询」确认或联系管理员。</span>';
}
// 清除 URL 中的 paid 标记,避免刷新重复提示
try {
const u = new URL(location.href);
u.searchParams.delete('paid');
history.replaceState({}, '', u);
} catch (e) {}
}
}
async function startRecharge() {
const btn = document.getElementById('rechargeBtn');
const st = document.getElementById('rechargeStatus');
if (!TOKEN) { toast('请先登录', 'error'); return; }
btn.disabled = true;
btn.textContent = '⏳ 正在创建订单…';
st.innerHTML = '<span style="color:var(--dim)">正在请求支付订单…</span>';
try {
const res = await api('pay.php', {action: 'create_order'});
if (!res.success) {
st.innerHTML = '<span style="color:var(--red)">创建订单失败:' + (res.message || '未知错误') + '</span>';
btn.disabled = false;
btn.textContent = '🅰️ 支付宝支付 ¥0.01';
return;
}
const { pay_url, out_trade_no } = res.data;
localStorage.setItem('sdf1_last_recharge_no', out_trade_no);
st.innerHTML = '<span style="color:var(--green)">订单已创建,正在跳转支付宝…(如未弹出,请允许浏览器打开新窗口)</span>';
document.getElementById('rechargeQueryBtn').style.display = 'block';
const w = window.open(pay_url, '_blank');
if (!w) {
st.innerHTML = '<span style="color:var(--yellow)">浏览器拦截了新窗口,<a href="' + pay_url + '" target="_blank" style="color:var(--accent)">点此手动打开支付页面</a></span>';
}
pollRechargeOrder(false);
} catch (e) {
st.innerHTML = '<span style="color:var(--red)">请求异常:' + e.message + '</span>';
btn.disabled = false;
btn.textContent = '🅰️ 支付宝支付 ¥0.01';
}
}
let rechargePollTimer = null;
async function pollRechargeOrder(manual) {
const st = document.getElementById('rechargeStatus');
const no = localStorage.getItem('sdf1_last_recharge_no');
if (!no) { if (manual) toast('没有可查询的订单', 'error'); return; }
if (!TOKEN) { if (manual) toast('请先登录', 'error'); return; }
if (rechargePollTimer && !manual) return; // 已在轮询
const doPoll = async () => {
try {
const res = await api('pay.php', {action: 'query_order', out_trade_no: no});
if (res.success && res.data) {
if (res.data.status === 'paid') {
st.innerHTML = '<span style="color:var(--green)">✅ 支付成功!游戏内债券发放中,请稍候刷新「余额查询」。</span>';
if (rechargePollTimer) { clearInterval(rechargePollTimer); rechargePollTimer = null; }
} else {
st.innerHTML = '<span style="color:var(--dim)">⏳ 订单处理中(' + (res.message || '等待支付平台通知') + ')…</span>';
}
} else {
st.innerHTML = '<span style="color:var(--orange)">' + (res.message || '查询失败') + '</span>';
}
} catch (e) {
st.innerHTML = '<span style="color:var(--orange)">查询异常:' + e.message + '</span>';
}
};
await doPoll();
if (!manual && !rechargePollTimer) {
rechargePollTimer = setInterval(doPoll, 3000);
setTimeout(() => { if (rechargePollTimer) { clearInterval(rechargePollTimer); rechargePollTimer = null; } }, 120000);
}
} }
// ★ 新增:Token轮询检查(不续期,只检查是否被Java端主动销毁) // ★ 新增:Token轮询检查(不续期,只检查是否被Java端主动销毁)