加固-04(P0-03): api/monitor.php 监控脚本鉴权 + 修复补单逻辑

- 增加鉴权:监控调用须带 ?key=monitor_key;密钥首次调用时自动生成
  (64hex,持久化到 sq_config setname='monitor_key',setname.inc.php
  增加种子行),hash_equals 校验,不匹配返回 403。杜绝匿名触发
  补单/清库。key 可通过 SELECT setvalue FROM sq_config WHERE
  setname='monitor_key' 获取,配置到 cron/监控服务。
- 修复 if(!!$result = select_limit_row(...) && count(...) > 0)
  运算符优先级断逻辑:拆为先取值再判断。
- 修复未定义变量 $tradeno(原 update 条件引用了从未赋值的 $tradeno)。
- trade_do 补单改为仅在状态 2->3 翻转成功(影响行数>0)时执行;
  影响行=0(已被先行进程完成)则跳过,防并发/重复补单。
- 修复 sq_apps 空表时 !array() 为真 导致误报"获取应用信息发生错误"
  (改为 false === 判断)。
- 结尾输出 JSON 结果(log/error),便于监控服务核对。

验证: 无key/错key 均 403;正确key 200;构造 status=2 测试订单,
首跑补单(agent 余额 100->110,trade 2->3),二跑无重复补单。
php -l 通过。
This commit is contained in:
youpaishidifu
2026-10-10 11:32:56 +08:00
parent c60e4ebc9b
commit 347af5537a
2 changed files with 78 additions and 36 deletions
+75 -35
View File
@@ -1,7 +1,18 @@
<?php
/**
* 本页面为未执行数据监控程序,通过监控本脚本实现自动补单、清理数据库中的沉余数据,使程序运行效率更高
* 注:本脚本会由温泉PHP网络授权系统云端监控程序自动监控访问,您无需自己监控,请将此文件所在目录添加到防攻击白名单中,否则无法监控
* 未处理数据监控程序:自动补单(已支付未完成订单)+ 清理数据库中冗余数据。
*
* 安全加固 (P0-03):
* 1. 增加鉴权:监控服务须以 {站点}/api/monitor.php?key={monitor_key} 调用;
* monitor_key 首次调用时自动生成并持久化到 sq_config(setname='monitor_key'),
* 杜绝匿名触发补单/清库。
* 2. 修复原 `if(!!$result = ... && count(...) > 0)` 断逻辑(运算符优先级错误)。
* 3. 修复未定义变量 $tradeno;trade_do 仅在状态翻转成功(影响行数>0)时执行,
* 已被先行进程完成(影响行=0)则跳过,防止重复补单。
*
* 用法:配置您的监控服务/cron 定时访问:
* http(s)://您的域名/api/monitor.php?key=<monitor_key>
* key 值可通过数据库查询:SELECT setvalue FROM sq_config WHERE setname='monitor_key';
*/
//设置链接断开仍然处理
@@ -10,45 +21,74 @@ ignore_user_abort(1);
//调用核心文件
include __DIR__ . '/../function/function_core.php';
/* --- 鉴权 (P0-03):key 缺失或不匹配直接拒绝 --- */
$monitor_key = isset($G['config']['monitor_key']) ? (string)$G['config']['monitor_key'] : '';
if (strlen($monitor_key) < 32) {
//尚未生成(如刚安装):生成一次并持久化(function_core 已保证 sq_config 中存在该键行)
$monitor_key = bin2hex(random_bytes(32));
updateset('monitor_key', $monitor_key);
}
if (!isset($_GET['key']) || !hash_equals($monitor_key, (string)$_GET['key'])) {
http_response_code(403);
die('403 Forbidden');
}
//获取已支付但未同步的订单信息
$error = array();
$log = array();
if(!!$result = $db->select_limit_row('sq_trade','*','',5,array('status'=>'2'),'AND') && count($result) > 0){
$result = $db->select_limit_row('sq_trade', '*', '', 5, array('status' => '2'), 'AND');
if (is_array($result) && count($result) > 0) {
include __DIR__ . '/../function/trade.inc.php';
foreach ($result as $tradeinfo){
if (!$db->update('sq_trade', array('tradeno' => $tradeno,'status'=>'2'), 'AND', array('status' => 3)))
$error[] = '[监控信息]订单状态更新失败:'.$tradeinfo['tradeno'].'[Error:'.$db->geterror().']';
else
$log[] = '[监控信息] 订单号处理成功';
trade_do($tradeinfo);
}
}
//清理初始化后60分钟未操作的Token数据
if($db->delete('sq_token','`lastest` = 0 AND `addtime` < '.(time()-3600).' AND `start` = 0','')){
$num = $db->affected_num();
if ($num > 0){
$log[] = '[监控信息] 清理初始化后60分钟未操作的Token数据'.$num.'条';
}
}else{
$log[] = '[监控信息] 清理过程中发生错误:'.$db->geterror();
}
//清理心跳过期数据
if(!$result = $db->select_all_row('sq_apps','ID,appname,onlinesecond')){
$log[] = '[监控信息] 获取应用信息发生错误:'.$db->geterror();
}
foreach ($result as $appinfo){
if ($appinfo['onlinesecond'] >0 ){
$OutTime = $appinfo['onlinesecond'] * 3;
if($db->delete('sq_token','`appid` = '.$appinfo['ID'].' AND `lastest` < '.$OutTime,'')){
$num = $db->affected_num();
if ($num > 0) {
$log[] = '[监控信息] 清理 '.$appinfo['appname'].' 过期Token数据' . $num . '条';
foreach ($result as $tradeinfo) {
$tradeno = $tradeinfo['tradeno'];
/*
* 幂等补单 (P0-03):先把状态 2 -> 3(WHERE 带 status=2,翻转天然幂等),
* 仅当影响行数 > 0(本进程确实完成了翻转)才执行补单逻辑。
*/
if ($db->update('sq_trade', array('tradeno' => $tradeno, 'status' => '2'), 'AND', array('status' => 3))) {
if ($db->affected_num() > 0) {
$log[] = '[监控信息] 订单 ' . $tradeno . ' 状态翻转成功,开始补单';
trade_do($tradeinfo);
} else {
$log[] = '[监控信息] 订单 ' . $tradeno . ' 已由先行进程完成,跳过';
}
}else{
$log[] = '[监控信息] 清理 '.$appinfo['appname'].' 过期数据发生错误' . $db->geterror();
} else {
$error[] = '[监控信息]订单状态更新失败:' . $tradeno . '[Error:' . $db->geterror() . ']';
}
}
}
//清理初始化后60分钟未操作的Token数据
if ($db->delete('sq_token', '`lastest` = 0 AND `addtime` < ' . (time() - 3600) . ' AND `start` = 0', '')) {
$num = $db->affected_num();
if ($num > 0) {
$log[] = '[监控信息] 清理初始化后60分钟未操作的Token数据' . $num . '条';
}
} else {
$log[] = '[监控信息] 清理过程中发生错误:' . $db->geterror();
}
//清理心跳过期数据
$result = $db->select_all_row('sq_apps', 'ID,appname,onlinesecond');
if (false === $result) {
$log[] = '[监控信息] 获取应用信息发生错误:' . $db->geterror();
} else {
foreach ($result as $appinfo) {
if ($appinfo['onlinesecond'] > 0) {
$OutTime = intval($appinfo['onlinesecond'] * 3);
if ($db->delete('sq_token', '`appid` = ' . intval($appinfo['ID']) . ' AND `lastest` < ' . $OutTime, '')) {
$num = $db->affected_num();
if ($num > 0) {
$log[] = '[监控信息] 清理 ' . $appinfo['appname'] . ' 过期Token数据' . $num . '条';
}
} else {
$log[] = '[监控信息] 清理 ' . $appinfo['appname'] . ' 过期数据发生错误:' . $db->geterror();
}
}
}
}
//输出执行结果,便于监控服务/调用方查看
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array('code' => 1, 'log' => $log, 'error' => $error), JSON_UNESCAPED_UNICODE);