diff --git a/dashboard.php b/dashboard.php index e240e13..6a8e8d0 100755 --- a/dashboard.php +++ b/dashboard.php @@ -35,14 +35,24 @@ if (!empty($_REQUEST["action"]) && $_REQUEST["action"] == "delUrlArgs") { // 确定当前配置上下文(供表单回显用) $isNewConfigDraft = false; +$configCopySource = ''; +$copyFrom = dash_sanitize_config_name($_REQUEST['copy_from'] ?? ''); if (!empty($_REQUEST["name"])) { $check_name = trim($_REQUEST["name"]); $config_name = str_replace(".php", "", $check_name); $config_file = "check_config/" . $config_name . "_config.php"; if (!file_exists($config_file)) { - // 新建或未保存的配置:仅回显 index 模板,不在此写盘 $isNewConfigDraft = true; - $config_file = "check_config/index_config.php"; + $sourceFile = ($copyFrom !== '') ? "check_config/{$copyFrom}_config.php" : ''; + if ($copyFrom !== '' && is_file($sourceFile)) { + $config_file = $sourceFile; + $configCopySource = $copyFrom; + if ($check_name === 'new_config' || $check_name === '') { + $check_name = dash_suggest_copy_config_name($copyFrom); + } + } else { + $config_file = "check_config/index_config.php"; + } } } else { $check_name = "index"; @@ -435,6 +445,31 @@ function dash_check_redis() { } catch (Throwable $e) { return false; } } +function dash_sanitize_config_name($name) +{ + $name = trim(str_replace('.php', '', (string) $name)); + if ($name === '' || !preg_match('/^[a-zA-Z0-9_-]+$/', $name)) { + return ''; + } + return $name; +} + +function dash_suggest_copy_config_name($source) +{ + $source = dash_sanitize_config_name($source); + if ($source === '') { + return 'new_config'; + } + $base = $source . '_copy'; + $candidate = $base; + $suffix = 2; + while (file_exists(__DIR__ . "/check_config/{$candidate}_config.php")) { + $candidate = $base . $suffix; + $suffix++; + } + return $candidate; +} + function dash_cfg_get($raw, $name) { if (preg_match("/define\('" . preg_quote($name, '/') . "',\s*'([^']*)'\)/", $raw, $m)) { @@ -662,7 +697,8 @@ $_d = function($name, $default) { return defined($name) ? constant($name) : $def
- 编辑 + 编辑 + 复制 查看 @@ -776,7 +812,7 @@ $_d = function($name, $default) { return defined($name) ? constant($name) : $def
-
未保存
+
未保存 复制
设置当前浏览器为真实访客 @@ -802,7 +838,7 @@ $_d = function($name, $default) { return defined($name) ? constant($name) : $def 配置名称 - 示例:f123、d258 + diff --git a/datatable/datalist.php b/datatable/datalist.php index 2ec376e..eb993da 100755 --- a/datatable/datalist.php +++ b/datatable/datalist.php @@ -4,6 +4,7 @@ require_once(dirname(__DIR__) . "/cong.php"); require_once(dirname(__DIR__) . "/lib/DbHelper.php"); require_once(dirname(__DIR__) . "/lib/Cloak/PipelineTimer.php"); require_once(dirname(__DIR__) . "/lib/Cloak/VisitorLogSchema.php"); +require_once(dirname(__DIR__) . "/lib/Cloak/VisitorApiAudit.php"); session_start(); $reasons = array( @@ -117,7 +118,7 @@ if ($conn->connect_error) { $where .= " AND " . join(" AND ", $map); } // SQL语句 - $sql = "SELECT `id`, `campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `fp_url`, `referer`, `vtimes`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_hdata`, `device`, `add_time` FROM `visitor_logs` WHERE {$where} ORDER BY `id` DESC LIMIT {$startPosition}, {$recordsPerPage}"; + $sql = "SELECT `id`, `campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `fp_url`, `referer`, `vtimes`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_hdata`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`, `device`, `add_time` FROM `visitor_logs` WHERE {$where} ORDER BY `id` DESC LIMIT {$startPosition}, {$recordsPerPage}"; // 执行查询并输出结果 $result = mysqli_query($conn, $sql); @@ -167,6 +168,10 @@ if ($conn->connect_error) { $row['fp_hdata_explain_html'] = ''; $row['fp_hdata_explain'] = null; } + $row['api_by_request_text'] = VisitorApiAudit::displayText($row['api_by_request'] ?? ''); + $row['api_by_response_text'] = VisitorApiAudit::displayText($row['api_by_response'] ?? ''); + $row['api_risk_request_text'] = VisitorApiAudit::displayText($row['api_risk_request'] ?? ''); + $row['api_risk_response_text']= VisitorApiAudit::displayText($row['api_risk_response'] ?? ''); $jsonData[] = $row; } diff --git a/datatable/sdatalist.php b/datatable/sdatalist.php index b13167b..249f2d4 100755 --- a/datatable/sdatalist.php +++ b/datatable/sdatalist.php @@ -4,6 +4,7 @@ require_once(dirname(__DIR__) . "/cong.php"); require_once(dirname(__DIR__) . "/lib/DbHelper.php"); require_once(dirname(__DIR__) . "/lib/Cloak/PipelineTimer.php"); require_once(dirname(__DIR__) . "/lib/Cloak/VisitorLogSchema.php"); +require_once(dirname(__DIR__) . "/lib/Cloak/VisitorApiAudit.php"); $reasons = array( "blank_referrer" => "过滤无来源的访问", @@ -111,7 +112,7 @@ if ($conn->connect_error) { $where .= " AND " . join(" AND ", $map); } // SQL语句 - $sql = "SELECT `id`, `campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `fp_url`, `referer`, `vtimes`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_hdata`, `device`, `add_time` FROM `visitor_logs` WHERE {$where} ORDER BY `id` DESC LIMIT {$startPosition}, {$recordsPerPage}"; + $sql = "SELECT `id`, `campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `fp_url`, `referer`, `vtimes`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_hdata`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`, `device`, `add_time` FROM `visitor_logs` WHERE {$where} ORDER BY `id` DESC LIMIT {$startPosition}, {$recordsPerPage}"; // 执行查询并输出结果 $result = mysqli_query($conn, $sql); @@ -157,6 +158,10 @@ if ($conn->connect_error) { $row['fp_hdata_explain_html'] = ''; $row['fp_hdata_explain'] = null; } + $row['api_by_request_text'] = VisitorApiAudit::displayText($row['api_by_request'] ?? ''); + $row['api_by_response_text'] = VisitorApiAudit::displayText($row['api_by_response'] ?? ''); + $row['api_risk_request_text'] = VisitorApiAudit::displayText($row['api_risk_request'] ?? ''); + $row['api_risk_response_text']= VisitorApiAudit::displayText($row['api_risk_response'] ?? ''); $jsonData[] = $row; } diff --git a/f_check.php b/f_check.php index 4d1f71f..07eecd0 100755 --- a/f_check.php +++ b/f_check.php @@ -37,6 +37,8 @@ require_once __DIR__ . '/lib/DbHelper.php'; require_once __DIR__ . '/lib/bootstrap.php'; require_once __DIR__ . '/config/ConfigLoader.php'; require_once __DIR__ . '/lib/Cloak/FpUrlHelper.php'; +require_once __DIR__ . '/lib/Cloak/VisitorApiAudit.php'; +require_once __DIR__ . '/lib/Cloak/RiskSecondaryGate.php'; $fp_host = parse_url($fingerprint_info['domain'], PHP_URL_HOST); if (empty($fp_host)) { @@ -44,6 +46,41 @@ if (empty($fp_host)) { } $config_name = ConfigLoader::loadByHost($fp_host); +$log_id = (int) ($fingerprint_info['log_id'] ?? 0); + +if (!RiskSecondaryGate::isEnabled()) { + $_SESSION['visit_to_3'] = 3; + $_SESSION['check_result'] = 'true'; + + $resolved = FpUrlHelper::resolve($config_name, true); + $fp_url = $resolved['url']; + $response = [ + 'reason' => '', + 'result' => true, + 'link' => $fp_url !== '' ? $fp_url : FpUrlHelper::fallbackUrl(), + ]; + + if ($log_id > 0) { + $conn = new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME); + if (!$conn->connect_error) { + require_once __DIR__ . '/lib/Cloak/VisitorLogSchema.php'; + VisitorLogSchema::ensureColumns($conn); + $resultEsc = cloak_db_escape($conn, 'true'); + $reasonEsc = cloak_db_escape($conn, '二次风控已关闭'); + $fpUrlEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($response['link']))); + $sql = "UPDATE `visitor_logs` SET `result`='{$resultEsc}',`reason`='{$reasonEsc}',`fp_url`='{$fpUrlEsc}' WHERE `id`='" . $log_id . "'"; + if ($conn->query($sql) !== true) { + $handle = fopen(dirname(__FILE__) . '/err.txt', 'a+'); + fwrite($handle, date('Y-m-d H:i:s') . ' | f_check risk disabled update: ' . $conn->error . "\n"); + fclose($handle); + } + $conn->close(); + } + } + + exit(json_encode($response)); +} + $jsonData = [ 'id' => COSTM_IP_SCORE, 'hdata' => $hdata, @@ -54,7 +91,7 @@ $jsonData = [ 'risk_number' => CLOAK_RISK_NUMBER, ]; -$ch = curl_init('https://www.tiktokba.com/cloak/byApiRisk'); +$ch = curl_init('www.tiktokba.com/cloak/byApiRisk'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); @@ -114,10 +151,8 @@ if($return['result']) { } } -$log_id = (int) ($fingerprint_info['log_id'] ?? 0); $update_log = []; - -$update_log['result'] = $response['result'] ? "true" : "false"; +$update_log['result'] = $response['result'] ? 'true' : 'false'; $update_log['reason'] = $response['reason'] ?? ''; $update_log['fp_url'] = trim((string) ($response['link'] ?? '')); if ($update_log['fp_url'] === '') { @@ -148,9 +183,11 @@ if ($conn->connect_error) { $fpHdataJson = ''; } $fpHdataEsc = cloak_db_escape($conn, $fpHdataJson); + $apiRiskReqEsc = cloak_db_escape($conn, VisitorApiAudit::encode($jsonData)); + $apiRiskRespEsc = cloak_db_escape($conn, VisitorApiAudit::encode($return)); require_once __DIR__ . '/lib/Cloak/VisitorLogSchema.php'; VisitorLogSchema::ensureColumns($conn); - $sql = "UPDATE `visitor_logs` SET `result`='{$resultEsc}',`reason`='{$reasonEsc}',`fp_url`='{$fpUrlEsc}',`fp_hdata`='{$fpHdataEsc}' WHERE `id`='" . (int) $log_id . "'"; + $sql = "UPDATE `visitor_logs` SET `result`='{$resultEsc}',`reason`='{$reasonEsc}',`fp_url`='{$fpUrlEsc}',`fp_hdata`='{$fpHdataEsc}',`api_risk_request`='{$apiRiskReqEsc}',`api_risk_response`='{$apiRiskRespEsc}' WHERE `id`='" . (int) $log_id . "'"; if ($conn->query($sql) != TRUE) { $handle = fopen(dirname(__FILE__) . "/err.txt", "a+"); diff --git a/jump.txt b/jump.txt index 79abc70..8b31ca2 100755 --- a/jump.txt +++ b/jump.txt @@ -99,3 +99,5 @@ 2026-06-14 15:23:23 | 写入数据库 | | regression_test | async=1 2026-06-14 15:47:16 | 来源判断 | 无法识别访客国家|false | index | 127.0.0.1 | https://www.baidu.com/ 2026-06-14 15:47:16 | 写入数据库 | false | index | async=1 +2026-06-15 16:45:08 | 来源判断 | 无法识别访客国家|false | index | 127.0.0.1 | https://www.baidu.com/ +2026-06-15 16:45:08 | 写入数据库 | false | index | async=1 diff --git a/lib/Cloak/CountryAllowlist.php b/lib/Cloak/CountryAllowlist.php index fe0f0eb..47f2609 100644 --- a/lib/Cloak/CountryAllowlist.php +++ b/lib/Cloak/CountryAllowlist.php @@ -2,6 +2,8 @@ /** * SHOW_SITE_COUNTRY 允许列表解析与比对 */ +require_once __DIR__ . '/CountryCodeNormalizer.php'; + class CountryAllowlist { /** @@ -34,9 +36,10 @@ class CountryAllowlist if ($allowlist === []) { return true; } - if ($countryCode === null || $countryCode === '') { + $normalized = CountryCodeNormalizer::toAlpha2($countryCode); + if ($normalized === null || $normalized === '') { return false; } - return in_array(strtoupper($countryCode), $allowlist, true); + return in_array($normalized, $allowlist, true); } } diff --git a/lib/Cloak/CountryCodeNormalizer.php b/lib/Cloak/CountryCodeNormalizer.php new file mode 100644 index 0000000..8f37331 --- /dev/null +++ b/lib/Cloak/CountryCodeNormalizer.php @@ -0,0 +1,60 @@ +,alpha3:array}|null */ + private static $maps = null; + + /** + * @param mixed $value ISO2、ISO3 或英文国名 + * @return string|null 两位国家码;无法识别时 null + */ + public static function toAlpha2($value) + { + $value = trim((string) $value); + if ($value === '') { + return null; + } + + $upper = strtoupper($value); + if (preg_match('/^[A-Z]{2}$/', $upper)) { + return $upper; + } + + $maps = self::maps(); + if (preg_match('/^[A-Z]{3}$/', $upper) && isset($maps['alpha3'][$upper])) { + return $maps['alpha3'][$upper]; + } + + $nameKey = self::nameKey($value); + if (isset($maps['names'][$nameKey])) { + return $maps['names'][$nameKey]; + } + + return null; + } + + /** + * @return array{names:array,alpha3:array} + */ + private static function maps() + { + if (self::$maps === null) { + $file = __DIR__ . '/iso3166_maps.php'; + $data = is_file($file) ? require $file : ['names' => [], 'alpha3' => []]; + self::$maps = [ + 'names' => isset($data['names']) && is_array($data['names']) ? $data['names'] : [], + 'alpha3' => isset($data['alpha3']) && is_array($data['alpha3']) ? $data['alpha3'] : [], + ]; + } + return self::$maps; + } + + private static function nameKey($name) + { + $name = preg_replace('/\s+/u', ' ', trim((string) $name)); + return strtoupper($name); + } +} diff --git a/lib/Cloak/GeoIpCountryCache.php b/lib/Cloak/GeoIpCountryCache.php index 6428832..e849d99 100644 --- a/lib/Cloak/GeoIpCountryCache.php +++ b/lib/Cloak/GeoIpCountryCache.php @@ -2,6 +2,8 @@ /** * IP → 国家码跨请求缓存(Redis 优先,文件降级,默认 TTL 30 天) */ +require_once __DIR__ . '/CountryCodeNormalizer.php'; + class GeoIpCountryCache { const REDIS_PREFIX = 'cloak:geoip:cc:'; @@ -28,7 +30,7 @@ class GeoIpCountryCache if ($value === false || $value === null) { return false; } - return $value === self::NONE_SENTINEL ? null : (string) $value; + return self::normalizeCachedValue($value === self::NONE_SENTINEL ? null : (string) $value); } catch (Throwable $e) { self::logError('Redis get failed: ' . $e->getMessage()); } @@ -48,7 +50,12 @@ class GeoIpCountryCache return; } - $value = $countryCode !== null && $countryCode !== '' ? strtoupper($countryCode) : self::NONE_SENTINEL; + if ($countryCode !== null && $countryCode !== '') { + $normalized = CountryCodeNormalizer::toAlpha2($countryCode); + $value = $normalized !== null ? $normalized : strtoupper($countryCode); + } else { + $value = self::NONE_SENTINEL; + } $ttl = self::cacheTtl(); $redis = self::redisClient(); @@ -137,7 +144,20 @@ class GeoIpCountryCache @unlink($path); return false; } - return $data['value'] === self::NONE_SENTINEL ? null : (string) $data['value']; + return self::normalizeCachedValue($data['value'] === self::NONE_SENTINEL ? null : (string) $data['value']); + } + + /** + * @param string|null $value + * @return string|null + */ + private static function normalizeCachedValue($value) + { + if ($value === null || $value === '') { + return null; + } + $normalized = CountryCodeNormalizer::toAlpha2($value); + return $normalized !== null ? $normalized : strtoupper((string) $value); } /** diff --git a/lib/Cloak/GeoIpCountryResolver.php b/lib/Cloak/GeoIpCountryResolver.php index 660b860..8c0ba3e 100644 --- a/lib/Cloak/GeoIpCountryResolver.php +++ b/lib/Cloak/GeoIpCountryResolver.php @@ -1,4 +1,6 @@ country($ip); - $code = $record->country->isoCode ?? null; - if ($code === null || $code === '') { - return null; + $isoCode = $record->country->isoCode ?? null; + $name = $record->country->name ?? null; + + $code = CountryCodeNormalizer::toAlpha2($isoCode); + if ($code === null && $name !== null && $name !== '') { + $code = CountryCodeNormalizer::toAlpha2($name); } - return strtoupper((string) $code); + return $code; } catch (GeoIp2\Exception\AddressNotFoundException $e) { return null; } catch (Throwable $e) { diff --git a/lib/Cloak/Pipeline/RouteResolver.php b/lib/Cloak/Pipeline/RouteResolver.php index 890113d..e1ed481 100755 --- a/lib/Cloak/Pipeline/RouteResolver.php +++ b/lib/Cloak/Pipeline/RouteResolver.php @@ -2,6 +2,7 @@ require_once __DIR__ . '/../Page/FpPageRenderer.php'; require_once __DIR__ . '/../Page/RiskPageRenderer.php'; require_once __DIR__ . '/../FpUrlHelper.php'; +require_once __DIR__ . '/../RiskSecondaryGate.php'; /** * 判定完成后的路由输出(真实页 / 安全页 / 二次风控) @@ -78,7 +79,7 @@ class RouteResolver $_SESSION['visit_to_3'] = '1'; } if (($_SESSION['visit_to_3'] ?? '1') != '3' - && (CLOAK_RISK_NUMBER > 0 && CLOAK_RISK_NUMBER <= 90)) { + && RiskSecondaryGate::isEnabled()) { // 二次风控:先写 wait 状态,f_check 完成后再更新 result / fp_url(避免 true+空跳转) $logs['result'] = 'wait'; $logs['reason'] = '二次风控检测中'; diff --git a/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php b/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php index de0d1fe..d8e59f0 100755 --- a/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php +++ b/lib/Cloak/Pipeline/stages/run_main_api_fingerprint.inc.php @@ -17,6 +17,9 @@ $_SESSION["gcu_Client"] = ''; $_SESSION["gcu_ip"] = ''; $_SESSION["gcu_Browser"] = ''; + if (class_exists('VisitorApiAudit', false)) { + VisitorApiAudit::clearByApi(); + } } if($_SESSION['visit_to_1'] == '1' && $_SESSION['visit_to_2'] != '2') // $_SESSION['visit_to_2'] 为2时表示已经判断过所有规则。 diff --git a/lib/Cloak/RiskSecondaryGate.php b/lib/Cloak/RiskSecondaryGate.php new file mode 100644 index 0000000..aab93fa --- /dev/null +++ b/lib/Cloak/RiskSecondaryGate.php @@ -0,0 +1,15 @@ + 0 && $riskNumber <= 90; + } +} diff --git a/lib/Cloak/VisitorApiAudit.php b/lib/Cloak/VisitorApiAudit.php new file mode 100644 index 0000000..669d736 --- /dev/null +++ b/lib/Cloak/VisitorApiAudit.php @@ -0,0 +1,76 @@ + self::SESSION_BY_REQUEST, + 'api_by_response' => self::SESSION_BY_RESPONSE, + ]; + foreach ($map as $col => $sessionKey) { + if (empty($logs[$col]) && !empty($_SESSION[$sessionKey])) { + $logs[$col] = (string) $_SESSION[$sessionKey]; + } + } + return $logs; + } + + /** + * @param mixed $data + */ + public static function encode($data) + { + if (is_string($data)) { + return $data; + } + $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + return $json !== false ? $json : ''; + } + + /** + * 日志表格展示:未调用时返回「-」 + * + * @param mixed $raw + */ + public static function displayText($raw) + { + $raw = trim((string) $raw); + return $raw === '' ? '-' : $raw; + } +} diff --git a/lib/Cloak/VisitorInfo.php b/lib/Cloak/VisitorInfo.php index b80fbd2..51377e8 100755 --- a/lib/Cloak/VisitorInfo.php +++ b/lib/Cloak/VisitorInfo.php @@ -1,5 +1,6 @@ check_country; //设置该参数后,将替换"广告策略》访问者地理位置>过滤"的设置,填写国家代码,多个用逗号分隔,如:US,GB,CA,AU,IE,NZ $jsonData['referer'] = get_SERVER_value('HTTP_REFERER'); $jsonData['headers'] = json_encode($headers); + VisitorApiAudit::recordByApiRequest($jsonData); $ch = curl_init('www.tiktokba.com/cloak/byApi'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -114,14 +116,19 @@ class visitorInfo if ($return == false) { $curl_error = curl_error($ch); curl_close($ch); - return ['result' => false, 'reason' => 'API请求失败:' . $curl_error, 'country' => '']; + $errorPayload = ['result' => false, 'reason' => 'API请求失败:' . $curl_error, 'country' => '']; + VisitorApiAudit::recordByApiResponse($errorPayload); + return $errorPayload; } curl_close($ch); $decoded = json_decode($return, true); if (!is_array($decoded)) { + $invalidPayload = ['result' => false, 'reason' => 'API响应失败,请检查API密钥以及广告策略 ID', 'country' => '', '_raw' => $return]; + VisitorApiAudit::recordByApiResponse($invalidPayload); return ['result' => false, 'reason' => 'API响应失败,请检查API密钥以及广告策略 ID', 'country' => '']; } + VisitorApiAudit::recordByApiResponse($decoded); return $decoded; } diff --git a/lib/Cloak/VisitorLogSchema.php b/lib/Cloak/VisitorLogSchema.php index 0f33963..2af4943 100755 --- a/lib/Cloak/VisitorLogSchema.php +++ b/lib/Cloak/VisitorLogSchema.php @@ -24,6 +24,10 @@ class VisitorLogSchema 'accept_language_raw' => "ALTER TABLE `visitor_logs` ADD COLUMN `accept_language_raw` TEXT NULL COMMENT 'Accept-Language 完整值' AFTER `http_referer`", 'judge_timing' => "ALTER TABLE `visitor_logs` ADD COLUMN `judge_timing` TEXT NULL COMMENT '判定用时 JSON' AFTER `accept_language_raw`", 'fp_hdata' => "ALTER TABLE `visitor_logs` ADD COLUMN `fp_hdata` TEXT NULL COMMENT '指纹 hdata 解码 JSON' AFTER `judge_timing`", + 'api_by_request' => "ALTER TABLE `visitor_logs` ADD COLUMN `api_by_request` TEXT NULL COMMENT 'byApi 请求参数 JSON' AFTER `fp_hdata`", + 'api_by_response' => "ALTER TABLE `visitor_logs` ADD COLUMN `api_by_response` TEXT NULL COMMENT 'byApi 响应 JSON' AFTER `api_by_request`", + 'api_risk_request' => "ALTER TABLE `visitor_logs` ADD COLUMN `api_risk_request` TEXT NULL COMMENT 'byApiRisk 请求参数 JSON' AFTER `api_by_response`", + 'api_risk_response' => "ALTER TABLE `visitor_logs` ADD COLUMN `api_risk_response` TEXT NULL COMMENT 'byApiRisk 响应 JSON' AFTER `api_risk_request`", ]; foreach ($columns as $name => $ddl) { if (!self::columnExists($conn, 'visitor_logs', $name)) { diff --git a/lib/Cloak/VisitorLogWorker.php b/lib/Cloak/VisitorLogWorker.php index a9724c7..cb5ef7f 100644 --- a/lib/Cloak/VisitorLogWorker.php +++ b/lib/Cloak/VisitorLogWorker.php @@ -70,7 +70,7 @@ class VisitorLogWorker return array_key_exists($key, $data) ? $data[$key] : cloak_db_escape($conn, ''); }; - $sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `referer`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_url`, `device`) VALUES ('" + $sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `referer`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_url`, `device`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`) VALUES ('" . $d('campagin_id') . "','" . $d('visit_md5_code') . "','" . $d('site_name') . "','" @@ -89,7 +89,11 @@ class VisitorLogWorker . $d('accept_language_raw') . "','" . $d('judge_timing') . "','" . $d('fp_url') . "','" - . $d('device') . "')"; + . $d('device') . "','" + . $d('api_by_request') . "','" + . $d('api_by_response') . "','" + . $d('api_risk_request') . "','" + . $d('api_risk_response') . "')"; if ($conn->query($sql) !== true) { VisitorLogQueue::logError('insert_full: ' . $conn->error . ' | ' . $sql); @@ -169,6 +173,10 @@ class VisitorLogWorker 'http_referer' => 'http_referer', 'accept_language_raw' => 'accept_language_raw', 'judge_timing' => 'judge_timing', + 'api_by_request' => 'api_by_request', + 'api_by_response' => 'api_by_response', + 'api_risk_request' => 'api_risk_request', + 'api_risk_response' => 'api_risk_response', ]; foreach ($map as $col => $logKey) { if (array_key_exists($logKey, $data)) { diff --git a/lib/Cloak/VisitorRepository.php b/lib/Cloak/VisitorRepository.php index 99e3a9f..0636855 100755 --- a/lib/Cloak/VisitorRepository.php +++ b/lib/Cloak/VisitorRepository.php @@ -4,6 +4,7 @@ */ require_once dirname(__DIR__) . '/DbHelper.php'; require_once dirname(__DIR__) . '/CloakHttpHelpers.php'; +require_once __DIR__ . '/VisitorApiAudit.php'; if (!function_exists('cloak_session_mark_real_visitor')) { /** @@ -123,7 +124,7 @@ if (!function_exists('cloak_visitor_log_enrich')) { if (empty($logs['judge_timing']) && !empty($GLOBALS['__cloak_judge_timing'])) { $logs['judge_timing'] = $GLOBALS['__cloak_judge_timing']; } - return $logs; + return VisitorApiAudit::mergeIntoLogs($logs); } } @@ -209,7 +210,7 @@ if (!function_exists('cloak_write_log_db')) { $d = static function ($key) use ($data, $conn) { return array_key_exists($key, $data) ? $data[$key] : cloak_db_escape($conn, ''); }; - $sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `referer`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_url`, `device`) VALUES ('" + $sql = "INSERT INTO `visitor_logs`(`campagin_id`, `visit_md5_code`, `domain`, `visit_date`, `IP`, `country`, `result`, `reason`, `referer`, `client`, `browser`, `page`, `language`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `fp_url`, `device`, `api_by_request`, `api_by_response`, `api_risk_request`, `api_risk_response`) VALUES ('" . $d('campagin_id') . "','" . $d('visit_md5_code') . "','" . $d('site_name') . "','" @@ -228,7 +229,11 @@ if (!function_exists('cloak_write_log_db')) { . $d('accept_language_raw') . "','" . $d('judge_timing') . "','" . $d('fp_url') . "','" - . $d('device') . "')"; + . $d('device') . "','" + . $d('api_by_request') . "','" + . $d('api_by_response') . "','" + . $d('api_risk_request') . "','" + . $d('api_risk_response') . "')"; if ($conn->query($sql) === true) { $last_id = $conn->insert_id; $conn->close(); diff --git a/lib/Cloak/iso3166_maps.php b/lib/Cloak/iso3166_maps.php new file mode 100644 index 0000000..692e587 --- /dev/null +++ b/lib/Cloak/iso3166_maps.php @@ -0,0 +1,526 @@ + [ + 'AFGHANISTAN' => 'AF', + 'ÅLAND ISLANDS' => 'AX', + 'ALBANIA' => 'AL', + 'ALGERIA' => 'DZ', + 'AMERICAN SAMOA' => 'AS', + 'ANDORRA' => 'AD', + 'ANGOLA' => 'AO', + 'ANGUILLA' => 'AI', + 'ANTARCTICA' => 'AQ', + 'ANTIGUA AND BARBUDA' => 'AG', + 'ARGENTINA' => 'AR', + 'ARMENIA' => 'AM', + 'ARUBA' => 'AW', + 'AUSTRALIA' => 'AU', + 'AUSTRIA' => 'AT', + 'AZERBAIJAN' => 'AZ', + 'BAHAMAS' => 'BS', + 'BAHRAIN' => 'BH', + 'BANGLADESH' => 'BD', + 'BARBADOS' => 'BB', + 'BELARUS' => 'BY', + 'BELGIUM' => 'BE', + 'BELIZE' => 'BZ', + 'BENIN' => 'BJ', + 'BERMUDA' => 'BM', + 'BHUTAN' => 'BT', + 'BOLIVIA' => 'BO', + 'BONAIRE, SINT EUSTATIUS AND SABA' => 'BQ', + 'BOSNIA AND HERZEGOVINA' => 'BA', + 'BOTSWANA' => 'BW', + 'BOUVET ISLAND' => 'BV', + 'BRAZIL' => 'BR', + 'BRITISH INDIAN OCEAN TERRITORY' => 'IO', + 'BRUNEI DARUSSALAM' => 'BN', + 'BULGARIA' => 'BG', + 'BURKINA FASO' => 'BF', + 'BURUNDI' => 'BI', + 'CABO VERDE' => 'CV', + 'CAMBODIA' => 'KH', + 'CAMEROON' => 'CM', + 'CANADA' => 'CA', + 'CAYMAN ISLANDS' => 'KY', + 'CENTRAL AFRICAN REPUBLIC' => 'CF', + 'CHAD' => 'TD', + 'CHILE' => 'CL', + 'CHINA' => 'CN', + 'CHRISTMAS ISLAND' => 'CX', + 'COCOS (KEELING) ISLANDS' => 'CC', + 'COLOMBIA' => 'CO', + 'COMOROS' => 'KM', + 'CONGO' => 'CG', + 'CONGO, DEMOCRATIC REPUBLIC OF THE' => 'CD', + 'COOK ISLANDS' => 'CK', + 'COSTA RICA' => 'CR', + 'CÔTE D\'IVOIRE' => 'CI', + 'CROATIA' => 'HR', + 'CUBA' => 'CU', + 'CURAÇAO' => 'CW', + 'CYPRUS' => 'CY', + 'CZECHIA' => 'CZ', + 'DENMARK' => 'DK', + 'DJIBOUTI' => 'DJ', + 'DOMINICA' => 'DM', + 'DOMINICAN REPUBLIC' => 'DO', + 'ECUADOR' => 'EC', + 'EGYPT' => 'EG', + 'EL SALVADOR' => 'SV', + 'EQUATORIAL GUINEA' => 'GQ', + 'ERITREA' => 'ER', + 'ESTONIA' => 'EE', + 'ESWATINI' => 'SZ', + 'ETHIOPIA' => 'ET', + 'FALKLAND ISLANDS' => 'FK', + 'FAROE ISLANDS' => 'FO', + 'FIJI' => 'FJ', + 'FINLAND' => 'FI', + 'FRANCE' => 'FR', + 'FRENCH GUIANA' => 'GF', + 'FRENCH POLYNESIA' => 'PF', + 'FRENCH SOUTHERN TERRITORIES' => 'TF', + 'GABON' => 'GA', + 'GAMBIA' => 'GM', + 'GEORGIA' => 'GE', + 'GERMANY' => 'DE', + 'GHANA' => 'GH', + 'GIBRALTAR' => 'GI', + 'GREECE' => 'GR', + 'GREENLAND' => 'GL', + 'GRENADA' => 'GD', + 'GUADELOUPE' => 'GP', + 'GUAM' => 'GU', + 'GUATEMALA' => 'GT', + 'GUERNSEY' => 'GG', + 'GUINEA' => 'GN', + 'GUINEA-BISSAU' => 'GW', + 'GUYANA' => 'GY', + 'HAITI' => 'HT', + 'HEARD ISLAND AND MCDONALD ISLANDS' => 'HM', + 'HOLY SEE' => 'VA', + 'HONDURAS' => 'HN', + 'HONG KONG' => 'HK', + 'HUNGARY' => 'HU', + 'ICELAND' => 'IS', + 'INDIA' => 'IN', + 'INDONESIA' => 'ID', + 'IRAN' => 'IR', + 'IRAQ' => 'IQ', + 'IRELAND' => 'IE', + 'ISLE OF MAN' => 'IM', + 'ISRAEL' => 'IL', + 'ITALY' => 'IT', + 'JAMAICA' => 'JM', + 'JAPAN' => 'JP', + 'JERSEY' => 'JE', + 'JORDAN' => 'JO', + 'KAZAKHSTAN' => 'KZ', + 'KENYA' => 'KE', + 'KIRIBATI' => 'KI', + 'KOREA, DEMOCRATIC PEOPLE\'S REPUBLIC OF' => 'KP', + 'KOREA, REPUBLIC OF' => 'KR', + 'KUWAIT' => 'KW', + 'KYRGYZSTAN' => 'KG', + 'LAO PEOPLE\'S DEMOCRATIC REPUBLIC' => 'LA', + 'LATVIA' => 'LV', + 'LEBANON' => 'LB', + 'LESOTHO' => 'LS', + 'LIBERIA' => 'LR', + 'LIBYA' => 'LY', + 'LIECHTENSTEIN' => 'LI', + 'LITHUANIA' => 'LT', + 'LUXEMBOURG' => 'LU', + 'MACAO' => 'MO', + 'MADAGASCAR' => 'MG', + 'MALAWI' => 'MW', + 'MALAYSIA' => 'MY', + 'MALDIVES' => 'MV', + 'MALI' => 'ML', + 'MALTA' => 'MT', + 'MARSHALL ISLANDS' => 'MH', + 'MARTINIQUE' => 'MQ', + 'MAURITANIA' => 'MR', + 'MAURITIUS' => 'MU', + 'MAYOTTE' => 'YT', + 'MEXICO' => 'MX', + 'MICRONESIA' => 'FM', + 'MOLDOVA' => 'MD', + 'MONACO' => 'MC', + 'MONGOLIA' => 'MN', + 'MONTENEGRO' => 'ME', + 'MONTSERRAT' => 'MS', + 'MOROCCO' => 'MA', + 'MOZAMBIQUE' => 'MZ', + 'MYANMAR' => 'MM', + 'NAMIBIA' => 'NA', + 'NAURU' => 'NR', + 'NEPAL' => 'NP', + 'NETHERLANDS' => 'NL', + 'NEW CALEDONIA' => 'NC', + 'NEW ZEALAND' => 'NZ', + 'NICARAGUA' => 'NI', + 'NIGER' => 'NE', + 'NIGERIA' => 'NG', + 'NIUE' => 'NU', + 'NORFOLK ISLAND' => 'NF', + 'NORTH MACEDONIA' => 'MK', + 'NORTHERN MARIANA ISLANDS' => 'MP', + 'NORWAY' => 'NO', + 'OMAN' => 'OM', + 'PAKISTAN' => 'PK', + 'PALAU' => 'PW', + 'PALESTINE, STATE OF' => 'PS', + 'PANAMA' => 'PA', + 'PAPUA NEW GUINEA' => 'PG', + 'PARAGUAY' => 'PY', + 'PERU' => 'PE', + 'PHILIPPINES' => 'PH', + 'PITCAIRN' => 'PN', + 'POLAND' => 'PL', + 'PORTUGAL' => 'PT', + 'PUERTO RICO' => 'PR', + 'QATAR' => 'QA', + 'RÉUNION' => 'RE', + 'ROMANIA' => 'RO', + 'RUSSIAN FEDERATION' => 'RU', + 'RWANDA' => 'RW', + 'SAINT BARTHÉLEMY' => 'BL', + 'SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA' => 'SH', + 'SAINT KITTS AND NEVIS' => 'KN', + 'SAINT LUCIA' => 'LC', + 'SAINT MARTIN' => 'MF', + 'SAINT PIERRE AND MIQUELON' => 'PM', + 'SAINT VINCENT AND THE GRENADINES' => 'VC', + 'SAMOA' => 'WS', + 'SAN MARINO' => 'SM', + 'SAO TOME AND PRINCIPE' => 'ST', + 'SAUDI ARABIA' => 'SA', + 'SENEGAL' => 'SN', + 'SERBIA' => 'RS', + 'SEYCHELLES' => 'SC', + 'SIERRA LEONE' => 'SL', + 'SINGAPORE' => 'SG', + 'SINT MAARTEN' => 'SX', + 'SLOVAKIA' => 'SK', + 'SLOVENIA' => 'SI', + 'SOLOMON ISLANDS' => 'SB', + 'SOMALIA' => 'SO', + 'SOUTH AFRICA' => 'ZA', + 'SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS' => 'GS', + 'SOUTH SUDAN' => 'SS', + 'SPAIN' => 'ES', + 'SRI LANKA' => 'LK', + 'SUDAN' => 'SD', + 'SURINAME' => 'SR', + 'SVALBARD AND JAN MAYEN' => 'SJ', + 'SWEDEN' => 'SE', + 'SWITZERLAND' => 'CH', + 'SYRIAN ARAB REPUBLIC' => 'SY', + 'TAIWAN' => 'TW', + 'TAJIKISTAN' => 'TJ', + 'TANZANIA' => 'TZ', + 'THAILAND' => 'TH', + 'TIMOR-LESTE' => 'TL', + 'TOGO' => 'TG', + 'TOKELAU' => 'TK', + 'TONGA' => 'TO', + 'TRINIDAD AND TOBAGO' => 'TT', + 'TUNISIA' => 'TN', + 'TÜRKIYE' => 'TR', + 'TURKMENISTAN' => 'TM', + 'TURKS AND CAICOS ISLANDS' => 'TC', + 'TUVALU' => 'TV', + 'UGANDA' => 'UG', + 'UKRAINE' => 'UA', + 'UNITED ARAB EMIRATES' => 'AE', + 'UNITED KINGDOM' => 'GB', + 'UNITED STATES OF AMERICA' => 'US', + 'UNITED STATES MINOR OUTLYING ISLANDS' => 'UM', + 'URUGUAY' => 'UY', + 'UZBEKISTAN' => 'UZ', + 'VANUATU' => 'VU', + 'VENEZUELA' => 'VE', + 'VIET NAM' => 'VN', + 'VIRGIN ISLANDS, BRITISH' => 'VG', + 'VIRGIN ISLANDS, U.S.' => 'VI', + 'WALLIS AND FUTUNA' => 'WF', + 'WESTERN SAHARA' => 'EH', + 'YEMEN' => 'YE', + 'ZAMBIA' => 'ZM', + 'ZIMBABWE' => 'ZW', + 'UNITED STATES' => 'US', + 'USA' => 'US', + 'U.S.A.' => 'US', + 'U.S.' => 'US', + 'UK' => 'GB', + 'GREAT BRITAIN' => 'GB', + 'ENGLAND' => 'GB', + 'RUSSIA' => 'RU', + 'SOUTH KOREA' => 'KR', + 'NORTH KOREA' => 'KP', + 'KOREA' => 'KR', + 'VIETNAM' => 'VN', + 'CZECH REPUBLIC' => 'CZ', + 'SYRIA' => 'SY', + 'LAOS' => 'LA', + 'MACAU' => 'MO', + 'HOLLAND' => 'NL', + 'THE NETHERLANDS' => 'NL', + 'IVORY COAST' => 'CI', + 'TURKEY' => 'TR', + ], + 'alpha3' => [ + 'AFG' => 'AF', + 'ALA' => 'AX', + 'ALB' => 'AL', + 'DZA' => 'DZ', + 'ASM' => 'AS', + 'AND' => 'AD', + 'AGO' => 'AO', + 'AIA' => 'AI', + 'ATA' => 'AQ', + 'ATG' => 'AG', + 'ARG' => 'AR', + 'ARM' => 'AM', + 'ABW' => 'AW', + 'AUS' => 'AU', + 'AUT' => 'AT', + 'AZE' => 'AZ', + 'BHS' => 'BS', + 'BHR' => 'BH', + 'BGD' => 'BD', + 'BRB' => 'BB', + 'BLR' => 'BY', + 'BEL' => 'BE', + 'BLZ' => 'BZ', + 'BEN' => 'BJ', + 'BMU' => 'BM', + 'BTN' => 'BT', + 'BOL' => 'BO', + 'BES' => 'BQ', + 'BIH' => 'BA', + 'BWA' => 'BW', + 'BVT' => 'BV', + 'BRA' => 'BR', + 'IOT' => 'IO', + 'BRN' => 'BN', + 'BGR' => 'BG', + 'BFA' => 'BF', + 'BDI' => 'BI', + 'CPV' => 'CV', + 'KHM' => 'KH', + 'CMR' => 'CM', + 'CAN' => 'CA', + 'CYM' => 'KY', + 'CAF' => 'CF', + 'TCD' => 'TD', + 'CHL' => 'CL', + 'CHN' => 'CN', + 'CXR' => 'CX', + 'CCK' => 'CC', + 'COL' => 'CO', + 'COM' => 'KM', + 'COG' => 'CG', + 'COD' => 'CD', + 'COK' => 'CK', + 'CRI' => 'CR', + 'CIV' => 'CI', + 'HRV' => 'HR', + 'CUB' => 'CU', + 'CUW' => 'CW', + 'CYP' => 'CY', + 'CZE' => 'CZ', + 'DNK' => 'DK', + 'DJI' => 'DJ', + 'DMA' => 'DM', + 'DOM' => 'DO', + 'ECU' => 'EC', + 'EGY' => 'EG', + 'SLV' => 'SV', + 'GNQ' => 'GQ', + 'ERI' => 'ER', + 'EST' => 'EE', + 'SWZ' => 'SZ', + 'ETH' => 'ET', + 'FLK' => 'FK', + 'FRO' => 'FO', + 'FJI' => 'FJ', + 'FIN' => 'FI', + 'FRA' => 'FR', + 'GUF' => 'GF', + 'PYF' => 'PF', + 'ATF' => 'TF', + 'GAB' => 'GA', + 'GMB' => 'GM', + 'GEO' => 'GE', + 'DEU' => 'DE', + 'GHA' => 'GH', + 'GIB' => 'GI', + 'GRC' => 'GR', + 'GRL' => 'GL', + 'GRD' => 'GD', + 'GLP' => 'GP', + 'GUM' => 'GU', + 'GTM' => 'GT', + 'GGY' => 'GG', + 'GIN' => 'GN', + 'GNB' => 'GW', + 'GUY' => 'GY', + 'HTI' => 'HT', + 'HMD' => 'HM', + 'VAT' => 'VA', + 'HND' => 'HN', + 'HKG' => 'HK', + 'HUN' => 'HU', + 'ISL' => 'IS', + 'IND' => 'IN', + 'IDN' => 'ID', + 'IRN' => 'IR', + 'IRQ' => 'IQ', + 'IRL' => 'IE', + 'IMN' => 'IM', + 'ISR' => 'IL', + 'ITA' => 'IT', + 'JAM' => 'JM', + 'JPN' => 'JP', + 'JEY' => 'JE', + 'JOR' => 'JO', + 'KAZ' => 'KZ', + 'KEN' => 'KE', + 'KIR' => 'KI', + 'PRK' => 'KP', + 'KOR' => 'KR', + 'KWT' => 'KW', + 'KGZ' => 'KG', + 'LAO' => 'LA', + 'LVA' => 'LV', + 'LBN' => 'LB', + 'LSO' => 'LS', + 'LBR' => 'LR', + 'LBY' => 'LY', + 'LIE' => 'LI', + 'LTU' => 'LT', + 'LUX' => 'LU', + 'MAC' => 'MO', + 'MDG' => 'MG', + 'MWI' => 'MW', + 'MYS' => 'MY', + 'MDV' => 'MV', + 'MLI' => 'ML', + 'MLT' => 'MT', + 'MHL' => 'MH', + 'MTQ' => 'MQ', + 'MRT' => 'MR', + 'MUS' => 'MU', + 'MYT' => 'YT', + 'MEX' => 'MX', + 'FSM' => 'FM', + 'MDA' => 'MD', + 'MCO' => 'MC', + 'MNG' => 'MN', + 'MNE' => 'ME', + 'MSR' => 'MS', + 'MAR' => 'MA', + 'MOZ' => 'MZ', + 'MMR' => 'MM', + 'NAM' => 'NA', + 'NRU' => 'NR', + 'NPL' => 'NP', + 'NLD' => 'NL', + 'NCL' => 'NC', + 'NZL' => 'NZ', + 'NIC' => 'NI', + 'NER' => 'NE', + 'NGA' => 'NG', + 'NIU' => 'NU', + 'NFK' => 'NF', + 'MKD' => 'MK', + 'MNP' => 'MP', + 'NOR' => 'NO', + 'OMN' => 'OM', + 'PAK' => 'PK', + 'PLW' => 'PW', + 'PSE' => 'PS', + 'PAN' => 'PA', + 'PNG' => 'PG', + 'PRY' => 'PY', + 'PER' => 'PE', + 'PHL' => 'PH', + 'PCN' => 'PN', + 'POL' => 'PL', + 'PRT' => 'PT', + 'PRI' => 'PR', + 'QAT' => 'QA', + 'REU' => 'RE', + 'ROU' => 'RO', + 'RUS' => 'RU', + 'RWA' => 'RW', + 'BLM' => 'BL', + 'SHN' => 'SH', + 'KNA' => 'KN', + 'LCA' => 'LC', + 'MAF' => 'MF', + 'SPM' => 'PM', + 'VCT' => 'VC', + 'WSM' => 'WS', + 'SMR' => 'SM', + 'STP' => 'ST', + 'SAU' => 'SA', + 'SEN' => 'SN', + 'SRB' => 'RS', + 'SYC' => 'SC', + 'SLE' => 'SL', + 'SGP' => 'SG', + 'SXM' => 'SX', + 'SVK' => 'SK', + 'SVN' => 'SI', + 'SLB' => 'SB', + 'SOM' => 'SO', + 'ZAF' => 'ZA', + 'SGS' => 'GS', + 'SSD' => 'SS', + 'ESP' => 'ES', + 'LKA' => 'LK', + 'SDN' => 'SD', + 'SUR' => 'SR', + 'SJM' => 'SJ', + 'SWE' => 'SE', + 'CHE' => 'CH', + 'SYR' => 'SY', + 'TWN' => 'TW', + 'TJK' => 'TJ', + 'TZA' => 'TZ', + 'THA' => 'TH', + 'TLS' => 'TL', + 'TGO' => 'TG', + 'TKL' => 'TK', + 'TON' => 'TO', + 'TTO' => 'TT', + 'TUN' => 'TN', + 'TUR' => 'TR', + 'TKM' => 'TM', + 'TCA' => 'TC', + 'TUV' => 'TV', + 'UGA' => 'UG', + 'UKR' => 'UA', + 'ARE' => 'AE', + 'GBR' => 'GB', + 'USA' => 'US', + 'UMI' => 'UM', + 'URY' => 'UY', + 'UZB' => 'UZ', + 'VUT' => 'VU', + 'VEN' => 'VE', + 'VNM' => 'VN', + 'VGB' => 'VG', + 'VIR' => 'VI', + 'WLF' => 'WF', + 'ESH' => 'EH', + 'YEM' => 'YE', + 'ZMB' => 'ZM', + 'ZWE' => 'ZW', + ], +]; diff --git a/lib/InstallSchema.php b/lib/InstallSchema.php index 27f3291..4e98356 100755 --- a/lib/InstallSchema.php +++ b/lib/InstallSchema.php @@ -63,6 +63,10 @@ class InstallSchema `accept_language_raw` text DEFAULT NULL COMMENT 'Accept-Language 完整值', `judge_timing` text DEFAULT NULL COMMENT '判定用时 JSON', `fp_hdata` text DEFAULT NULL COMMENT '指纹 hdata 解码 JSON', + `api_by_request` text DEFAULT NULL COMMENT 'byApi 请求参数 JSON', + `api_by_response` text DEFAULT NULL COMMENT 'byApi 响应 JSON', + `api_risk_request` text DEFAULT NULL COMMENT 'byApiRisk 请求参数 JSON', + `api_risk_response` text DEFAULT NULL COMMENT 'byApiRisk 响应 JSON', `add_time` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '日志记录时间', PRIMARY KEY (`id`), KEY `IP` (`IP`), diff --git a/lib/bootstrap.php b/lib/bootstrap.php index 0b541d4..4f2fb97 100755 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -23,8 +23,11 @@ require_once __DIR__ . '/Cloak/PipelineTimer.php'; require_once __DIR__ . '/Cloak/ClientIpResolver.php'; require_once __DIR__ . '/Cloak/GeoIpCountryCache.php'; require_once __DIR__ . '/Cloak/GeoIpCountryResolver.php'; +require_once __DIR__ . '/Cloak/CountryCodeNormalizer.php'; require_once __DIR__ . '/Cloak/CountryAllowlist.php'; +require_once __DIR__ . '/Cloak/RiskSecondaryGate.php'; require_once __DIR__ . '/Cloak/VisitorLogSchema.php'; +require_once __DIR__ . '/Cloak/VisitorApiAudit.php'; require_once __DIR__ . '/Cloak/VisitorLogQueue.php'; require_once __DIR__ . '/Cloak/VisitorLogWorker.php'; require_once __DIR__ . '/Cloak/Pipeline/CheckPipeline.php'; diff --git a/lib_FLTSX.zip b/lib_FLTSX.zip deleted file mode 100755 index 2085555..0000000 Binary files a/lib_FLTSX.zip and /dev/null differ diff --git a/table.php b/table.php index 71a8b84..963aaad 100755 --- a/table.php +++ b/table.php @@ -183,8 +183,32 @@ if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) { .cell-copy-link:hover { color: var(--log-primary-dark); } .cell-ip { font-family: 'SF Mono', 'Consolas', monospace; font-size: 12px; } .text-muted { color: #adb5bd; } - .fp-hdata-cell { display: flex; flex-direction: column; gap: 4px; max-width: 100%; } - .fp-hdata-summary { font-size: 11.5px; color: #475569; line-height: 1.35; } + .fp-hdata-cell { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + gap: 4px 8px; + max-width: 100%; + min-height: 24px; + } + .fp-hdata-summary { + flex: 1 1 140px; + min-width: 0; + font-size: 11.5px; + color: #475569; + line-height: 1.35; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .fp-hdata-actions { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: 4px; + flex: 0 0 auto; + } .fp-hdata-btn { display: inline-block; padding: 1px 8px; @@ -197,6 +221,28 @@ if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) { width: fit-content; } .fp-hdata-btn:hover { background: #d1ecf1; } + /* 指纹 / API 审计列:解除 layui 固定行高,避免「判定详情」等按钮被裁切 */ + .log-table-wrap .layui-table-body td[data-field="fp_hdata_text"], + .log-table-wrap .layui-table-body td[data-field="api_by_request_text"], + .log-table-wrap .layui-table-body td[data-field="api_by_response_text"], + .log-table-wrap .layui-table-body td[data-field="api_risk_request_text"], + .log-table-wrap .layui-table-body td[data-field="api_risk_response_text"] { + height: auto !important; + vertical-align: top; + } + .log-table-wrap .layui-table-body td[data-field="fp_hdata_text"] > .layui-table-cell, + .log-table-wrap .layui-table-body td[data-field="api_by_request_text"] > .layui-table-cell, + .log-table-wrap .layui-table-body td[data-field="api_by_response_text"] > .layui-table-cell, + .log-table-wrap .layui-table-body td[data-field="api_risk_request_text"] > .layui-table-cell, + .log-table-wrap .layui-table-body td[data-field="api_risk_response_text"] > .layui-table-cell { + height: auto !important; + max-height: none !important; + white-space: normal !important; + overflow: visible !important; + line-height: 1.35; + padding-top: 7px !important; + padding-bottom: 7px !important; + } .fp-risk-explain { font-size: 13px; line-height: 1.55; color: #2c3e50; } .fp-risk-explain-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .fp-risk-tag { font-size: 12px; padding: 2px 10px; border-radius: 999px; background: #eef2ff; color: #3730a3; font-weight: 600; } @@ -455,6 +501,26 @@ layui.use(function(){ return '' + short + ''; } + function formatJsonPretty(str) { + try { + return JSON.stringify(JSON.parse(str), null, 2); + } catch (e) { + return str; + } + } + + function cellApiAudit(val, title) { + if (val == null || val === '' || val === '-') { + return ''; + } + var text = String(val); + var short = text.length > 40 ? text.slice(0, 40) + '…' : text; + return '
' + + '' + escapeHtml(short) + '' + + '查看 JSON' + + '
'; + } + function cellFpHdata(d) { if (!d.fp_hdata && !d.fp_hdata_text) { return ''; @@ -465,14 +531,16 @@ layui.use(function(){ if (summary) { html += '' + escapeHtml(summary.length > 48 ? summary.slice(0, 48) + '…' : summary) + ''; } + html += ''; if (d.fp_hdata_explain_html) { html += '判定详情'; window.__fpExplainCache = window.__fpExplainCache || {}; window.__fpExplainCache[d.id] = d.fp_hdata_explain_html; - } else if (d.fp_hdata || d.fp_hdata_text) { + } + if (d.fp_hdata || d.fp_hdata_text) { html += '原始 hdata'; } - html += '
'; + html += '
'; return html; } @@ -502,6 +570,10 @@ layui.use(function(){ { field: 'http_referer', title: 'HTTP-Referer', width: 260, defaultVisible: false, templet: function(d){ return cellLinkEllipsis(d.http_referer, LINK_CELL_MAX); } }, { field: 'accept_language_raw', title: 'Accept-Language', width: 220, defaultVisible: false, templet: function(d){ return cellEllipsis(d.accept_language_raw, 32); } }, { field: 'fp_hdata_text', title: '指纹 hdata', width: 360, defaultVisible: false, templet: function(d){ return cellFpHdata(d); } }, + { field: 'api_by_request_text', title: 'byApi 请求参数', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_by_request_text, 'byApi 请求参数 #' + d.id); } }, + { field: 'api_by_response_text', title: 'byApi 返回值', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_by_response_text, 'byApi 返回值 #' + d.id); } }, + { field: 'api_risk_request_text', title: 'byApiRisk 请求参数', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_risk_request_text, 'byApiRisk 请求参数 #' + d.id); } }, + { field: 'api_risk_response_text', title: 'byApiRisk 返回值', width: 300, defaultVisible: false, templet: function(d){ return cellApiAudit(d.api_risk_response_text, 'byApiRisk 返回值 #' + d.id); } }, ]; var COLUMN_DEF_MAP = {}; @@ -598,8 +670,27 @@ layui.use(function(){ return [cols]; } + function fixRichTableCellHeights() { + var fields = ['fp_hdata_text', 'api_by_request_text', 'api_by_response_text', 'api_risk_request_text', 'api_risk_response_text']; + fields.forEach(function(field) { + $('.log-table-wrap .layui-table-body td[data-field="' + field + '"]').each(function() { + var $cell = $(this).children('.layui-table-cell'); + $cell.css({ height: 'auto', whiteSpace: 'normal', overflow: 'visible' }); + $(this).css('height', 'auto'); + }); + }); + } + function reloadTableCols() { - table.reload(TABLE_ID, { cols: buildCols(colConfig) }); + table.reload(TABLE_ID, { + cols: buildCols(colConfig), + done: function(res, curr, count) { + if (typeof count === 'number') { + $("#total_nums").text(count); + } + fixRichTableCellHeights(); + } + }); } function renderColSettingsList() { @@ -736,8 +827,15 @@ layui.use(function(){ return 'color:#856404;background-color:#fff8e6;'; } }, + lineStyle: function(row) { + if (row.fp_hdata || row.fp_hdata_text || row.fp_hdata_explain_html) { + return 'height:auto;'; + } + return ''; + }, done: function(res, curr, count, origin){ $("#total_nums").text(count); + fixRichTableCellHeights(); }, }); @@ -758,12 +856,16 @@ layui.use(function(){ } var full = this.getAttribute('data-full') || ''; if (full) { + var popupTitle = this.getAttribute('data-title') || '指纹 hdata'; + var body = this.classList.contains('fp-hdata-btn-json') + ? escapeHtml(formatJsonPretty(full)) + : escapeHtml(full); layer.open({ type: 1, - title: '指纹 hdata', + title: popupTitle, area: ['680px', '70vh'], shadeClose: true, - content: '
' + escapeHtml(full) + '
' + content: '
' + body + '
' }); } }); diff --git a/tools/verify_geoip.php b/tools/verify_geoip.php index 609328a..808a738 100644 --- a/tools/verify_geoip.php +++ b/tools/verify_geoip.php @@ -51,6 +51,13 @@ if ($tests['resolver_available']) { if (!$tests['allowlist_us'] || !$tests['allowlist_cn']) { $ok = false; } + + $tests['normalize_iso2'] = CountryCodeNormalizer::toAlpha2('us') === 'US'; + $tests['normalize_name'] = CountryCodeNormalizer::toAlpha2('United States') === 'US'; + $tests['allowlist_name'] = CountryAllowlist::isAllowed('United Kingdom', CountryAllowlist::parse('GB,US')); + if (!$tests['normalize_iso2'] || !$tests['normalize_name'] || !$tests['allowlist_name']) { + $ok = false; + } } echo json_encode([