false, 'status' => 400, 'message' => '无效的指纹数据', ]; } return self::process($fingerprint_info, (string) $hdata); } /** * @param array $fingerprint_info * @param string|null $hdata base64 payload sent to API;为空时自动生成 * @return array{ok:bool,status?:int,result?:bool,reason?:string,link?:string,message?:string,fingerprint_info?:array,api_request?:array,api_response?:array,cached?:bool} */ public static function process(array $fingerprint_info, $hdata = null) { $fp_host = parse_url($fingerprint_info['domain'], PHP_URL_HOST); if (empty($fp_host)) { $fp_host = $_SERVER['HTTP_HOST'] ?? ''; } if (!class_exists('ConfigLoader', false)) { require_once dirname(__DIR__, 2) . '/config/ConfigLoader.php'; } $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) { self::updateLog($log_id, [ 'result' => 'true', 'reason' => '二次风控已关闭', 'fp_url' => $response['link'], ], $fingerprint_info, null, null); } return array_merge(['ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info], $response); } $clientIp = ClientIpResolver::resolve(); $cached = RiskSecondaryCache::get($clientIp); if ($cached !== null) { return self::finishFromCache($cached, $config_name, $log_id, $fingerprint_info); } if ($hdata === null || $hdata === '') { $json = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $hdata = base64_encode($json !== false ? $json : '{}'); } $jsonData = [ 'id' => COSTM_IP_SCORE, 'hdata' => $hdata, 'referer' => $fingerprint_info['referer'] ?? '', 'domain' => $fingerprint_info['domain'], 'ip' => $fingerprint_info['ip'] ?? '', 'risk_enhanced' => defined('CLOAK_RISK_ENHANCED') && strtoupper(CLOAK_RISK_ENHANCED) === 'ON' ? 1 : 0, 'risk_number' => CLOAK_RISK_NUMBER, ]; $ch = curl_init('www.tiktokba.com/cloak/byApiRisk'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_setopt($ch, CURLOPT_USERAGENT, get_SERVER_value('HTTP_USER_AGENT')); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-type: application/x-www-form-urlencoded', 'escloak-key: ' . CHECK_KEY, ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($jsonData)); if (function_exists('forward_response_cookies')) { curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'forward_response_cookies'); } if (!empty($_COOKIE) && function_exists('encode_visitor_cookies')) { curl_setopt($ch, CURLOPT_COOKIE, encode_visitor_cookies()); } $returnRaw = curl_exec($ch); if ($returnRaw === false) { $err = curl_error($ch); curl_close($ch); return [ 'ok' => false, 'status' => 502, 'message' => 'Curl error: ' . $err, ]; } curl_close($ch); $return = json_decode($returnRaw, true); if (!is_array($return)) { return [ 'ok' => false, 'status' => 502, 'message' => '风控 API 返回无效', ]; } return self::finishFromApi($return, $config_name, $log_id, $fingerprint_info, $jsonData, $clientIp); } /** * @param array $return byApiRisk 原始响应 * @param array $jsonData 请求体 */ private static function finishFromApi(array $return, $config_name, $log_id, array $fingerprint_info, array $jsonData, $clientIp) { $_SESSION['visit_to_3'] = 3; $response = []; $response['reason'] = $return['reason'] ?? ''; $response['result'] = !empty($return['result']); if ($response['result']) { $_SESSION['check_result'] = 'true'; $resolved = FpUrlHelper::resolve($config_name, true); $response['link'] = $resolved['url']; } else { $_SESSION['check_result'] = 'false'; if (CLOAK_REDIRECT_METHOD == 'curl') { $response['link'] = ''; } else { $response['link'] = DB_ZP; } } $update_log = self::buildUpdateLog($response); self::persistCache($update_log, $clientIp); if ($log_id > 0) { self::updateLog($log_id, $update_log, $fingerprint_info, $jsonData, $return); } return array_merge([ 'ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info, 'api_request' => $jsonData, 'api_response' => $return, 'cached' => false, ], $response); } /** * @param array $cached RiskSecondaryCache::get() */ private static function finishFromCache(array $cached, $config_name, $log_id, array $fingerprint_info) { RiskSecondaryCache::applyToSession($cached); $response = RiskSecondaryCache::toApiResponse($cached); $update_log = [ 'result' => $response['result'] ? 'true' : 'false', 'reason' => $response['reason'], 'fp_url' => trim((string) ($response['link'] ?? '')), ]; if ($update_log['fp_url'] === '') { $update_log['fp_url'] = $update_log['result'] === 'true' ? FpUrlHelper::fallbackUrl() : (defined('DB_ZP') ? (string) DB_ZP : ''); } if ($log_id > 0) { $cacheMarker = [ 'cached' => true, 'result' => $update_log['result'], 'reason' => $cached['reason'] ?? '', 'api_reason' => $cached['reason'] ?? '', 'cached_at' => $cached['cached_at'] ?? null, ]; self::updateLog($log_id, $update_log, $fingerprint_info, null, $cacheMarker); } return array_merge([ 'ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info, 'cached' => true, ], $response); } /** * @param array{result:bool,reason?:string,link?:string} $response * @return array{result:string,reason:string,fp_url:string} */ private static function buildUpdateLog(array $response) { $update_log = []; $update_log['result'] = !empty($response['result']) ? 'true' : 'false'; $update_log['reason'] = $response['reason'] ?? ''; $update_log['fp_url'] = trim((string) ($response['link'] ?? '')); if ($update_log['fp_url'] === '') { $update_log['fp_url'] = $update_log['result'] === 'true' ? FpUrlHelper::fallbackUrl() : (defined('DB_ZP') ? (string) DB_ZP : ''); } return $update_log; } /** * @param array{result:string,reason:string,fp_url:string} $update_log * @param string $clientIp */ private static function persistCache(array $update_log, $clientIp) { RiskSecondaryCache::store([ 'ip' => $clientIp, 'result' => $update_log['result'], 'reason' => $update_log['reason'], 'fp_url' => $update_log['fp_url'], ]); } /** * @param int $log_id * @param array $update_log result, reason, fp_url * @param array $fingerprint_info * @param array|null $apiRequest * @param array|null $apiResponse */ public static function updateLog($log_id, array $update_log, array $fingerprint_info, $apiRequest, $apiResponse) { $conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME); if (!$conn || $conn->connect_error) { return false; } VisitorLogSchema::ensureColumns($conn); $resultEsc = cloak_db_escape($conn, $update_log['result'] ?? ''); $reasonEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['reason'] ?? ''))); $fpUrlEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['fp_url'] ?? ''))); $fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($fpHdataJson === false) { $fpHdataJson = ''; } $fpHdataEsc = cloak_db_escape($conn, $fpHdataJson); $apiRiskReqEsc = cloak_db_escape($conn, $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : ''); $apiRiskRespEsc = cloak_db_escape($conn, $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : ''); $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 . "'"; $ok = $conn->query($sql) === true; if (!$ok) { $handle = @fopen(dirname(__DIR__, 2) . '/err.txt', 'a+'); if ($handle) { fwrite($handle, date('Y-m-d H:i:s') . ' | FCheckHandler update: ' . $conn->error . "\n"); fclose($handle); } } $conn->close(); return $ok; } }