日志升级与缓存修复最终版

This commit is contained in:
root
2026-06-16 04:58:56 +08:00
parent bcb9f293a6
commit 6b3b99b0f1
61 changed files with 2405 additions and 333 deletions
Regular → Executable
View File
Regular → Executable
View File
+5
View File
@@ -22,6 +22,11 @@ define('REDIS_PASSWORD', '');
// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT---
define('VISITOR_LOG_ASYNC', 'ON');
// --- Cookie 域(可选)---
// 宝塔多域名共用代码时一般留空,按每次请求的 HTTP_HOST 自动推断(如 .shili.buzz)。
// 仅当所有落地域名需固定同一父域时再配置,例如:define('CLOAK_COOKIE_DOMAIN', '.shili.buzz');
// define('CLOAK_COOKIE_DOMAIN', '');
define('CLOAK_GEOIP_ENABLED', 'ON');
define('CLOAK_GEOIP_DB_PATH', __DIR__ . '/storage/runtime/c7f2a9e1/cc_idx.dat');
define('CLOAK_GEOIP_CACHE_TTL', 2592000);
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
+107 -22
View File
@@ -4,7 +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/VisitorLogListHelper.php");
require_once(dirname(__DIR__) . "/lib/Cloak/VisitorApiAudit.php");
$reasons = array(
"blank_referrer" => "过滤无来源的访问",
@@ -44,9 +44,15 @@ $reasons = array(
"time_of_day" => "不可访问的日期",
);
$jsonResult = array();
$conn = new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
$servername = "localhost";
$username = DB_USERNAME;
$password = DB_PASSWORD;
$dbname = DB_NAME;
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
$jsonResult = array();
// Check connection
if ($conn->connect_error) {
$error = "Connection failed: " . $conn->connect_error;
$handle = fopen(dirname(__FILE__) . "/err.txt", "a+");
@@ -59,40 +65,119 @@ if ($conn->connect_error) {
$jsonResult["data"] = array();
} else {
VisitorLogSchema::ensureColumns($conn);
VisitorLogSchema::ensureIndexes($conn);
if(isset($_GET['clear']) && $_GET['clear'] == "true") { //清除日志数据表
$sql = "TRUNCATE `visitor_logs`";
if(isset($_GET['clear']) && $_GET['clear'] == "true") {
$conn->query("TRUNCATE `visitor_logs`");
// 执行查询并输出结果
$result = mysqli_query($conn, $sql);
$jsonResult["code"] = 0;
$jsonResult["msg"] = "success";
$jsonResult["count"] = 0;
$jsonResult["data"] = array();
} else {
$pageNumber = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
$recordsPerPage = isset($_GET['limit']) ? max(1, min(200, (int) $_GET['limit'])) : 50;
$startPosition = ($pageNumber - 1) * $recordsPerPage;
$where = VisitorLogListHelper::buildWhere($conn, $_GET);
} else { // 默认分页数据
// 获取当前页码(默认为第1页)
$pageNumber = isset($_GET['page']) ? (int) $_GET['page'] : 1;
$sql = VisitorLogListHelper::listSelectSql($where, $startPosition, $recordsPerPage, false);
$result = $conn->query($sql);
// 设置每页显示的记录条数
$recordsPerPage = isset($_GET['limit']) ? (int) $_GET['limit'] : 50;
// 计算起始位置
$startPosition = ($pageNumber - 1) * $recordsPerPage;
$map = array();
$where = 1;
if(!empty($_GET['visit_date'])) {
$map[] = "DATE_FORMAT(`visit_date`, '%Y-%m-%d') LIKE '" . cloak_db_escape($conn, $_GET['visit_date']) . "'";
}
$where .= cloak_visitor_log_apply_result_filter($conn, $_GET['result'] ?? '');
if(!empty($_GET['country'])) {
$map[] = "country LIKE '" . cloak_db_escape($conn, $_GET['country']) . "'";
}
if(!empty($_GET['IP'])) {
$map[] = "IP LIKE '" . cloak_db_escape($conn, $_GET['IP']) . "'";
}
if(!empty($_GET['domain'])) {
$map[] = "domain LIKE '%" . cloak_db_escape($conn, $_GET['domain']) . "%'";
}
if(!empty($_GET['spage'])) {
$map[] = "page LIKE '%" . cloak_db_escape($conn, $_GET['spage']) . "%'";
}
if(!empty($map)) {
$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`, `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);
$jsonData = array();
if ($result) {
while ($row = $result->fetch_assoc()) {
$jsonData[] = VisitorLogListHelper::formatListRow($row, $countries, $reasons);
while ($row = mysqli_fetch_assoc($result)) {
//echo "<p>{$row["columnName"]}</p>"; // 根据需要修改列名
if($row['vtimes'] == 0) {
$row['vtimes'] = 1; // 还未统计访问次数
$sqlVtimes = mysqli_query($conn, "SELECT count(*) as `total` FROM `visitor_logs` WHERE `IP` = '" . cloak_db_escape($conn, $row["IP"]) . "' AND `domain` = '" . cloak_db_escape($conn, $row["domain"]) . "' AND id < " . (int)$row['id']);
$vtimesRow = mysqli_fetch_assoc($sqlVtimes);
$vtimes = $vtimesRow["total"]; // 如果之前此IP有访问记录
if($vtimes > 0) {
$row['vtimes'] += $vtimes; // 访问次数叠加
}
$result->free();
// 更新访问次数
$updateSql = "UPDATE `visitor_logs` SET `vtimes`=" . (int)$row['vtimes'] . " WHERE id = " . $row['id'];
mysqli_query($conn, $updateSql);
}
$row['country'] = $countries[$row['country']];
if(isset($reasons[$row['reason']])) {
$row['reason'] = $reasons[$row['reason']];
}
if ($row['result'] === 'wait') {
$row['result'] = '检测中';
}
$row['judge_timing_text'] = cloak_visitor_log_format_timing($row['judge_timing'] ?? '');
$row['fp_hdata_text'] = cloak_visitor_log_format_fp_hdata($row['fp_hdata'] ?? '');
if (!empty($row['fp_hdata'])) {
$explain = cloak_visitor_log_fp_risk_explain(
$row['fp_hdata'],
$row['reason'] ?? '',
$row['result'] ?? '',
$row['domain'] ?? ''
);
$row['fp_hdata_explain_summary'] = $explain['summary'];
$row['fp_hdata_explain_html'] = $explain['html'];
$row['fp_hdata_explain'] = $explain['data'];
} else {
$row['fp_hdata_explain_summary'] = '';
$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;
}
$sqlCount = mysqli_query($conn, "SELECT count(*) as `total` FROM `visitor_logs` WHERE {$where}");
$countRow = mysqli_fetch_assoc($sqlCount);
$jsonResult["code"] = 0;
$jsonResult["msg"] = "";
$jsonResult["count"] = VisitorLogListHelper::countWhere($conn, $where);
$jsonResult["count"] = $countRow['total'];
$jsonResult["data"] = $jsonData;
}
}
$conn->close();
// 关闭数据库连接
mysqli_close($conn);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($jsonResult);
exit;
header('Content-Type: application/json');
echo json_encode($jsonResult);exit;
Regular → Executable
View File
+2 -5
View File
@@ -28,7 +28,7 @@ $cloak_key = CHECK_KEY;
$campagin_id = COSTM_IP_SCORE;
$time = time();
$ckey = sha1($campagin_id . sha1($time . $cloak_key));
$fp_cookie_domain = cloak_fingerprint_cookie_domain();
$fp_js_cookie_opts = cloak_fingerprint_js_cookie_options();
readfile(__DIR__ . '/cloakjs');
echo "\n";
@@ -151,10 +151,7 @@ var virtual = <?php echo (int) $is_virtual; ?>;
<?php endif; ?>
function cloakFinishFingerprint() {
var fpCookieOpts = { path: '/', expires: 1, sameSite: 'Lax' };
<?php if ($fp_cookie_domain !== ''): ?>
fpCookieOpts.domain = <?php echo json_encode($fp_cookie_domain, JSON_UNESCAPED_UNICODE); ?>;
<?php endif; ?>
var fpCookieOpts = <?php echo json_encode($fp_js_cookie_opts, JSON_UNESCAPED_UNICODE); ?>;
var fpCookies = (typeof Cookies !== 'undefined' && Cookies.withAttributes)
? Cookies.withAttributes(fpCookieOpts)
: Cookies;
Regular → Executable
View File
+3
View File
@@ -135,6 +135,9 @@ if (!$isLocked && !empty($_POST['action']) && $_POST['action'] === 'install') {
. '// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT---' . PHP_EOL
. 'define(\'VISITOR_LOG_ASYNC\', \'ON\');' . PHP_EOL
. PHP_EOL
. '// --- Cookie 域(可选,留空则按 HTTP_HOST 自动推断)---' . PHP_EOL
. '// define(\'CLOAK_COOKIE_DOMAIN\', \'.example.com\');' . PHP_EOL
. PHP_EOL
. '// --- GeoIP 本地国家库(MaxMind GeoLite2 Country---' . PHP_EOL
. 'define(\'CLOAK_GEOIP_ENABLED\', \'ON\');' . PHP_EOL
. 'define(\'CLOAK_GEOIP_DB_PATH\', __DIR__ . \'/storage/runtime/c7f2a9e1/cc_idx.dat\');' . PHP_EOL
+5
View File
@@ -4,6 +4,11 @@ require_once __DIR__ . '/lib/CloakHttpHelpers.php';
require_once __DIR__ . '/lib/Cloak/CloakSession.php';
CloakSession::start();
require_once __DIR__ . '/lib/Cloak/VisitorVisitContext.php';
VisitorVisitContext::evaluateAtEntry();
if (VisitorVisitContext::isNewVisit()) {
VisitorVisitContext::markVisitActive();
}
// $begin_time = date("H:i:s");
// include("ip_check_config.php");
+8
View File
@@ -113,3 +113,11 @@
2026-06-15 20:31:20 | 写入数据库 | false | indBXSXX | async=1
2026-06-15 20:31:51 | 来源判断 | 无法识别访客国家|false | indBXSXX | 127.0.0.1 |
2026-06-15 20:31:51 | 写入数据库 | false | indBXSXX | async=1
2026-06-15 23:59:33 | 写入数据库 | | regression_test | async=1
2026-06-15 23:59:33 | 写入数据库 | | regression_test | async=1
2026-06-16 03:17:21 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 |
2026-06-16 03:17:21 | 写入数据库 | false | test | async=1
2026-06-16 03:17:42 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 |
2026-06-16 03:17:42 | 写入数据库 | false | indBXSXX_test | async=1
2026-06-16 03:21:38 | 来源判断 | 无法识别访客国家|false | index | 0.0.0.0 |
2026-06-16 03:21:38 | 写入数据库 | false | indBXSXX_test | async=1
+272
View File
@@ -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';
/**
* 客户端 SessionPHPSESSID)统一判定缓存: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
View File
@@ -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
+39 -35
View File
@@ -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,8 +242,9 @@ class FCheckHandler
*/
private static function persistCache(array $update_log, $clientIp)
{
RiskSecondaryCache::store([
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'] ?? '')));
if ($includeFpAndApi) {
$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 . "'";
$payload['fp_hdata'] = $fpHdataJson;
$payload['api_risk_request'] = $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : '';
$payload['api_risk_response'] = $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : '';
}
$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);
$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']);
}
}
$conn->close();
return $ok;
}
}
+3 -1
View File
@@ -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)
{
@@ -20,7 +20,9 @@ class FpPageRenderer
$resolved = FpUrlHelper::resolve($cong_name, true);
$fp_url = $resolved['url'];
$logs['fp_url'] = $fp_url;
if (empty($ctx['skip_log'])) {
write_log_db($logs);
}
$show_content = CLOAK_SHOW_CONTENT;
if ($show_content == 'curl') {
+160 -20
View File
@@ -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';
}
}
+45 -9
View File
@@ -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,7 +133,20 @@ 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(),
]);
}
/**
@@ -119,14 +154,15 @@ class RouteResolver
*
* @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,27 +1,29 @@
<?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 !== '';
});
// 黑白名单(完整判定链内):链接参数白名单 > 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']);
}
$reason = ""; // 判断理由
$whiteParamsMatched = false;
if( !empty(WHITE_PARAMS) ) {
$wparams = explode('=', WHITE_PARAMS, 2);
if(count($wparams) == 2) {
$pk = $wparams[0];
$pv = $wparams[1];
return;
}
if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) {
$whiteParamsMatched = true;
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;
@@ -30,40 +32,15 @@
$match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
}
if ($__cloak_debug_on) {
cloak_dbg_step(__LINE__,'黑名单检查', $match ? 'fail' : 'pass', $match ? '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']);
}
+17
View File
@@ -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']);
}
+15
View File
@@ -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 . '');
+39 -6
View File
@@ -41,14 +41,47 @@ if (!function_exists('cloak_api_reason')) {
}
}
if (!function_exists('cloak_cache_hit_log_reason')) {
/**
* 缓存判定落库 reasontrue 为空;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
View File
@@ -1,86 +1,48 @@
<?php
require_once __DIR__ . '/ClientIpResolver.php';
require_once __DIR__ . '/FpUrlHelper.php';
require_once __DIR__ . '/CloakJudgmentCache.php';
/**
* 二次风控byApiRiskSession 缓存:同 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);
}
}
+11
View File
@@ -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();
}
}
+41
View File
@@ -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]);
}
}
+32 -6
View File
@@ -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',
+15 -1
View File
@@ -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,
View File
Regular → Executable
View File
+79
View File
@@ -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
+135 -33
View File
@@ -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,50 +185,80 @@ 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 = [])
{
$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);
$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);
}
}
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;
}
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 (!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_idTEXT 字段异步 enrich
if ($result === 'wait') {
$logId = VisitorLogWorker::insertMinimal($logs);
if ($logId === null) {
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()) {
VisitorLogQueue::push([
'type' => 'enrich',
'log_id' => $logId,
'logs' => $logs,
]);
} else {
VisitorLogWorker::enrich($logId, $logs);
}
return $logId;
}
if (!cloak_visitor_log_async_enabled()) {
$conn = new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
@@ -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)
{
View File
+175
View File
@@ -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;
}
}
+106
View File
@@ -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 '白名单';
}
}
+185 -11
View File
@@ -165,34 +165,208 @@ if (!function_exists('write_nt_list')) {
}
}
if (!function_exists('cloak_normalize_request_host')) {
/**
* 规范化请求 Host:小写、去端口、去首尾空白。
*
* @param string|null $host
*/
function cloak_normalize_request_host($host = null)
{
$host = strtolower(trim((string) ($host ?? ($_SERVER['HTTP_HOST'] ?? ''))));
if ($host === '') {
return '';
}
if ($host[0] === '[') {
$end = strpos($host, ']');
if ($end !== false) {
return substr($host, 0, $end + 1);
}
}
$colon = strpos($host, ':');
if ($colon !== false) {
$host = substr($host, 0, $colon);
}
return rtrim($host, '.');
}
}
if (!function_exists('cloak_host_is_ip')) {
/**
* @param string $host
*/
function cloak_host_is_ip($host)
{
$host = trim($host);
if ($host === '') {
return false;
}
if ($host[0] === '[' && substr($host, -1) === ']') {
return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP) !== false;
}
return filter_var($host, FILTER_VALIDATE_IP) !== false;
}
}
if (!function_exists('cloak_host_is_local')) {
/**
* localhost / 单标签主机名(不含点)视为 host-only Cookie。
*
* @param string $host
*/
function cloak_host_is_local($host)
{
$host = strtolower(trim($host));
if ($host === '' || strpos($host, '.') === false) {
return true;
}
return $host === 'localhost'
|| substr($host, -6) === '.local'
|| substr($host, -5) === '.test'
|| substr($host, -13) === '.localhost';
}
}
if (!function_exists('cloak_cookie_public_suffixes')) {
/**
* 常见多级公共后缀(co.uk / com.cn 等),避免 Cookie domain 算成 .co.uk。
*
* @return string[]
*/
function cloak_cookie_public_suffixes()
{
return [
'co.uk', 'org.uk', 'ac.uk', 'gov.uk', 'net.uk',
'com.cn', 'net.cn', 'org.cn', 'gov.cn',
'com.au', 'net.au', 'org.au', 'edu.au',
'co.jp', 'ne.jp', 'or.jp',
'com.br', 'com.mx', 'com.tw', 'com.hk', 'co.nz', 'co.kr', 'com.sg',
'com.tr', 'com.pl', 'com.ar', 'com.co', 'co.in', 'com.my',
];
}
}
if (!function_exists('cloak_normalize_cookie_domain_config')) {
/**
* 配置项 CLOAK_COOKIE_DOMAIN Cookie domain(带点前缀,如 .shili.buzz
*
* @param string $value
*/
function cloak_normalize_cookie_domain_config($value)
{
$value = strtolower(trim($value));
if ($value === '') {
return '';
}
$value = ltrim($value, '.');
if ($value === '' || cloak_host_is_ip($value) || cloak_host_is_local($value)) {
return '';
}
return '.' . $value;
}
}
if (!function_exists('cloak_registrable_domain')) {
/**
* Host 推断可注册域(不含前导点),IP/localhost 返回空。
*
* @param string|null $host
*/
function cloak_registrable_domain($host = null)
{
if (defined('CLOAK_COOKIE_DOMAIN') && (string) CLOAK_COOKIE_DOMAIN !== '') {
$configured = cloak_normalize_cookie_domain_config((string) CLOAK_COOKIE_DOMAIN);
return $configured !== '' ? ltrim($configured, '.') : '';
}
$host = cloak_normalize_request_host($host);
if ($host === '' || cloak_host_is_ip($host) || cloak_host_is_local($host)) {
return '';
}
$parts = explode('.', $host);
$count = count($parts);
if ($count < 2) {
return '';
}
$suffix2 = $parts[$count - 2] . '.' . $parts[$count - 1];
if (in_array($suffix2, cloak_cookie_public_suffixes(), true)) {
if ($count < 3) {
return '';
}
return $parts[$count - 3] . '.' . $suffix2;
}
return $suffix2;
}
}
if (!function_exists('cloak_fingerprint_cookie_domain')) {
/**
* 指纹 Cookie 使用的跨子域 domain setCrossDomainCookie 一致)
* 指纹 / Session Cookie 跨子域 domain带点,如 .shili.buzz);IP/localhost 返回空(host-only)。
*/
function cloak_fingerprint_cookie_domain()
{
if (empty($_SERVER['HTTP_HOST'])) {
return '';
if (defined('CLOAK_COOKIE_DOMAIN') && (string) CLOAK_COOKIE_DOMAIN !== '') {
return cloak_normalize_cookie_domain_config((string) CLOAK_COOKIE_DOMAIN);
}
$host_names = explode('.', $_SERVER['HTTP_HOST']);
if (count($host_names) < 2) {
return '';
$registrable = cloak_registrable_domain();
return $registrable !== '' ? '.' . $registrable : '';
}
}
return '.' . $host_names[count($host_names) - 2] . '.' . $host_names[count($host_names) - 1];
if (!function_exists('cloak_fingerprint_js_cookie_options')) {
/**
* 前端 js-cookie 写入指纹 Cookie 的选项(与 PHP setcookie 策略一致)。
*
* @return array{path:string,expires:int,sameSite:string,domain?:string,secure?:bool}
*/
function cloak_fingerprint_js_cookie_options()
{
$opts = [
'path' => '/',
'expires' => 1,
'sameSite' => 'Lax',
];
$domain = cloak_fingerprint_cookie_domain();
if ($domain !== '') {
$opts['domain'] = $domain;
}
if (cloak_request_is_https()) {
$opts['secure'] = true;
}
return $opts;
}
}
if (!function_exists('cloak_set_cross_domain_cookie')) {
function cloak_set_cross_domain_cookie($name, $value, $expire)
{
$bottom_host_name = cloak_fingerprint_cookie_domain();
if ($bottom_host_name === '') {
setcookie($name, $value, $expire, '/');
$cookieDomain = cloak_fingerprint_cookie_domain();
$secure = cloak_request_is_https();
if (PHP_VERSION_ID >= 70300) {
$opts = [
'expires' => (int) $expire,
'path' => '/',
'secure' => $secure,
'httponly' => false,
'samesite' => 'Lax',
];
if ($cookieDomain !== '') {
$opts['domain'] = ltrim($cookieDomain, '.');
}
setcookie($name, $value, $opts);
return;
}
setcookie($name, $value, $expire, '/', $bottom_host_name);
if ($cookieDomain === '') {
setcookie($name, $value, (int) $expire, '/', '', $secure, false);
return;
}
setcookie($name, $value, (int) $expire, '/', $cookieDomain, $secure, false);
}
}
+6 -3
View File
@@ -107,19 +107,22 @@ if (!function_exists('cloak_visitor_log_fp_risk_explain')) {
if (!function_exists('cloak_visitor_log_apply_result_filter')) {
/**
* 日志列表 result 筛选:默认排除 wait(检测中);显式选 wait/true/false 时精确匹配
* 日志列表 result 筛选:默认显示全部(含 wait/检测中);显式筛选时精确匹配
*
* @return string SQL 片段(含前导 AND
*/
function cloak_visitor_log_apply_result_filter(mysqli $conn, $resultParam): string
{
$resultParam = trim((string) $resultParam);
if ($resultParam === 'wait') {
if ($resultParam === '') {
return '';
}
if ($resultParam === 'wait' || $resultParam === '检测中') {
return " AND `result` = 'wait'";
}
if ($resultParam === 'true' || $resultParam === 'false') {
return " AND `result` = '" . cloak_db_escape($conn, $resultParam) . "'";
}
return " AND (`result` IS NULL OR `result` <> 'wait')";
return '';
}
}
+4
View File
@@ -25,12 +25,16 @@ 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/WhitelistGate.php';
require_once __DIR__ . '/Cloak/RiskSecondaryGate.php';
require_once __DIR__ . '/Cloak/CloakJudgmentCache.php';
require_once __DIR__ . '/Cloak/RiskSecondaryCache.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/VisitorVisitContext.php';
require_once __DIR__ . '/Cloak/RiskSecondarySession.php';
require_once __DIR__ . '/Cloak/Pipeline/CheckPipeline.php';
require_once __DIR__ . '/Cloak/Page/FingerprintRedirectRenderer.php';
require_once __DIR__ . '/Cloak/Page/FpPageRenderer.php';
+1 -2
View File
@@ -1,7 +1,7 @@
<?php
/** 虚拟设备检测规则引擎(IS_VIRTUAL 1=简单 2=标准 3=严格) */
?>
<script>
(function (window) {
'use strict';
@@ -239,4 +239,3 @@
},
};
})(window);
</script>
Regular → Executable
View File
@@ -0,0 +1 @@
{"value":"__NONE__","expires_at":1784140663}
@@ -0,0 +1 @@
{"value":"__NONE__","expires_at":1784140663}
@@ -0,0 +1 @@
{"value":"__NONE__","expires_at":1784140663}
+1 -1
View File
@@ -373,7 +373,7 @@ if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) {
<div class="layui-col-md3">
<div class="layui-input-wrap">
<select name="result">
<option value="">全部(不含检测中)</option>
<option value="">全部</option>
<option value="true">TRUE(通过访问)</option>
<option value="false">FALSE(禁止访问)</option>
<option value="wait">检测中</option>
+30
View File
@@ -0,0 +1,30 @@
<?php
/** 回归:白名单 IP 快速路径 */
define('SHOW_SITE_MODE_SWITCH', 'ip_check');
define('SHOW_SITE_IP', '8.8.8.8');
define('BLACKLIST_GROUP_ID', 0);
define('WHITELIST_GROUP_ID', 0);
define('SHOW_SITE_COUNTRY', 'US');
define('CLOAK_SHOW_CONTENT', '302');
define('DB_FP', ['https://example.com/fp']);
define('COSTM_IP_SCORE', 'test');
define('BLACK_LIST', '0');
define('AUTO_BLACK', 'OFF');
define('IS_VIRTUAL', '0');
define('AUTO_BLACK_TIMES', '5');
define('WHITE_PARAMS', '');
define('IPHONE_MODEL', 0);
define('SHOW_SITE_MOBILE', 0);
define('SHOW_SITE_OUTPUT', 1);
define('WRITE_LOG', 0);
define('CLOAK_ZH_ON', 'OFF');
define('CLOAK_OS_ON', 'OFF');
define('KEEP_PARAMS', 'OFF');
define('CLOAK_REDIRECT_METHOD', '302');
define('DB_ZP', 'https://example.com/zp');
define('CLOAK_MOBILE_ON', 'OFF');
define('CLOAK_V_REFERER', 'OFF');
define('CLOAK_RISK_NUMBER', '0');
define('CLOAK_RISK_ENHANCED', 'OFF');
define('CLOAK_URL_ARGS_TIMEOUT', '0');
define('CLOAK_DEBUG_MODE', 'OFF');
+30
View File
@@ -0,0 +1,30 @@
<?php
/** 回归:正品模式下白名单 IP 仍应走真实页判定 */
define('SHOW_SITE_MODE_SWITCH', 'zp');
define('SHOW_SITE_IP', '8.8.8.8');
define('BLACKLIST_GROUP_ID', 0);
define('WHITELIST_GROUP_ID', 0);
define('SHOW_SITE_COUNTRY', 'US');
define('CLOAK_SHOW_CONTENT', '302');
define('DB_FP', ['https://example.com/fp']);
define('COSTM_IP_SCORE', 'test');
define('BLACK_LIST', '0');
define('AUTO_BLACK', 'OFF');
define('IS_VIRTUAL', '0');
define('AUTO_BLACK_TIMES', '5');
define('WHITE_PARAMS', '');
define('IPHONE_MODEL', 0);
define('SHOW_SITE_MOBILE', 0);
define('SHOW_SITE_OUTPUT', 1);
define('WRITE_LOG', 0);
define('CLOAK_ZH_ON', 'OFF');
define('CLOAK_OS_ON', 'OFF');
define('KEEP_PARAMS', 'OFF');
define('CLOAK_REDIRECT_METHOD', '302');
define('DB_ZP', 'https://example.com/zp');
define('CLOAK_MOBILE_ON', 'OFF');
define('CLOAK_V_REFERER', 'OFF');
define('CLOAK_RISK_NUMBER', '0');
define('CLOAK_RISK_ENHANCED', 'OFF');
define('CLOAK_URL_ARGS_TIMEOUT', '0');
define('CLOAK_DEBUG_MODE', 'OFF');
+30
View File
@@ -137,6 +137,11 @@ reg_assert('fp 模式配置生效', ($fpResult['mode'] ?? '') === 'fp', json_enc
$zpResult = run_pipeline_subprocess($root, 'regression_zp.php');
reg_assert('zp 模式配置生效', ($zpResult['mode'] ?? '') === 'zp', json_encode($zpResult, JSON_UNESCAPED_UNICODE));
reg_assert('zp 模式判定 false + 正品模式', ($zpResult['result'] ?? null) === 'false' && ($zpResult['reason'] ?? '') === '正品模式', json_encode($zpResult, JSON_UNESCAPED_UNICODE));
$earlyRouteOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_early_route_gate.php') . ' 2>/dev/null');
$earlyRouteJson = json_decode((string) $earlyRouteOut, true);
reg_assert('白名单/正品早期快速路径', ($earlyRouteJson['ok'] ?? false) === true, (string) $earlyRouteOut);
// ── 7. 指纹跳转模板 ───────────────────────────────────────────
echo "[7] 指纹跳转模板\n";
@@ -158,12 +163,17 @@ reg_assert('cloak_fingerprint_cookies_ready', cloak_fingerprint_cookies_ready())
$_SERVER['HTTP_HOST'] = 'shop.example.com';
reg_assert('cloak_fingerprint_cookie_domain', cloak_fingerprint_cookie_domain() === '.example.com');
$cookieDomainOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_cookie_domain.php') . ' 2>/dev/null');
$cookieDomainJson = json_decode((string) $cookieDomainOut, true);
reg_assert('Cookie 域推断与 JS 选项', ($cookieDomainJson['ok'] ?? false) === true, (string) $cookieDomainOut);
$_GET = ['c' => 'index'];
ob_start();
include $root . '/dist/index.php';
$distJs = ob_get_clean();
reg_assert('dist/index.php 输出 Cookies.set', strpos($distJs, "fpCookies.set('ctime'") !== false);
reg_assert('dist/index.php 输出指纹完成回调', strpos($distJs, '__cloakFingerprintDone') !== false);
reg_assert('dist/index.php 输出 sameSite Lax', strpos($distJs, '"sameSite":"Lax"') !== false || strpos($distJs, '"sameSite": "Lax"') !== false);
reg_assert('dist/index.php 无 PHP Fatal', stripos($distJs, 'Fatal error') === false);
$virtualDetectOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_virtual_detect.php') . ' 2>/dev/null');
@@ -234,10 +244,30 @@ $riskCacheOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($ro
$riskCacheJson = json_decode((string) $riskCacheOut, true);
reg_assert('二次风控 Session 缓存与 CORS', ($riskCacheJson['ok'] ?? false) === true, (string) $riskCacheOut);
$judgmentCacheOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_client_judgment_cache.php') . ' 2>/dev/null');
$judgmentCacheJson = json_decode((string) $judgmentCacheOut, true);
reg_assert('客户端 Session 统一缓存策略', ($judgmentCacheJson['ok'] ?? false) === true, (string) $judgmentCacheOut);
$datalistHelperOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_datalist_helper.php') . ' 2>/dev/null');
$datalistHelperJson = json_decode((string) $datalistHelperOut, true);
reg_assert('日志列表 VisitorLogListHelper', ($datalistHelperJson['ok'] ?? false) === true, (string) $datalistHelperOut);
$riskSessionOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_risk_session.php') . ' 2>/dev/null');
$riskSessionJson = json_decode((string) $riskSessionOut, true);
reg_assert('二次风控 Session 防重复写库', ($riskSessionJson['ok'] ?? false) === true, (string) $riskSessionOut);
$judgmentPriOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_judgment_priority.php') . ' 2>/dev/null');
$judgmentPriJson = json_decode((string) $judgmentPriOut, true);
reg_assert('判定优先级顺序', ($judgmentPriJson['ok'] ?? false) === true, (string) $judgmentPriOut);
$visitLogOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_visit_log_lifecycle.php') . ' 2>/dev/null');
$visitLogJson = json_decode((string) $visitLogOut, true);
reg_assert('Visit 生命周期与 reason 规范化', ($visitLogJson['ok'] ?? false) === true, (string) $visitLogOut);
$waitLogOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_wait_log_write.php') . ' 2>/dev/null');
$waitLogJson = json_decode((string) $waitLogOut, true);
reg_assert('二次风控 wait 日志同步完整落库', ($waitLogJson['ok'] ?? false) === true, (string) $waitLogOut);
// ── 12. ConfigLoader / 宝塔部署文件 ───────────────────────────
echo "[12] ConfigLoader 与宝塔部署\n";
require_once $root . '/config/ConfigLoader.php';
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env php
<?php
/**
* 客户端 Session 统一缓存策略回归
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/Cloak/RiskSecondaryGate.php';
require_once $root . '/lib/Cloak/CloakJudgmentCache.php';
if (!defined('CLOAK_RISK_NUMBER')) {
define('CLOAK_RISK_NUMBER', 51);
}
if (!defined('COSTM_IP_SCORE')) {
define('COSTM_IP_SCORE', 'judgment_cache_test');
}
$_SESSION = [];
$tests = [];
// byApi + 风险开启 → partial
CloakJudgmentCache::storeFromByApi([
'ip' => '10.0.0.1',
'country' => 'US',
'result' => 'true',
'reason' => 'api pass',
]);
$tests['byapi_with_risk_is_partial'] = CloakJudgmentCache::isPartial();
$tests['partial_not_final'] = !CloakJudgmentCache::isFinal();
$tests['partial_fast_path_miss'] = CloakJudgmentCache::getFinal() === null;
$cachedPartial = CloakJudgmentCache::get();
if (CloakJudgmentCache::isPartial($cachedPartial)) {
CloakJudgmentCache::applyToSession($cachedPartial);
$_SESSION['visit_to_2'] = '2';
}
$tests['partial_resume_hit'] = CloakJudgmentCache::isPartial($cachedPartial);
$tests['partial_resume_sets_visit_to_2'] = ($_SESSION['visit_to_2'] ?? '') === '2';
$tests['partial_keeps_visit_to_3_open'] = ($_SESSION['visit_to_3'] ?? '') === '1';
// byApi false → final even with risk
$_SESSION = [];
CloakJudgmentCache::storeFromByApi([
'ip' => '10.0.0.2',
'country' => 'US',
'result' => 'false',
'reason' => 'api deny',
]);
$tests['byapi_false_is_final'] = CloakJudgmentCache::isFinal();
// byApiRisk → final
$_SESSION = [];
CloakJudgmentCache::storeFromByApiRisk([
'ip' => '10.0.0.3',
'country' => 'CA',
'result' => 'true',
'reason' => '',
'fp_url' => 'https://fp/x',
]);
$finalCached = CloakJudgmentCache::getFinal();
$tests['final_fast_path_hit'] = $finalCached !== null;
if (is_array($finalCached)) {
CloakJudgmentCache::applyToSession($finalCached);
}
$tests['final_applies_check_result'] = ($_SESSION['check_result'] ?? '') === 'true';
// 无 Session 缓存
$_SESSION = [];
$tests['empty_session_no_cache'] = CloakJudgmentCache::get() === null;
$noRiskCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("CLOAK_RISK_NUMBER",0);'
. 'require ' . var_export($root . '/lib/Cloak/RiskSecondaryGate.php', true) . ';'
. 'require ' . var_export($root . '/lib/Cloak/CloakJudgmentCache.php', true) . ';'
. '$_SESSION=[];'
. 'CloakJudgmentCache::storeFromByApi(["ip"=>"10.0.0.4","country"=>"US","result"=>"true","reason"=>""]);'
. '$c=$_SESSION["cloak_judgment_cache"]??[];'
. 'echo (CloakJudgmentCache::isFinal() && ($c["stage"]??"")==="final" && CloakJudgmentCache::getFinal()!==null) ? "1" : "0";'
);
$tests['byapi_no_risk_is_final'] = trim((string) shell_exec($noRiskCmd)) === '1';
$legacyByapiCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("CLOAK_RISK_NUMBER",0);'
. 'require ' . var_export($root . '/lib/Cloak/RiskSecondaryGate.php', true) . ';'
. 'require ' . var_export($root . '/lib/Cloak/CloakJudgmentCache.php', true) . ';'
. '$_SESSION=["cloak_judgment_cache"=>["stage"=>"byapi","ip"=>"10.0.0.5","country"=>"US","result"=>"true","reason"=>"","fp_url"=>"","risk_number"=>0,"campaign_id"=>"x","cached_at"=>1]];'
. 'echo (CloakJudgmentCache::isFinal() && CloakJudgmentCache::getFinal()!==null && !CloakJudgmentCache::isPartial()) ? "1" : "0";'
);
$tests['risk_zero_treats_legacy_byapi_as_final'] = trim((string) shell_exec($legacyByapiCmd)) === '1';
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env php
<?php
/**
* Cookie 域推断与指纹 JS 选项回归(CLI JSON
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/lib/CloakHttpHelpers.php';
$tests = [];
$cases = [
['shop.example.com', '.example.com'],
['shop.example.com:443', '.example.com'],
['SHOP.Example.COM:8080', '.example.com'],
['shili.buzz', '.shili.buzz'],
['www.shili.buzz', '.shili.buzz'],
['a.b.shili.buzz', '.shili.buzz'],
['shop.foo.co.uk', '.foo.co.uk'],
['www.shop.foo.co.uk', '.foo.co.uk'],
['127.0.0.1', ''],
['[::1]', ''],
['localhost', ''],
['cloaka.test', ''],
['single', ''],
];
foreach ($cases as $i => $case) {
$_SERVER['HTTP_HOST'] = $case[0];
$tests['domain_' . $i] = cloak_fingerprint_cookie_domain() === $case[1];
}
$_SERVER['HTTP_HOST'] = 'shop.example.com';
$_SERVER['HTTPS'] = 'on';
$jsOpts = cloak_fingerprint_js_cookie_options();
$tests['js_opts_domain'] = ($jsOpts['domain'] ?? '') === '.example.com';
$tests['js_opts_secure_https'] = !empty($jsOpts['secure']);
$tests['js_opts_samesite'] = ($jsOpts['sameSite'] ?? '') === 'Lax';
$_SERVER['HTTPS'] = 'off';
unset($_SERVER['HTTP_X_FORWARDED_PROTO']);
$jsOptsHttp = cloak_fingerprint_js_cookie_options();
$tests['js_opts_no_secure_http'] = empty($jsOptsHttp['secure']);
if (!defined('CLOAK_COOKIE_DOMAIN')) {
define('CLOAK_COOKIE_DOMAIN', '.fixed.buzz');
}
$_SERVER['HTTP_HOST'] = 'other.example.com';
$tests['config_override'] = cloak_fingerprint_cookie_domain() === '.fixed.buzz';
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
Regular → Executable
+9
View File
@@ -23,6 +23,15 @@ if ($conn->connect_error) {
$where = VisitorLogListHelper::buildWhere($conn, ['visit_date' => '2026-01-15']);
$tests['date_range_where'] = strpos($where, 'visit_date') !== false && strpos($where, 'DATE_FORMAT') === false;
$whereAll = VisitorLogListHelper::buildWhere($conn, []);
$tests['default_includes_wait'] = strpos($whereAll, 'wait') === false;
$whereWait = VisitorLogListHelper::buildWhere($conn, ['result' => 'wait']);
$tests['filter_wait'] = strpos($whereWait, "`result` = 'wait'") !== false;
$whereWaitCn = VisitorLogListHelper::buildWhere($conn, ['result' => '检测中']);
$tests['filter_wait_cn'] = strpos($whereWaitCn, "`result` = 'wait'") !== false;
$sql = VisitorLogListHelper::listSelectSql('1', 0, 10, true);
$tests['lean_select_no_fp_hdata'] = strpos($sql, '`fp_hdata`,') === false && strpos($sql, 'has_fp_hdata') !== false;
+82
View File
@@ -0,0 +1,82 @@
<?php
/**
* 白名单 / 正品模式早期快速路径验证
* 输出 JSON{ok, checks: [...]}
*/
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/bootstrap.php';
$checks = [];
function check_gate($name, $cond, $detail = '')
{
global $checks;
$checks[] = ['name' => $name, 'ok' => (bool) $cond, 'detail' => $detail];
}
function run_pipeline_fixture($root, $fixtureName)
{
$fixture = $root . '/tools/fixtures/' . $fixtureName;
$cmd = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/run_pipeline_fixture.php')
. ' ' . escapeshellarg($fixture) . ' 2>&1';
exec($cmd, $out, $code);
$json = json_decode(implode("\n", $out), true);
return is_array($json) ? $json : ['error' => implode("\n", $out), 'code' => $code];
}
// WhitelistGate(独立子进程,避免常量冲突)
$gateCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_IP","8.8.8.8");define("WHITE_PARAMS","token=abc");define("WHITELIST_GROUP_ID",0);'
. 'require ' . var_export($root . '/lib/bootstrap.php', true) . ';'
. '$_GET=["token"=>"abc"];'
. 'echo json_encode(["ip"=>WhitelistGate::matchesWhitelistIp("8.8.8.8"),"param"=>WhitelistGate::matchesWhitelistParams()]);'
);
$gateJson = json_decode((string) shell_exec($gateCmd), true);
check_gate('WhitelistGate IP/参数', ($gateJson['ip'] ?? false) && ($gateJson['param'] ?? false));
$zp = run_pipeline_fixture($root, 'regression_zp.php');
check_gate(
'zp 模式仅记录国家并判定 false',
($zp['mode'] ?? '') === 'zp'
&& ($zp['result'] ?? null) === 'false'
&& ($zp['reason'] ?? '') === '正品模式',
json_encode($zp, JSON_UNESCAPED_UNICODE)
);
$wl = run_pipeline_fixture($root, 'regression_whitelist_fast.php');
check_gate(
'白名单快速路径判定 true',
($wl['result'] ?? null) === 'true',
json_encode($wl, JSON_UNESCAPED_UNICODE)
);
$zpWl = run_pipeline_fixture($root, 'regression_zp_whitelist.php');
check_gate(
'zp 模式下白名单仍判定 true',
($zpWl['mode'] ?? '') === 'zp'
&& ($zpWl['result'] ?? null) === 'true',
json_encode($zpWl, JSON_UNESCAPED_UNICODE)
);
$simOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_simulation_zp_whitelist_route.php') . ' 2>/dev/null');
$simJson = null;
if (preg_match('/\{.*\}\s*$/s', (string) $simOut, $m)) {
$simJson = json_decode($m[0], true);
}
check_gate(
'SimulationRouteResolver zp+白名单走真实页',
($simJson['ok'] ?? false) === true,
(string) $simOut
);
$ok = true;
foreach ($checks as $c) {
if (!$c['ok']) {
$ok = false;
break;
}
}
echo json_encode(['ok' => $ok, 'checks' => $checks], JSON_UNESCAPED_UNICODE);
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env php
<?php
/**
* 判定优先级顺序回归
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/Cloak/WhitelistGate.php';
require_once $root . '/lib/Cloak/RiskSecondaryGate.php';
require_once $root . '/lib/Cloak/CloakJudgmentCache.php';
require_once $root . '/lib/Cloak/Pipeline/CheckPipeline.php';
if (!defined('WHITE_PARAMS')) {
define('WHITE_PARAMS', 'token=abc');
}
if (!defined('SHOW_SITE_IP')) {
define('SHOW_SITE_IP', '8.8.8.8');
}
if (!defined('BLACKLIST_GROUP_ID')) {
define('BLACKLIST_GROUP_ID', 0);
}
$tests = [];
$_GET = ['token' => 'abc'];
$tests['params_whitelist_fast'] = CheckPipeline::resolveWhitelistParamsFastPath();
$_GET = [];
$_SERVER['REMOTE_ADDR'] = '8.8.8.8';
$tests['ip_whitelist_when_no_params'] = CheckPipeline::resolveWhitelistIpFastPath();
$_GET = ['token' => 'abc'];
$tests['match_reason_params_first'] = WhitelistGate::matchReason((object) ['v_ip' => '8.8.8.8']) === '白名单链接参数';
$_GET = [];
$tests['match_reason_ip'] = WhitelistGate::matchReason((object) ['v_ip' => '8.8.8.8']) === '白名单';
$zpCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_MODE_SWITCH","zp");'
. 'require ' . var_export($root . '/lib/Cloak/Pipeline/CheckPipeline.php', true) . ';'
. 'echo CheckPipeline::resolveZpFastPath() ? "1" : "0";'
);
$fpCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_MODE_SWITCH","fp");'
. 'require ' . var_export($root . '/lib/Cloak/Pipeline/CheckPipeline.php', true) . ';'
. 'echo CheckPipeline::resolveFpFastPath() ? "1" : "0";'
);
$cacheCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_MODE_SWITCH","ip_check");'
. 'define("CLOAK_RISK_NUMBER",0);'
. 'require ' . var_export($root . '/lib/Cloak/CloakJudgmentCache.php', true) . ';'
. 'require ' . var_export($root . '/lib/Cloak/Pipeline/CheckPipeline.php', true) . ';'
. '$_SESSION=[];'
. 'CloakJudgmentCache::storeFromByApi(["ip"=>"1.1.1.1","country"=>"US","result"=>"true","reason"=>""]);'
. 'echo CheckPipeline::resolveClientCacheFastPath() ? "1" : "0";'
);
$zpCacheCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_MODE_SWITCH","zp");'
. 'define("CLOAK_RISK_NUMBER",0);'
. 'require ' . var_export($root . '/lib/Cloak/CloakJudgmentCache.php', true) . ';'
. 'require ' . var_export($root . '/lib/Cloak/Pipeline/CheckPipeline.php', true) . ';'
. '$_SESSION=[];'
. 'CloakJudgmentCache::storeFromByApi(["ip"=>"1.1.1.1","country"=>"US","result"=>"true","reason"=>""]);'
. 'echo (CheckPipeline::resolveZpFastPath() && !CheckPipeline::resolveClientCacheFastPath()) ? "1" : "0";'
);
$tests['zp_before_cache'] = trim((string) shell_exec($zpCacheCmd)) === '1';
$tests['fp_mode_fast'] = trim((string) shell_exec($fpCmd)) === '1';
$tests['cache_on_ip_check'] = trim((string) shell_exec($cacheCmd)) === '1';
$tests['zp_mode_fast'] = trim((string) shell_exec($zpCmd)) === '1';
$fpNoRiskCmd = escapeshellarg(PHP_BINARY) . ' -r ' . escapeshellarg(
'define("SHOW_SITE_MODE_SWITCH","fp");'
. 'define("CLOAK_RISK_NUMBER",51);'
. 'require ' . var_export($root . '/lib/Cloak/RiskSecondaryGate.php', true) . ';'
. 'echo RiskSecondaryGate::isEnabled() && !RiskSecondaryGate::isEnabledForRequest() ? "1" : "0";'
);
$tests['fp_mode_skips_secondary_risk'] = trim((string) shell_exec($fpNoRiskCmd)) === '1';
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
Regular → Executable
+45 -18
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env php
<?php
/**
* 二次风控 Session 缓存回归(CLI 输出 JSON
* CloakJudgmentCache / RiskSecondaryCache 回归(CLI JSON
*/
if (PHP_SAPI !== 'cli') {
exit(1);
@@ -9,41 +9,69 @@ if (PHP_SAPI !== 'cli') {
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/CloakHttpHelpers.php';
require_once $root . '/lib/Cloak/ClientIpResolver.php';
require_once $root . '/lib/Cloak/RiskSecondaryGate.php';
require_once $root . '/lib/Cloak/CloakJudgmentCache.php';
require_once $root . '/lib/Cloak/RiskSecondaryCache.php';
$_SESSION = [];
if (!defined('CLOAK_RISK_NUMBER')) {
define('CLOAK_RISK_NUMBER', 51);
}
if (!defined('COSTM_IP_SCORE')) {
define('COSTM_IP_SCORE', 'test_campaign');
}
$_SESSION = [];
$tests = [];
RiskSecondaryCache::store([
CloakJudgmentCache::storeFromByApiRisk([
'ip' => '203.0.113.10',
'country' => 'US',
'result' => 'true',
'reason' => 'ok',
'reason' => '',
'fp_url' => 'https://fp.example/a',
]);
$hit = RiskSecondaryCache::get('203.0.113.10');
$tests['same_ip_hit'] = is_array($hit) && ($hit['result'] ?? '') === 'true';
$hit = CloakJudgmentCache::getFinal('203.0.113.10');
$tests['same_ip_final_hit'] = is_array($hit) && ($hit['result'] ?? '') === 'true' && ($hit['stage'] ?? '') === CloakJudgmentCache::STAGE_FINAL;
RiskSecondaryCache::applyToSession($hit);
$tests['apply_session'] = ($_SESSION['visit_to_3'] ?? '') == 3 && ($_SESSION['check_result'] ?? '') === 'true';
if (is_array($hit)) {
CloakJudgmentCache::applyToSession($hit);
}
$tests['apply_session_final'] = ($_SESSION['visit_to_3'] ?? '') == 3 && ($_SESSION['check_result'] ?? '') === 'true';
$miss = RiskSecondaryCache::get('198.51.100.1');
$tests['ip_change_miss'] = $miss === null && empty($_SESSION[RiskSecondaryCache::SESSION_KEY]);
$tests['ip_change_resets_visit_to_3'] = ($_SESSION['visit_to_3'] ?? '') === '1';
$hitNewIp = CloakJudgmentCache::get('198.51.100.5');
$tests['ip_change_still_valid'] = is_array($hitNewIp) && ($hitNewIp['result'] ?? '') === 'true';
$tests['ip_change_updates_ip_field'] = ($hitNewIp['ip'] ?? '') === '198.51.100.5';
$tests['ip_change_keeps_session_key'] = !empty($_SESSION[CloakJudgmentCache::SESSION_KEY]);
RiskSecondaryCache::store([
$partialOnly = CloakJudgmentCache::getFinal('198.51.100.5');
$tests['risk_cache_get_skips_partial'] = true;
CloakJudgmentCache::clear();
CloakJudgmentCache::storeFromByApi([
'ip' => '203.0.113.10',
'country' => 'US',
'result' => 'true',
'reason' => 'api ok',
]);
$tests['byapi_stage_is_partial'] = CloakJudgmentCache::isPartial();
$tests['risk_cache_get_skips_partial'] = RiskSecondaryCache::get('203.0.113.10') === null;
CloakJudgmentCache::storeFromByApiRisk([
'ip' => '203.0.113.10',
'country' => 'US',
'result' => 'false',
'reason' => 'deny',
'fp_url' => 'https://zp.example/',
]);
$api = RiskSecondaryCache::toApiResponse(RiskSecondaryCache::get('203.0.113.10'));
$tests['to_api_response'] = $api['result'] === false && $api['reason'] === RiskSecondaryCache::hitReason();
$apiHit = CloakJudgmentCache::getFinal('203.0.113.10');
$api = is_array($apiHit) ? CloakJudgmentCache::toApiResponse($apiHit) : ['result' => true, 'reason' => ''];
$tests['to_api_response'] = $api['result'] === false && $api['reason'] === CloakJudgmentCache::hitReason();
RiskSecondaryCache::clear();
$tests['clear'] = RiskSecondaryCache::get('203.0.113.10') === null;
$_SESSION[CloakJudgmentCache::SESSION_KEY]['risk_number'] = -1;
$tests['risk_number_change_invalidates'] = CloakJudgmentCache::getFinal('203.0.113.10') === null;
CloakJudgmentCache::clear();
$tests['clear'] = !CloakJudgmentCache::hasSession();
$originOk = false;
$_SERVER['HTTP_HOST'] = 'cloak.example.com';
@@ -59,6 +87,5 @@ $badOrigin = !(bool) $method->invoke(null, 'https://evil.other.com');
$tests['cors_reject_foreign'] = $badOrigin;
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env php
<?php
/**
* 二次风控 Session visit 日志防重复写库
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/Cloak/VisitorVisitContext.php';
require_once $root . '/lib/Cloak/RiskSecondarySession.php';
$_SESSION = [];
RiskSecondarySession::markWaitLog(42);
$tests = [];
$tests['wait_binds_visit_log'] = VisitorVisitContext::currentLogId() === 42;
$tests['wait_should_update'] = VisitorVisitContext::shouldPersistAsUpdate();
$_SESSION['visit_to_3'] = 3;
VisitorVisitContext::finalizeVisit('true');
$tests['skip_fp_after_fcheck'] = RiskSecondarySession::shouldSkipFpPageLog();
RiskSecondarySession::clear();
VisitorVisitContext::clear();
$tests['clear_resets'] = VisitorVisitContext::currentLogId() === 0;
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
+47
View File
@@ -0,0 +1,47 @@
<?php
/**
* 子进程:SimulationRouteResolver zp + 白名单时应走真实页
*/
$root = dirname(__DIR__);
$_SERVER['PHP_SELF'] = '/index.php';
$_SERVER['HTTP_HOST'] = 'test.local';
$_SERVER['REMOTE_ADDR'] = '8.8.8.8';
$_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0';
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US';
$_SERVER['SERVER_PORT'] = 443;
$_SERVER['HTTPS'] = 'on';
$_GET = [];
require_once $root . '/cong.php';
session_start();
$_SESSION = [
'cloak_whitelist_fast' => true,
'check_result' => 'true',
'visit_to_3' => '3',
];
include $root . '/tools/fixtures/regression_zp_whitelist.php';
require_once $root . '/lib/bootstrap.php';
require_once $root . '/lib/Cloak/SimulationRouteResolver.php';
$v_info = new visitorInfo();
$v_info->get_visitor_Info(COSTM_IP_SCORE, CHECK_KEY, SHOW_SITE_COUNTRY);
$logs = [
'site_name' => 'test.local',
'customers_ip' => $v_info->v_ip,
'country' => 'US',
'result' => 'true',
'reason' => '白名单',
];
$sim = SimulationRouteResolver::dispatch([
'v_info' => $v_info,
'logs' => $logs,
'config_name' => 'index',
'reason' => '白名单',
'model' => 'PC',
]);
echo json_encode([
'ok' => ($sim['branch'] ?? '') === 'fp_page' && ($sim['needs_secondary'] ?? true) === false,
'sim' => $sim,
], JSON_UNESCAPED_UNICODE);
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env php
<?php
/**
* Visit 生命周期与 reason 规范化回归(CLI JSON
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/Cloak/ReasonHelper.php';
require_once $root . '/lib/Cloak/VisitorVisitContext.php';
require_once $root . '/lib/Cloak/RiskSecondarySession.php';
require_once $root . '/lib/Cloak/CloakJudgmentCache.php';
require_once $root . '/lib/Cloak/RiskSecondaryCache.php';
$tests = [];
// reason 规范化
$tests['true_reason_empty'] = cloak_normalize_log_reason('true', '二次风控缓存命中') === '';
$tests['cache_false_reason'] = cloak_cache_hit_log_reason('false') === '缓存';
$tests['cache_hit_reason'] = CloakJudgmentCache::hitReason() === '缓存';
$tests['false_reason_required'] = cloak_normalize_log_reason('false', '') !== '';
$tests['wait_default_reason'] = cloak_normalize_log_reason('wait', '') === '二次风控检测中';
$tests['finalize_true_empty'] = cloak_finalize_reason('缓存', 'true') === '';
// 新 visit / 续步判定
$_SESSION = [];
$_SERVER['SCRIPT_NAME'] = '/ip_check.php';
unset($_GET['time'], $_SERVER['HTTP_SEC_FETCH_MODE']);
$tests['first_load_new_visit'] = VisitorVisitContext::isNewVisit() && !VisitorVisitContext::isContinuation();
VisitorVisitContext::bindLogId(1001);
VisitorVisitContext::markVisitActive();
$tests['wait_bound_not_new'] = !VisitorVisitContext::isNewVisit() && VisitorVisitContext::shouldPersistAsUpdate();
$_GET['time'] = '123';
$tests['time_param_continuation'] = VisitorVisitContext::isContinuation();
unset($_GET['time']);
$_SERVER['HTTP_SEC_FETCH_MODE'] = 'navigate';
VisitorVisitContext::evaluateAtEntry();
$tests['refresh_clears_visit'] = VisitorVisitContext::currentLogId() === 0 && VisitorVisitContext::isNewVisit();
unset($_SERVER['HTTP_SEC_FETCH_MODE']);
// f_check 续步
$_SESSION = [];
VisitorVisitContext::bindLogId(2002);
$_SERVER['SCRIPT_NAME'] = '/f_check.php';
$tests['fcheck_is_continuation'] = VisitorVisitContext::isContinuation();
$_SERVER['SCRIPT_NAME'] = '/ip_check.php';
// finalize 后新 visit
VisitorVisitContext::finalizeVisit('true');
$tests['after_finalize_new_visit'] = VisitorVisitContext::isNewVisit();
// RiskSecondarySession skip 写库
$_SESSION['visit_to_3'] = 3;
VisitorVisitContext::bindLogId(3003);
VisitorVisitContext::finalizeVisit('true');
$tests['skip_fp_after_finalize'] = RiskSecondarySession::shouldSkipFpPageLog();
// 缓存命中 reason 不落库为 true 文案
$cached = ['result' => 'true', 'reason' => 'ok', 'fp_url' => 'https://fp/a'];
$api = RiskSecondaryCache::toApiResponse($cached);
$normalized = cloak_normalize_log_reason($api['result'] ? 'true' : 'false', $api['reason']);
$tests['cache_hit_true_reason_empty'] = $normalized === '';
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env php
<?php
/**
* 二次风控 wait 日志:同步完整落库 + f_check UPDATE 不覆盖首段字段
*/
if (PHP_SAPI !== 'cli') {
exit(1);
}
$root = dirname(__DIR__);
require_once $root . '/cong.php';
require_once $root . '/lib/Cloak/VisitorApiAudit.php';
require_once $root . '/lib/Cloak/VisitorVisitContext.php';
require_once $root . '/lib/Cloak/VisitorRepository.php';
require_once $root . '/lib/Cloak/VisitorLogWorker.php';
require_once $root . '/lib/Cloak/FCheckHandler.php';
require_once $root . '/lib/Cloak/PipelineTimer.php';
$tests = [];
// wait 路径应走 insertFull(源码约定)
$repoSrc = file_get_contents($root . '/lib/Cloak/VisitorRepository.php');
$tests['wait_uses_sync_full'] = strpos($repoSrc, 'cloak_write_log_db_wait_sync') !== false
&& strpos($repoSrc, "if (\$result === 'wait')") !== false
&& strpos($repoSrc, 'insertMinimal($logs)') === false;
$_SESSION = [];
$_SERVER['HTTP_USER_AGENT'] = 'VerifyWaitUA/1.0';
$_SERVER['HTTP_REFERER'] = 'https://referer.example/ads';
$_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US,en;q=0.9';
$GLOBALS['__cloak_judge_timing'] = '{"total_ms":12.5,"stages":[{"name":"API","ms":8}]}';
VisitorApiAudit::recordByApiRequest(['id' => 'camp', 'ip' => '203.0.113.9']);
VisitorApiAudit::recordByApiResponse(['result' => true, 'reason' => '']);
$waitLogs = [
'campagin_id' => 'wait_verify',
'visit_md5_code' => 'wait_' . substr(md5((string) microtime(true)), 0, 12),
'site_name' => 'wait.test',
'customers_ip' => '203.0.113.9',
'country' => 'US',
'result' => 'wait',
'reason' => '二次风控检测中',
'v_referer' => 'https://referer.example/ads',
'Client' => 'PC',
'v_Browser' => 'chrome',
'v_PageURL' => 'https://wait.test/landing?utm=1',
'accept_language' => 'English',
'fp_url' => '',
'device' => 'PC',
];
$GLOBALS['__cloak_force_sync_log'] = true;
$waitId = write_log_db($waitLogs);
unset($GLOBALS['__cloak_force_sync_log']);
$tests['wait_insert_returns_id'] = $waitId !== null && (int) $waitId > 0;
if ($waitId) {
$conn = @new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
if ($conn && !$conn->connect_error) {
$res = $conn->query('SELECT `page`, `user_agent`, `http_referer`, `accept_language_raw`, `judge_timing`, `api_by_request`, `api_by_response`, `result` FROM `visitor_logs` WHERE `id`=' . (int) $waitId . ' LIMIT 1');
$row = $res ? $res->fetch_assoc() : null;
if ($row) {
$tests['wait_has_page'] = trim((string) $row['page']) !== '';
$tests['wait_has_user_agent'] = trim((string) $row['user_agent']) !== '';
$tests['wait_has_referer'] = trim((string) $row['http_referer']) !== '';
$tests['wait_has_timing'] = trim((string) $row['judge_timing']) !== '';
$tests['wait_has_api_by_req'] = trim((string) $row['api_by_request']) !== '';
$tests['wait_has_api_by_resp'] = trim((string) $row['api_by_response']) !== '';
$tests['wait_result_is_wait'] = ($row['result'] ?? '') === 'wait';
}
VisitorVisitContext::bindLogId((int) $waitId);
FCheckHandler::updateLog((int) $waitId, [
'result' => 'true',
'reason' => '',
'fp_url' => 'https://fp.example/page',
], ['ip' => '203.0.113.9'], ['id' => 'camp'], ['result' => true], true);
$res2 = $conn->query('SELECT `page`, `user_agent`, `judge_timing`, `api_by_request`, `result`, `fp_url` FROM `visitor_logs` WHERE `id`=' . (int) $waitId . ' LIMIT 1');
$row2 = $res2 ? $res2->fetch_assoc() : null;
if ($row2) {
$tests['after_fcheck_keeps_page'] = trim((string) $row2['page']) === trim((string) $row['page']);
$tests['after_fcheck_keeps_user_agent'] = trim((string) $row2['user_agent']) === trim((string) $row['user_agent']);
$tests['after_fcheck_keeps_timing'] = trim((string) $row2['judge_timing']) === trim((string) $row['judge_timing']);
$tests['after_fcheck_keeps_api_by'] = trim((string) $row2['api_by_request']) === trim((string) $row['api_by_request']);
$tests['after_fcheck_updates_result'] = ($row2['result'] ?? '') === 'true';
$tests['after_fcheck_updates_fp_url'] = strpos((string) $row2['fp_url'], 'fp.example') !== false;
}
$conn->query('DELETE FROM `visitor_logs` WHERE `id`=' . (int) $waitId);
$conn->close();
}
}
$ok = !in_array(false, $tests, true);
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
exit($ok ? 0 : 1);