$title, 'status' => $status, 'detail' => $detail, 'file' => self::IP_CHECK_FILE, 'line' => (int)$line, 'extra' => $extra, 'at' => microtime(true), ]; } /** * 记录 check_result 写入(请从 ip_check.php 使用 cloak_dbg_record(__LINE__, ...) 调用) */ public static function record($result, $reason, array $extra = []) { $loc = self::callerLoc(); self::recordAt($loc['line'], $result, $reason, $extra); } /** * 记录 check_result 写入,指定 ip_check.php 行号 */ public static function recordAt($line, $result, $reason, array $extra = []) { $stage = isset($extra['stage']) ? (string)$extra['stage'] : 'unknown'; $entry = [ 'result' => $result, 'reason' => $reason, 'file' => self::IP_CHECK_FILE, 'line' => (int)$line, 'stage' => $stage, 'extra' => $extra, 'at' => microtime(true), ]; self::$trail[] = $entry; $decideDetail = $reason !== '' ? $reason : '(理由为空)'; if ($result === 'true' || $result === 'false') { $decideDetail .= ' → check_result=' . $result; self::$flags['final'] = $entry; } elseif ($result === null) { $decideDetail = ($reason !== '' ? $reason : '(理由为空)') . '(未写入 check_result,流程继续)'; } self::$flow[] = [ 'title' => self::stageTitle($stage), 'status' => ($result === 'true' || $result === 'false') ? 'decide' : 'warn', 'detail' => $decideDetail, 'file' => self::IP_CHECK_FILE, 'line' => (int)$line, 'extra' => array_merge($extra, ['check_result' => $result]), 'at' => microtime(true), ]; } public static function setApiResponse(array $response) { self::$apiResponse = $response; } public static function setFlag($key, $value) { self::$flags[$key] = $value; } public static function setExitPoint($line, $label, array $extra = []) { self::$exitPoint = [ 'file' => self::IP_CHECK_FILE, 'line' => (int)$line, 'label' => $label, 'extra' => $extra, ]; } public static function renderAndExit(array $ctx) { $result = (string)($ctx['check_result'] ?? 'false'); $reason = (string)($ctx['reason'] ?? ''); $final = self::$flags['final'] ?? null; $isRealPage = ($result === 'true'); $elapsed = isset($ctx['started_at']) ? round((microtime(true) - $ctx['started_at']) * 1000) : round((microtime(true) - self::$startedAt) * 1000); $routeInfo = self::buildRouteInfo($ctx, $result); $diagnosis = self::buildReasonDiagnosis($reason, $result, $ctx); $flowHtml = self::buildFlowHtml(); $trailHtml = self::buildTrailHtml(); $sessionHtml = self::buildSessionHtml($ctx['session'] ?? []); $verdictLabel = $isRealPage ? '真实页 (DB_FP)' : '安全页 (DB_ZP)'; $verdictClass = $isRealPage ? 'verdict-pass' : 'verdict-block'; $verdictIcon = $isRealPage ? '✓' : '✕'; $finalLoc = $final ? self::formatLoc($final['file'], $final['line']) : '—'; $finalStage = $final ? htmlspecialchars(self::stageTitle($final['stage'])) : '—'; $finalReason = $final ? htmlspecialchars($final['reason'] !== '' ? $final['reason'] : '(空)') : '—'; $exit = self::$exitPoint; $exitLoc = $exit ? self::formatLoc($exit['file'], $exit['line']) : self::IP_CHECK_FILE . ':?'; $exitLabel = $exit ? htmlspecialchars($exit['label']) : 'CloakDebugPage::renderAndExit()'; header('Content-Type: text/html; charset=utf-8'); header('X-Robots-Tag: noindex, nofollow'); echo ''; echo ''; echo 'Cloak DEBUG — ' . htmlspecialchars($ctx['config_name'] ?? '') . ''; echo self::styles(); echo '
'; echo '
'; echo '
DEBUG MODE
'; echo '

流量判定调试报告

'; echo '

配置 ' . htmlspecialchars($ctx['config_name'] ?? '') . ''; echo ' · 耗时 ' . (int)$elapsed . ' ms · ' . date('Y-m-d H:i:s') . '

'; echo '
'; // 流程终止位置(醒目) echo '
'; echo '

流程终止位置

'; echo '
' . htmlspecialchars($exitLoc) . '
'; echo '

DEBUG 在此截停执行(' . $exitLabel . '),未继续跳转真实页/安全页。

'; echo '
'; echo '
未开启 DEBUG 时下一分支
' . htmlspecialchars($routeInfo['next_branch']) . '
'; echo '
预计跳转目标
' . htmlspecialchars($routeInfo['dest'] !== '' ? $routeInfo['dest'] : '—') . '
'; echo '
路由条件
' . htmlspecialchars($routeInfo['condition']) . '
'; echo '
'; echo '
'; echo '
' . $verdictIcon . '
'; echo '
'; echo '
' . htmlspecialchars($verdictLabel) . '
'; echo '
check_result = ' . htmlspecialchars($result) . '
'; echo '
'; // 判断理由 + 空理由诊断 echo '

判定结论

'; echo '
当前 $reason
'; if ($reason !== '') { echo htmlspecialchars($reason); } else { echo '为空'; } echo '
'; echo '
最终写入 check_result
' . htmlspecialchars($finalLoc) . '(' . $finalStage . ')
'; echo '
写入时 $reason
' . $finalReason . '
'; echo '
'; if ($diagnosis !== '') { echo '
' . $diagnosis . '
'; } echo '
'; // 完整判断流程 echo '

完整判断流程

'; echo '

按 ip_check.php 实际执行顺序展示; 表示写入 check_result, 拦截, 跳过。

'; echo $flowHtml !== '' ? $flowHtml : '

暂无流程记录(请确认 CLOAK_DEBUG_MODE 已保存为 ON)。

'; echo '
'; if ($trailHtml !== '') { echo '

check_result 写入轨迹

'; echo '

每次对 $_SESSION[\'check_result\'] 的赋值;最后一项为最终判定依据。

'; echo $trailHtml . '
'; } if (self::$apiResponse) { echo '

API 判定接口 (cloak_check_curl)

'; echo self::buildApiHtml(self::$apiResponse); echo '
'; } echo '
'; echo '

Session 状态

'; echo $sessionHtml . '
'; echo '

路由预览

'; echo '
SHOW_SITE_MODE
' . htmlspecialchars($ctx['mode'] ?? '') . '
'; echo '
visit_to_2
' . htmlspecialchars($ctx['session']['visit_to_2'] ?? '—') . ' =2 时跳过 API 块
'; echo '
visit_to_3
' . htmlspecialchars($ctx['session']['visit_to_3'] ?? '—') . '
'; echo '
'; echo '
'; echo '

访客依据信息

'; echo '' . self::buildVisitorRows($ctx) . '
'; echo '

请求参数

'; echo '' . self::buildQueryRows() . '
'; echo '

配置快照

'; echo '' . self::buildConfigRows() . '
'; echo ''; echo '
'; exit; } private static function callerLoc() { $bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 20); foreach ($bt as $frame) { if (empty($frame['file'])) { continue; } if (basename($frame['file']) === self::IP_CHECK_FILE) { return [ 'file' => self::IP_CHECK_FILE, 'line' => (int)($frame['line'] ?? 0), ]; } } return [ 'file' => self::IP_CHECK_FILE, 'line' => 0, ]; } private static function formatLoc($file, $line) { $file = ($file !== '' && $file !== '—') ? $file : self::IP_CHECK_FILE; if (strpos($file, 'ip_check') !== false) { $file = self::IP_CHECK_FILE; } return $file . ':' . (int)$line; } private static function stageTitle($stage) { $map = [ 'session_fast_path' => 'Session 快速路径', 'url_args_timeout' => '临时链接参数', 'blacklist' => '黑名单', 'whitelist_ip' => 'IP 白名单', 'whitelist_params' => '链接参数白名单', 'fingerprint_cookie' => 'JS 指纹 Cookie', 'api_cloak' => 'API 远程判定', 'no_referer_or_gcuid' => '无 Referer/gcuid', 'session_partial' => 'Session 已有结果', 'main_block_skipped' => '主判定块跳过', ]; return isset($map[$stage]) ? $map[$stage] : $stage; } private static function buildRouteInfo(array $ctx, $result) { $mode = $ctx['mode'] ?? ''; $isReal = ($mode !== 'zp') && ((($mode === 'ip_check' && $result !== 'false') || $mode === 'fp')); $forceRisk = !empty($ctx['force_risk']); $visit3 = $ctx['session']['visit_to_3'] ?? ''; if (!$isReal) { return [ 'next_branch' => '安全页分支 (ip_check.php ~859)', 'dest' => (string)($ctx['zp_url'] ?? ''), 'condition' => "mode={$mode}, check_result={$result} → false 或 mode=zp", ]; } if ($forceRisk && $visit3 !== '3') { return [ 'next_branch' => '真实页 + 二次风控 (page_666.php / cloakjs)', 'dest' => self::firstUrl($ctx['fp_urls'] ?? []), 'condition' => 'check_result!=false 且 CLOAK_RISK_NUMBER>0', ]; } return [ 'next_branch' => '真实页 (page_6.php)', 'dest' => self::firstUrl($ctx['fp_urls'] ?? []), 'condition' => "mode={$mode}, check_result={$result}", ]; } private static function buildReasonDiagnosis($reason, $result, array $ctx) { if ($reason !== '') { return ''; } $items = []; $final = self::$flags['final'] ?? null; if (!$final) { $items[] = '整个流程未记录到任何 check_result 写入点;最终 result 可能来自默认值或未赋值。'; } elseif ($final['reason'] === '') { $items[] = '最终写入点 ' . htmlspecialchars(self::formatLoc($final['file'], $final['line'])) . '(' . htmlspecialchars(self::stageTitle($final['stage'])) . ')写入时 $reason 即为空。'; } if (self::$apiResponse) { $apiReason = self::$apiResponse['reason'] ?? ''; $apiCalled = !empty(self::$apiResponse['called']); if ($apiCalled && $apiReason === '') { $items[] = 'API 接口 cloak_check_curl 已调用,但返回 JSON 中 reason 字段为空(见下方 API 详情)。'; } elseif (!$apiCalled) { $items[] = 'API 判定未执行(可能缺少 ip/Browser/Accept-Language,或主判定块被跳过)。'; } } $visit2 = $ctx['session']['visit_to_2'] ?? ''; if ($visit2 === '2' && empty(self::$apiResponse['called'])) { $items[] = '$_SESSION[\'visit_to_2\']=2 导致主判定块(API 调用)被跳过;若同时 check_result 未重建,$reason 会保持初始空字符串。DEBUG 模式已重置 visit_to_*,若仍出现请检查是否在判定前被重新写入。'; } if ($result === 'true') { $items[] = '生产环境中,进入真实页分支后 ip_check.php:818 会将 $logs[\'reason\'] 清空(注释:访问仿品时没用跳转理由),日志里可能显示空理由,但 DEBUG 截停前应仍能看到写入时的 reason。'; } if (empty($items)) { $items[] = '$reason 在流程开始时初始化为空字符串,且后续没有任何分支对其赋值。'; } $html = '

⚠ $reason 为空 — 溯源分析

'; return $html; } private static function buildFlowHtml() { if (empty(self::$flow)) { return ''; } $html = '
'; $final = self::$flags['final'] ?? null; foreach (self::$flow as $i => $s) { $isFinal = $final && isset($s['extra']['check_result']) && ($s['extra']['check_result'] === 'true' || $s['extra']['check_result'] === 'false') && $final['line'] === $s['line'] && $final['stage'] === ($s['extra']['stage'] ?? ''); $cls = 'flow-step status-' . htmlspecialchars($s['status']); if ($isFinal) { $cls .= ' flow-final'; } $html .= '
'; $html .= '
' . ($i + 1) . '
'; $html .= '
'; $html .= '
'; $html .= '' . htmlspecialchars($s['title']) . ''; $html .= '' . htmlspecialchars(self::statusLabel($s['status'])) . ''; $html .= '' . htmlspecialchars(self::formatLoc($s['file'], $s['line'])) . ''; $html .= '
'; if ($s['detail'] !== '') { $html .= '
' . htmlspecialchars($s['detail']) . '
'; } if (!empty($s['extra'])) { $show = $s['extra']; unset($show['stage'], $show['check_result']); if (!empty($show)) { $html .= '
' . htmlspecialchars(json_encode($show, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '
'; } } $html .= '
'; } $html .= '
'; return $html; } private static function statusLabel($status) { $map = [ 'pass' => '通过', 'fail' => '拦截', 'skip' => '跳过', 'warn' => '可疑', 'info' => '信息', 'decide' => '写入结果', ]; return isset($map[$status]) ? $map[$status] : $status; } private static function buildTrailHtml() { if (empty(self::$trail)) { return ''; } $html = '
'; $final = self::$flags['final'] ?? null; foreach (self::$trail as $e) { $isFinal = $final && $final['line'] === $e['line'] && $final['stage'] === $e['stage']; $cls = $isFinal ? ' trail-item final' : ' trail-item'; $res = $e['result'] !== null ? $e['result'] : '—'; $html .= '
'; $html .= '
'; $html .= '' . htmlspecialchars(self::formatLoc($e['file'], $e['line'])) . ''; $html .= '' . htmlspecialchars(self::stageTitle($e['stage'])) . ''; if ($e['result'] !== null) { $html .= '' . htmlspecialchars((string)$res) . ''; } $html .= '
'; $html .= '
' . htmlspecialchars($e['reason'] !== '' ? $e['reason'] : '(理由为空)') . '
'; $html .= '
'; } $html .= '
'; return $html; } private static function buildApiHtml(array $api) { $html = '
'; $html .= '
是否调用
' . (!empty($api['called']) ? '是' : '否') . '
'; if (!empty($api['skip_reason'])) { $html .= '
未调用原因
' . htmlspecialchars($api['skip_reason']) . '
'; } $html .= '
API result
' . htmlspecialchars((string)($api['result_raw'] ?? $api['check_result'] ?? '—')) . '
'; $html .= '
API reason
' . htmlspecialchars((string)($api['reason'] ?? '—')) . '
'; $html .= '
API country
' . htmlspecialchars((string)($api['country'] ?? '—')) . '
'; if (!empty($api['curl_error'])) { $html .= '
Curl 错误
' . htmlspecialchars($api['curl_error']) . '
'; } if (!empty($api['raw'])) { $html .= '
原始响应
' . htmlspecialchars(is_string($api['raw']) ? $api['raw'] : json_encode($api['raw'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) . '
'; } $html .= '
'; return $html; } private static function buildSessionHtml(array $session) { if (empty($session)) { return '

无 Session 快照

'; } $html = ''; foreach ($session as $k => $v) { $html .= ''; } $html .= '
' . htmlspecialchars((string)$k) . '' . htmlspecialchars(is_scalar($v) ? (string)$v : json_encode($v, JSON_UNESCAPED_UNICODE)) . '
'; return $html; } private static function buildVisitorRows(array $ctx) { $v = $ctx['visitor'] ?? null; $rows = [ 'IP 地址' => $v ? $v->v_ip : ($ctx['logs']['customers_ip'] ?? ''), '国家/地区' => $v ? $v->v_country : '', '浏览器' => $v ? $v->v_Browser : ($ctx['logs']['v_Browser'] ?? ''), '客户端类型' => $v ? $v->v_Client : ($ctx['logs']['Client'] ?? ''), 'Referer' => $v ? $v->v_referer : ($ctx['logs']['v_referer'] ?? ''), '当前 URL' => $v ? $v->v_curPageURL : ($ctx['logs']['v_PageURL'] ?? ''), 'Accept-Language' => $v ? $v->accept_language : ($ctx['logs']['accept_language'] ?? ''), 'User-Agent' => isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '', 'visit_md5' => $v ? $v->visit_md5_code : ($ctx['logs']['visit_md5_code'] ?? ''), '设备型号' => $ctx['device'] ?? '', 'gcuid' => $_SESSION['gcuid'] ?? '', ]; return self::rowsHtml($rows); } private static function buildQueryRows() { $keys = ['fbclid', 'gclid', 'wbraid', 'gbraid', 'ttclid', 'ad_spm_id']; $rows = []; foreach ($keys as $k) { $rows[$k] = isset($_GET[$k]) ? $_GET[$k] : '(未携带)'; } return self::rowsHtml($rows); } private static function buildConfigRows() { $map = [ 'SHOW_SITE_MODE_SWITCH' => defined('SHOW_SITE_MODE_SWITCH') ? SHOW_SITE_MODE_SWITCH : '', 'SHOW_SITE_COUNTRY' => defined('SHOW_SITE_COUNTRY') ? SHOW_SITE_COUNTRY : '', 'CLOAK_DEBUG_MODE' => defined('CLOAK_DEBUG_MODE') ? CLOAK_DEBUG_MODE : '', 'CLOAK_RISK_NUMBER' => defined('CLOAK_RISK_NUMBER') ? CLOAK_RISK_NUMBER : '', 'CLOAK_RISK_ENHANCED' => defined('CLOAK_RISK_ENHANCED') ? CLOAK_RISK_ENHANCED : '', 'CLOAK_URL_ARGS_TIMEOUT' => defined('CLOAK_URL_ARGS_TIMEOUT') ? CLOAK_URL_ARGS_TIMEOUT : '', ]; return self::rowsHtml($map); } private static function rowsHtml(array $rows) { $html = ''; foreach ($rows as $label => $val) { $html .= '' . htmlspecialchars((string)$label) . '' . htmlspecialchars((string)$val) . ''; } return $html; } private static function firstUrl($urls) { if (is_array($urls) && !empty($urls)) { return (string)$urls[0]; } return is_string($urls) ? $urls : ''; } private static function styles() { return <<<'CSS' CSS; } }