From 347af5537a86bb6baa82754d2cc16e5a5469650a Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Sat, 10 Oct 2026 11:32:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=9B=BA-04(P0-03):=20api/monitor.php?= =?UTF-8?q?=20=E7=9B=91=E6=8E=A7=E8=84=9A=E6=9C=AC=E9=89=B4=E6=9D=83=20+?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E8=A1=A5=E5=8D=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加鉴权:监控调用须带 ?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 通过。 --- api/monitor.php | 110 ++++++++++++++++++++++++++------------- function/setname.inc.php | 4 +- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/api/monitor.php b/api/monitor.php index 34897c0..a4fd653 100644 --- a/api/monitor.php +++ b/api/monitor.php @@ -1,7 +1,18 @@ 0)` 断逻辑(运算符优先级错误)。 + * 3. 修复未定义变量 $tradeno;trade_do 仅在状态翻转成功(影响行数>0)时执行, + * 已被先行进程完成(影响行=0)则跳过,防止重复补单。 + * + * 用法:配置您的监控服务/cron 定时访问: + * http(s)://您的域名/api/monitor.php?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); diff --git a/function/setname.inc.php b/function/setname.inc.php index 344e98c..991f3a7 100644 --- a/function/setname.inc.php +++ b/function/setname.inc.php @@ -33,5 +33,7 @@ $SetName = array( 'maindrawer'=>'', 'token'=>'', 'sid'=>'0', - 'stime'=>'0' + 'stime'=>'0', + // P0-03: api/monitor.php 监控鉴权密钥(首次访问监控脚本时自动生成,勿手工置空) + 'monitor_key'=>'' );