日志升级与缓存修复最终版
This commit is contained in:
Executable
+272
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/ClientIpResolver.php';
|
||||
require_once __DIR__ . '/GeoIpCountryCache.php';
|
||||
require_once __DIR__ . '/GeoIpCountryResolver.php';
|
||||
require_once __DIR__ . '/FpUrlHelper.php';
|
||||
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||
|
||||
/**
|
||||
* 客户端 Session(PHPSESSID)统一判定缓存:byApi 阶段缓存 + byApiRisk 最终缓存。
|
||||
*/
|
||||
class CloakJudgmentCache
|
||||
{
|
||||
const SESSION_KEY = 'cloak_judgment_cache';
|
||||
const STAGE_BYAPI = 'byapi';
|
||||
const STAGE_FINAL = 'final';
|
||||
|
||||
public static function hasSession()
|
||||
{
|
||||
return !empty($_SESSION[self::SESSION_KEY])
|
||||
&& is_array($_SESSION[self::SESSION_KEY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $currentIp
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get($currentIp = null)
|
||||
{
|
||||
if (!self::hasSession()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $_SESSION[self::SESSION_KEY];
|
||||
if (!self::isConfigValid($cached)) {
|
||||
self::clear();
|
||||
$_SESSION['visit_to_3'] = '1';
|
||||
return null;
|
||||
}
|
||||
|
||||
$ip = ClientIpResolver::normalizeIp(
|
||||
$currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve()
|
||||
);
|
||||
$cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? '');
|
||||
|
||||
if ($ip !== null && $cachedIp !== null && $ip !== $cachedIp) {
|
||||
self::refreshCountryForCurrentIp($ip);
|
||||
$cached = $_SESSION[self::SESSION_KEY];
|
||||
}
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二次风控关闭(CLOAK_RISK_NUMBER=0 等)时,byApi 阶段缓存即视为最终缓存。
|
||||
*
|
||||
* @param array $cached
|
||||
*/
|
||||
public static function isEffectivelyFinal(array $cached)
|
||||
{
|
||||
$stage = (string) ($cached['stage'] ?? '');
|
||||
if ($stage === self::STAGE_FINAL) {
|
||||
return true;
|
||||
}
|
||||
if ($stage === self::STAGE_BYAPI && !RiskSecondaryGate::isEnabled()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅返回最终缓存(可短路全流程)。
|
||||
*
|
||||
* @param string|null $currentIp
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getFinal($currentIp = null)
|
||||
{
|
||||
$cached = self::get($currentIp);
|
||||
if ($cached === null || !self::isEffectivelyFinal($cached)) {
|
||||
return null;
|
||||
}
|
||||
return $cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $cached
|
||||
*/
|
||||
public static function isFinal($cached = null)
|
||||
{
|
||||
if ($cached === null) {
|
||||
$cached = self::get();
|
||||
}
|
||||
return is_array($cached) && self::isEffectivelyFinal($cached);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|null $cached
|
||||
*/
|
||||
public static function isPartial($cached = null)
|
||||
{
|
||||
if ($cached === null) {
|
||||
$cached = self::get();
|
||||
}
|
||||
if (!is_array($cached) || ($cached['stage'] ?? '') !== self::STAGE_BYAPI) {
|
||||
return false;
|
||||
}
|
||||
return RiskSecondaryGate::isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* byApi 完成后写入;按风险系数决定 stage。
|
||||
* CLOAK_RISK_NUMBER=0(或未启用二次风控)时,byApi 结果直接记为 final。
|
||||
*
|
||||
* @param array{result:string,reason?:string,ip?:string,country?:string,fp_url?:string} $payload
|
||||
*/
|
||||
public static function storeFromByApi(array $payload)
|
||||
{
|
||||
$result = ($payload['result'] ?? '') === 'true' ? 'true' : 'false';
|
||||
$stage = self::STAGE_FINAL;
|
||||
if ($result === 'true' && RiskSecondaryGate::isEnabled()) {
|
||||
$stage = self::STAGE_BYAPI;
|
||||
}
|
||||
self::writeCache($stage, $payload);
|
||||
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* byApiRisk 完成后写入最终缓存。
|
||||
*
|
||||
* @param array{result:string,reason?:string,fp_url?:string,ip?:string,country?:string} $payload
|
||||
*/
|
||||
public static function storeFromByApiRisk(array $payload)
|
||||
{
|
||||
self::writeCache(self::STAGE_FINAL, $payload);
|
||||
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cached
|
||||
*/
|
||||
public static function applyToSession(array $cached)
|
||||
{
|
||||
$result = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
|
||||
$_SESSION['check_result'] = $result;
|
||||
$_SESSION['visit_to_2'] = '2';
|
||||
|
||||
if (self::isEffectivelyFinal($cached)) {
|
||||
$_SESSION['visit_to_3'] = '3';
|
||||
} else {
|
||||
$_SESSION['visit_to_3'] = '1';
|
||||
}
|
||||
|
||||
if (!empty($cached['ip'])) {
|
||||
$_SESSION['gcu_ip'] = $cached['ip'];
|
||||
}
|
||||
if (array_key_exists('country', $cached)) {
|
||||
$_SESSION['gcu_country'] = (string) $cached['country'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $v_info visitorInfo
|
||||
* @param array $cached
|
||||
*/
|
||||
public static function applyToVisitorInfo($v_info, array $cached)
|
||||
{
|
||||
if (!is_object($v_info)) {
|
||||
return;
|
||||
}
|
||||
if (!empty($cached['ip'])) {
|
||||
$v_info->v_ip = $cached['ip'];
|
||||
}
|
||||
if (array_key_exists('country', $cached) && (string) $cached['country'] !== '') {
|
||||
$v_info->v_country = (string) $cached['country'];
|
||||
}
|
||||
}
|
||||
|
||||
public static function clear()
|
||||
{
|
||||
unset($_SESSION[self::SESSION_KEY]);
|
||||
}
|
||||
|
||||
public static function hitReason()
|
||||
{
|
||||
return '缓存';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cached
|
||||
* @return array{result:bool,reason:string,link:string}
|
||||
*/
|
||||
public static function toApiResponse(array $cached)
|
||||
{
|
||||
$passed = ($cached['result'] ?? '') === 'true';
|
||||
$link = (string) ($cached['fp_url'] ?? '');
|
||||
|
||||
if ($passed && $link === '') {
|
||||
$link = FpUrlHelper::fallbackUrl();
|
||||
}
|
||||
if (!$passed && $link === '' && defined('DB_ZP')) {
|
||||
$link = (string) DB_ZP;
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => $passed,
|
||||
'reason' => self::hitReason(),
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $stage
|
||||
* @param array $payload
|
||||
*/
|
||||
private static function writeCache($stage, array $payload)
|
||||
{
|
||||
$ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
|
||||
if ($ip === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$country = (string) ($payload['country'] ?? '');
|
||||
if ($country === '' && !empty($_SESSION['gcu_country'])) {
|
||||
$country = (string) $_SESSION['gcu_country'];
|
||||
}
|
||||
if ($country === '') {
|
||||
$country = (string) (GeoIpCountryResolver::resolve($ip) ?? '');
|
||||
}
|
||||
|
||||
$_SESSION[self::SESSION_KEY] = [
|
||||
'stage' => $stage,
|
||||
'ip' => $ip,
|
||||
'country' => $country,
|
||||
'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false',
|
||||
'reason' => (string) ($payload['reason'] ?? ''),
|
||||
'fp_url' => (string) ($payload['fp_url'] ?? ''),
|
||||
'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0,
|
||||
'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '',
|
||||
'cached_at' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $currentIp
|
||||
*/
|
||||
private static function refreshCountryForCurrentIp($currentIp)
|
||||
{
|
||||
if (!self::hasSession()) {
|
||||
return;
|
||||
}
|
||||
$cached = $_SESSION[self::SESSION_KEY];
|
||||
$cached['ip'] = $currentIp;
|
||||
$cached['country'] = (string) (GeoIpCountryResolver::resolve($currentIp) ?? '');
|
||||
$_SESSION[self::SESSION_KEY] = $cached;
|
||||
$_SESSION['gcu_ip'] = $currentIp;
|
||||
$_SESSION['gcu_country'] = $cached['country'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $cached
|
||||
*/
|
||||
private static function isConfigValid(array $cached)
|
||||
{
|
||||
if (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) {
|
||||
return false;
|
||||
}
|
||||
if (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Regular → Executable
+13
-10
@@ -18,7 +18,7 @@ class CloakSession
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'domain' => $domain,
|
||||
'domain' => ltrim($domain, '.'),
|
||||
'secure' => function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
@@ -27,7 +27,7 @@ class CloakSession
|
||||
session_set_cookie_params(
|
||||
0,
|
||||
'/',
|
||||
$domain,
|
||||
ltrim($domain, '.'),
|
||||
function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
||||
true
|
||||
);
|
||||
@@ -80,7 +80,13 @@ class CloakFcheckCors
|
||||
return false;
|
||||
}
|
||||
|
||||
$requestHost = (string) ($_SERVER['HTTP_HOST'] ?? '');
|
||||
$requestHost = function_exists('cloak_normalize_request_host')
|
||||
? cloak_normalize_request_host($_SERVER['HTTP_HOST'] ?? '')
|
||||
: strtolower(trim((string) ($_SERVER['HTTP_HOST'] ?? '')));
|
||||
$originHost = function_exists('cloak_normalize_request_host')
|
||||
? cloak_normalize_request_host($originHost)
|
||||
: strtolower(trim($originHost));
|
||||
|
||||
if ($requestHost !== '' && strcasecmp($originHost, $requestHost) === 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -103,18 +109,15 @@ class CloakFcheckCors
|
||||
*/
|
||||
private static function registrableSuffix($host)
|
||||
{
|
||||
if (function_exists('cloak_registrable_domain')) {
|
||||
return cloak_registrable_domain($host);
|
||||
}
|
||||
|
||||
$host = strtolower(trim($host));
|
||||
if ($host === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (function_exists('cloak_fingerprint_cookie_domain')) {
|
||||
$cookieDomain = ltrim(cloak_fingerprint_cookie_domain(), '.');
|
||||
if ($cookieDomain !== '' && self::hostMatchesSuffix($host, $cookieDomain)) {
|
||||
return $cookieDomain;
|
||||
}
|
||||
}
|
||||
|
||||
$parts = explode('.', $host);
|
||||
if (count($parts) < 2) {
|
||||
return $host;
|
||||
|
||||
Regular → Executable
+47
-43
@@ -6,7 +6,11 @@ require_once dirname(__DIR__) . '/DbHelper.php';
|
||||
require_once __DIR__ . '/FpUrlHelper.php';
|
||||
require_once __DIR__ . '/VisitorApiAudit.php';
|
||||
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||
require_once __DIR__ . '/CloakJudgmentCache.php';
|
||||
require_once __DIR__ . '/RiskSecondaryCache.php';
|
||||
require_once __DIR__ . '/VisitorVisitContext.php';
|
||||
require_once __DIR__ . '/ReasonHelper.php';
|
||||
require_once __DIR__ . '/VisitorRepository.php';
|
||||
require_once __DIR__ . '/VisitorLogSchema.php';
|
||||
require_once __DIR__ . '/ClientIpResolver.php';
|
||||
|
||||
@@ -46,6 +50,9 @@ class FCheckHandler
|
||||
}
|
||||
$config_name = ConfigLoader::loadByHost($fp_host);
|
||||
$log_id = (int) ($fingerprint_info['log_id'] ?? 0);
|
||||
if ($log_id <= 0) {
|
||||
$log_id = VisitorVisitContext::currentLogId();
|
||||
}
|
||||
|
||||
if (!RiskSecondaryGate::isEnabled()) {
|
||||
$_SESSION['visit_to_3'] = 3;
|
||||
@@ -62,9 +69,9 @@ class FCheckHandler
|
||||
if ($log_id > 0) {
|
||||
self::updateLog($log_id, [
|
||||
'result' => 'true',
|
||||
'reason' => '二次风控已关闭',
|
||||
'reason' => '',
|
||||
'fp_url' => $response['link'],
|
||||
], $fingerprint_info, null, null);
|
||||
], $fingerprint_info, null, null, false);
|
||||
}
|
||||
|
||||
return array_merge(['ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info], $response);
|
||||
@@ -163,7 +170,7 @@ class FCheckHandler
|
||||
self::persistCache($update_log, $clientIp);
|
||||
|
||||
if ($log_id > 0) {
|
||||
self::updateLog($log_id, $update_log, $fingerprint_info, $jsonData, $return);
|
||||
self::updateLog($log_id, $update_log, $fingerprint_info, $jsonData, $return, true);
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
@@ -186,7 +193,7 @@ class FCheckHandler
|
||||
|
||||
$update_log = [
|
||||
'result' => $response['result'] ? 'true' : 'false',
|
||||
'reason' => $response['reason'],
|
||||
'reason' => cloak_cache_hit_log_reason($update_log['result']),
|
||||
'fp_url' => trim((string) ($response['link'] ?? '')),
|
||||
];
|
||||
if ($update_log['fp_url'] === '') {
|
||||
@@ -196,14 +203,7 @@ class FCheckHandler
|
||||
}
|
||||
|
||||
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);
|
||||
self::updateLog($log_id, $update_log, $fingerprint_info, null, null, false);
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
@@ -222,7 +222,10 @@ class FCheckHandler
|
||||
{
|
||||
$update_log = [];
|
||||
$update_log['result'] = !empty($response['result']) ? 'true' : 'false';
|
||||
$update_log['reason'] = $response['reason'] ?? '';
|
||||
$update_log['reason'] = cloak_normalize_log_reason(
|
||||
$update_log['result'],
|
||||
$response['reason'] ?? ''
|
||||
);
|
||||
$update_log['fp_url'] = trim((string) ($response['link'] ?? ''));
|
||||
if ($update_log['fp_url'] === '') {
|
||||
$update_log['fp_url'] = $update_log['result'] === 'true'
|
||||
@@ -239,11 +242,12 @@ class FCheckHandler
|
||||
*/
|
||||
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'],
|
||||
CloakJudgmentCache::storeFromByApiRisk([
|
||||
'ip' => $clientIp,
|
||||
'country' => (string) ($_SESSION['gcu_country'] ?? ''),
|
||||
'result' => $update_log['result'],
|
||||
'reason' => $update_log['reason'],
|
||||
'fp_url' => $update_log['fp_url'],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -253,36 +257,36 @@ class FCheckHandler
|
||||
* @param array $fingerprint_info
|
||||
* @param array|null $apiRequest
|
||||
* @param array|null $apiResponse
|
||||
* @param bool $includeFpAndApi 缓存路径不写 fp_hdata / api_risk_*
|
||||
*/
|
||||
public static function updateLog($log_id, array $update_log, array $fingerprint_info, $apiRequest, $apiResponse)
|
||||
public static function updateLog($log_id, array $update_log, array $fingerprint_info, $apiRequest, $apiResponse, $includeFpAndApi = true)
|
||||
{
|
||||
$conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
|
||||
if (!$conn || $conn->connect_error) {
|
||||
return false;
|
||||
}
|
||||
VisitorLogSchema::ensureColumns($conn);
|
||||
$payload = [
|
||||
'result' => (string) ($update_log['result'] ?? ''),
|
||||
'reason' => cloak_normalize_log_reason(
|
||||
$update_log['result'] ?? '',
|
||||
$update_log['reason'] ?? ''
|
||||
),
|
||||
'fp_url' => (string) ($update_log['fp_url'] ?? ''),
|
||||
];
|
||||
|
||||
$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);
|
||||
if ($includeFpAndApi) {
|
||||
$fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($fpHdataJson === false) {
|
||||
$fpHdataJson = '';
|
||||
}
|
||||
$payload['fp_hdata'] = $fpHdataJson;
|
||||
$payload['api_risk_request'] = $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : '';
|
||||
$payload['api_risk_response'] = $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : '';
|
||||
}
|
||||
$conn->close();
|
||||
|
||||
$hadForceSync = !empty($GLOBALS['__cloak_force_sync_log']);
|
||||
$GLOBALS['__cloak_force_sync_log'] = true;
|
||||
$ok = cloak_update_log_db((int) $log_id, $payload, ['skip_enrich' => true]) !== false;
|
||||
if (!$hadForceSync) {
|
||||
unset($GLOBALS['__cloak_force_sync_log']);
|
||||
}
|
||||
|
||||
return $ok;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ require_once __DIR__ . '/../FpUrlHelper.php';
|
||||
class FpPageRenderer
|
||||
{
|
||||
/**
|
||||
* @param array $ctx 必须包含 logs 数组
|
||||
* @param array $ctx 必须包含 logs 数组;可选 skip_log
|
||||
*/
|
||||
public static function render(array $ctx)
|
||||
{
|
||||
@@ -17,10 +17,12 @@ class FpPageRenderer
|
||||
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
||||
$cong_name = str_replace('.php', '', $my_file_name);
|
||||
|
||||
$resolved = FpUrlHelper::resolve($cong_name, true);
|
||||
$fp_url = $resolved['url'];
|
||||
$resolved = FpUrlHelper::resolve($cong_name, true);
|
||||
$fp_url = $resolved['url'];
|
||||
$logs['fp_url'] = $fp_url;
|
||||
write_log_db($logs);
|
||||
if (empty($ctx['skip_log'])) {
|
||||
write_log_db($logs);
|
||||
}
|
||||
|
||||
$show_content = CLOAK_SHOW_CONTENT;
|
||||
if ($show_content == 'curl') {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../WhitelistGate.php';
|
||||
require_once __DIR__ . '/../CloakJudgmentCache.php';
|
||||
require_once __DIR__ . '/../RiskSecondaryGate.php';
|
||||
|
||||
/**
|
||||
* 流量判定主流水线
|
||||
*
|
||||
* 阶段顺序(不可调换):
|
||||
* 1. Session 快速路径 → 2. GeoIP 国家 → 3. 黑白名单 → 4. 广告来源 → 5. 临时链接 → 6. API/指纹
|
||||
* 优先级(高 → 低):
|
||||
* 1. 白名单链接参数 WHITE_PARAMS
|
||||
* 2. 白名单 IP
|
||||
* 3. 黑名单
|
||||
* 4. 屏蔽模式:安全页(zp) / 真实页(fp)
|
||||
* 5. 正常屏蔽(ip_check):Session 最终缓存 > 阶段缓存续跑 > 完整判定
|
||||
*/
|
||||
class CheckPipeline
|
||||
{
|
||||
/**
|
||||
* 在调用方(ip_check 顶层)作用域执行完整判定链。
|
||||
* 依赖全局:$v_info, $config_name, $__cloak_debug_on, $reason
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function run()
|
||||
@@ -23,15 +28,64 @@ class CheckPipeline
|
||||
|
||||
CloakPipelineTimer::init();
|
||||
|
||||
CloakPipelineTimer::stage('Session快速路径');
|
||||
if (self::resolveSessionFastPath()) {
|
||||
CloakPipelineTimer::stage('白名单链接参数');
|
||||
if (self::resolveWhitelistParamsFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_whitelist_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('白名单IP');
|
||||
if (self::resolveWhitelistIpFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_whitelist_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('黑名单');
|
||||
if (self::resolveBlacklistFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_blacklist_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('安全页模式');
|
||||
if (self::resolveZpFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_zp_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('真实页模式');
|
||||
if (self::resolveFpFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_fp_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('客户端缓存快速路径');
|
||||
if (self::resolveClientCacheFastPath()) {
|
||||
include __DIR__ . '/stages/resolve_session_fast_path_hit.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('客户端阶段缓存续跑');
|
||||
if (self::resolvePartialCacheResume()) {
|
||||
include __DIR__ . '/stages/run_main_api_fingerprint.inc.php';
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, 'Session 快速路径', 'skip', '未命中缓存,进入完整判定流程(DEBUG 已重置 visit_to_*)');
|
||||
cloak_dbg_step(__LINE__, '完整判定', 'info', '未命中早期快速路径,进入完整判定');
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('黑白名单');
|
||||
include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php';
|
||||
if (!empty($_SESSION['check_result'])) {
|
||||
CloakPipelineTimer::finish();
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('GeoIP国家判定');
|
||||
@@ -41,8 +95,6 @@ class CheckPipeline
|
||||
return;
|
||||
}
|
||||
|
||||
CloakPipelineTimer::stage('黑白名单');
|
||||
include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php';
|
||||
CloakPipelineTimer::stage('广告来源限制');
|
||||
include __DIR__ . '/stages/run_ad_source_guard.inc.php';
|
||||
CloakPipelineTimer::stage('临时链接参数');
|
||||
@@ -51,20 +103,108 @@ class CheckPipeline
|
||||
CloakPipelineTimer::finish();
|
||||
}
|
||||
|
||||
public static function resolveWhitelistParamsFastPath()
|
||||
{
|
||||
return WhitelistGate::matchesWhitelistParams();
|
||||
}
|
||||
|
||||
public static function resolveWhitelistIpFastPath()
|
||||
{
|
||||
global $v_info;
|
||||
|
||||
if (WhitelistGate::matchesWhitelistParams()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return WhitelistGate::matchesWhitelistIp(WhitelistGate::resolveVisitorIp($v_info));
|
||||
}
|
||||
|
||||
public static function resolveBlacklistFastPath()
|
||||
{
|
||||
global $v_info;
|
||||
|
||||
if (!defined('BLACKLIST_GROUP_ID') || (int) BLACKLIST_GROUP_ID <= 0) {
|
||||
return false;
|
||||
}
|
||||
$blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID);
|
||||
|
||||
return ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
|
||||
}
|
||||
|
||||
public static function resolveClientCacheFastPath()
|
||||
{
|
||||
global $__cloak_debug_on, $v_info;
|
||||
|
||||
if ($__cloak_debug_on || SHOW_SITE_MODE_SWITCH !== 'ip_check') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cached = CloakJudgmentCache::getFinal();
|
||||
if ($cached === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CloakJudgmentCache::applyToSession($cached);
|
||||
CloakJudgmentCache::applyToVisitorInfo($v_info, $cached);
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
CloakDebugPage::setFlag('session_cached', true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function resolvePartialCacheResume()
|
||||
{
|
||||
global $__cloak_debug_on, $v_info, $reason;
|
||||
|
||||
if ($__cloak_debug_on || SHOW_SITE_MODE_SWITCH !== 'ip_check') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cached = CloakJudgmentCache::get();
|
||||
if (!CloakJudgmentCache::isPartial($cached)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CloakJudgmentCache::applyToSession($cached);
|
||||
CloakJudgmentCache::applyToVisitorInfo($v_info, $cached);
|
||||
$_SESSION['visit_to_2'] = '2';
|
||||
|
||||
if ($reason === '') {
|
||||
$reason = 'Session byApi阶段缓存:待二次风控';
|
||||
}
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '客户端阶段缓存续跑', 'pass', '命中 byapi 阶段缓存,跳过 GeoIP/Guard/byApi');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否命中 session 缓存结果(true = 跳过完整判定)
|
||||
*
|
||||
* @return bool
|
||||
* @deprecated
|
||||
*/
|
||||
public static function resolveSessionFastPath()
|
||||
{
|
||||
global $__cloak_debug_on;
|
||||
return self::resolveClientCacheFastPath();
|
||||
}
|
||||
|
||||
return (
|
||||
!$__cloak_debug_on
|
||||
&& SHOW_SITE_MODE_SWITCH === 'ip_check'
|
||||
&& isset($_SESSION['check_result'])
|
||||
&& ($_SESSION['check_result'] === 'true' || $_SESSION['check_result'] === 'false')
|
||||
);
|
||||
/**
|
||||
* @deprecated 使用 resolveWhitelistParamsFastPath / resolveWhitelistIpFastPath
|
||||
*/
|
||||
public static function resolveWhitelistFastPath()
|
||||
{
|
||||
return self::resolveWhitelistParamsFastPath() || self::resolveWhitelistIpFastPath();
|
||||
}
|
||||
|
||||
public static function resolveZpFastPath()
|
||||
{
|
||||
return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'zp';
|
||||
}
|
||||
|
||||
public static function resolveFpFastPath()
|
||||
{
|
||||
return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../WhitelistGate.php';
|
||||
require_once __DIR__ . '/../Page/FpPageRenderer.php';
|
||||
require_once __DIR__ . '/../Page/RiskPageRenderer.php';
|
||||
require_once __DIR__ . '/../FpUrlHelper.php';
|
||||
require_once __DIR__ . '/../RiskSecondaryGate.php';
|
||||
require_once __DIR__ . '/../RiskSecondaryCache.php';
|
||||
require_once __DIR__ . '/../RiskSecondarySession.php';
|
||||
require_once __DIR__ . '/../ClientIpResolver.php';
|
||||
|
||||
/**
|
||||
@@ -18,6 +20,12 @@ class RouteResolver
|
||||
{
|
||||
extract($ctx, EXTR_SKIP);
|
||||
|
||||
// 白名单优先于正品模式:跳过二次风控等,直达真实页
|
||||
if (!empty($_SESSION['cloak_whitelist_fast'])) {
|
||||
self::renderWhitelistFp($logs, $config_name ?? '');
|
||||
return;
|
||||
}
|
||||
|
||||
// 正品模式:始终走安全页逻辑
|
||||
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
||||
self::renderSafePage($logs, $v_info);
|
||||
@@ -28,13 +36,26 @@ class RouteResolver
|
||||
|| SHOW_SITE_MODE_SWITCH == 'fp';
|
||||
|
||||
if ($showFp) {
|
||||
self::renderFpOrRisk($logs, $v_info);
|
||||
self::renderFpOrRisk($logs, $v_info, $config_name ?? '');
|
||||
return;
|
||||
}
|
||||
|
||||
self::renderSafePage($logs, $v_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 白名单快速路径:写日志并输出真实页,不触发二次风控 / API
|
||||
*
|
||||
* @param array $logs
|
||||
* @param string $config_name
|
||||
*/
|
||||
private static function renderWhitelistFp(array $logs, $config_name = '')
|
||||
{
|
||||
$logs['result'] = 'true';
|
||||
$logs['reason'] = '';
|
||||
self::renderFpPage($logs, $config_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $logs
|
||||
*/
|
||||
@@ -69,7 +90,7 @@ class RouteResolver
|
||||
}
|
||||
}
|
||||
|
||||
private static function renderFpOrRisk(array $logs, $v_info)
|
||||
private static function renderFpOrRisk(array $logs, $v_info, $config_name = '')
|
||||
{
|
||||
if (AUTO_BLACK == 'ON') {
|
||||
$times = howmanytimesbyip($v_info->v_ip);
|
||||
@@ -81,21 +102,22 @@ class RouteResolver
|
||||
$_SESSION['visit_to_3'] = '1';
|
||||
}
|
||||
|
||||
if (RiskSecondaryGate::isEnabled()) {
|
||||
if (RiskSecondaryGate::isEnabledForRequest()) {
|
||||
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
||||
if ($cached !== null) {
|
||||
self::renderFromRiskCache($logs, $v_info, $cached);
|
||||
self::renderFromRiskCache($logs, $v_info, $cached, $config_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (($_SESSION['visit_to_3'] ?? '1') != '3'
|
||||
&& RiskSecondaryGate::isEnabled()) {
|
||||
&& RiskSecondaryGate::isEnabledForRequest()) {
|
||||
// 二次风控:先写 wait 状态,f_check 完成后再更新 result / fp_url(避免 true+空跳转)
|
||||
$logs['result'] = 'wait';
|
||||
$logs['reason'] = '二次风控检测中';
|
||||
$logs['fp_url'] = '';
|
||||
$log_id = write_log_db($logs);
|
||||
RiskSecondarySession::markWaitLog($log_id);
|
||||
$riskCtx = [
|
||||
'logs' => $logs,
|
||||
'v_info' => $v_info,
|
||||
@@ -111,22 +133,36 @@ class RouteResolver
|
||||
|
||||
$logs['result'] = 'true';
|
||||
$logs['reason'] = '';
|
||||
FpPageRenderer::render(['logs' => $logs]);
|
||||
self::renderFpPage($logs, $config_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $logs
|
||||
* @param string $config_name
|
||||
*/
|
||||
private static function renderFpPage(array $logs, $config_name = '')
|
||||
{
|
||||
FpPageRenderer::render([
|
||||
'logs' => $logs,
|
||||
'config_name' => $config_name,
|
||||
'skip_log' => RiskSecondarySession::shouldSkipFpPageLog(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二次风控 Session 缓存命中:跳过 cloakjs / byApiRisk。
|
||||
*
|
||||
* @param array $logs
|
||||
* @param array $cached
|
||||
* @param array $logs
|
||||
* @param array $cached
|
||||
* @param string $config_name
|
||||
*/
|
||||
private static function renderFromRiskCache(array $logs, $v_info, array $cached)
|
||||
private static function renderFromRiskCache(array $logs, $v_info, array $cached, $config_name = '')
|
||||
{
|
||||
RiskSecondaryCache::applyToSession($cached);
|
||||
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
||||
|
||||
$logs['result'] = $apiResponse['result'] ? 'true' : 'false';
|
||||
$logs['reason'] = $apiResponse['reason'];
|
||||
$logs['reason'] = cloak_cache_hit_log_reason($logs['result']);
|
||||
$logs['fp_url'] = trim((string) ($apiResponse['link'] ?? ''));
|
||||
if ($logs['fp_url'] === '') {
|
||||
$logs['fp_url'] = $logs['result'] === 'true'
|
||||
@@ -139,7 +175,7 @@ class RouteResolver
|
||||
return;
|
||||
}
|
||||
|
||||
FpPageRenderer::render(['logs' => $logs]);
|
||||
self::renderFpPage($logs, $config_name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* 仅用本地 MaxMind 解析国家并写入访客信息/Session(不做国家允许列表或其它判定)
|
||||
*
|
||||
* 依赖全局:$v_info, $__cloak_debug_on
|
||||
*/
|
||||
$ip = ClientIpResolver::resolve();
|
||||
if ($ip !== '' && $ip !== '0.0.0.0') {
|
||||
$v_info->v_ip = $ip;
|
||||
$_SESSION['gcu_ip'] = $ip;
|
||||
}
|
||||
|
||||
if (GeoIpCountryResolver::isAvailable()) {
|
||||
$geoMeta = GeoIpCountryResolver::resolveWithMeta($ip);
|
||||
$countryCode = $geoMeta['code'];
|
||||
$geoSource = $geoMeta['source'];
|
||||
if ($countryCode !== null && $countryCode !== '') {
|
||||
$v_info->v_country = $countryCode;
|
||||
$_SESSION['gcu_country'] = $countryCode;
|
||||
}
|
||||
if (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||
cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'log', '仅记录国家,不参与判定', [
|
||||
'ip' => $ip,
|
||||
'country' => $countryCode,
|
||||
'geo_source' => $geoSource,
|
||||
]);
|
||||
}
|
||||
} elseif (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||
cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'skip', '本地 GeoIP 库不可用');
|
||||
}
|
||||
@@ -1,69 +1,46 @@
|
||||
<?php
|
||||
//指定ip显示仿品(优先数据库白名单组,其次兼容旧版 SHOW_SITE_IP 逗号列表)
|
||||
if (defined('WHITELIST_GROUP_ID') && (int) WHITELIST_GROUP_ID > 0) {
|
||||
$ips_fp_ary = ip_group_list_addresses((int) WHITELIST_GROUP_ID);
|
||||
} else {
|
||||
$ips_fp_ary = array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) {
|
||||
return $v !== '';
|
||||
});
|
||||
}
|
||||
$reason = ""; // 判断理由
|
||||
|
||||
$whiteParamsMatched = false;
|
||||
if( !empty(WHITE_PARAMS) ) {
|
||||
$wparams = explode('=', WHITE_PARAMS, 2);
|
||||
|
||||
if(count($wparams) == 2) {
|
||||
$pk = $wparams[0];
|
||||
$pv = $wparams[1];
|
||||
}
|
||||
|
||||
if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) {
|
||||
$whiteParamsMatched = true;
|
||||
|
||||
// 黑白名单(完整判定链内):链接参数白名单 > IP 白名单 > 黑名单
|
||||
$reason = $reason ?? '';
|
||||
|
||||
if (WhitelistGate::matchesWhitelistParams()) {
|
||||
$_SESSION['check_result'] = 'true';
|
||||
$reason = '白名单链接参数';
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '链接参数白名单', 'pass', defined('WHITE_PARAMS') ? WHITE_PARAMS : '');
|
||||
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_params']);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (WhitelistGate::matchesWhitelistIp(WhitelistGate::resolveVisitorIp($v_info))) {
|
||||
$_SESSION['check_result'] = 'true';
|
||||
$reason = '白名单';
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, 'IP 白名单', 'pass', 'IP 在白名单组/列表中');
|
||||
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_ip']);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($__cloak_debug_on && defined('WHITE_PARAMS') && WHITE_PARAMS !== '') {
|
||||
cloak_dbg_step(__LINE__, '链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS);
|
||||
}
|
||||
|
||||
$match = false;
|
||||
if (defined('BLACKLIST_GROUP_ID') && (int) BLACKLIST_GROUP_ID > 0) {
|
||||
$blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID);
|
||||
$match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
|
||||
$match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
|
||||
}
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__,'黑名单检查', $match ? 'fail' : 'pass', $match ? 'IP 在黑名单组内' : '未命中黑名单', [
|
||||
'ip' => $v_info->v_ip,
|
||||
cloak_dbg_step(__LINE__, '黑名单检查', $match ? 'fail' : 'pass', $match ? 'IP 在黑名单组内' : '未命中黑名单', [
|
||||
'ip' => $v_info->v_ip,
|
||||
'group_id' => defined('BLACKLIST_GROUP_ID') ? BLACKLIST_GROUP_ID : 0,
|
||||
]);
|
||||
}
|
||||
if ($match) {
|
||||
$_SESSION["check_result"] = "false";
|
||||
$reason = "黑名单";
|
||||
$_SESSION['check_result'] = 'false';
|
||||
$reason = '黑名单';
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_record(__LINE__,'false', $reason, ['stage' => 'blacklist']);
|
||||
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'blacklist']);
|
||||
}
|
||||
}
|
||||
|
||||
if(in_array($v_info->v_ip, $ips_fp_ary) ) {
|
||||
$_SESSION["check_result"] = "true";
|
||||
$reason = "白名单";
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__,'IP 白名单', 'pass', 'IP 在白名单组/列表中');
|
||||
cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_ip']);
|
||||
}
|
||||
} elseif( !empty(WHITE_PARAMS) ) {
|
||||
$wparams = explode('=', WHITE_PARAMS, 2);
|
||||
if(count($wparams) == 2) {
|
||||
$pk = $wparams[0];
|
||||
$pv = $wparams[1];
|
||||
}
|
||||
if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) {
|
||||
$_SESSION["check_result"] = "true";
|
||||
$reason = "白名单链接参数";
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__,'链接参数白名单', 'pass', WHITE_PARAMS);
|
||||
cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_params']);
|
||||
}
|
||||
}
|
||||
} elseif ($__cloak_debug_on && !empty(WHITE_PARAMS)) {
|
||||
cloak_dbg_step(__LINE__,'链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* 黑名单快速路径:记录国家后直达安全页判定
|
||||
*/
|
||||
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||
|
||||
global $reason;
|
||||
|
||||
$_SESSION['check_result'] = 'false';
|
||||
$reason = '黑名单';
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '黑名单快速路径', 'fail', 'IP 在黑名单组内');
|
||||
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'blacklist_fast_path']);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* 真实页模式(fp)快速路径:跳过正常屏蔽判定与二次风控,直达真实页
|
||||
*/
|
||||
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||
|
||||
global $reason;
|
||||
|
||||
$_SESSION['check_result'] = 'true';
|
||||
$reason = '真实页模式';
|
||||
$_SESSION['visit_to_2'] = '2';
|
||||
$_SESSION['visit_to_3'] = '3';
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '真实页模式', 'pass', '跳过正常屏蔽判定链');
|
||||
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'fp_fast_path']);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* 白名单快速路径:仅 MaxMind 国家记录,跳过其余判定,直达真实页
|
||||
*/
|
||||
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||
|
||||
global $reason;
|
||||
|
||||
$_SESSION['check_result'] = 'true';
|
||||
$reason = WhitelistGate::matchReason($v_info);
|
||||
$_SESSION['visit_to_3'] = '3';
|
||||
$_SESSION['cloak_whitelist_fast'] = true;
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '白名单快速路径', 'pass', $reason);
|
||||
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_fast_path']);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* 正品模式快速路径:仅 MaxMind 国家记录,跳过其余判定,无条件安全页
|
||||
*/
|
||||
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||
|
||||
global $reason;
|
||||
|
||||
$_SESSION['check_result'] = 'false';
|
||||
$reason = '正品模式';
|
||||
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, '正品模式', 'pass', '仅记录国家,无条件安全页');
|
||||
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'zp_fast_path']);
|
||||
}
|
||||
@@ -22,6 +22,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (($_SESSION['visit_to_2'] ?? '') == '2' && class_exists('CloakJudgmentCache', false) && !CloakJudgmentCache::hasSession()) {
|
||||
$_SESSION['visit_to_2'] = '1';
|
||||
}
|
||||
|
||||
if($_SESSION['visit_to_1'] == '1' && $_SESSION['visit_to_2'] != '2') // $_SESSION['visit_to_2'] 为2时表示已经判断过所有规则。
|
||||
{
|
||||
if ($__cloak_debug_on) {
|
||||
@@ -86,7 +90,7 @@
|
||||
// 已完成指纹跳转(URL 含 time),但 Cookie 未写入:终止再次重定向
|
||||
$v_info->check_result = 'false';
|
||||
|
||||
$reason = cloak_reason_or($reason, '指纹Cookie未写入(已完成前端检测)');
|
||||
$reason = cloak_reason_or($reason, '已完成前端检测,但指纹Cookie未写入(隐私模式、广告拦截等可能阻止Cookie写入)');
|
||||
$_SESSION['check_result'] = 'false';
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__, 'JS 指纹检测', 'fail', 'URL 含 time 但指纹 Cookie 缺失,停止重定向');
|
||||
@@ -149,6 +153,14 @@
|
||||
if ($__api_return !== null) {
|
||||
$_SESSION["check_result"] = trim($v_info->check_result);
|
||||
$reason = cloak_api_reason($__api_return, $_SESSION["check_result"]);
|
||||
if (class_exists('CloakJudgmentCache', false)) {
|
||||
CloakJudgmentCache::storeFromByApi([
|
||||
'ip' => $v_info->v_ip,
|
||||
'country' => $v_info->v_country,
|
||||
'result' => $_SESSION['check_result'],
|
||||
'reason' => $reason,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$_SESSION["check_result"] = "false";
|
||||
$reason = cloak_reason_or($reason, 'API未调用:缺少访客信息(ip/Browser/Accept-Language)');
|
||||
@@ -175,15 +187,33 @@
|
||||
}
|
||||
}
|
||||
} elseif (($_SESSION['visit_to_2'] ?? '') == '2') {
|
||||
if (!empty($_SESSION["check_result"])) {
|
||||
$cached = class_exists('CloakJudgmentCache', false) ? CloakJudgmentCache::get() : null;
|
||||
if ($cached !== null) {
|
||||
CloakJudgmentCache::applyToSession($cached);
|
||||
CloakJudgmentCache::applyToVisitorInfo($v_info, $cached);
|
||||
$_SESSION['check_result'] = trim((string) ($cached['result'] ?? 'false'));
|
||||
if ($reason === '') {
|
||||
if (CloakJudgmentCache::isPartial($cached)) {
|
||||
$reason = 'Session byApi阶段缓存:待二次风控';
|
||||
} else {
|
||||
$reason = ($_SESSION['check_result'] === 'true')
|
||||
? 'Session API缓存:判定通过'
|
||||
: '缓存';
|
||||
}
|
||||
}
|
||||
} elseif (!empty($_SESSION["check_result"])) {
|
||||
if ($reason === '') {
|
||||
$reason = ($_SESSION['check_result'] === 'true')
|
||||
? 'Session API缓存:判定通过'
|
||||
: 'Session API缓存:判定拒绝';
|
||||
: '缓存';
|
||||
}
|
||||
} else {
|
||||
$_SESSION["check_result"] = 'false';
|
||||
$_SESSION['check_result'] = 'false';
|
||||
$reason = cloak_reason_or($reason, 'Session状态异常(visit_to_2=2但无缓存结果),默认安全页');
|
||||
$_SESSION['visit_to_2'] = '1';
|
||||
if (class_exists('CloakJudgmentCache', false)) {
|
||||
CloakJudgmentCache::clear();
|
||||
}
|
||||
}
|
||||
if ($__cloak_debug_on) {
|
||||
cloak_dbg_step(__LINE__,'主判定块 (API)', 'skip', 'visit_to_2=2,本块已跳过(' . $reason . ')');
|
||||
|
||||
@@ -41,14 +41,47 @@ if (!function_exists('cloak_api_reason')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_cache_hit_log_reason')) {
|
||||
/**
|
||||
* 缓存判定落库 reason:true 为空;false 固定为「缓存」。
|
||||
*
|
||||
* @param string $result
|
||||
*/
|
||||
function cloak_cache_hit_log_reason($result)
|
||||
{
|
||||
return cloak_normalize_log_reason((string) $result, (string) $result === 'true' ? '' : '缓存');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_normalize_log_reason')) {
|
||||
/**
|
||||
* 落库 reason 规范化:true 必须为空;false 必须有值;wait 保留检测中文案。
|
||||
*
|
||||
* @param string $result
|
||||
* @param mixed $reason
|
||||
*/
|
||||
function cloak_normalize_log_reason($result, $reason)
|
||||
{
|
||||
$result = (string) $result;
|
||||
$reason = trim((string) $reason);
|
||||
|
||||
if ($result === 'true') {
|
||||
return '';
|
||||
}
|
||||
if ($result === 'wait') {
|
||||
return $reason !== '' ? $reason : '二次风控检测中';
|
||||
}
|
||||
if ($reason === '') {
|
||||
return '判定拒绝(安全页)';
|
||||
}
|
||||
return $reason;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_finalize_reason')) {
|
||||
function cloak_finalize_reason($reason, $checkResult)
|
||||
{
|
||||
$reason = trim((string) $reason);
|
||||
if ($reason !== '') {
|
||||
return $reason;
|
||||
}
|
||||
$pass = ($checkResult === 'true');
|
||||
return $pass ? '判定通过(真实页)' : '判定拒绝(安全页)';
|
||||
$result = ($checkResult === 'true') ? 'true' : (($checkResult === 'false') ? 'false' : (string) $checkResult);
|
||||
return cloak_normalize_log_reason($result, $reason);
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+13
-65
@@ -1,86 +1,48 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/ClientIpResolver.php';
|
||||
require_once __DIR__ . '/FpUrlHelper.php';
|
||||
require_once __DIR__ . '/CloakJudgmentCache.php';
|
||||
|
||||
/**
|
||||
* 二次风控(byApiRisk)Session 缓存:同 Session 内复用判定结果,IP 变化时失效。
|
||||
* 二次风控缓存薄封装(委托 CloakJudgmentCache,保持既有调用点兼容)。
|
||||
*/
|
||||
class RiskSecondaryCache
|
||||
{
|
||||
const SESSION_KEY = 'cloak_risk_cache';
|
||||
const SESSION_KEY = 'cloak_judgment_cache';
|
||||
|
||||
/**
|
||||
* @param array{result:string,reason?:string,fp_url?:string,ip?:string} $payload
|
||||
* @param array{result:string,reason?:string,fp_url?:string,ip?:string,country?:string} $payload
|
||||
*/
|
||||
public static function store(array $payload)
|
||||
{
|
||||
$ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
|
||||
if ($ip === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$_SESSION[self::SESSION_KEY] = [
|
||||
'ip' => $ip,
|
||||
'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false',
|
||||
'reason' => (string) ($payload['reason'] ?? ''),
|
||||
'fp_url' => (string) ($payload['fp_url'] ?? ''),
|
||||
'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0,
|
||||
'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '',
|
||||
'cached_at' => time(),
|
||||
];
|
||||
CloakJudgmentCache::storeFromByApiRisk($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $currentIp 为空时使用 ClientIpResolver::resolve()
|
||||
* 仅返回最终缓存(partial 不视为命中)。
|
||||
*
|
||||
* @param string|null $currentIp
|
||||
* @return array|null
|
||||
*/
|
||||
public static function get($currentIp = null)
|
||||
{
|
||||
if (empty($_SESSION[self::SESSION_KEY]) || !is_array($_SESSION[self::SESSION_KEY])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $_SESSION[self::SESSION_KEY];
|
||||
$ip = ClientIpResolver::normalizeIp($currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve());
|
||||
$cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? '');
|
||||
|
||||
$invalidate = false;
|
||||
if ($ip === null || $cachedIp === null || $ip !== $cachedIp) {
|
||||
$invalidate = true;
|
||||
} elseif (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) {
|
||||
$invalidate = true;
|
||||
} elseif (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) {
|
||||
$invalidate = true;
|
||||
}
|
||||
|
||||
if ($invalidate) {
|
||||
self::clear();
|
||||
$_SESSION['visit_to_3'] = '1';
|
||||
return null;
|
||||
}
|
||||
|
||||
return $cached;
|
||||
return CloakJudgmentCache::getFinal($currentIp);
|
||||
}
|
||||
|
||||
public static function clear()
|
||||
{
|
||||
unset($_SESSION[self::SESSION_KEY]);
|
||||
CloakJudgmentCache::clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缓存同步到 visit_to_3 / check_result,供 RouteResolver 等复用。
|
||||
*
|
||||
* @param array $cached
|
||||
*/
|
||||
public static function applyToSession(array $cached)
|
||||
{
|
||||
$_SESSION['visit_to_3'] = 3;
|
||||
$_SESSION['check_result'] = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
|
||||
CloakJudgmentCache::applyToSession($cached);
|
||||
}
|
||||
|
||||
public static function hitReason()
|
||||
{
|
||||
return '二次风控缓存命中';
|
||||
return CloakJudgmentCache::hitReason();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,20 +51,6 @@ class RiskSecondaryCache
|
||||
*/
|
||||
public static function toApiResponse(array $cached)
|
||||
{
|
||||
$passed = ($cached['result'] ?? '') === 'true';
|
||||
$link = (string) ($cached['fp_url'] ?? '');
|
||||
|
||||
if ($passed && $link === '') {
|
||||
$link = FpUrlHelper::fallbackUrl();
|
||||
}
|
||||
if (!$passed && $link === '' && defined('DB_ZP')) {
|
||||
$link = (string) DB_ZP;
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => $passed,
|
||||
'reason' => self::hitReason(),
|
||||
'link' => $link,
|
||||
];
|
||||
return CloakJudgmentCache::toApiResponse($cached);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,15 @@ class RiskSecondaryGate
|
||||
$riskNumber = (int) CLOAK_RISK_NUMBER;
|
||||
return $riskNumber > 0 && $riskNumber <= 90;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前站点模式是否应执行二次风控(真实页 fp 模式永不触发)。
|
||||
*/
|
||||
public static function isEnabledForRequest()
|
||||
{
|
||||
if (defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp') {
|
||||
return false;
|
||||
}
|
||||
return self::isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/VisitorVisitContext.php';
|
||||
|
||||
/**
|
||||
* 二次风控 wait / f_check 与 visit 日志绑定(与 VisitorVisitContext 对齐)
|
||||
*/
|
||||
class RiskSecondarySession
|
||||
{
|
||||
const KEY_WAIT_LOG = 'cloak_risk_wait_log_id';
|
||||
|
||||
/**
|
||||
* @param int|null $logId
|
||||
*/
|
||||
public static function markWaitLog($logId)
|
||||
{
|
||||
$logId = (int) $logId;
|
||||
if ($logId > 0) {
|
||||
$_SESSION[self::KEY_WAIT_LOG] = $logId;
|
||||
VisitorVisitContext::bindLogId($logId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* f_check 已 finalize 后,FpPageRenderer 不再重复写库。
|
||||
*/
|
||||
public static function shouldSkipFpPageLog()
|
||||
{
|
||||
if (VisitorVisitContext::isFinalized()) {
|
||||
return true;
|
||||
}
|
||||
if (VisitorVisitContext::currentLogId() > 0 && (string) ($_SESSION['visit_to_3'] ?? '') === '3') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function clear()
|
||||
{
|
||||
unset($_SESSION[self::KEY_WAIT_LOG]);
|
||||
}
|
||||
}
|
||||
Regular → Executable
+32
-6
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/WhitelistGate.php';
|
||||
require_once __DIR__ . '/FpUrlHelper.php';
|
||||
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||
require_once __DIR__ . '/RiskSecondaryCache.php';
|
||||
require_once __DIR__ . '/RiskSecondarySession.php';
|
||||
require_once __DIR__ . '/ClientIpResolver.php';
|
||||
|
||||
/**
|
||||
@@ -17,6 +19,10 @@ class SimulationRouteResolver
|
||||
{
|
||||
extract($ctx, EXTR_SKIP);
|
||||
|
||||
if (!empty($_SESSION['cloak_whitelist_fast'])) {
|
||||
return self::whitelistFpBranch($logs, $config_name ?? '');
|
||||
}
|
||||
|
||||
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
||||
return self::safeBranch($logs, $v_info);
|
||||
}
|
||||
@@ -31,6 +37,25 @@ class SimulationRouteResolver
|
||||
return self::safeBranch($logs, $v_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $logs
|
||||
*/
|
||||
private static function whitelistFpBranch(array $logs, $config_name)
|
||||
{
|
||||
$logs['result'] = 'true';
|
||||
$logs['reason'] = '';
|
||||
$resolved = FpUrlHelper::resolve($config_name, true);
|
||||
$logs['fp_url'] = $resolved['url'] !== '' ? $resolved['url'] : FpUrlHelper::fallbackUrl();
|
||||
$log_id = write_log_db($logs);
|
||||
|
||||
return [
|
||||
'branch' => 'fp_page',
|
||||
'log_id' => $log_id,
|
||||
'logs' => $logs,
|
||||
'needs_secondary' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $logs
|
||||
*/
|
||||
@@ -67,18 +92,19 @@ class SimulationRouteResolver
|
||||
$_SESSION['visit_to_3'] = '1';
|
||||
}
|
||||
|
||||
if (RiskSecondaryGate::isEnabled()) {
|
||||
if (RiskSecondaryGate::isEnabledForRequest()) {
|
||||
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
||||
if ($cached !== null) {
|
||||
return self::fpFromRiskCache($logs, $v_info, $config_name, $cached);
|
||||
}
|
||||
}
|
||||
|
||||
if (($_SESSION['visit_to_3'] ?? '1') != '3' && RiskSecondaryGate::isEnabled()) {
|
||||
if (($_SESSION['visit_to_3'] ?? '1') != '3' && RiskSecondaryGate::isEnabledForRequest()) {
|
||||
$logs['result'] = 'wait';
|
||||
$logs['reason'] = self::mergeReason($logs['reason'] ?? '', '二次风控检测中');
|
||||
$logs['fp_url'] = '';
|
||||
$log_id = write_log_db($logs);
|
||||
RiskSecondarySession::markWaitLog($log_id);
|
||||
|
||||
return [
|
||||
'branch' => 'secondary_risk',
|
||||
@@ -89,10 +115,10 @@ class SimulationRouteResolver
|
||||
}
|
||||
|
||||
$logs['result'] = 'true';
|
||||
$logs['reason'] = $logs['reason'] ?? '';
|
||||
$logs['reason'] = '';
|
||||
$resolved = FpUrlHelper::resolve($config_name, true);
|
||||
$logs['fp_url'] = $resolved['url'] !== '' ? $resolved['url'] : FpUrlHelper::fallbackUrl();
|
||||
$log_id = write_log_db($logs);
|
||||
$log_id = RiskSecondarySession::shouldSkipFpPageLog() ? null : write_log_db($logs);
|
||||
|
||||
return [
|
||||
'branch' => 'fp_page',
|
||||
@@ -112,7 +138,7 @@ class SimulationRouteResolver
|
||||
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
||||
|
||||
$logs['result'] = $apiResponse['result'] ? 'true' : 'false';
|
||||
$logs['reason'] = self::mergeReason($logs['reason'] ?? '', $apiResponse['reason']);
|
||||
$logs['reason'] = cloak_cache_hit_log_reason($logs['result']);
|
||||
$logs['fp_url'] = trim((string) ($apiResponse['link'] ?? ''));
|
||||
if ($logs['fp_url'] === '') {
|
||||
$logs['fp_url'] = $logs['result'] === 'true'
|
||||
@@ -124,7 +150,7 @@ class SimulationRouteResolver
|
||||
return self::safeBranch($logs, $v_info);
|
||||
}
|
||||
|
||||
$log_id = write_log_db($logs);
|
||||
$log_id = RiskSecondarySession::shouldSkipFpPageLog() ? null : write_log_db($logs);
|
||||
|
||||
return [
|
||||
'branch' => 'fp_page_cache',
|
||||
|
||||
@@ -6,10 +6,20 @@ class VisitorApiAudit
|
||||
{
|
||||
const SESSION_BY_REQUEST = 'cloak_api_by_request';
|
||||
const SESSION_BY_RESPONSE = 'cloak_api_by_response';
|
||||
const SESSION_BY_CALLED = 'cloak_api_by_called';
|
||||
|
||||
public static function clearByApi()
|
||||
{
|
||||
unset($_SESSION[self::SESSION_BY_REQUEST], $_SESSION[self::SESSION_BY_RESPONSE]);
|
||||
unset(
|
||||
$_SESSION[self::SESSION_BY_REQUEST],
|
||||
$_SESSION[self::SESSION_BY_RESPONSE],
|
||||
$_SESSION[self::SESSION_BY_CALLED]
|
||||
);
|
||||
}
|
||||
|
||||
public static function wasByApiCalledThisRequest()
|
||||
{
|
||||
return !empty($_SESSION[self::SESSION_BY_CALLED]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -17,6 +27,7 @@ class VisitorApiAudit
|
||||
*/
|
||||
public static function recordByApiRequest($payload)
|
||||
{
|
||||
$_SESSION[self::SESSION_BY_CALLED] = 1;
|
||||
$_SESSION[self::SESSION_BY_REQUEST] = self::encode($payload);
|
||||
}
|
||||
|
||||
@@ -39,6 +50,9 @@ class VisitorApiAudit
|
||||
*/
|
||||
public static function mergeIntoLogs(array $logs)
|
||||
{
|
||||
if (!self::wasByApiCalledThisRequest()) {
|
||||
return $logs;
|
||||
}
|
||||
$map = [
|
||||
'api_by_request' => self::SESSION_BY_REQUEST,
|
||||
'api_by_response' => self::SESSION_BY_RESPONSE,
|
||||
|
||||
Regular → Executable
Regular → Executable
@@ -50,6 +50,9 @@ class VisitorLogWorker
|
||||
if ($type === 'enrich') {
|
||||
return self::enrich((int) ($job['log_id'] ?? 0), $job['logs'] ?? []);
|
||||
}
|
||||
if ($type === 'update') {
|
||||
return self::updateFromLogs((int) ($job['log_id'] ?? 0), $job['logs'] ?? []);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -208,6 +211,82 @@ class VisitorLogWorker
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* 续步 UPDATE:合并同一次 visit 内的日志字段(不修改 vtimes)。
|
||||
*
|
||||
* @param int $logId
|
||||
* @param array $logs
|
||||
*/
|
||||
public static function updateFromLogs($logId, array $logs)
|
||||
{
|
||||
$logId = (int) $logId;
|
||||
if ($logId <= 0) {
|
||||
return false;
|
||||
}
|
||||
$conn = self::connect();
|
||||
if (!$conn) {
|
||||
return false;
|
||||
}
|
||||
VisitorLogSchema::ensureColumns($conn);
|
||||
|
||||
$data = self::escapeLogs($conn, $logs);
|
||||
$sets = [];
|
||||
$map = [
|
||||
'country' => 'country',
|
||||
'referer' => 'v_referer',
|
||||
'client' => 'Client',
|
||||
'browser' => 'v_Browser',
|
||||
'page' => 'v_PageURL',
|
||||
'language' => 'accept_language',
|
||||
'user_agent' => 'user_agent',
|
||||
'http_referer' => 'http_referer',
|
||||
'accept_language_raw' => 'accept_language_raw',
|
||||
'judge_timing' => 'judge_timing',
|
||||
'fp_url' => 'fp_url',
|
||||
'device' => 'device',
|
||||
'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 (['result', 'reason', 'fp_url'] as $col) {
|
||||
if (array_key_exists($col, $logs)) {
|
||||
$key = array_key_exists($col, $data) ? $col : $col;
|
||||
$logKey = $col === 'fp_url' ? 'fp_url' : $col;
|
||||
$val = array_key_exists($logKey, $data) ? $data[$logKey] : cloak_db_escape($conn, $logs[$col]);
|
||||
$sets[] = "`{$col}`='" . $val . "'";
|
||||
}
|
||||
}
|
||||
foreach ($map as $col => $logKey) {
|
||||
if (!array_key_exists($logKey, $data)) {
|
||||
continue;
|
||||
}
|
||||
if ((string) $data[$logKey] === '') {
|
||||
continue;
|
||||
}
|
||||
$sets[] = "`{$col}`='" . $data[$logKey] . "'";
|
||||
}
|
||||
foreach (['fp_hdata', 'api_risk_request', 'api_risk_response'] as $col) {
|
||||
if (!array_key_exists($col, $logs)) {
|
||||
continue;
|
||||
}
|
||||
$val = array_key_exists($col, $data) ? $data[$col] : cloak_db_escape($conn, $logs[$col]);
|
||||
$sets[] = "`{$col}`='" . $val . "'";
|
||||
}
|
||||
if ($sets === []) {
|
||||
$conn->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
$sql = 'UPDATE `visitor_logs` SET ' . implode(', ', array_unique($sets)) . ' WHERE `id`=' . $logId;
|
||||
$ok = $conn->query($sql) === true;
|
||||
if (!$ok) {
|
||||
VisitorLogQueue::logError('updateFromLogs: ' . $conn->error);
|
||||
}
|
||||
$conn->close();
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mysqli $conn
|
||||
* @param array $logs
|
||||
|
||||
+136
-34
@@ -6,6 +6,9 @@ require_once dirname(__DIR__) . '/DbHelper.php';
|
||||
require_once dirname(__DIR__) . '/CloakHttpHelpers.php';
|
||||
require_once __DIR__ . '/VisitorApiAudit.php';
|
||||
require_once __DIR__ . '/VisitorLogVtimes.php';
|
||||
require_once __DIR__ . '/VisitorVisitContext.php';
|
||||
require_once __DIR__ . '/RiskSecondarySession.php';
|
||||
require_once __DIR__ . '/VisitorLogWorker.php';
|
||||
|
||||
if (!function_exists('cloak_session_mark_real_visitor')) {
|
||||
/**
|
||||
@@ -27,6 +30,17 @@ if (!function_exists('cloak_session_mark_real_visitor')) {
|
||||
}
|
||||
|
||||
setCrossDomainCookie('gfuid', $gcuid, time() + 3600 * 24);
|
||||
|
||||
if (class_exists('CloakJudgmentCache', false)) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
CloakJudgmentCache::storeFromByApiRisk([
|
||||
'ip' => $ip,
|
||||
'country' => $_SESSION['gcu_country'] ?? '',
|
||||
'result' => 'true',
|
||||
'reason' => '',
|
||||
'fp_url' => '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +57,19 @@ if (!function_exists('cloak_session_clear_real_visitor')) {
|
||||
$_SESSION['gcu_country'],
|
||||
$_SESSION['gcu_Client'],
|
||||
$_SESSION['gcu_Browser'],
|
||||
$_SESSION['gcu_referer']
|
||||
$_SESSION['gcu_referer'],
|
||||
$_SESSION['cloak_whitelist_fast']
|
||||
);
|
||||
if (class_exists('RiskSecondaryCache', false)) {
|
||||
RiskSecondaryCache::clear();
|
||||
}
|
||||
if (class_exists('CloakJudgmentCache', false)) {
|
||||
CloakJudgmentCache::clear();
|
||||
}
|
||||
if (class_exists('RiskSecondarySession', false)) {
|
||||
RiskSecondarySession::clear();
|
||||
}
|
||||
VisitorVisitContext::clear();
|
||||
setCrossDomainCookie('gfuid', '', time() - 3600);
|
||||
}
|
||||
}
|
||||
@@ -163,49 +185,79 @@ if (!function_exists('cloak_write_log_db_sync')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_write_log_db')) {
|
||||
function cloak_write_log_db($logs)
|
||||
if (!function_exists('cloak_update_log_db')) {
|
||||
/**
|
||||
* 同 visit 续步 UPDATE(不新增 vtimes)。
|
||||
*
|
||||
* @param int $logId
|
||||
* @param array $logs
|
||||
* @return int|false log id on success
|
||||
*/
|
||||
function cloak_update_log_db($logId, array $logs, array $options = [])
|
||||
{
|
||||
$logs = cloak_visitor_log_enrich($logs);
|
||||
$logId = (int) $logId;
|
||||
if ($logId <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (empty($options['skip_enrich'])) {
|
||||
$logs = cloak_visitor_log_enrich($logs);
|
||||
}
|
||||
$logs = VisitorVisitContext::normalizeLogsForDb($logs);
|
||||
|
||||
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
||||
if (($logs['result'] ?? '') === 'wait') {
|
||||
$logId = VisitorLogWorker::insertMinimal($logs);
|
||||
if ($logId !== null) {
|
||||
VisitorLogWorker::enrich($logId, $logs);
|
||||
}
|
||||
return $logId;
|
||||
$ok = false;
|
||||
$forceSync = !empty($GLOBALS['__cloak_force_sync_log'])
|
||||
|| !cloak_visitor_log_async_enabled()
|
||||
|| VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '');
|
||||
if ($forceSync) {
|
||||
$ok = VisitorLogWorker::updateFromLogs($logId, $logs);
|
||||
} else {
|
||||
if (VisitorLogQueue::push(['type' => 'update', 'log_id' => $logId, 'logs' => $logs])) {
|
||||
cloak_visitor_log_register_shutdown_drain();
|
||||
$ok = true;
|
||||
} else {
|
||||
$ok = VisitorLogWorker::updateFromLogs($logId, $logs);
|
||||
}
|
||||
return cloak_write_log_db_sync($logs);
|
||||
}
|
||||
|
||||
if (defined('WRITE_LOG') && WRITE_LOG == '1') {
|
||||
$my_url = $_SERVER['PHP_SELF'] ?? '';
|
||||
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
||||
$config_name = str_replace('.php', '', $my_file_name);
|
||||
$fp = fopen(dirname(__DIR__, 2) . '/jump.txt', 'a+');
|
||||
fwrite($fp, date('Y-m-d H:i:s') . ' | 写入数据库 ' . ' | ' . ($_SESSION['check_result'] ?? '') . ' | ' . $config_name . ' | async=' . (cloak_visitor_log_async_enabled() ? '1' : '0') . "\n");
|
||||
fclose($fp);
|
||||
if ($ok && VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||
}
|
||||
return $ok ? $logId : false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_write_log_db_wait_sync')) {
|
||||
/**
|
||||
* 二次风控 wait:同步完整 INSERT(需立即 log_id,且必须带上 byApi/页面/UA 等字段)。
|
||||
*
|
||||
* @param array $logs 已 enrich + normalize
|
||||
* @return int|null
|
||||
*/
|
||||
function cloak_write_log_db_wait_sync(array $logs)
|
||||
{
|
||||
$logId = VisitorLogWorker::insertFull($logs);
|
||||
if ($logId === null) {
|
||||
$logId = cloak_write_log_db_sync($logs);
|
||||
}
|
||||
return $logId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_write_log_db_insert')) {
|
||||
/**
|
||||
* @param array $logs
|
||||
* @return int|null
|
||||
*/
|
||||
function cloak_write_log_db_insert(array $logs)
|
||||
{
|
||||
$result = isset($logs['result']) ? (string) $logs['result'] : '';
|
||||
|
||||
// 二次风控 wait:同步最小 INSERT 获取 log_id,TEXT 字段异步 enrich
|
||||
if ($result === 'wait') {
|
||||
$logId = VisitorLogWorker::insertMinimal($logs);
|
||||
if ($logId === null) {
|
||||
return cloak_write_log_db_sync($logs);
|
||||
}
|
||||
if (cloak_visitor_log_async_enabled()) {
|
||||
VisitorLogQueue::push([
|
||||
'type' => 'enrich',
|
||||
'log_id' => $logId,
|
||||
'logs' => $logs,
|
||||
]);
|
||||
} else {
|
||||
VisitorLogWorker::enrich($logId, $logs);
|
||||
}
|
||||
return $logId;
|
||||
return cloak_write_log_db_wait_sync($logs);
|
||||
}
|
||||
|
||||
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
||||
return cloak_write_log_db_sync($logs);
|
||||
}
|
||||
|
||||
if (!cloak_visitor_log_async_enabled()) {
|
||||
@@ -284,6 +336,56 @@ if (!function_exists('cloak_write_log_db')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('cloak_write_log_db')) {
|
||||
function cloak_write_log_db($logs)
|
||||
{
|
||||
$logs = cloak_visitor_log_enrich($logs);
|
||||
$logs = VisitorVisitContext::normalizeLogsForDb($logs);
|
||||
|
||||
if (VisitorVisitContext::shouldPersistAsUpdate()) {
|
||||
return cloak_update_log_db(VisitorVisitContext::currentLogId(), $logs);
|
||||
}
|
||||
|
||||
VisitorVisitContext::prepareForNewInsert();
|
||||
|
||||
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
||||
if (($logs['result'] ?? '') === 'wait') {
|
||||
$logId = cloak_write_log_db_wait_sync($logs);
|
||||
if ($logId !== null) {
|
||||
VisitorVisitContext::bindLogId($logId);
|
||||
}
|
||||
return $logId;
|
||||
}
|
||||
$logId = cloak_write_log_db_sync($logs);
|
||||
if ($logId !== null) {
|
||||
VisitorVisitContext::bindLogId($logId);
|
||||
if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||
}
|
||||
}
|
||||
return $logId;
|
||||
}
|
||||
|
||||
if (defined('WRITE_LOG') && WRITE_LOG == '1') {
|
||||
$my_url = $_SERVER['PHP_SELF'] ?? '';
|
||||
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
||||
$config_name = str_replace('.php', '', $my_file_name);
|
||||
$fp = fopen(dirname(__DIR__, 2) . '/jump.txt', 'a+');
|
||||
fwrite($fp, date('Y-m-d H:i:s') . ' | 写入数据库 ' . ' | ' . ($_SESSION['check_result'] ?? '') . ' | ' . $config_name . ' | async=' . (cloak_visitor_log_async_enabled() ? '1' : '0') . "\n");
|
||||
fclose($fp);
|
||||
}
|
||||
|
||||
$logId = cloak_write_log_db_insert($logs);
|
||||
if ($logId !== null) {
|
||||
VisitorVisitContext::bindLogId($logId);
|
||||
if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||
}
|
||||
}
|
||||
return $logId;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('write_log_db')) {
|
||||
function write_log_db($logs)
|
||||
{
|
||||
|
||||
Regular → Executable
Executable
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* 单次访问(Visit)生命周期:一次独立打开 = 一条日志;同 visit 内续步仅 UPDATE。
|
||||
*/
|
||||
class VisitorVisitContext
|
||||
{
|
||||
const KEY_LOG_ID = 'cloak_visit_log_id';
|
||||
const KEY_FINALIZED = 'cloak_visit_finalized';
|
||||
const KEY_ACTIVE = 'cloak_visit_active';
|
||||
|
||||
public static function clear()
|
||||
{
|
||||
unset(
|
||||
$_SESSION[self::KEY_LOG_ID],
|
||||
$_SESSION[self::KEY_FINALIZED],
|
||||
$_SESSION[self::KEY_ACTIVE]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function currentLogId()
|
||||
{
|
||||
return (int) ($_SESSION[self::KEY_LOG_ID] ?? 0);
|
||||
}
|
||||
|
||||
public static function isFinalized()
|
||||
{
|
||||
return !empty($_SESSION[self::KEY_FINALIZED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* ip_check 入口:识别刷新导航并开启新 visit。
|
||||
*/
|
||||
public static function evaluateAtEntry()
|
||||
{
|
||||
if (self::isFcheckRequest()) {
|
||||
return;
|
||||
}
|
||||
if (self::isRefreshNavigation()) {
|
||||
self::clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static function markVisitActive()
|
||||
{
|
||||
$_SESSION[self::KEY_ACTIVE] = 1;
|
||||
}
|
||||
|
||||
public static function isFcheckRequest()
|
||||
{
|
||||
$script = $_SERVER['SCRIPT_NAME'] ?? $_SERVER['PHP_SELF'] ?? '';
|
||||
return basename($script) === 'f_check.php';
|
||||
}
|
||||
|
||||
public static function hasContinuationMarker()
|
||||
{
|
||||
if (isset($_GET['time']) && (string) $_GET['time'] !== '') {
|
||||
return true;
|
||||
}
|
||||
return self::isFcheckRequest();
|
||||
}
|
||||
|
||||
public static function isRefreshNavigation()
|
||||
{
|
||||
if (self::hasContinuationMarker()) {
|
||||
return false;
|
||||
}
|
||||
if (self::currentLogId() <= 0 || self::isFinalized()) {
|
||||
return false;
|
||||
}
|
||||
$mode = strtolower((string) ($_SERVER['HTTP_SEC_FETCH_MODE'] ?? ''));
|
||||
return $mode === 'navigate';
|
||||
}
|
||||
|
||||
public static function isNewVisit()
|
||||
{
|
||||
if (self::isRefreshNavigation()) {
|
||||
return true;
|
||||
}
|
||||
if (self::currentLogId() <= 0) {
|
||||
return true;
|
||||
}
|
||||
if (self::isFinalized()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isContinuation()
|
||||
{
|
||||
if (self::isRefreshNavigation()) {
|
||||
return false;
|
||||
}
|
||||
if (self::hasContinuationMarker()) {
|
||||
return true;
|
||||
}
|
||||
if (self::currentLogId() > 0 && !self::isFinalized()) {
|
||||
return true;
|
||||
}
|
||||
if (!empty($_SESSION[self::KEY_ACTIVE]) && self::currentLogId() <= 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* INSERT 前:已 finalize 或刷新后的新 visit 清除旧绑定。
|
||||
*/
|
||||
public static function prepareForNewInsert()
|
||||
{
|
||||
if (!self::isNewVisit()) {
|
||||
return;
|
||||
}
|
||||
$hadFinalized = self::isFinalized();
|
||||
unset($_SESSION[self::KEY_FINALIZED]);
|
||||
if ($hadFinalized || self::isRefreshNavigation()) {
|
||||
unset($_SESSION[self::KEY_LOG_ID]);
|
||||
}
|
||||
if (empty($_SESSION[self::KEY_ACTIVE])) {
|
||||
self::markVisitActive();
|
||||
}
|
||||
if (class_exists('VisitorApiAudit', false)) {
|
||||
VisitorApiAudit::clearByApi();
|
||||
}
|
||||
}
|
||||
|
||||
public static function shouldPersistAsUpdate()
|
||||
{
|
||||
return self::currentLogId() > 0 && !self::isFinalized() && !self::isNewVisit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $logId
|
||||
*/
|
||||
public static function bindLogId($logId)
|
||||
{
|
||||
$logId = (int) $logId;
|
||||
if ($logId > 0) {
|
||||
$_SESSION[self::KEY_LOG_ID] = $logId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $result
|
||||
*/
|
||||
public static function finalizeVisit($result = null)
|
||||
{
|
||||
$_SESSION[self::KEY_FINALIZED] = 1;
|
||||
unset($_SESSION[self::KEY_ACTIVE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $result
|
||||
*/
|
||||
public static function shouldFinalizeOnResult($result)
|
||||
{
|
||||
$r = (string) $result;
|
||||
return $r === 'true' || $r === 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $logs
|
||||
* @return array
|
||||
*/
|
||||
public static function normalizeLogsForDb(array $logs)
|
||||
{
|
||||
$result = (string) ($logs['result'] ?? '');
|
||||
if (function_exists('cloak_normalize_log_reason')) {
|
||||
$logs['reason'] = cloak_normalize_log_reason($result, $logs['reason'] ?? '');
|
||||
}
|
||||
return $logs;
|
||||
}
|
||||
}
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/ClientIpResolver.php';
|
||||
|
||||
/**
|
||||
* IP 白名单与链接参数白名单判定(供流水线早期快速路径与黑白名单阶段复用)
|
||||
*/
|
||||
class WhitelistGate
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getWhitelistIps()
|
||||
{
|
||||
if (defined('WHITELIST_GROUP_ID') && (int) WHITELIST_GROUP_ID > 0) {
|
||||
return ip_group_list_addresses((int) WHITELIST_GROUP_ID);
|
||||
}
|
||||
if (!defined('SHOW_SITE_IP') || SHOW_SITE_IP === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) {
|
||||
return $v !== '';
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $ip
|
||||
*/
|
||||
public static function matchesWhitelistIp($ip)
|
||||
{
|
||||
$ip = trim((string) $ip);
|
||||
if ($ip === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (self::getWhitelistIps() as $entry) {
|
||||
if (function_exists('ip_visitor_matches_list_entry') && ip_visitor_matches_list_entry($ip, $entry)) {
|
||||
return true;
|
||||
}
|
||||
if ($ip === $entry) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function matchesWhitelistParams()
|
||||
{
|
||||
if (!defined('WHITE_PARAMS') || WHITE_PARAMS === '') {
|
||||
return false;
|
||||
}
|
||||
$wparams = explode('=', WHITE_PARAMS, 2);
|
||||
if (count($wparams) !== 2) {
|
||||
return false;
|
||||
}
|
||||
$pk = $wparams[0];
|
||||
$pv = $wparams[1];
|
||||
|
||||
return isset($_GET[$pk]) && strip_tags((string) $_GET[$pk]) === $pv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param visitorInfo|object|null $v_info
|
||||
*/
|
||||
public static function resolveVisitorIp($v_info = null)
|
||||
{
|
||||
$ip = '';
|
||||
if ($v_info !== null && isset($v_info->v_ip)) {
|
||||
$ip = (string) $v_info->v_ip;
|
||||
}
|
||||
if ($ip === '' || $ip === '0.0.0.0') {
|
||||
$ip = ClientIpResolver::resolve();
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param visitorInfo|object|null $v_info
|
||||
*/
|
||||
public static function isMatched($v_info = null)
|
||||
{
|
||||
if (self::matchesWhitelistParams()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return self::matchesWhitelistIp(self::resolveVisitorIp($v_info));
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先级:链接参数白名单 > IP 白名单
|
||||
*
|
||||
* @param visitorInfo|object|null $v_info
|
||||
*/
|
||||
public static function matchReason($v_info = null)
|
||||
{
|
||||
if (self::matchesWhitelistParams()) {
|
||||
return '白名单链接参数';
|
||||
}
|
||||
if (self::matchesWhitelistIp(self::resolveVisitorIp($v_info))) {
|
||||
return '白名单';
|
||||
}
|
||||
|
||||
return '白名单';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user