<?php
error_reporting(0);
define('SYSTEM_ROOT', dirname(__FILE__).'/');
define('ROOT', dirname(SYSTEM_ROOT).'/');
define('TEMPLATE_ROOT', ROOT.'template/');
date_default_timezone_set('Asia/Shanghai');
$date = date("Y-m-d H:i:s");
include_once(ROOT."includes/autoloader.php");
Autoloader::register();
require ROOT.'config.php';
$DB = new \lib\PdoHelper($dbconfig);
$conf = [];
$conf_ = $DB->query("SELECT * FROM pre_config");
if($conf_){
    while($res = $conf_->fetch()){
        $conf[$res['k']] = $res['v'];
    }
}
$conf_frame = $conf;
$frame_ = $DB->query("SELECT * FROM pre_frame_conf");
if($frame_){
    while($res = $frame_->fetch()){
        if(!array_key_exists($res['k'], $conf)){
            $conf_frame[$res['k']] = $res['v'];
        }
    }
}
include_once(ROOT."includes/functions.php");
include_once(ROOT."includes/robot_fun.php");
if(function_exists("set_time_limit")){
	@set_time_limit(0);
}
if(function_exists("ignore_user_abort")){
	@ignore_user_abort(true);
}
@header('Content-Type: text/html; charset=UTF-8');
$_key = isset($_GET['key'])?daddslashes($_GET['key']):exit('参数不正确');
if($_key == '')exit("参数不能为空");
if($_key != $conf['cronkey'] && $_key != $conf['apikey'])exit("监控密钥不正确----".$_key."----".$conf['cronkey']);
$time = time();
if($_GET['act']=='end'){

	$count=0;
	$no_end_count = 0;
	$deleted_count = 0;
	$logout_count = 0;
	$count_dg=0;
	$no_end_count_dg = 0;
	$deleted_count_dg = 0;
	$logout_count_dg = 0;
	$qq_end_day = isset($conf_frame['qq_end_day']) ? intval($conf_frame['qq_end_day']) : 0;
	$del_qq_type = isset($conf_frame['del_qq_type']) ? intval($conf_frame['del_qq_type']) : 0;
	$notify_end_switch = isset($conf['notice_end']) ? intval($conf['notice_end']) : 1;
	$notify_end_day = isset($conf['notify_end_day']) ? intval($conf['notify_end_day']) : 5;
	$notify_interval_hours = isset($conf['end_time']) && $conf['end_time'] !== '' ? intval($conf['end_time']) : 8;
	if($notify_interval_hours < 1){
		$notify_interval_hours = 8;
	}
	$notify_interval_sec = $notify_interval_hours * 3600;
	$login = get_loginconfig();
	$class = get_login_class();
	$do_end_check = function($qq_table, &$no_end, &$end_count, &$del_count, &$logout_count) use ($DB, $login, $class, $conf, $time, $qq_end_day, $del_qq_type, $notify_end_switch, $notify_end_day, $notify_interval_sec){
		$sql = "SELECT a.*,c.email,c.phone,c.wechat_appuid,c.notice_type FROM `{$qq_table}` a LEFT JOIN `pre_user` c ON a.zid=c.zid LIMIT 500";
		$rs = $DB->query($sql);
		if($rs === false){
			exit("SQL错误: " . $DB->error() . "\nSQL: " . $sql);
		}
		while($res = $rs->fetch()){
			if(empty($login[$res['cityid']])){
				$no_end++;
				continue;
			}
			if(($login[$res['cityid']]['apipwd'] ?? '') != ''){
				$no_end++;
				continue;
			}
			$endtime_arr = json_decode($res['endtime_arr'],true);
			if(!is_array($endtime_arr) || empty($endtime_arr)){
				$end_count++;
				continue;
			}
			$end_days_arr = array();
			$is_all_end = true;
			$is_any_end = false;
			$end_notify_arr = array();
			if(!empty($res['end_notify_arr'])){
				$_dec = json_decode($res['end_notify_arr'], true);
				if(is_array($_dec)){
					$end_notify_arr = $_dec;
				}
			}
			$end_notify_dirty = false;
			foreach ($endtime_arr as $k => $t){
				$end_timestamp = strtotime($t);
				if($time > $end_timestamp){
					$days = floor(($time - $end_timestamp) / 86400);
					if($qq_end_day > 0 && $days >= $qq_end_day){
						$end_days_arr[$k] = $days;
						$is_any_end = true;
					} elseif($qq_end_day == 0){
						$is_any_end = true;
					}
				}else{
					$is_all_end = false;
					$secs_left = $end_timestamp - $time;
					$days_left_ceil = $secs_left > 0 ? (int)ceil($secs_left / 86400) : 0;
					if($notify_end_switch == 1 && $notify_end_day > 0 && $secs_left > 0 && $days_left_ceil <= $notify_end_day){
						$class_key = (string)$k;
						$last_sent = isset($end_notify_arr[$class_key]) ? intval($end_notify_arr[$class_key]) : 0;
						$skip_notify = ($notify_interval_sec > 0 && $last_sent > 0 && ($time - $last_sent) < $notify_interval_sec);
						if(!$skip_notify){
						$days_left = $days_left_ceil;
						$sent_ok = false;
						if($res['notice_type'] == 1 && $res['phone'] != ''){
							if(strlen($res['phone'])==11){
							$class_name = isset($class[$k]['name']) ? $class[$k]['name'] : '未知分类';
							$con = $conf['qq_end_notify_nr']==''?'尊贵的用户，您好！账号：[qq] [换行]通道：[接口分类名称] [换行]到期剩余：[剩余天数]天 [换行]请及时续费，避免服务中断。':$conf['qq_end_notify_nr'];
							$con = str_replace(array("[qq]","[接口分类名称]","[剩余天数]"),array($res['qq'],$class_name,$days_left),$con);
							$ret = send_sms($res['phone'], $con);
							$sent_ok = ($ret === true || (is_array($ret) && intval($ret['code'] ?? 0) == 1));
							}
						}else{
							$class_name = isset($class[$k]['name']) ? $class[$k]['name'] : '未知分类';
							$con = $conf['qq_end_notify_nr']==''?'<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">尊贵的用户，您好！</h2><p style="padding-left:2em;margin:0">账号：[qq][换行]通道：<font color="red">[接口分类名称]</font>[换行]到期剩余：<font color="red">[剩余天数]天</font>[换行]请及时续费，避免服务中断。</p>':$conf['qq_end_notify_nr'];
							$con = str_replace(array("[qq]","[接口分类名称]","[剩余天数]","[换行]"),array($res['qq'],$class_name,$days_left,"<br>"),$con);
							if($res['notice_type'] == 2){
								$ret = send_wx($con,'到期提醒', 1, [], [$res['wechat_appuid']]);
								$code = is_array($ret) ? intval($ret['code'] ?? 0) : 0;
								$sent_ok = ($ret === true || $code === 1 || $code === 1000);
							}else{
								$email = ($res['email']==''?$res['qq'].'@qq.com':$res['email']);
								$sent_ok = (bool)\lib\MessageSend::send($email,'账号到期提醒','到期提醒',$con);
							}
						}
						if($sent_ok){
							$end_notify_arr[$class_key] = $time;
							$end_notify_dirty = true;
						}
						}
					}
				}
			}
			if(!empty($end_notify_dirty)){
				foreach(array_keys($end_notify_arr) as $_nk){
					if(!array_key_exists($_nk, $endtime_arr)){
						unset($end_notify_arr[$_nk]);
					}
				}
				$json_notify = addslashes(json_encode($end_notify_arr, JSON_UNESCAPED_UNICODE));
				$DB->exec("UPDATE `{$qq_table}` SET end_notify_arr='{$json_notify}' WHERE id='".intval($res['id'])."'");
			}
			if(intval($res['login_zt'] ?? 0) === 1 && !empty($login[$res['cityid']])){
				if(function_exists('is_end_for_can_use') && is_end_for_can_use(json_encode($endtime_arr, JSON_UNESCAPED_UNICODE), $login[$res['cityid']])){
					$logout_count++;
					outqq($login[$res['cityid']], $res['qq']);
				}
			}
			if($is_any_end && $is_all_end){
				$end_count++;
			}else{
				$no_end++;
			}
			if(count($end_days_arr) != 0 && count($end_days_arr) == count($endtime_arr)){
				$do_whole_del = true;
				if(!empty($login[$res['cityid']]) && function_exists('is_end_for_can_use')){
					if(!is_end_for_can_use(json_encode($endtime_arr, JSON_UNESCAPED_UNICODE), $login[$res['cityid']])){
						$do_whole_del = false;
					}
				}
				if($do_whole_del){
					$del_count++;
					if($del_qq_type === 0){
						if($qq_table === 'pre_qq'){
							set_qq_user($res['qq'],0,1);
						}else{
							$DB->exec("DELETE FROM `{$qq_table}` WHERE qq='{$res['qq']}'");
						}
						deleteqq($login[$res['cityid']],$res['qq']);
					}elseif($del_qq_type === 1){
						if($qq_table === 'pre_qq'){
							set_qq_user($res['qq'],0,1);
						}else{
							$DB->exec("DELETE FROM `{$qq_table}` WHERE qq='{$res['qq']}'");
						}
					}elseif($del_qq_type === 2){
						deleteqq($login[$res['cityid']],$res['qq']);
					}
				}else{
					foreach ($end_days_arr as $k2 => $t2) {
						unset($endtime_arr[$k2]);
					}
					$DB->exec("UPDATE `{$qq_table}` SET endtime_arr='".json_encode($endtime_arr)."' WHERE id='{$res['id']}'");
					$del_count++;
				}
			}else if(count($end_days_arr) != 0){
				foreach ($end_days_arr as $k2 => $t2) {
					unset($endtime_arr[$k2]);
				}
				$DB->exec("UPDATE `{$qq_table}` SET endtime_arr='".json_encode($endtime_arr)."' WHERE id='{$res['id']}'");
				$del_count++;
			}
		}
	};
	$do_end_check('pre_qq', $no_end_count, $count, $deleted_count, $logout_count);
	if(($conf['dg_open'] ?? '0') == '1'){
		$do_end_check('pre_dg_qq', $no_end_count_dg, $count_dg, $deleted_count_dg, $logout_count_dg);
	}
	echo '云端 - 未到期: '.$no_end_count."个\n云端 - 已到期: ".$count."个\n云端 - 已删除: ".$deleted_count."个\n云端 - 已发下线: ".$logout_count."个\n";
	if(($conf['dg_open'] ?? '0') == '1'){
		echo "代挂 - 未到期: {$no_end_count_dg}个\n代挂 - 已到期: {$count_dg}个\n代挂 - 已删除: {$deleted_count_dg}个\n代挂 - 已发下线: {$logout_count_dg}个\n";
	}

}elseif($_GET['act']=='qqzt'){

	$qqzt_lock_seconds = isset($conf_frame['get_frame_qq_time']) ? intval($conf_frame['get_frame_qq_time']) : 60;
	if($qqzt_lock_seconds <= 0) $qqzt_lock_seconds = 60;
	$lock_until = intval($conf['time_qqzt'] ?? 0);
	$remain = $lock_until - $time;
	$stale_threshold = max($qqzt_lock_seconds * 3, 120);
	if($remain > 0){
		if($remain <= $stale_threshold){
			exit('不能多处监控，剩余'.$remain.'秒');
		}else{
			echo "⚠️ 检测到残留锁(剩余{$remain}秒)，已自动释放\n";
		}
	}
	$DB->exec("REPLACE INTO `pre_config` SET v='".($time + $qqzt_lock_seconds)."',k='time_qqzt'");
	register_shutdown_function(function() use ($DB) {
		$DB->exec("REPLACE INTO `pre_config` SET v='".time()."',k='time_qqzt'");
	});

	$notice_frame = intval($conf['notice_frame']);
	$login_notify_count = intval($conf['notice_frame_count'] ?? 0);
	if($login_notify_count <= 0) $login_notify_count = 5;
	$login_notify_time = intval($conf['notice_frame_time'] ?? 0);
	if($login_notify_time <= 0) $login_notify_time = 5;
	$login_notify_rate = intval($conf['notice_frame_rate'] ?? 0);
	if($login_notify_rate <= 0) $login_notify_rate = 10;
	$login_notify_rate_start = 3;
	echo "🔧 通知配置 - 渠道:{$notice_frame} 次数上限:{$login_notify_count} 间隔:{$login_notify_time}分钟\n";
	echo "🔧 收件配置 - mail_recv:".($conf['mail_recv'] ?? '')." sms_phone:".($conf['sms_phone'] ?? '')." wechat_appuid:".($conf['wechat_appuid'] ?? '')."\n";
	$rs=$DB->query("SELECT * FROM `pre_loginconfig` WHERE status='1'");
	$frames = [];
	while($res = $rs->fetch(PDO::FETCH_ASSOC)){
		$frames[] = $res;
	}
	
	$total_frames = count($frames);
	$success_frames = 0;
	$total_qqs = 0;
	$online_qqs = 0;
	$offline_qqs = 0;
	$pending_frame_notifies = [];
	
	if($total_frames == 0){
		echo "没有启用的接口";
		exit;
	}
	
	echo "\n🚀 开始多线程监控 {$total_frames} 个接口...\n";
	
	$requests = [];
	foreach($frames as $frame){
		$ip = trim(str_replace(['`', '"', "'", "\n", "\r", "\t"], '', $frame['ip'] ?? $frame['url'] ?? ''));
		if(empty($ip)) continue;
		
		$port = $frame['port'] ?? '';
		if(strpos($ip, 'http://') === 0 || strpos($ip, 'https://') === 0){
			$base_url = rtrim($ip, '/');
			$parsed = parse_url($base_url);
			if(!isset($parsed['port']) && $port) $base_url .= ':' . $port;
		}else{
			$base_url = $port ? "http://{$ip}:{$port}" : "http://" . rtrim($ip, '/');
		}
		
		$httpkey = $frame['httpkey'] ?? $frame['apikey'] ?? $conf['httpkey'] ?? $conf['apikey'] ?? '';
		
		$requests[] = [
			'url' => $base_url . '/get_bot_list',
			'params' => [],
			'timeout' => 5,
			'headers' => ['httpkey: ' . $httpkey, 'Content-Type: application/json'],
			'frame' => $frame,
			'frame_id' => $frame['id'],
			'frame_name' => $frame['citybz']
		];
	}
	
	$results = curl_multi_request($requests);
	
	foreach($results as $index => $result){
		if(!isset($requests[$index]['frame'])) continue;
		$frame = $requests[$index]['frame'];
		$frame_id = $frame['id'];
		$frame_name = $frame['citybz'];
		
		$frameBiz = strtolower(trim((string)($frame['biz'] ?? 'cloud')));
		$qq_table = ($frameBiz === 'dg') ? 'pre_dg_qq' : 'pre_qq';
		$bizTitle = ($frameBiz === 'dg') ? '代挂' : '云端';
		echo "\n开始处理{$bizTitle}节点\n";
		echo "接口：{$frame_name} (ID: {$frame_id})\n";
		try{
		if(isset($result['retcode']) && $result['retcode'] == 200){
			$success_frames++;
			echo "✅ 框架通讯正常\n";
			$DB->exec("UPDATE `pre_loginconfig` SET status_api='1', notify_count='0', notify_time='0' WHERE id='{$frame_id}'");
			$streak_key = 'frame_err_streak_'.$frame_id;
			$DB->exec("REPLACE INTO `pre_config` SET v='0',k='{$streak_key}'");
			$conf[$streak_key] = 0;
			
			$qq_key = function($q) {
				if ($q === '' || $q === null || $q === false) return '';
				$s = trim((string)$q);
				if ($s === '' || $s === '0') return '';
				if (ctype_digit($s)) return (string)((int)$s);
				return $s;
			};
			
			$qqlist = array();
			$qqzt_arr = array();
			$qq_has_bind = array();
			$rs_qq = $DB->query("SELECT a.qq,a.login_zt,a.dropline,a.endtime_arr,
				CASE WHEN b.id IS NULL THEN 0 ELSE 1 END AS has_bind
				FROM `{$qq_table}` a
				LEFT JOIN `pre_loginconfig` b ON a.cityid=b.id AND b.biz='{$frameBiz}'
				WHERE a.cityid='{$frame_id}'");
			while($qres = $rs_qq->fetch(PDO::FETCH_ASSOC)){
				$qqlist[$qres['qq']] = $qres;
				$qqzt_arr[$qres['qq']] = intval($qres['login_zt']);
				$qq_has_bind[$qres['qq']] = intval($qres['has_bind']);
			}
			
			$frame_qq_list = array();
			if(isset($result['bot']['list'])){
				$online_uins = array();
				foreach($result['bot']['online_list'] ?? [] as $r){
					$qk = $qq_key($r['bot'] ?? $r['uin'] ?? 0);
					if($qk !== '') $online_uins[$qk] = 1;
				}
				foreach($result['bot']['list'] as $row){
					$qq = $qq_key($row['bot'] ?? $row['uin'] ?? 0);
					if($qq === '') continue;
					$online = ($row['online'] === true || $row['online'] === 'true' || ($row['status'] ?? null) == 1 || ($row['status'] ?? null) === '1')
						? 1 : (isset($online_uins[$qq]) ? 1 : 0);
					$frame_qq_list[$qq] = $online;
				}
			}elseif(isset($result['QQlist'])){
				foreach($result['QQlist'] as $qq => $status){
					$qk = $qq_key($qq);
					if($qk === '') continue;
					$frame_qq_list[$qk] = $status ? 1 : 0;
				}
			}elseif(isset($result['data'])){
				foreach($result['data'] as $qq => $status){
					$qk = $qq_key($qq);
					if($qk === '') continue;
					$frame_qq_list[$qk] = $status ? 1 : 0;
				}
			}
			
			$frame_zt_for = function($qq) use ($frame_qq_list, $qq_key) {
				$k = $qq_key($qq);
				if ($k !== '' && isset($frame_qq_list[$k])) return intval($frame_qq_list[$k]);
				if (isset($frame_qq_list[$qq])) return intval($frame_qq_list[$qq]);
				return null;
			};
			
			$notice_qq = array();
			$relogin_qq = array();
			$zt_arr = array();
			$qq_arr = array();
			
			foreach($qqzt_arr as $qq => $current_zt){
				$has_bind = isset($qq_has_bind[$qq]) ? intval($qq_has_bind[$qq]) : 1;
				if($has_bind !== 1){
					$zt_arr[] = "WHEN ".$qq." THEN 0";
					$qq_arr[] = $qq;
					continue;
				}

				$fz = $frame_zt_for($qq);
				if($fz !== null){
					$new_zt = $fz;
					if($new_zt == 0 && $current_zt == 1){
						$notice_qq[] = $qq;
						if(isset($qqlist[$qq]) && intval($qqlist[$qq]['dropline']) == 1){
							$relogin_qq[] = $qq;
						}
					}
					$zt_arr[] = "WHEN ".$qq." THEN ".$new_zt;
				}else{
					if($current_zt == 1){
						$notice_qq[] = $qq;
						if(isset($qqlist[$qq]) && intval($qqlist[$qq]['dropline']) == 1){
							$relogin_qq[] = $qq;
						}
					}
					$zt_arr[] = "WHEN ".$qq." THEN 0";
				}
				$qq_arr[] = $qq;
			}
			
			if(count($qq_arr)>0){
				$sql = "UPDATE `{$qq_table}` 
					SET uptime=NOW() ,login_zt = CASE qq 
						".implode(' ',$zt_arr)."
					END
				WHERE qq IN (".implode(',',$qq_arr).")";
				$DB->exec($sql);
			}

			$relogin_ok = array();
			if(count($relogin_qq) > 0){
				echo "🔄 监测到 ".count($relogin_qq)." 个账号掉线且开启了自动重登，准备执行...\n";
				foreach($relogin_qq as $qq){
					$ft = isset($frame['type']) ? strtolower(trim($frame['type'])) : '';
					if(in_array($ft, ['myqq','myqqa','dr'], true)){
						echo "⚠️ 账号: {$qq} 所属节点类型 [{$ft}] 不支持二次登录，跳过\n";
						continue;
					}

					$apipwd_f = $frame['apipwd'] ?? '';
					$is_free_node = ($apipwd_f === '0' || $apipwd_f === 0 || ($apipwd_f && $apipwd_f !== ''));
					$end_arr_json = isset($qqlist[$qq]['endtime_arr']) ? $qqlist[$qq]['endtime_arr'] : '';
					if(!$is_free_node && is_end($end_arr_json, $frame)){
						echo "⏭️ 账号: {$qq} 已到期，跳过自动重登\n";
						continue;
					}

					echo "➡️ 正在尝试二次登录账号: {$qq} ... ";
					$relogin_res = cache_login($frame, $qq);
					$isLoginSuccess = (($relogin_res['retcode'] ?? -1) == 200) && (
						(isset($relogin_res['bool']) && ($relogin_res['bool'] === true || $relogin_res['bool'] === 'true')) ||
						(isset($relogin_res['msg']) && strpos((string)$relogin_res['msg'], '登录成功') !== false)
					);

					if($isLoginSuccess){
						echo "✅ 重登请求已发送\n";
						$DB->exec("UPDATE `{$qq_table}` SET login_zt='1' WHERE qq='{$qq}'");
						$relogin_ok[$qq] = true;
					}else{
						echo "❌ 重登失败: ".($relogin_res['msg'] ?? '接口响应异常')."\n";
					}
				}
			}
			
			$online_count = 0;
			foreach($qqzt_arr as $qq => $current_zt){
				$has_bind = isset($qq_has_bind[$qq]) ? intval($qq_has_bind[$qq]) : 1;
				if($has_bind !== 1) continue;
				$fz = $frame_zt_for($qq);
				$new_zt = ($fz !== null) ? $fz : 0;
				if($new_zt === 1){
					$online_count++;
				}elseif(!empty($relogin_ok[$qq])){
					$online_count++;
				}
			}
			$total_qq = count($qqzt_arr);
			$offline_qq = $total_qq - $online_count;
			
			$DB->exec("UPDATE `pre_loginconfig` SET online_census='{$online_count}', uptime=NOW() WHERE id='{$frame_id}'");
			
			$total_qqs += $total_qq;
			$online_qqs += $online_count;
			$offline_qqs += $offline_qq;
			
			echo "📊 接口统计 - 总QQ: {$total_qq}, 在线: {$online_count}, 离线: {$offline_qq}\n";
			
			if($conf['notice_qqzt'] == 1 && count($notice_qq)>0){
				foreach($notice_qq as $qq){
					if(isset($qqlist[$qq]) && $qqlist[$qq]['login_zt'] != 3){
						$cuont = intval($conf['notice_qqzt_cuont'])==0?3:$conf['notice_qqzt_cuont'];
						$abnor_time = intval($qqlist[$qq]['abnor_time']);
						$abnor_cuont = intval($qqlist[$qq]['abnor_cuont']);
						if($abnor_time>$time)continue;
						if($abnor_cuont>=$cuont)continue;
						
						$rs_user = $DB->query("SELECT a.*,b.email,b.phone,b.wechat_appuid,b.notice_type FROM `{$qq_table}` a LEFT JOIN `pre_user` b ON a.zid=b.zid WHERE a.qq='{$qq}'");
						$user = $rs_user->fetch(PDO::FETCH_ASSOC);
						
						if($user['notice_type'] == 1){
							if(strlen($user['phone'])!=11)continue;
							$con = $conf['notice_qqzt_nr']==''?'尊贵的用户，您好！检测到您的账号：[qq]，掉线了！请重新登录。':$conf['notice_qqzt_nr'];
							$con = str_replace(array("[qq]","[换行]"),array($qq,"<br>"),$con);
							send_sms($user['phone'], $con);
						}else{
							$con = $conf['notice_qqzt_nr']==''?'<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">尊贵的用户，您好！</h2><p style="padding-left:2em;margin:0">检测到您的账号：[qq]，掉线了！请重新登录。</p>':$conf['notice_qqzt_nr'];
							$con = str_replace(array("[qq]","[换行]"),array($qq,"<br>"),$con);
							if($user['notice_type'] == 2){
								send_wx($con,'掉线提醒', 1, [], [$user['wechat_appuid']]);
							}else{
								$email = ($user['email']==''?$qq.'@qq.com':$user['email']);
								\lib\MessageSend::send($email,'账号掉线提醒','掉线提醒',$con);
							}
						}
						
						$t = intval($conf['notice_qqzt_time'])==0?30:$conf['notice_qqzt_time'];
						$t = strtotime($t.'minute');
						$c = intval($qqlist[$qq]['abnor_cuont'])+1;
						$DB->exec("UPDATE `{$qq_table}` SET abnor_cuont='{$c}', abnor_time='{$t}' WHERE qq='{$qq}'");
					}
				}
			}
			
		}else{
			echo "❌ 框架通讯异常\n";
			$DB->exec("UPDATE `{$qq_table}` SET login_zt='0' WHERE cityid='{$frame_id}'");
			$DB->exec("UPDATE `pre_loginconfig` SET status_api='0', online_census='0', uptime=NOW() WHERE id='{$frame_id}'");
			
			$streak_key = 'frame_err_streak_'.$frame_id;
			$streak = intval($conf[$streak_key] ?? 0) + 1;
			$DB->exec("REPLACE INTO `pre_config` SET v='{$streak}',k='{$streak_key}'");
			$conf[$streak_key] = $streak;
			
			$notify_after_streak = $login_notify_rate_start + $login_notify_rate;
			if($streak < $notify_after_streak){
				echo "⚠️ 连续异常{$streak}次，未达到{$notify_after_streak}次阈值，暂不通知\n";
			}else{
				$should_notify = (($streak - $login_notify_rate_start) % $login_notify_rate) == 0;
				if(!$should_notify){
					$next_notify_in = $login_notify_rate - (($streak - $login_notify_rate_start) % $login_notify_rate);
					echo "⏳ 连续异常{$streak}次，下一次通知还需{$next_notify_in}次\n";
				}else{
					if($notice_frame > 0){
						$pending_frame_notifies[] = [
							'frame_id' => $frame_id,
							'frame_name' => $frame_name,
							'url' => $frame['url'] ?? '',
							'streak' => $streak,
							'notify_count' => intval($frame['notify_count'] ?? 0),
							'notify_time' => intval($frame['notify_time'] ?? 0),
						];
						echo "📌 已加入异常通知队列，监控结束后统一处理\n";
					}else{
						echo "🔕 通知渠道关闭(notice_frame=0)，仅记录异常不发通知\n";
					}
				}
			}
		}
		echo "=== 接口处理结束 ===\n";
		}catch(\Throwable $e){
			echo "❌ 接口处理异常: ".$e->getMessage()."\n";
			echo "=== 接口处理结束(异常) ===\n";
		}
	}

	$dg_tbl_qqzt = $DB->getColumn("SHOW TABLES LIKE 'pre_dg_qq'", 0);
	echo "\n🔧 未绑定有效启用接口 → 强制离线：\n";
	$n_unbind_cloud = $DB->exec("UPDATE `pre_qq` a LEFT JOIN `pre_loginconfig` b ON a.cityid=b.id AND b.status='1' AND b.biz='cloud' SET a.login_zt='0', a.uptime=NOW() WHERE b.id IS NULL AND a.cityid NOT LIKE 'link_%'");
	if($n_unbind_cloud === false) $n_unbind_cloud = 0;
	echo "  云端 pre_qq: ".intval($n_unbind_cloud)." 条\n";
	if($dg_tbl_qqzt){
		$n_unbind_dg = $DB->exec("UPDATE `pre_dg_qq` a LEFT JOIN `pre_loginconfig` b ON a.cityid=b.id AND b.status='1' AND b.biz='dg' SET a.login_zt='0', a.uptime=NOW() WHERE b.id IS NULL AND a.cityid NOT LIKE 'link_%'");
		if($n_unbind_dg === false) $n_unbind_dg = 0;
		echo "  代挂 pre_dg_qq: ".intval($n_unbind_dg)." 条\n";
	}

	if(!empty($pending_frame_notifies)){
		echo "\n📮 开始处理接口异常通知队列(".count($pending_frame_notifies)."个)...\n";
		$eligible_notifies = [];
		foreach($pending_frame_notifies as $nf){
			$frame_name = $nf['frame_name'];
			$current_notify_count = intval($nf['notify_count']);
			$current_notify_time = intval($nf['notify_time']);
			if($current_notify_count >= $login_notify_count){
				echo "🔕 [{$frame_name}] 通知次数已达上限({$login_notify_count})\n";
				continue;
			}
			if($current_notify_time > $time){
				$left_min = ceil(($current_notify_time - $time) / 60);
				echo "⏳ [{$frame_name}] 通知冷却中，约{$left_min}分钟后可再次通知\n";
				continue;
			}
			$eligible_notifies[] = $nf;
		}

		if(empty($eligible_notifies)){
			echo "📭 无可发送的异常通知（全部在冷却或达上限）\n";
		}else{
			$lines = [];
			$sms_items = [];
			foreach($eligible_notifies as $nf){
				$lines[] = '【'.$nf['frame_name'].'】(ID:'.$nf['frame_id'].') 异常'.$nf['streak'].'次<br>地址：'.($nf['url'] ?? '');
				$sms_items[] = $nf['frame_name'].'(ID:'.$nf['frame_id'].')';
			}
			$content = '本轮监控检测到 '.count($eligible_notifies).' 个接口异常：<br><br>'.implode('<br><br>', $lines).'<br><br>请及时检查框架服务、端口和网络连通性。';
			$sms_content = '接口异常共'.count($eligible_notifies).'个：'.implode('、', $sms_items).'，请及时处理';
			if(strlen($sms_content) > 240){
				$sms_content = substr($sms_content, 0, 237).'...';
			}
			$sent = false;
			$ret_msg = '';
			if($notice_frame == 1){
				if(!empty($conf['mail_recv'])){
					$sent = \lib\MessageSend::send($conf['mail_recv'], '接口异常提醒（汇总）', '接口异常提醒', $content, $conf['sitename'] ?? '系统通知');
					if(!$sent){
						$ret_msg = '邮件发送失败';
					}
				}else{
					echo "⚠️ 邮件渠道未配置收件邮箱(mail_recv)\n";
				}
			}elseif($notice_frame == 2){
				if(!empty($conf['sms_phone'])){
					$ret = send_sms($conf['sms_phone'], $sms_content);
					$sent = ($ret === true || (is_array($ret) && intval($ret['code'] ?? 0) == 1));
					$ret_msg = is_array($ret) ? ($ret['msg'] ?? '') : '';
				}else{
					echo "⚠️ 短信渠道未配置接收手机号(sms_phone)\n";
				}
			}elseif($notice_frame == 3){
				if(!empty($conf['wechat_appuid'])){
					$ret = send_wx($content, '接口异常提醒（汇总）', 1, [], [$conf['wechat_appuid']]);
					$sent = ($ret === true || (is_array($ret) && intval($ret['code'] ?? 0) == 1));
					$ret_msg = is_array($ret) ? ($ret['msg'] ?? ($ret['message'] ?? '')) : '';
				}else{
					echo "⚠️ 微信渠道未配置接收UID(wechat_appuid)\n";
				}
			}

			if($sent){
				$next_notify_time = strtotime('+'.$login_notify_time.' minute');
				foreach($eligible_notifies as $nf){
					$frame_id = intval($nf['frame_id']);
					$new_notify_count = intval($nf['notify_count']) + 1;
					$DB->exec("UPDATE `pre_loginconfig` SET notify_count='{$new_notify_count}', notify_time='{$next_notify_time}' WHERE id='{$frame_id}'");
				}
				echo "📨 已汇总发送异常通知，涉及".count($eligible_notifies)."个接口\n";
			}else{
				echo "⚠️ 汇总异常通知发送失败".($ret_msg!=='' ? "，原因: {$ret_msg}" : "或未配置接收地址")."\n";
			}
		}
		echo "📮 接口异常通知队列处理完毕\n";
	}
	
	$DB->exec("REPLACE INTO `pre_config` SET v='".time()."',k='time_qqzt'");
	
	$qq_unbound_cloud = intval($DB->getColumn("SELECT COUNT(*) FROM pre_qq a LEFT JOIN pre_loginconfig b ON a.cityid=b.id AND b.status='1' AND b.biz='cloud' WHERE b.id IS NULL AND a.cityid NOT LIKE 'link_%'"));
	$qq_unbound_dg = 0;
	if($dg_tbl_qqzt){
		$qq_unbound_dg = intval($DB->getColumn("SELECT COUNT(*) FROM pre_dg_qq a LEFT JOIN pre_loginconfig b ON a.cityid=b.id AND b.status='1' AND b.biz='dg' WHERE b.id IS NULL AND a.cityid NOT LIKE 'link_%'"));
	}
	$qq_unbound_total = $qq_unbound_cloud + $qq_unbound_dg;
	$offline_with_unbound = $offline_qqs + $qq_unbound_total;
	
	echo "\n📊 ====== 监控统计报告 ======\n";
	echo "📡 总接口数: {$total_frames}\n";
	echo "✅ 成功监控: {$success_frames}\n";
	echo "❌ 失败接口: " . ($total_frames - $success_frames) . "\n";
	echo "👥 总QQ数(各节点内): {$total_qqs}\n";
	echo "🟢 在线QQ: {$online_qqs}\n";
	echo "🔴 离线QQ(仅节点内): {$offline_qqs}\n";
	echo "⚪ 未挂启用节点(全库): {$qq_unbound_total}（云端 {$qq_unbound_cloud}" . ($dg_tbl_qqzt ? " · 代挂 {$qq_unbound_dg}" : "") . "）\n";
	echo "🔴 离线合计(节点内离线+未挂): {$offline_with_unbound}\n";
	echo "📈 在线率(仅节点内): " . ($total_qqs > 0 ? round(($online_qqs / $total_qqs) * 100, 2) : 0) . "%\n";
	echo "=============================\n";
	echo '框架QQ状态监控完毕！';
}elseif($_GET['act']=='node_link'){
	header('Location: node_link_cron.php?key='.$_key);
	exit;
}
?>
