日志升级与缓存修复最终版
This commit is contained in:
Regular → Executable
Regular → Executable
@@ -22,6 +22,11 @@ define('REDIS_PASSWORD', '');
|
|||||||
// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---
|
// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---
|
||||||
define('VISITOR_LOG_ASYNC', 'ON');
|
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_ENABLED', 'ON');
|
||||||
define('CLOAK_GEOIP_DB_PATH', __DIR__ . '/storage/runtime/c7f2a9e1/cc_idx.dat');
|
define('CLOAK_GEOIP_DB_PATH', __DIR__ . '/storage/runtime/c7f2a9e1/cc_idx.dat');
|
||||||
define('CLOAK_GEOIP_CACHE_TTL', 2592000);
|
define('CLOAK_GEOIP_CACHE_TTL', 2592000);
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
Regular → Executable
+111
-26
@@ -4,7 +4,7 @@ require_once(dirname(__DIR__) . "/cong.php");
|
|||||||
require_once(dirname(__DIR__) . "/lib/DbHelper.php");
|
require_once(dirname(__DIR__) . "/lib/DbHelper.php");
|
||||||
require_once(dirname(__DIR__) . "/lib/Cloak/PipelineTimer.php");
|
require_once(dirname(__DIR__) . "/lib/Cloak/PipelineTimer.php");
|
||||||
require_once(dirname(__DIR__) . "/lib/Cloak/VisitorLogSchema.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(
|
$reasons = array(
|
||||||
"blank_referrer" => "过滤无来源的访问",
|
"blank_referrer" => "过滤无来源的访问",
|
||||||
@@ -44,9 +44,15 @@ $reasons = array(
|
|||||||
"time_of_day" => "不可访问的日期",
|
"time_of_day" => "不可访问的日期",
|
||||||
);
|
);
|
||||||
|
|
||||||
$jsonResult = array();
|
$servername = "localhost";
|
||||||
$conn = new mysqli('localhost', DB_USERNAME, DB_PASSWORD, DB_NAME);
|
$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) {
|
if ($conn->connect_error) {
|
||||||
$error = "Connection failed: " . $conn->connect_error;
|
$error = "Connection failed: " . $conn->connect_error;
|
||||||
$handle = fopen(dirname(__FILE__) . "/err.txt", "a+");
|
$handle = fopen(dirname(__FILE__) . "/err.txt", "a+");
|
||||||
@@ -59,40 +65,119 @@ if ($conn->connect_error) {
|
|||||||
$jsonResult["data"] = array();
|
$jsonResult["data"] = array();
|
||||||
} else {
|
} else {
|
||||||
VisitorLogSchema::ensureColumns($conn);
|
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["code"] = 0;
|
||||||
$jsonResult["msg"] = "success";
|
$jsonResult["msg"] = "success";
|
||||||
$jsonResult["count"] = 0;
|
$jsonResult["count"] = 0;
|
||||||
$jsonResult["data"] = array();
|
$jsonResult["data"] = array();
|
||||||
} else {
|
} else { // 默认分页数据
|
||||||
$pageNumber = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
|
// 获取当前页码(默认为第1页)
|
||||||
$recordsPerPage = isset($_GET['limit']) ? max(1, min(200, (int) $_GET['limit'])) : 50;
|
$pageNumber = isset($_GET['page']) ? (int) $_GET['page'] : 1;
|
||||||
|
|
||||||
|
// 设置每页显示的记录条数
|
||||||
|
$recordsPerPage = isset($_GET['limit']) ? (int) $_GET['limit'] : 50;
|
||||||
|
|
||||||
|
// 计算起始位置
|
||||||
$startPosition = ($pageNumber - 1) * $recordsPerPage;
|
$startPosition = ($pageNumber - 1) * $recordsPerPage;
|
||||||
$where = VisitorLogListHelper::buildWhere($conn, $_GET);
|
$map = array();
|
||||||
|
$where = 1;
|
||||||
$sql = VisitorLogListHelper::listSelectSql($where, $startPosition, $recordsPerPage, false);
|
|
||||||
$result = $conn->query($sql);
|
if(!empty($_GET['visit_date'])) {
|
||||||
$jsonData = array();
|
$map[] = "DATE_FORMAT(`visit_date`, '%Y-%m-%d') LIKE '" . cloak_db_escape($conn, $_GET['visit_date']) . "'";
|
||||||
|
|
||||||
if ($result) {
|
|
||||||
while ($row = $result->fetch_assoc()) {
|
|
||||||
$jsonData[] = VisitorLogListHelper::formatListRow($row, $countries, $reasons);
|
|
||||||
}
|
|
||||||
$result->free();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$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();
|
||||||
|
|
||||||
|
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; // 访问次数叠加
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新访问次数
|
||||||
|
$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["code"] = 0;
|
||||||
$jsonResult["msg"] = "";
|
$jsonResult["msg"] = "";
|
||||||
$jsonResult["count"] = VisitorLogListHelper::countWhere($conn, $where);
|
$jsonResult["count"] = $countRow['total'];
|
||||||
$jsonResult["data"] = $jsonData;
|
$jsonResult["data"] = $jsonData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
$conn->close();
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
echo json_encode($jsonResult);exit;
|
||||||
echo json_encode($jsonResult);
|
|
||||||
exit;
|
|
||||||
|
|||||||
Regular → Executable
Vendored
+2
-5
@@ -28,7 +28,7 @@ $cloak_key = CHECK_KEY;
|
|||||||
$campagin_id = COSTM_IP_SCORE;
|
$campagin_id = COSTM_IP_SCORE;
|
||||||
$time = time();
|
$time = time();
|
||||||
$ckey = sha1($campagin_id . sha1($time . $cloak_key));
|
$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');
|
readfile(__DIR__ . '/cloakjs');
|
||||||
echo "\n";
|
echo "\n";
|
||||||
@@ -151,10 +151,7 @@ var virtual = <?php echo (int) $is_virtual; ?>;
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
function cloakFinishFingerprint() {
|
function cloakFinishFingerprint() {
|
||||||
var fpCookieOpts = { path: '/', expires: 1, sameSite: 'Lax' };
|
var fpCookieOpts = <?php echo json_encode($fp_js_cookie_opts, JSON_UNESCAPED_UNICODE); ?>;
|
||||||
<?php if ($fp_cookie_domain !== ''): ?>
|
|
||||||
fpCookieOpts.domain = <?php echo json_encode($fp_cookie_domain, JSON_UNESCAPED_UNICODE); ?>;
|
|
||||||
<?php endif; ?>
|
|
||||||
var fpCookies = (typeof Cookies !== 'undefined' && Cookies.withAttributes)
|
var fpCookies = (typeof Cookies !== 'undefined' && Cookies.withAttributes)
|
||||||
? Cookies.withAttributes(fpCookieOpts)
|
? Cookies.withAttributes(fpCookieOpts)
|
||||||
: Cookies;
|
: Cookies;
|
||||||
|
|||||||
Regular → Executable
@@ -135,6 +135,9 @@ if (!$isLocked && !empty($_POST['action']) && $_POST['action'] === 'install') {
|
|||||||
. '// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---' . PHP_EOL
|
. '// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---' . PHP_EOL
|
||||||
. 'define(\'VISITOR_LOG_ASYNC\', \'ON\');' . PHP_EOL
|
. 'define(\'VISITOR_LOG_ASYNC\', \'ON\');' . PHP_EOL
|
||||||
. 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
|
. '// --- GeoIP 本地国家库(MaxMind GeoLite2 Country)---' . PHP_EOL
|
||||||
. 'define(\'CLOAK_GEOIP_ENABLED\', \'ON\');' . PHP_EOL
|
. 'define(\'CLOAK_GEOIP_ENABLED\', \'ON\');' . PHP_EOL
|
||||||
. 'define(\'CLOAK_GEOIP_DB_PATH\', __DIR__ . \'/storage/runtime/c7f2a9e1/cc_idx.dat\');' . PHP_EOL
|
. 'define(\'CLOAK_GEOIP_DB_PATH\', __DIR__ . \'/storage/runtime/c7f2a9e1/cc_idx.dat\');' . PHP_EOL
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ require_once __DIR__ . '/lib/CloakHttpHelpers.php';
|
|||||||
require_once __DIR__ . '/lib/Cloak/CloakSession.php';
|
require_once __DIR__ . '/lib/Cloak/CloakSession.php';
|
||||||
|
|
||||||
CloakSession::start();
|
CloakSession::start();
|
||||||
|
require_once __DIR__ . '/lib/Cloak/VisitorVisitContext.php';
|
||||||
|
VisitorVisitContext::evaluateAtEntry();
|
||||||
|
if (VisitorVisitContext::isNewVisit()) {
|
||||||
|
VisitorVisitContext::markVisitActive();
|
||||||
|
}
|
||||||
// $begin_time = date("H:i:s");
|
// $begin_time = date("H:i:s");
|
||||||
// include("ip_check_config.php");
|
// include("ip_check_config.php");
|
||||||
|
|
||||||
|
|||||||
@@ -113,3 +113,11 @@
|
|||||||
2026-06-15 20:31:20 | 写入数据库 | false | indBXSXX | async=1
|
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 | 127.0.0.1 |
|
||||||
2026-06-15 20:31:51 | 写入数据库 | false | indBXSXX | async=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
|
||||||
|
|||||||
Executable
+272
@@ -0,0 +1,272 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/ClientIpResolver.php';
|
||||||
|
require_once __DIR__ . '/GeoIpCountryCache.php';
|
||||||
|
require_once __DIR__ . '/GeoIpCountryResolver.php';
|
||||||
|
require_once __DIR__ . '/FpUrlHelper.php';
|
||||||
|
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端 Session(PHPSESSID)统一判定缓存:byApi 阶段缓存 + byApiRisk 最终缓存。
|
||||||
|
*/
|
||||||
|
class CloakJudgmentCache
|
||||||
|
{
|
||||||
|
const SESSION_KEY = 'cloak_judgment_cache';
|
||||||
|
const STAGE_BYAPI = 'byapi';
|
||||||
|
const STAGE_FINAL = 'final';
|
||||||
|
|
||||||
|
public static function hasSession()
|
||||||
|
{
|
||||||
|
return !empty($_SESSION[self::SESSION_KEY])
|
||||||
|
&& is_array($_SESSION[self::SESSION_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $currentIp
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public static function get($currentIp = null)
|
||||||
|
{
|
||||||
|
if (!self::hasSession()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cached = $_SESSION[self::SESSION_KEY];
|
||||||
|
if (!self::isConfigValid($cached)) {
|
||||||
|
self::clear();
|
||||||
|
$_SESSION['visit_to_3'] = '1';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = ClientIpResolver::normalizeIp(
|
||||||
|
$currentIp !== null && $currentIp !== '' ? $currentIp : ClientIpResolver::resolve()
|
||||||
|
);
|
||||||
|
$cachedIp = ClientIpResolver::normalizeIp($cached['ip'] ?? '');
|
||||||
|
|
||||||
|
if ($ip !== null && $cachedIp !== null && $ip !== $cachedIp) {
|
||||||
|
self::refreshCountryForCurrentIp($ip);
|
||||||
|
$cached = $_SESSION[self::SESSION_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二次风控关闭(CLOAK_RISK_NUMBER=0 等)时,byApi 阶段缓存即视为最终缓存。
|
||||||
|
*
|
||||||
|
* @param array $cached
|
||||||
|
*/
|
||||||
|
public static function isEffectivelyFinal(array $cached)
|
||||||
|
{
|
||||||
|
$stage = (string) ($cached['stage'] ?? '');
|
||||||
|
if ($stage === self::STAGE_FINAL) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($stage === self::STAGE_BYAPI && !RiskSecondaryGate::isEnabled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅返回最终缓存(可短路全流程)。
|
||||||
|
*
|
||||||
|
* @param string|null $currentIp
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public static function getFinal($currentIp = null)
|
||||||
|
{
|
||||||
|
$cached = self::get($currentIp);
|
||||||
|
if ($cached === null || !self::isEffectivelyFinal($cached)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|null $cached
|
||||||
|
*/
|
||||||
|
public static function isFinal($cached = null)
|
||||||
|
{
|
||||||
|
if ($cached === null) {
|
||||||
|
$cached = self::get();
|
||||||
|
}
|
||||||
|
return is_array($cached) && self::isEffectivelyFinal($cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|null $cached
|
||||||
|
*/
|
||||||
|
public static function isPartial($cached = null)
|
||||||
|
{
|
||||||
|
if ($cached === null) {
|
||||||
|
$cached = self::get();
|
||||||
|
}
|
||||||
|
if (!is_array($cached) || ($cached['stage'] ?? '') !== self::STAGE_BYAPI) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return RiskSecondaryGate::isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* byApi 完成后写入;按风险系数决定 stage。
|
||||||
|
* CLOAK_RISK_NUMBER=0(或未启用二次风控)时,byApi 结果直接记为 final。
|
||||||
|
*
|
||||||
|
* @param array{result:string,reason?:string,ip?:string,country?:string,fp_url?:string} $payload
|
||||||
|
*/
|
||||||
|
public static function storeFromByApi(array $payload)
|
||||||
|
{
|
||||||
|
$result = ($payload['result'] ?? '') === 'true' ? 'true' : 'false';
|
||||||
|
$stage = self::STAGE_FINAL;
|
||||||
|
if ($result === 'true' && RiskSecondaryGate::isEnabled()) {
|
||||||
|
$stage = self::STAGE_BYAPI;
|
||||||
|
}
|
||||||
|
self::writeCache($stage, $payload);
|
||||||
|
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* byApiRisk 完成后写入最终缓存。
|
||||||
|
*
|
||||||
|
* @param array{result:string,reason?:string,fp_url?:string,ip?:string,country?:string} $payload
|
||||||
|
*/
|
||||||
|
public static function storeFromByApiRisk(array $payload)
|
||||||
|
{
|
||||||
|
self::writeCache(self::STAGE_FINAL, $payload);
|
||||||
|
self::applyToSession($_SESSION[self::SESSION_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $cached
|
||||||
|
*/
|
||||||
|
public static function applyToSession(array $cached)
|
||||||
|
{
|
||||||
|
$result = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
|
||||||
|
$_SESSION['check_result'] = $result;
|
||||||
|
$_SESSION['visit_to_2'] = '2';
|
||||||
|
|
||||||
|
if (self::isEffectivelyFinal($cached)) {
|
||||||
|
$_SESSION['visit_to_3'] = '3';
|
||||||
|
} else {
|
||||||
|
$_SESSION['visit_to_3'] = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($cached['ip'])) {
|
||||||
|
$_SESSION['gcu_ip'] = $cached['ip'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('country', $cached)) {
|
||||||
|
$_SESSION['gcu_country'] = (string) $cached['country'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $v_info visitorInfo
|
||||||
|
* @param array $cached
|
||||||
|
*/
|
||||||
|
public static function applyToVisitorInfo($v_info, array $cached)
|
||||||
|
{
|
||||||
|
if (!is_object($v_info)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!empty($cached['ip'])) {
|
||||||
|
$v_info->v_ip = $cached['ip'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('country', $cached) && (string) $cached['country'] !== '') {
|
||||||
|
$v_info->v_country = (string) $cached['country'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clear()
|
||||||
|
{
|
||||||
|
unset($_SESSION[self::SESSION_KEY]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function hitReason()
|
||||||
|
{
|
||||||
|
return '缓存';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $cached
|
||||||
|
* @return array{result:bool,reason:string,link:string}
|
||||||
|
*/
|
||||||
|
public static function toApiResponse(array $cached)
|
||||||
|
{
|
||||||
|
$passed = ($cached['result'] ?? '') === 'true';
|
||||||
|
$link = (string) ($cached['fp_url'] ?? '');
|
||||||
|
|
||||||
|
if ($passed && $link === '') {
|
||||||
|
$link = FpUrlHelper::fallbackUrl();
|
||||||
|
}
|
||||||
|
if (!$passed && $link === '' && defined('DB_ZP')) {
|
||||||
|
$link = (string) DB_ZP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'result' => $passed,
|
||||||
|
'reason' => self::hitReason(),
|
||||||
|
'link' => $link,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $stage
|
||||||
|
* @param array $payload
|
||||||
|
*/
|
||||||
|
private static function writeCache($stage, array $payload)
|
||||||
|
{
|
||||||
|
$ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
|
||||||
|
if ($ip === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$country = (string) ($payload['country'] ?? '');
|
||||||
|
if ($country === '' && !empty($_SESSION['gcu_country'])) {
|
||||||
|
$country = (string) $_SESSION['gcu_country'];
|
||||||
|
}
|
||||||
|
if ($country === '') {
|
||||||
|
$country = (string) (GeoIpCountryResolver::resolve($ip) ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION[self::SESSION_KEY] = [
|
||||||
|
'stage' => $stage,
|
||||||
|
'ip' => $ip,
|
||||||
|
'country' => $country,
|
||||||
|
'result' => ($payload['result'] ?? '') === 'true' ? 'true' : 'false',
|
||||||
|
'reason' => (string) ($payload['reason'] ?? ''),
|
||||||
|
'fp_url' => (string) ($payload['fp_url'] ?? ''),
|
||||||
|
'risk_number' => defined('CLOAK_RISK_NUMBER') ? (int) CLOAK_RISK_NUMBER : 0,
|
||||||
|
'campaign_id' => defined('COSTM_IP_SCORE') ? (string) COSTM_IP_SCORE : '',
|
||||||
|
'cached_at' => time(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $currentIp
|
||||||
|
*/
|
||||||
|
private static function refreshCountryForCurrentIp($currentIp)
|
||||||
|
{
|
||||||
|
if (!self::hasSession()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$cached = $_SESSION[self::SESSION_KEY];
|
||||||
|
$cached['ip'] = $currentIp;
|
||||||
|
$cached['country'] = (string) (GeoIpCountryResolver::resolve($currentIp) ?? '');
|
||||||
|
$_SESSION[self::SESSION_KEY] = $cached;
|
||||||
|
$_SESSION['gcu_ip'] = $currentIp;
|
||||||
|
$_SESSION['gcu_country'] = $cached['country'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $cached
|
||||||
|
*/
|
||||||
|
private static function isConfigValid(array $cached)
|
||||||
|
{
|
||||||
|
if (defined('CLOAK_RISK_NUMBER') && (int) ($cached['risk_number'] ?? -1) !== (int) CLOAK_RISK_NUMBER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (defined('COSTM_IP_SCORE') && (string) ($cached['campaign_id'] ?? '') !== (string) COSTM_IP_SCORE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Regular → Executable
+13
-10
@@ -18,7 +18,7 @@ class CloakSession
|
|||||||
session_set_cookie_params([
|
session_set_cookie_params([
|
||||||
'lifetime' => 0,
|
'lifetime' => 0,
|
||||||
'path' => '/',
|
'path' => '/',
|
||||||
'domain' => $domain,
|
'domain' => ltrim($domain, '.'),
|
||||||
'secure' => function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
'secure' => function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
||||||
'httponly' => true,
|
'httponly' => true,
|
||||||
'samesite' => 'Lax',
|
'samesite' => 'Lax',
|
||||||
@@ -27,7 +27,7 @@ class CloakSession
|
|||||||
session_set_cookie_params(
|
session_set_cookie_params(
|
||||||
0,
|
0,
|
||||||
'/',
|
'/',
|
||||||
$domain,
|
ltrim($domain, '.'),
|
||||||
function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
function_exists('cloak_request_is_https') && cloak_request_is_https(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@@ -80,7 +80,13 @@ class CloakFcheckCors
|
|||||||
return false;
|
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) {
|
if ($requestHost !== '' && strcasecmp($originHost, $requestHost) === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -103,18 +109,15 @@ class CloakFcheckCors
|
|||||||
*/
|
*/
|
||||||
private static function registrableSuffix($host)
|
private static function registrableSuffix($host)
|
||||||
{
|
{
|
||||||
|
if (function_exists('cloak_registrable_domain')) {
|
||||||
|
return cloak_registrable_domain($host);
|
||||||
|
}
|
||||||
|
|
||||||
$host = strtolower(trim($host));
|
$host = strtolower(trim($host));
|
||||||
if ($host === '') {
|
if ($host === '') {
|
||||||
return '';
|
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);
|
$parts = explode('.', $host);
|
||||||
if (count($parts) < 2) {
|
if (count($parts) < 2) {
|
||||||
return $host;
|
return $host;
|
||||||
|
|||||||
Regular → Executable
+47
-43
@@ -6,7 +6,11 @@ require_once dirname(__DIR__) . '/DbHelper.php';
|
|||||||
require_once __DIR__ . '/FpUrlHelper.php';
|
require_once __DIR__ . '/FpUrlHelper.php';
|
||||||
require_once __DIR__ . '/VisitorApiAudit.php';
|
require_once __DIR__ . '/VisitorApiAudit.php';
|
||||||
require_once __DIR__ . '/RiskSecondaryGate.php';
|
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||||
|
require_once __DIR__ . '/CloakJudgmentCache.php';
|
||||||
require_once __DIR__ . '/RiskSecondaryCache.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__ . '/VisitorLogSchema.php';
|
||||||
require_once __DIR__ . '/ClientIpResolver.php';
|
require_once __DIR__ . '/ClientIpResolver.php';
|
||||||
|
|
||||||
@@ -46,6 +50,9 @@ class FCheckHandler
|
|||||||
}
|
}
|
||||||
$config_name = ConfigLoader::loadByHost($fp_host);
|
$config_name = ConfigLoader::loadByHost($fp_host);
|
||||||
$log_id = (int) ($fingerprint_info['log_id'] ?? 0);
|
$log_id = (int) ($fingerprint_info['log_id'] ?? 0);
|
||||||
|
if ($log_id <= 0) {
|
||||||
|
$log_id = VisitorVisitContext::currentLogId();
|
||||||
|
}
|
||||||
|
|
||||||
if (!RiskSecondaryGate::isEnabled()) {
|
if (!RiskSecondaryGate::isEnabled()) {
|
||||||
$_SESSION['visit_to_3'] = 3;
|
$_SESSION['visit_to_3'] = 3;
|
||||||
@@ -62,9 +69,9 @@ class FCheckHandler
|
|||||||
if ($log_id > 0) {
|
if ($log_id > 0) {
|
||||||
self::updateLog($log_id, [
|
self::updateLog($log_id, [
|
||||||
'result' => 'true',
|
'result' => 'true',
|
||||||
'reason' => '二次风控已关闭',
|
'reason' => '',
|
||||||
'fp_url' => $response['link'],
|
'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);
|
return array_merge(['ok' => true, 'status' => 200, 'fingerprint_info' => $fingerprint_info], $response);
|
||||||
@@ -163,7 +170,7 @@ class FCheckHandler
|
|||||||
self::persistCache($update_log, $clientIp);
|
self::persistCache($update_log, $clientIp);
|
||||||
|
|
||||||
if ($log_id > 0) {
|
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([
|
return array_merge([
|
||||||
@@ -186,7 +193,7 @@ class FCheckHandler
|
|||||||
|
|
||||||
$update_log = [
|
$update_log = [
|
||||||
'result' => $response['result'] ? 'true' : 'false',
|
'result' => $response['result'] ? 'true' : 'false',
|
||||||
'reason' => $response['reason'],
|
'reason' => cloak_cache_hit_log_reason($update_log['result']),
|
||||||
'fp_url' => trim((string) ($response['link'] ?? '')),
|
'fp_url' => trim((string) ($response['link'] ?? '')),
|
||||||
];
|
];
|
||||||
if ($update_log['fp_url'] === '') {
|
if ($update_log['fp_url'] === '') {
|
||||||
@@ -196,14 +203,7 @@ class FCheckHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($log_id > 0) {
|
if ($log_id > 0) {
|
||||||
$cacheMarker = [
|
self::updateLog($log_id, $update_log, $fingerprint_info, null, null, false);
|
||||||
'cached' => true,
|
|
||||||
'result' => $update_log['result'],
|
|
||||||
'reason' => $cached['reason'] ?? '',
|
|
||||||
'api_reason' => $cached['reason'] ?? '',
|
|
||||||
'cached_at' => $cached['cached_at'] ?? null,
|
|
||||||
];
|
|
||||||
self::updateLog($log_id, $update_log, $fingerprint_info, null, $cacheMarker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge([
|
return array_merge([
|
||||||
@@ -222,7 +222,10 @@ class FCheckHandler
|
|||||||
{
|
{
|
||||||
$update_log = [];
|
$update_log = [];
|
||||||
$update_log['result'] = !empty($response['result']) ? 'true' : 'false';
|
$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'] ?? ''));
|
$update_log['fp_url'] = trim((string) ($response['link'] ?? ''));
|
||||||
if ($update_log['fp_url'] === '') {
|
if ($update_log['fp_url'] === '') {
|
||||||
$update_log['fp_url'] = $update_log['result'] === 'true'
|
$update_log['fp_url'] = $update_log['result'] === 'true'
|
||||||
@@ -239,11 +242,12 @@ class FCheckHandler
|
|||||||
*/
|
*/
|
||||||
private static function persistCache(array $update_log, $clientIp)
|
private static function persistCache(array $update_log, $clientIp)
|
||||||
{
|
{
|
||||||
RiskSecondaryCache::store([
|
CloakJudgmentCache::storeFromByApiRisk([
|
||||||
'ip' => $clientIp,
|
'ip' => $clientIp,
|
||||||
'result' => $update_log['result'],
|
'country' => (string) ($_SESSION['gcu_country'] ?? ''),
|
||||||
'reason' => $update_log['reason'],
|
'result' => $update_log['result'],
|
||||||
'fp_url' => $update_log['fp_url'],
|
'reason' => $update_log['reason'],
|
||||||
|
'fp_url' => $update_log['fp_url'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,36 +257,36 @@ class FCheckHandler
|
|||||||
* @param array $fingerprint_info
|
* @param array $fingerprint_info
|
||||||
* @param array|null $apiRequest
|
* @param array|null $apiRequest
|
||||||
* @param array|null $apiResponse
|
* @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);
|
$payload = [
|
||||||
if (!$conn || $conn->connect_error) {
|
'result' => (string) ($update_log['result'] ?? ''),
|
||||||
return false;
|
'reason' => cloak_normalize_log_reason(
|
||||||
}
|
$update_log['result'] ?? '',
|
||||||
VisitorLogSchema::ensureColumns($conn);
|
$update_log['reason'] ?? ''
|
||||||
|
),
|
||||||
|
'fp_url' => (string) ($update_log['fp_url'] ?? ''),
|
||||||
|
];
|
||||||
|
|
||||||
$resultEsc = cloak_db_escape($conn, $update_log['result'] ?? '');
|
if ($includeFpAndApi) {
|
||||||
$reasonEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['reason'] ?? '')));
|
$fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
$fpUrlEsc = cloak_db_escape($conn, strip_tags(cloak_db_string($update_log['fp_url'] ?? '')));
|
if ($fpHdataJson === false) {
|
||||||
$fpHdataJson = json_encode($fingerprint_info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$fpHdataJson = '';
|
||||||
if ($fpHdataJson === false) {
|
|
||||||
$fpHdataJson = '';
|
|
||||||
}
|
|
||||||
$fpHdataEsc = cloak_db_escape($conn, $fpHdataJson);
|
|
||||||
$apiRiskReqEsc = cloak_db_escape($conn, $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : '');
|
|
||||||
$apiRiskRespEsc = cloak_db_escape($conn, $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : '');
|
|
||||||
$sql = "UPDATE `visitor_logs` SET `result`='{$resultEsc}',`reason`='{$reasonEsc}',`fp_url`='{$fpUrlEsc}',`fp_hdata`='{$fpHdataEsc}',`api_risk_request`='{$apiRiskReqEsc}',`api_risk_response`='{$apiRiskRespEsc}' WHERE `id`='" . (int) $log_id . "'";
|
|
||||||
|
|
||||||
$ok = $conn->query($sql) === true;
|
|
||||||
if (!$ok) {
|
|
||||||
$handle = @fopen(dirname(__DIR__, 2) . '/err.txt', 'a+');
|
|
||||||
if ($handle) {
|
|
||||||
fwrite($handle, date('Y-m-d H:i:s') . ' | FCheckHandler update: ' . $conn->error . "\n");
|
|
||||||
fclose($handle);
|
|
||||||
}
|
}
|
||||||
|
$payload['fp_hdata'] = $fpHdataJson;
|
||||||
|
$payload['api_risk_request'] = $apiRequest !== null ? VisitorApiAudit::encode($apiRequest) : '';
|
||||||
|
$payload['api_risk_response'] = $apiResponse !== null ? VisitorApiAudit::encode($apiResponse) : '';
|
||||||
}
|
}
|
||||||
$conn->close();
|
|
||||||
|
$hadForceSync = !empty($GLOBALS['__cloak_force_sync_log']);
|
||||||
|
$GLOBALS['__cloak_force_sync_log'] = true;
|
||||||
|
$ok = cloak_update_log_db((int) $log_id, $payload, ['skip_enrich' => true]) !== false;
|
||||||
|
if (!$hadForceSync) {
|
||||||
|
unset($GLOBALS['__cloak_force_sync_log']);
|
||||||
|
}
|
||||||
|
|
||||||
return $ok;
|
return $ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ require_once __DIR__ . '/../FpUrlHelper.php';
|
|||||||
class FpPageRenderer
|
class FpPageRenderer
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param array $ctx 必须包含 logs 数组
|
* @param array $ctx 必须包含 logs 数组;可选 skip_log
|
||||||
*/
|
*/
|
||||||
public static function render(array $ctx)
|
public static function render(array $ctx)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,12 @@ class FpPageRenderer
|
|||||||
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
||||||
$cong_name = str_replace('.php', '', $my_file_name);
|
$cong_name = str_replace('.php', '', $my_file_name);
|
||||||
|
|
||||||
$resolved = FpUrlHelper::resolve($cong_name, true);
|
$resolved = FpUrlHelper::resolve($cong_name, true);
|
||||||
$fp_url = $resolved['url'];
|
$fp_url = $resolved['url'];
|
||||||
$logs['fp_url'] = $fp_url;
|
$logs['fp_url'] = $fp_url;
|
||||||
write_log_db($logs);
|
if (empty($ctx['skip_log'])) {
|
||||||
|
write_log_db($logs);
|
||||||
|
}
|
||||||
|
|
||||||
$show_content = CLOAK_SHOW_CONTENT;
|
$show_content = CLOAK_SHOW_CONTENT;
|
||||||
if ($show_content == 'curl') {
|
if ($show_content == 'curl') {
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
<?php
|
<?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
|
class CheckPipeline
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 在调用方(ip_check 顶层)作用域执行完整判定链。
|
|
||||||
* 依赖全局:$v_info, $config_name, $__cloak_debug_on, $reason
|
|
||||||
*
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function run()
|
public static function run()
|
||||||
@@ -23,15 +28,64 @@ class CheckPipeline
|
|||||||
|
|
||||||
CloakPipelineTimer::init();
|
CloakPipelineTimer::init();
|
||||||
|
|
||||||
CloakPipelineTimer::stage('Session快速路径');
|
CloakPipelineTimer::stage('白名单链接参数');
|
||||||
if (self::resolveSessionFastPath()) {
|
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';
|
include __DIR__ . '/stages/resolve_session_fast_path_hit.inc.php';
|
||||||
CloakPipelineTimer::finish();
|
CloakPipelineTimer::finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloakPipelineTimer::stage('客户端阶段缓存续跑');
|
||||||
|
if (self::resolvePartialCacheResume()) {
|
||||||
|
include __DIR__ . '/stages/run_main_api_fingerprint.inc.php';
|
||||||
|
CloakPipelineTimer::finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($__cloak_debug_on) {
|
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国家判定');
|
CloakPipelineTimer::stage('GeoIP国家判定');
|
||||||
@@ -41,8 +95,6 @@ class CheckPipeline
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CloakPipelineTimer::stage('黑白名单');
|
|
||||||
include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php';
|
|
||||||
CloakPipelineTimer::stage('广告来源限制');
|
CloakPipelineTimer::stage('广告来源限制');
|
||||||
include __DIR__ . '/stages/run_ad_source_guard.inc.php';
|
include __DIR__ . '/stages/run_ad_source_guard.inc.php';
|
||||||
CloakPipelineTimer::stage('临时链接参数');
|
CloakPipelineTimer::stage('临时链接参数');
|
||||||
@@ -51,20 +103,108 @@ class CheckPipeline
|
|||||||
CloakPipelineTimer::finish();
|
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 = 跳过完整判定)
|
* @deprecated
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public static function resolveSessionFastPath()
|
public static function resolveSessionFastPath()
|
||||||
{
|
{
|
||||||
global $__cloak_debug_on;
|
return self::resolveClientCacheFastPath();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
/**
|
||||||
!$__cloak_debug_on
|
* @deprecated 使用 resolveWhitelistParamsFastPath / resolveWhitelistIpFastPath
|
||||||
&& SHOW_SITE_MODE_SWITCH === 'ip_check'
|
*/
|
||||||
&& isset($_SESSION['check_result'])
|
public static function resolveWhitelistFastPath()
|
||||||
&& ($_SESSION['check_result'] === 'true' || $_SESSION['check_result'] === 'false')
|
{
|
||||||
);
|
return self::resolveWhitelistParamsFastPath() || self::resolveWhitelistIpFastPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resolveZpFastPath()
|
||||||
|
{
|
||||||
|
return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'zp';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function resolveFpFastPath()
|
||||||
|
{
|
||||||
|
return defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/../WhitelistGate.php';
|
||||||
require_once __DIR__ . '/../Page/FpPageRenderer.php';
|
require_once __DIR__ . '/../Page/FpPageRenderer.php';
|
||||||
require_once __DIR__ . '/../Page/RiskPageRenderer.php';
|
require_once __DIR__ . '/../Page/RiskPageRenderer.php';
|
||||||
require_once __DIR__ . '/../FpUrlHelper.php';
|
require_once __DIR__ . '/../FpUrlHelper.php';
|
||||||
require_once __DIR__ . '/../RiskSecondaryGate.php';
|
require_once __DIR__ . '/../RiskSecondaryGate.php';
|
||||||
require_once __DIR__ . '/../RiskSecondaryCache.php';
|
require_once __DIR__ . '/../RiskSecondaryCache.php';
|
||||||
|
require_once __DIR__ . '/../RiskSecondarySession.php';
|
||||||
require_once __DIR__ . '/../ClientIpResolver.php';
|
require_once __DIR__ . '/../ClientIpResolver.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,6 +20,12 @@ class RouteResolver
|
|||||||
{
|
{
|
||||||
extract($ctx, EXTR_SKIP);
|
extract($ctx, EXTR_SKIP);
|
||||||
|
|
||||||
|
// 白名单优先于正品模式:跳过二次风控等,直达真实页
|
||||||
|
if (!empty($_SESSION['cloak_whitelist_fast'])) {
|
||||||
|
self::renderWhitelistFp($logs, $config_name ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 正品模式:始终走安全页逻辑
|
// 正品模式:始终走安全页逻辑
|
||||||
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
||||||
self::renderSafePage($logs, $v_info);
|
self::renderSafePage($logs, $v_info);
|
||||||
@@ -28,13 +36,26 @@ class RouteResolver
|
|||||||
|| SHOW_SITE_MODE_SWITCH == 'fp';
|
|| SHOW_SITE_MODE_SWITCH == 'fp';
|
||||||
|
|
||||||
if ($showFp) {
|
if ($showFp) {
|
||||||
self::renderFpOrRisk($logs, $v_info);
|
self::renderFpOrRisk($logs, $v_info, $config_name ?? '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self::renderSafePage($logs, $v_info);
|
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
|
* @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') {
|
if (AUTO_BLACK == 'ON') {
|
||||||
$times = howmanytimesbyip($v_info->v_ip);
|
$times = howmanytimesbyip($v_info->v_ip);
|
||||||
@@ -81,21 +102,22 @@ class RouteResolver
|
|||||||
$_SESSION['visit_to_3'] = '1';
|
$_SESSION['visit_to_3'] = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RiskSecondaryGate::isEnabled()) {
|
if (RiskSecondaryGate::isEnabledForRequest()) {
|
||||||
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
||||||
if ($cached !== null) {
|
if ($cached !== null) {
|
||||||
self::renderFromRiskCache($logs, $v_info, $cached);
|
self::renderFromRiskCache($logs, $v_info, $cached, $config_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($_SESSION['visit_to_3'] ?? '1') != '3'
|
if (($_SESSION['visit_to_3'] ?? '1') != '3'
|
||||||
&& RiskSecondaryGate::isEnabled()) {
|
&& RiskSecondaryGate::isEnabledForRequest()) {
|
||||||
// 二次风控:先写 wait 状态,f_check 完成后再更新 result / fp_url(避免 true+空跳转)
|
// 二次风控:先写 wait 状态,f_check 完成后再更新 result / fp_url(避免 true+空跳转)
|
||||||
$logs['result'] = 'wait';
|
$logs['result'] = 'wait';
|
||||||
$logs['reason'] = '二次风控检测中';
|
$logs['reason'] = '二次风控检测中';
|
||||||
$logs['fp_url'] = '';
|
$logs['fp_url'] = '';
|
||||||
$log_id = write_log_db($logs);
|
$log_id = write_log_db($logs);
|
||||||
|
RiskSecondarySession::markWaitLog($log_id);
|
||||||
$riskCtx = [
|
$riskCtx = [
|
||||||
'logs' => $logs,
|
'logs' => $logs,
|
||||||
'v_info' => $v_info,
|
'v_info' => $v_info,
|
||||||
@@ -111,22 +133,36 @@ class RouteResolver
|
|||||||
|
|
||||||
$logs['result'] = 'true';
|
$logs['result'] = 'true';
|
||||||
$logs['reason'] = '';
|
$logs['reason'] = '';
|
||||||
FpPageRenderer::render(['logs' => $logs]);
|
self::renderFpPage($logs, $config_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $logs
|
||||||
|
* @param string $config_name
|
||||||
|
*/
|
||||||
|
private static function renderFpPage(array $logs, $config_name = '')
|
||||||
|
{
|
||||||
|
FpPageRenderer::render([
|
||||||
|
'logs' => $logs,
|
||||||
|
'config_name' => $config_name,
|
||||||
|
'skip_log' => RiskSecondarySession::shouldSkipFpPageLog(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二次风控 Session 缓存命中:跳过 cloakjs / byApiRisk。
|
* 二次风控 Session 缓存命中:跳过 cloakjs / byApiRisk。
|
||||||
*
|
*
|
||||||
* @param array $logs
|
* @param array $logs
|
||||||
* @param array $cached
|
* @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);
|
RiskSecondaryCache::applyToSession($cached);
|
||||||
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
||||||
|
|
||||||
$logs['result'] = $apiResponse['result'] ? 'true' : 'false';
|
$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'] ?? ''));
|
$logs['fp_url'] = trim((string) ($apiResponse['link'] ?? ''));
|
||||||
if ($logs['fp_url'] === '') {
|
if ($logs['fp_url'] === '') {
|
||||||
$logs['fp_url'] = $logs['result'] === 'true'
|
$logs['fp_url'] = $logs['result'] === 'true'
|
||||||
@@ -139,7 +175,7 @@ class RouteResolver
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FpPageRenderer::render(['logs' => $logs]);
|
self::renderFpPage($logs, $config_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 仅用本地 MaxMind 解析国家并写入访客信息/Session(不做国家允许列表或其它判定)
|
||||||
|
*
|
||||||
|
* 依赖全局:$v_info, $__cloak_debug_on
|
||||||
|
*/
|
||||||
|
$ip = ClientIpResolver::resolve();
|
||||||
|
if ($ip !== '' && $ip !== '0.0.0.0') {
|
||||||
|
$v_info->v_ip = $ip;
|
||||||
|
$_SESSION['gcu_ip'] = $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GeoIpCountryResolver::isAvailable()) {
|
||||||
|
$geoMeta = GeoIpCountryResolver::resolveWithMeta($ip);
|
||||||
|
$countryCode = $geoMeta['code'];
|
||||||
|
$geoSource = $geoMeta['source'];
|
||||||
|
if ($countryCode !== null && $countryCode !== '') {
|
||||||
|
$v_info->v_country = $countryCode;
|
||||||
|
$_SESSION['gcu_country'] = $countryCode;
|
||||||
|
}
|
||||||
|
if (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||||
|
cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'log', '仅记录国家,不参与判定', [
|
||||||
|
'ip' => $ip,
|
||||||
|
'country' => $countryCode,
|
||||||
|
'geo_source' => $geoSource,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} elseif (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||||
|
cloak_dbg_step(__LINE__, 'MaxMind国家记录', 'skip', '本地 GeoIP 库不可用');
|
||||||
|
}
|
||||||
@@ -1,69 +1,46 @@
|
|||||||
<?php
|
<?php
|
||||||
//指定ip显示仿品(优先数据库白名单组,其次兼容旧版 SHOW_SITE_IP 逗号列表)
|
// 黑白名单(完整判定链内):链接参数白名单 > IP 白名单 > 黑名单
|
||||||
if (defined('WHITELIST_GROUP_ID') && (int) WHITELIST_GROUP_ID > 0) {
|
$reason = $reason ?? '';
|
||||||
$ips_fp_ary = ip_group_list_addresses((int) WHITELIST_GROUP_ID);
|
|
||||||
} else {
|
if (WhitelistGate::matchesWhitelistParams()) {
|
||||||
$ips_fp_ary = array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) {
|
$_SESSION['check_result'] = 'true';
|
||||||
return $v !== '';
|
$reason = '白名单链接参数';
|
||||||
});
|
if ($__cloak_debug_on) {
|
||||||
}
|
cloak_dbg_step(__LINE__, '链接参数白名单', 'pass', defined('WHITE_PARAMS') ? WHITE_PARAMS : '');
|
||||||
$reason = ""; // 判断理由
|
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_params']);
|
||||||
|
|
||||||
$whiteParamsMatched = false;
|
|
||||||
if( !empty(WHITE_PARAMS) ) {
|
|
||||||
$wparams = explode('=', WHITE_PARAMS, 2);
|
|
||||||
|
|
||||||
if(count($wparams) == 2) {
|
|
||||||
$pk = $wparams[0];
|
|
||||||
$pv = $wparams[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) {
|
|
||||||
$whiteParamsMatched = true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (WhitelistGate::matchesWhitelistIp(WhitelistGate::resolveVisitorIp($v_info))) {
|
||||||
|
$_SESSION['check_result'] = 'true';
|
||||||
|
$reason = '白名单';
|
||||||
|
if ($__cloak_debug_on) {
|
||||||
|
cloak_dbg_step(__LINE__, 'IP 白名单', 'pass', 'IP 在白名单组/列表中');
|
||||||
|
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_ip']);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($__cloak_debug_on && defined('WHITE_PARAMS') && WHITE_PARAMS !== '') {
|
||||||
|
cloak_dbg_step(__LINE__, '链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
$match = false;
|
$match = false;
|
||||||
if (defined('BLACKLIST_GROUP_ID') && (int) BLACKLIST_GROUP_ID > 0) {
|
if (defined('BLACKLIST_GROUP_ID') && (int) BLACKLIST_GROUP_ID > 0) {
|
||||||
$blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID);
|
$blEntries = ip_group_list_addresses((int) BLACKLIST_GROUP_ID);
|
||||||
$match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
|
$match = ip_visitor_in_blacklist_entries($v_info->v_ip, $blEntries);
|
||||||
}
|
}
|
||||||
if ($__cloak_debug_on) {
|
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,
|
'ip' => $v_info->v_ip,
|
||||||
'group_id' => defined('BLACKLIST_GROUP_ID') ? BLACKLIST_GROUP_ID : 0,
|
'group_id' => defined('BLACKLIST_GROUP_ID') ? BLACKLIST_GROUP_ID : 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($match) {
|
if ($match) {
|
||||||
$_SESSION["check_result"] = "false";
|
$_SESSION['check_result'] = 'false';
|
||||||
$reason = "黑名单";
|
$reason = '黑名单';
|
||||||
if ($__cloak_debug_on) {
|
if ($__cloak_debug_on) {
|
||||||
cloak_dbg_record(__LINE__,'false', $reason, ['stage' => 'blacklist']);
|
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'blacklist']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(in_array($v_info->v_ip, $ips_fp_ary) ) {
|
|
||||||
$_SESSION["check_result"] = "true";
|
|
||||||
$reason = "白名单";
|
|
||||||
if ($__cloak_debug_on) {
|
|
||||||
cloak_dbg_step(__LINE__,'IP 白名单', 'pass', 'IP 在白名单组/列表中');
|
|
||||||
cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_ip']);
|
|
||||||
}
|
|
||||||
} elseif( !empty(WHITE_PARAMS) ) {
|
|
||||||
$wparams = explode('=', WHITE_PARAMS, 2);
|
|
||||||
if(count($wparams) == 2) {
|
|
||||||
$pk = $wparams[0];
|
|
||||||
$pv = $wparams[1];
|
|
||||||
}
|
|
||||||
if(isset($_GET[$pk]) && strip_tags($_GET[$pk]) == $pv) {
|
|
||||||
$_SESSION["check_result"] = "true";
|
|
||||||
$reason = "白名单链接参数";
|
|
||||||
if ($__cloak_debug_on) {
|
|
||||||
cloak_dbg_step(__LINE__,'链接参数白名单', 'pass', WHITE_PARAMS);
|
|
||||||
cloak_dbg_record(__LINE__,'true', $reason, ['stage' => 'whitelist_params']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} elseif ($__cloak_debug_on && !empty(WHITE_PARAMS)) {
|
|
||||||
cloak_dbg_step(__LINE__,'链接参数白名单', 'skip', '参数不匹配: ' . WHITE_PARAMS);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 黑名单快速路径:记录国家后直达安全页判定
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||||
|
|
||||||
|
global $reason;
|
||||||
|
|
||||||
|
$_SESSION['check_result'] = 'false';
|
||||||
|
$reason = '黑名单';
|
||||||
|
|
||||||
|
if ($__cloak_debug_on) {
|
||||||
|
cloak_dbg_step(__LINE__, '黑名单快速路径', 'fail', 'IP 在黑名单组内');
|
||||||
|
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'blacklist_fast_path']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 真实页模式(fp)快速路径:跳过正常屏蔽判定与二次风控,直达真实页
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||||
|
|
||||||
|
global $reason;
|
||||||
|
|
||||||
|
$_SESSION['check_result'] = 'true';
|
||||||
|
$reason = '真实页模式';
|
||||||
|
$_SESSION['visit_to_2'] = '2';
|
||||||
|
$_SESSION['visit_to_3'] = '3';
|
||||||
|
|
||||||
|
if ($__cloak_debug_on) {
|
||||||
|
cloak_dbg_step(__LINE__, '真实页模式', 'pass', '跳过正常屏蔽判定链');
|
||||||
|
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'fp_fast_path']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 白名单快速路径:仅 MaxMind 国家记录,跳过其余判定,直达真实页
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||||
|
|
||||||
|
global $reason;
|
||||||
|
|
||||||
|
$_SESSION['check_result'] = 'true';
|
||||||
|
$reason = WhitelistGate::matchReason($v_info);
|
||||||
|
$_SESSION['visit_to_3'] = '3';
|
||||||
|
$_SESSION['cloak_whitelist_fast'] = true;
|
||||||
|
|
||||||
|
if ($__cloak_debug_on) {
|
||||||
|
cloak_dbg_step(__LINE__, '白名单快速路径', 'pass', $reason);
|
||||||
|
cloak_dbg_record(__LINE__, 'true', $reason, ['stage' => 'whitelist_fast_path']);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 正品模式快速路径:仅 MaxMind 国家记录,跳过其余判定,无条件安全页
|
||||||
|
*/
|
||||||
|
include __DIR__ . '/apply_geoip_country_log_only.inc.php';
|
||||||
|
|
||||||
|
global $reason;
|
||||||
|
|
||||||
|
$_SESSION['check_result'] = 'false';
|
||||||
|
$reason = '正品模式';
|
||||||
|
|
||||||
|
if ($__cloak_debug_on) {
|
||||||
|
cloak_dbg_step(__LINE__, '正品模式', 'pass', '仅记录国家,无条件安全页');
|
||||||
|
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'zp_fast_path']);
|
||||||
|
}
|
||||||
@@ -22,6 +22,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (($_SESSION['visit_to_2'] ?? '') == '2' && class_exists('CloakJudgmentCache', false) && !CloakJudgmentCache::hasSession()) {
|
||||||
|
$_SESSION['visit_to_2'] = '1';
|
||||||
|
}
|
||||||
|
|
||||||
if($_SESSION['visit_to_1'] == '1' && $_SESSION['visit_to_2'] != '2') // $_SESSION['visit_to_2'] 为2时表示已经判断过所有规则。
|
if($_SESSION['visit_to_1'] == '1' && $_SESSION['visit_to_2'] != '2') // $_SESSION['visit_to_2'] 为2时表示已经判断过所有规则。
|
||||||
{
|
{
|
||||||
if ($__cloak_debug_on) {
|
if ($__cloak_debug_on) {
|
||||||
@@ -86,7 +90,7 @@
|
|||||||
// 已完成指纹跳转(URL 含 time),但 Cookie 未写入:终止再次重定向
|
// 已完成指纹跳转(URL 含 time),但 Cookie 未写入:终止再次重定向
|
||||||
$v_info->check_result = 'false';
|
$v_info->check_result = 'false';
|
||||||
|
|
||||||
$reason = cloak_reason_or($reason, '指纹Cookie未写入(已完成前端检测)');
|
$reason = cloak_reason_or($reason, '已完成前端检测,但指纹Cookie未写入(隐私模式、广告拦截等可能阻止Cookie写入)');
|
||||||
$_SESSION['check_result'] = 'false';
|
$_SESSION['check_result'] = 'false';
|
||||||
if ($__cloak_debug_on) {
|
if ($__cloak_debug_on) {
|
||||||
cloak_dbg_step(__LINE__, 'JS 指纹检测', 'fail', 'URL 含 time 但指纹 Cookie 缺失,停止重定向');
|
cloak_dbg_step(__LINE__, 'JS 指纹检测', 'fail', 'URL 含 time 但指纹 Cookie 缺失,停止重定向');
|
||||||
@@ -149,6 +153,14 @@
|
|||||||
if ($__api_return !== null) {
|
if ($__api_return !== null) {
|
||||||
$_SESSION["check_result"] = trim($v_info->check_result);
|
$_SESSION["check_result"] = trim($v_info->check_result);
|
||||||
$reason = cloak_api_reason($__api_return, $_SESSION["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 {
|
} else {
|
||||||
$_SESSION["check_result"] = "false";
|
$_SESSION["check_result"] = "false";
|
||||||
$reason = cloak_reason_or($reason, 'API未调用:缺少访客信息(ip/Browser/Accept-Language)');
|
$reason = cloak_reason_or($reason, 'API未调用:缺少访客信息(ip/Browser/Accept-Language)');
|
||||||
@@ -175,15 +187,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif (($_SESSION['visit_to_2'] ?? '') == '2') {
|
} 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 === '') {
|
if ($reason === '') {
|
||||||
$reason = ($_SESSION['check_result'] === 'true')
|
$reason = ($_SESSION['check_result'] === 'true')
|
||||||
? 'Session API缓存:判定通过'
|
? 'Session API缓存:判定通过'
|
||||||
: 'Session API缓存:判定拒绝';
|
: '缓存';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$_SESSION["check_result"] = 'false';
|
$_SESSION['check_result'] = 'false';
|
||||||
$reason = cloak_reason_or($reason, 'Session状态异常(visit_to_2=2但无缓存结果),默认安全页');
|
$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) {
|
if ($__cloak_debug_on) {
|
||||||
cloak_dbg_step(__LINE__,'主判定块 (API)', 'skip', 'visit_to_2=2,本块已跳过(' . $reason . ')');
|
cloak_dbg_step(__LINE__,'主判定块 (API)', 'skip', 'visit_to_2=2,本块已跳过(' . $reason . ')');
|
||||||
|
|||||||
@@ -41,14 +41,47 @@ if (!function_exists('cloak_api_reason')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('cloak_cache_hit_log_reason')) {
|
||||||
|
/**
|
||||||
|
* 缓存判定落库 reason:true 为空;false 固定为「缓存」。
|
||||||
|
*
|
||||||
|
* @param string $result
|
||||||
|
*/
|
||||||
|
function cloak_cache_hit_log_reason($result)
|
||||||
|
{
|
||||||
|
return cloak_normalize_log_reason((string) $result, (string) $result === 'true' ? '' : '缓存');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('cloak_normalize_log_reason')) {
|
||||||
|
/**
|
||||||
|
* 落库 reason 规范化:true 必须为空;false 必须有值;wait 保留检测中文案。
|
||||||
|
*
|
||||||
|
* @param string $result
|
||||||
|
* @param mixed $reason
|
||||||
|
*/
|
||||||
|
function cloak_normalize_log_reason($result, $reason)
|
||||||
|
{
|
||||||
|
$result = (string) $result;
|
||||||
|
$reason = trim((string) $reason);
|
||||||
|
|
||||||
|
if ($result === 'true') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($result === 'wait') {
|
||||||
|
return $reason !== '' ? $reason : '二次风控检测中';
|
||||||
|
}
|
||||||
|
if ($reason === '') {
|
||||||
|
return '判定拒绝(安全页)';
|
||||||
|
}
|
||||||
|
return $reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('cloak_finalize_reason')) {
|
if (!function_exists('cloak_finalize_reason')) {
|
||||||
function cloak_finalize_reason($reason, $checkResult)
|
function cloak_finalize_reason($reason, $checkResult)
|
||||||
{
|
{
|
||||||
$reason = trim((string) $reason);
|
$result = ($checkResult === 'true') ? 'true' : (($checkResult === 'false') ? 'false' : (string) $checkResult);
|
||||||
if ($reason !== '') {
|
return cloak_normalize_log_reason($result, $reason);
|
||||||
return $reason;
|
|
||||||
}
|
|
||||||
$pass = ($checkResult === 'true');
|
|
||||||
return $pass ? '判定通过(真实页)' : '判定拒绝(安全页)';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Regular → Executable
+13
-65
@@ -1,86 +1,48 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once __DIR__ . '/ClientIpResolver.php';
|
require_once __DIR__ . '/CloakJudgmentCache.php';
|
||||||
require_once __DIR__ . '/FpUrlHelper.php';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 二次风控(byApiRisk)Session 缓存:同 Session 内复用判定结果,IP 变化时失效。
|
* 二次风控缓存薄封装(委托 CloakJudgmentCache,保持既有调用点兼容)。
|
||||||
*/
|
*/
|
||||||
class RiskSecondaryCache
|
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)
|
public static function store(array $payload)
|
||||||
{
|
{
|
||||||
$ip = ClientIpResolver::normalizeIp($payload['ip'] ?? ClientIpResolver::resolve());
|
CloakJudgmentCache::storeFromByApiRisk($payload);
|
||||||
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(),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|null $currentIp 为空时使用 ClientIpResolver::resolve()
|
* 仅返回最终缓存(partial 不视为命中)。
|
||||||
|
*
|
||||||
|
* @param string|null $currentIp
|
||||||
* @return array|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
public static function get($currentIp = null)
|
public static function get($currentIp = null)
|
||||||
{
|
{
|
||||||
if (empty($_SESSION[self::SESSION_KEY]) || !is_array($_SESSION[self::SESSION_KEY])) {
|
return CloakJudgmentCache::getFinal($currentIp);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function clear()
|
public static function clear()
|
||||||
{
|
{
|
||||||
unset($_SESSION[self::SESSION_KEY]);
|
CloakJudgmentCache::clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将缓存同步到 visit_to_3 / check_result,供 RouteResolver 等复用。
|
|
||||||
*
|
|
||||||
* @param array $cached
|
* @param array $cached
|
||||||
*/
|
*/
|
||||||
public static function applyToSession(array $cached)
|
public static function applyToSession(array $cached)
|
||||||
{
|
{
|
||||||
$_SESSION['visit_to_3'] = 3;
|
CloakJudgmentCache::applyToSession($cached);
|
||||||
$_SESSION['check_result'] = ($cached['result'] ?? '') === 'true' ? 'true' : 'false';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function hitReason()
|
public static function hitReason()
|
||||||
{
|
{
|
||||||
return '二次风控缓存命中';
|
return CloakJudgmentCache::hitReason();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,20 +51,6 @@ class RiskSecondaryCache
|
|||||||
*/
|
*/
|
||||||
public static function toApiResponse(array $cached)
|
public static function toApiResponse(array $cached)
|
||||||
{
|
{
|
||||||
$passed = ($cached['result'] ?? '') === 'true';
|
return CloakJudgmentCache::toApiResponse($cached);
|
||||||
$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,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,4 +12,15 @@ class RiskSecondaryGate
|
|||||||
$riskNumber = (int) CLOAK_RISK_NUMBER;
|
$riskNumber = (int) CLOAK_RISK_NUMBER;
|
||||||
return $riskNumber > 0 && $riskNumber <= 90;
|
return $riskNumber > 0 && $riskNumber <= 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前站点模式是否应执行二次风控(真实页 fp 模式永不触发)。
|
||||||
|
*/
|
||||||
|
public static function isEnabledForRequest()
|
||||||
|
{
|
||||||
|
if (defined('SHOW_SITE_MODE_SWITCH') && SHOW_SITE_MODE_SWITCH === 'fp') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return self::isEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/VisitorVisitContext.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二次风控 wait / f_check 与 visit 日志绑定(与 VisitorVisitContext 对齐)
|
||||||
|
*/
|
||||||
|
class RiskSecondarySession
|
||||||
|
{
|
||||||
|
const KEY_WAIT_LOG = 'cloak_risk_wait_log_id';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int|null $logId
|
||||||
|
*/
|
||||||
|
public static function markWaitLog($logId)
|
||||||
|
{
|
||||||
|
$logId = (int) $logId;
|
||||||
|
if ($logId > 0) {
|
||||||
|
$_SESSION[self::KEY_WAIT_LOG] = $logId;
|
||||||
|
VisitorVisitContext::bindLogId($logId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* f_check 已 finalize 后,FpPageRenderer 不再重复写库。
|
||||||
|
*/
|
||||||
|
public static function shouldSkipFpPageLog()
|
||||||
|
{
|
||||||
|
if (VisitorVisitContext::isFinalized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (VisitorVisitContext::currentLogId() > 0 && (string) ($_SESSION['visit_to_3'] ?? '') === '3') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function clear()
|
||||||
|
{
|
||||||
|
unset($_SESSION[self::KEY_WAIT_LOG]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Regular → Executable
+32
-6
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
require_once __DIR__ . '/WhitelistGate.php';
|
||||||
require_once __DIR__ . '/FpUrlHelper.php';
|
require_once __DIR__ . '/FpUrlHelper.php';
|
||||||
require_once __DIR__ . '/RiskSecondaryGate.php';
|
require_once __DIR__ . '/RiskSecondaryGate.php';
|
||||||
require_once __DIR__ . '/RiskSecondaryCache.php';
|
require_once __DIR__ . '/RiskSecondaryCache.php';
|
||||||
|
require_once __DIR__ . '/RiskSecondarySession.php';
|
||||||
require_once __DIR__ . '/ClientIpResolver.php';
|
require_once __DIR__ . '/ClientIpResolver.php';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,6 +19,10 @@ class SimulationRouteResolver
|
|||||||
{
|
{
|
||||||
extract($ctx, EXTR_SKIP);
|
extract($ctx, EXTR_SKIP);
|
||||||
|
|
||||||
|
if (!empty($_SESSION['cloak_whitelist_fast'])) {
|
||||||
|
return self::whitelistFpBranch($logs, $config_name ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
if (SHOW_SITE_MODE_SWITCH == 'zp') {
|
||||||
return self::safeBranch($logs, $v_info);
|
return self::safeBranch($logs, $v_info);
|
||||||
}
|
}
|
||||||
@@ -31,6 +37,25 @@ class SimulationRouteResolver
|
|||||||
return self::safeBranch($logs, $v_info);
|
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
|
* @param array $logs
|
||||||
*/
|
*/
|
||||||
@@ -67,18 +92,19 @@ class SimulationRouteResolver
|
|||||||
$_SESSION['visit_to_3'] = '1';
|
$_SESSION['visit_to_3'] = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RiskSecondaryGate::isEnabled()) {
|
if (RiskSecondaryGate::isEnabledForRequest()) {
|
||||||
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
$cached = RiskSecondaryCache::get(ClientIpResolver::resolve());
|
||||||
if ($cached !== null) {
|
if ($cached !== null) {
|
||||||
return self::fpFromRiskCache($logs, $v_info, $config_name, $cached);
|
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['result'] = 'wait';
|
||||||
$logs['reason'] = self::mergeReason($logs['reason'] ?? '', '二次风控检测中');
|
$logs['reason'] = self::mergeReason($logs['reason'] ?? '', '二次风控检测中');
|
||||||
$logs['fp_url'] = '';
|
$logs['fp_url'] = '';
|
||||||
$log_id = write_log_db($logs);
|
$log_id = write_log_db($logs);
|
||||||
|
RiskSecondarySession::markWaitLog($log_id);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'branch' => 'secondary_risk',
|
'branch' => 'secondary_risk',
|
||||||
@@ -89,10 +115,10 @@ class SimulationRouteResolver
|
|||||||
}
|
}
|
||||||
|
|
||||||
$logs['result'] = 'true';
|
$logs['result'] = 'true';
|
||||||
$logs['reason'] = $logs['reason'] ?? '';
|
$logs['reason'] = '';
|
||||||
$resolved = FpUrlHelper::resolve($config_name, true);
|
$resolved = FpUrlHelper::resolve($config_name, true);
|
||||||
$logs['fp_url'] = $resolved['url'] !== '' ? $resolved['url'] : FpUrlHelper::fallbackUrl();
|
$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 [
|
return [
|
||||||
'branch' => 'fp_page',
|
'branch' => 'fp_page',
|
||||||
@@ -112,7 +138,7 @@ class SimulationRouteResolver
|
|||||||
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
$apiResponse = RiskSecondaryCache::toApiResponse($cached);
|
||||||
|
|
||||||
$logs['result'] = $apiResponse['result'] ? 'true' : 'false';
|
$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'] ?? ''));
|
$logs['fp_url'] = trim((string) ($apiResponse['link'] ?? ''));
|
||||||
if ($logs['fp_url'] === '') {
|
if ($logs['fp_url'] === '') {
|
||||||
$logs['fp_url'] = $logs['result'] === 'true'
|
$logs['fp_url'] = $logs['result'] === 'true'
|
||||||
@@ -124,7 +150,7 @@ class SimulationRouteResolver
|
|||||||
return self::safeBranch($logs, $v_info);
|
return self::safeBranch($logs, $v_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
$log_id = write_log_db($logs);
|
$log_id = RiskSecondarySession::shouldSkipFpPageLog() ? null : write_log_db($logs);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'branch' => 'fp_page_cache',
|
'branch' => 'fp_page_cache',
|
||||||
|
|||||||
@@ -6,10 +6,20 @@ class VisitorApiAudit
|
|||||||
{
|
{
|
||||||
const SESSION_BY_REQUEST = 'cloak_api_by_request';
|
const SESSION_BY_REQUEST = 'cloak_api_by_request';
|
||||||
const SESSION_BY_RESPONSE = 'cloak_api_by_response';
|
const SESSION_BY_RESPONSE = 'cloak_api_by_response';
|
||||||
|
const SESSION_BY_CALLED = 'cloak_api_by_called';
|
||||||
|
|
||||||
public static function clearByApi()
|
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)
|
public static function recordByApiRequest($payload)
|
||||||
{
|
{
|
||||||
|
$_SESSION[self::SESSION_BY_CALLED] = 1;
|
||||||
$_SESSION[self::SESSION_BY_REQUEST] = self::encode($payload);
|
$_SESSION[self::SESSION_BY_REQUEST] = self::encode($payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +50,9 @@ class VisitorApiAudit
|
|||||||
*/
|
*/
|
||||||
public static function mergeIntoLogs(array $logs)
|
public static function mergeIntoLogs(array $logs)
|
||||||
{
|
{
|
||||||
|
if (!self::wasByApiCalledThisRequest()) {
|
||||||
|
return $logs;
|
||||||
|
}
|
||||||
$map = [
|
$map = [
|
||||||
'api_by_request' => self::SESSION_BY_REQUEST,
|
'api_by_request' => self::SESSION_BY_REQUEST,
|
||||||
'api_by_response' => self::SESSION_BY_RESPONSE,
|
'api_by_response' => self::SESSION_BY_RESPONSE,
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
@@ -50,6 +50,9 @@ class VisitorLogWorker
|
|||||||
if ($type === 'enrich') {
|
if ($type === 'enrich') {
|
||||||
return self::enrich((int) ($job['log_id'] ?? 0), $job['logs'] ?? []);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +211,82 @@ class VisitorLogWorker
|
|||||||
return $ok;
|
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 mysqli $conn
|
||||||
* @param array $logs
|
* @param array $logs
|
||||||
|
|||||||
+136
-34
@@ -6,6 +6,9 @@ require_once dirname(__DIR__) . '/DbHelper.php';
|
|||||||
require_once dirname(__DIR__) . '/CloakHttpHelpers.php';
|
require_once dirname(__DIR__) . '/CloakHttpHelpers.php';
|
||||||
require_once __DIR__ . '/VisitorApiAudit.php';
|
require_once __DIR__ . '/VisitorApiAudit.php';
|
||||||
require_once __DIR__ . '/VisitorLogVtimes.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')) {
|
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);
|
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_country'],
|
||||||
$_SESSION['gcu_Client'],
|
$_SESSION['gcu_Client'],
|
||||||
$_SESSION['gcu_Browser'],
|
$_SESSION['gcu_Browser'],
|
||||||
$_SESSION['gcu_referer']
|
$_SESSION['gcu_referer'],
|
||||||
|
$_SESSION['cloak_whitelist_fast']
|
||||||
);
|
);
|
||||||
if (class_exists('RiskSecondaryCache', false)) {
|
if (class_exists('RiskSecondaryCache', false)) {
|
||||||
RiskSecondaryCache::clear();
|
RiskSecondaryCache::clear();
|
||||||
}
|
}
|
||||||
|
if (class_exists('CloakJudgmentCache', false)) {
|
||||||
|
CloakJudgmentCache::clear();
|
||||||
|
}
|
||||||
|
if (class_exists('RiskSecondarySession', false)) {
|
||||||
|
RiskSecondarySession::clear();
|
||||||
|
}
|
||||||
|
VisitorVisitContext::clear();
|
||||||
setCrossDomainCookie('gfuid', '', time() - 3600);
|
setCrossDomainCookie('gfuid', '', time() - 3600);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,49 +185,79 @@ if (!function_exists('cloak_write_log_db_sync')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!function_exists('cloak_write_log_db')) {
|
if (!function_exists('cloak_update_log_db')) {
|
||||||
function cloak_write_log_db($logs)
|
/**
|
||||||
|
* 同 visit 续步 UPDATE(不新增 vtimes)。
|
||||||
|
*
|
||||||
|
* @param int $logId
|
||||||
|
* @param array $logs
|
||||||
|
* @return int|false log id on success
|
||||||
|
*/
|
||||||
|
function cloak_update_log_db($logId, array $logs, array $options = [])
|
||||||
{
|
{
|
||||||
$logs = cloak_visitor_log_enrich($logs);
|
$logId = (int) $logId;
|
||||||
|
if ($logId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (empty($options['skip_enrich'])) {
|
||||||
|
$logs = cloak_visitor_log_enrich($logs);
|
||||||
|
}
|
||||||
|
$logs = VisitorVisitContext::normalizeLogsForDb($logs);
|
||||||
|
|
||||||
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
$ok = false;
|
||||||
if (($logs['result'] ?? '') === 'wait') {
|
$forceSync = !empty($GLOBALS['__cloak_force_sync_log'])
|
||||||
$logId = VisitorLogWorker::insertMinimal($logs);
|
|| !cloak_visitor_log_async_enabled()
|
||||||
if ($logId !== null) {
|
|| VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '');
|
||||||
VisitorLogWorker::enrich($logId, $logs);
|
if ($forceSync) {
|
||||||
}
|
$ok = VisitorLogWorker::updateFromLogs($logId, $logs);
|
||||||
return $logId;
|
} else {
|
||||||
|
if (VisitorLogQueue::push(['type' => 'update', 'log_id' => $logId, 'logs' => $logs])) {
|
||||||
|
cloak_visitor_log_register_shutdown_drain();
|
||||||
|
$ok = true;
|
||||||
|
} else {
|
||||||
|
$ok = VisitorLogWorker::updateFromLogs($logId, $logs);
|
||||||
}
|
}
|
||||||
return cloak_write_log_db_sync($logs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defined('WRITE_LOG') && WRITE_LOG == '1') {
|
if ($ok && VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||||
$my_url = $_SERVER['PHP_SELF'] ?? '';
|
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
return $ok ? $logId : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('cloak_write_log_db_wait_sync')) {
|
||||||
|
/**
|
||||||
|
* 二次风控 wait:同步完整 INSERT(需立即 log_id,且必须带上 byApi/页面/UA 等字段)。
|
||||||
|
*
|
||||||
|
* @param array $logs 已 enrich + normalize
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
function cloak_write_log_db_wait_sync(array $logs)
|
||||||
|
{
|
||||||
|
$logId = VisitorLogWorker::insertFull($logs);
|
||||||
|
if ($logId === null) {
|
||||||
|
$logId = cloak_write_log_db_sync($logs);
|
||||||
|
}
|
||||||
|
return $logId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('cloak_write_log_db_insert')) {
|
||||||
|
/**
|
||||||
|
* @param array $logs
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
function cloak_write_log_db_insert(array $logs)
|
||||||
|
{
|
||||||
$result = isset($logs['result']) ? (string) $logs['result'] : '';
|
$result = isset($logs['result']) ? (string) $logs['result'] : '';
|
||||||
|
|
||||||
// 二次风控 wait:同步最小 INSERT 获取 log_id,TEXT 字段异步 enrich
|
|
||||||
if ($result === 'wait') {
|
if ($result === 'wait') {
|
||||||
$logId = VisitorLogWorker::insertMinimal($logs);
|
return cloak_write_log_db_wait_sync($logs);
|
||||||
if ($logId === null) {
|
}
|
||||||
return cloak_write_log_db_sync($logs);
|
|
||||||
}
|
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
||||||
if (cloak_visitor_log_async_enabled()) {
|
return cloak_write_log_db_sync($logs);
|
||||||
VisitorLogQueue::push([
|
|
||||||
'type' => 'enrich',
|
|
||||||
'log_id' => $logId,
|
|
||||||
'logs' => $logs,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
VisitorLogWorker::enrich($logId, $logs);
|
|
||||||
}
|
|
||||||
return $logId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cloak_visitor_log_async_enabled()) {
|
if (!cloak_visitor_log_async_enabled()) {
|
||||||
@@ -284,6 +336,56 @@ if (!function_exists('cloak_write_log_db')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('cloak_write_log_db')) {
|
||||||
|
function cloak_write_log_db($logs)
|
||||||
|
{
|
||||||
|
$logs = cloak_visitor_log_enrich($logs);
|
||||||
|
$logs = VisitorVisitContext::normalizeLogsForDb($logs);
|
||||||
|
|
||||||
|
if (VisitorVisitContext::shouldPersistAsUpdate()) {
|
||||||
|
return cloak_update_log_db(VisitorVisitContext::currentLogId(), $logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
VisitorVisitContext::prepareForNewInsert();
|
||||||
|
|
||||||
|
if (!empty($GLOBALS['__cloak_force_sync_log'])) {
|
||||||
|
if (($logs['result'] ?? '') === 'wait') {
|
||||||
|
$logId = cloak_write_log_db_wait_sync($logs);
|
||||||
|
if ($logId !== null) {
|
||||||
|
VisitorVisitContext::bindLogId($logId);
|
||||||
|
}
|
||||||
|
return $logId;
|
||||||
|
}
|
||||||
|
$logId = cloak_write_log_db_sync($logs);
|
||||||
|
if ($logId !== null) {
|
||||||
|
VisitorVisitContext::bindLogId($logId);
|
||||||
|
if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||||
|
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $logId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined('WRITE_LOG') && WRITE_LOG == '1') {
|
||||||
|
$my_url = $_SERVER['PHP_SELF'] ?? '';
|
||||||
|
$my_file_name = substr($my_url, strrpos($my_url, '/') + 1);
|
||||||
|
$config_name = str_replace('.php', '', $my_file_name);
|
||||||
|
$fp = fopen(dirname(__DIR__, 2) . '/jump.txt', 'a+');
|
||||||
|
fwrite($fp, date('Y-m-d H:i:s') . ' | 写入数据库 ' . ' | ' . ($_SESSION['check_result'] ?? '') . ' | ' . $config_name . ' | async=' . (cloak_visitor_log_async_enabled() ? '1' : '0') . "\n");
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
$logId = cloak_write_log_db_insert($logs);
|
||||||
|
if ($logId !== null) {
|
||||||
|
VisitorVisitContext::bindLogId($logId);
|
||||||
|
if (VisitorVisitContext::shouldFinalizeOnResult($logs['result'] ?? '')) {
|
||||||
|
VisitorVisitContext::finalizeVisit($logs['result'] ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $logId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('write_log_db')) {
|
if (!function_exists('write_log_db')) {
|
||||||
function write_log_db($logs)
|
function write_log_db($logs)
|
||||||
{
|
{
|
||||||
|
|||||||
Regular → Executable
Executable
+175
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 单次访问(Visit)生命周期:一次独立打开 = 一条日志;同 visit 内续步仅 UPDATE。
|
||||||
|
*/
|
||||||
|
class VisitorVisitContext
|
||||||
|
{
|
||||||
|
const KEY_LOG_ID = 'cloak_visit_log_id';
|
||||||
|
const KEY_FINALIZED = 'cloak_visit_finalized';
|
||||||
|
const KEY_ACTIVE = 'cloak_visit_active';
|
||||||
|
|
||||||
|
public static function clear()
|
||||||
|
{
|
||||||
|
unset(
|
||||||
|
$_SESSION[self::KEY_LOG_ID],
|
||||||
|
$_SESSION[self::KEY_FINALIZED],
|
||||||
|
$_SESSION[self::KEY_ACTIVE]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function currentLogId()
|
||||||
|
{
|
||||||
|
return (int) ($_SESSION[self::KEY_LOG_ID] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isFinalized()
|
||||||
|
{
|
||||||
|
return !empty($_SESSION[self::KEY_FINALIZED]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ip_check 入口:识别刷新导航并开启新 visit。
|
||||||
|
*/
|
||||||
|
public static function evaluateAtEntry()
|
||||||
|
{
|
||||||
|
if (self::isFcheckRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (self::isRefreshNavigation()) {
|
||||||
|
self::clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function markVisitActive()
|
||||||
|
{
|
||||||
|
$_SESSION[self::KEY_ACTIVE] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isFcheckRequest()
|
||||||
|
{
|
||||||
|
$script = $_SERVER['SCRIPT_NAME'] ?? $_SERVER['PHP_SELF'] ?? '';
|
||||||
|
return basename($script) === 'f_check.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function hasContinuationMarker()
|
||||||
|
{
|
||||||
|
if (isset($_GET['time']) && (string) $_GET['time'] !== '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return self::isFcheckRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isRefreshNavigation()
|
||||||
|
{
|
||||||
|
if (self::hasContinuationMarker()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (self::currentLogId() <= 0 || self::isFinalized()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$mode = strtolower((string) ($_SERVER['HTTP_SEC_FETCH_MODE'] ?? ''));
|
||||||
|
return $mode === 'navigate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isNewVisit()
|
||||||
|
{
|
||||||
|
if (self::isRefreshNavigation()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (self::currentLogId() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (self::isFinalized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isContinuation()
|
||||||
|
{
|
||||||
|
if (self::isRefreshNavigation()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (self::hasContinuationMarker()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (self::currentLogId() > 0 && !self::isFinalized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!empty($_SESSION[self::KEY_ACTIVE]) && self::currentLogId() <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INSERT 前:已 finalize 或刷新后的新 visit 清除旧绑定。
|
||||||
|
*/
|
||||||
|
public static function prepareForNewInsert()
|
||||||
|
{
|
||||||
|
if (!self::isNewVisit()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$hadFinalized = self::isFinalized();
|
||||||
|
unset($_SESSION[self::KEY_FINALIZED]);
|
||||||
|
if ($hadFinalized || self::isRefreshNavigation()) {
|
||||||
|
unset($_SESSION[self::KEY_LOG_ID]);
|
||||||
|
}
|
||||||
|
if (empty($_SESSION[self::KEY_ACTIVE])) {
|
||||||
|
self::markVisitActive();
|
||||||
|
}
|
||||||
|
if (class_exists('VisitorApiAudit', false)) {
|
||||||
|
VisitorApiAudit::clearByApi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function shouldPersistAsUpdate()
|
||||||
|
{
|
||||||
|
return self::currentLogId() > 0 && !self::isFinalized() && !self::isNewVisit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $logId
|
||||||
|
*/
|
||||||
|
public static function bindLogId($logId)
|
||||||
|
{
|
||||||
|
$logId = (int) $logId;
|
||||||
|
if ($logId > 0) {
|
||||||
|
$_SESSION[self::KEY_LOG_ID] = $logId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $result
|
||||||
|
*/
|
||||||
|
public static function finalizeVisit($result = null)
|
||||||
|
{
|
||||||
|
$_SESSION[self::KEY_FINALIZED] = 1;
|
||||||
|
unset($_SESSION[self::KEY_ACTIVE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $result
|
||||||
|
*/
|
||||||
|
public static function shouldFinalizeOnResult($result)
|
||||||
|
{
|
||||||
|
$r = (string) $result;
|
||||||
|
return $r === 'true' || $r === 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $logs
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function normalizeLogsForDb(array $logs)
|
||||||
|
{
|
||||||
|
$result = (string) ($logs['result'] ?? '');
|
||||||
|
if (function_exists('cloak_normalize_log_reason')) {
|
||||||
|
$logs['reason'] = cloak_normalize_log_reason($result, $logs['reason'] ?? '');
|
||||||
|
}
|
||||||
|
return $logs;
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+106
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/ClientIpResolver.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP 白名单与链接参数白名单判定(供流水线早期快速路径与黑白名单阶段复用)
|
||||||
|
*/
|
||||||
|
class WhitelistGate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function getWhitelistIps()
|
||||||
|
{
|
||||||
|
if (defined('WHITELIST_GROUP_ID') && (int) WHITELIST_GROUP_ID > 0) {
|
||||||
|
return ip_group_list_addresses((int) WHITELIST_GROUP_ID);
|
||||||
|
}
|
||||||
|
if (!defined('SHOW_SITE_IP') || SHOW_SITE_IP === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_filter(array_map('trim', explode(',', SHOW_SITE_IP)), static function ($v) {
|
||||||
|
return $v !== '';
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
*/
|
||||||
|
public static function matchesWhitelistIp($ip)
|
||||||
|
{
|
||||||
|
$ip = trim((string) $ip);
|
||||||
|
if ($ip === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::getWhitelistIps() as $entry) {
|
||||||
|
if (function_exists('ip_visitor_matches_list_entry') && ip_visitor_matches_list_entry($ip, $entry)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($ip === $entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function matchesWhitelistParams()
|
||||||
|
{
|
||||||
|
if (!defined('WHITE_PARAMS') || WHITE_PARAMS === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$wparams = explode('=', WHITE_PARAMS, 2);
|
||||||
|
if (count($wparams) !== 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$pk = $wparams[0];
|
||||||
|
$pv = $wparams[1];
|
||||||
|
|
||||||
|
return isset($_GET[$pk]) && strip_tags((string) $_GET[$pk]) === $pv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param visitorInfo|object|null $v_info
|
||||||
|
*/
|
||||||
|
public static function resolveVisitorIp($v_info = null)
|
||||||
|
{
|
||||||
|
$ip = '';
|
||||||
|
if ($v_info !== null && isset($v_info->v_ip)) {
|
||||||
|
$ip = (string) $v_info->v_ip;
|
||||||
|
}
|
||||||
|
if ($ip === '' || $ip === '0.0.0.0') {
|
||||||
|
$ip = ClientIpResolver::resolve();
|
||||||
|
}
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param visitorInfo|object|null $v_info
|
||||||
|
*/
|
||||||
|
public static function isMatched($v_info = null)
|
||||||
|
{
|
||||||
|
if (self::matchesWhitelistParams()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::matchesWhitelistIp(self::resolveVisitorIp($v_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级:链接参数白名单 > IP 白名单
|
||||||
|
*
|
||||||
|
* @param visitorInfo|object|null $v_info
|
||||||
|
*/
|
||||||
|
public static function matchReason($v_info = null)
|
||||||
|
{
|
||||||
|
if (self::matchesWhitelistParams()) {
|
||||||
|
return '白名单链接参数';
|
||||||
|
}
|
||||||
|
if (self::matchesWhitelistIp(self::resolveVisitorIp($v_info))) {
|
||||||
|
return '白名单';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '白名单';
|
||||||
|
}
|
||||||
|
}
|
||||||
+186
-12
@@ -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')) {
|
if (!function_exists('cloak_fingerprint_cookie_domain')) {
|
||||||
/**
|
/**
|
||||||
* 指纹 Cookie 使用的跨子域 domain(与 setCrossDomainCookie 一致)
|
* 指纹 / Session Cookie 跨子域 domain(带点,如 .shili.buzz);IP/localhost 返回空(host-only)。
|
||||||
*/
|
*/
|
||||||
function cloak_fingerprint_cookie_domain()
|
function cloak_fingerprint_cookie_domain()
|
||||||
{
|
{
|
||||||
if (empty($_SERVER['HTTP_HOST'])) {
|
if (defined('CLOAK_COOKIE_DOMAIN') && (string) CLOAK_COOKIE_DOMAIN !== '') {
|
||||||
return '';
|
return cloak_normalize_cookie_domain_config((string) CLOAK_COOKIE_DOMAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
$host_names = explode('.', $_SERVER['HTTP_HOST']);
|
$registrable = cloak_registrable_domain();
|
||||||
if (count($host_names) < 2) {
|
return $registrable !== '' ? '.' . $registrable : '';
|
||||||
return '';
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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')) {
|
if (!function_exists('cloak_set_cross_domain_cookie')) {
|
||||||
function cloak_set_cross_domain_cookie($name, $value, $expire)
|
function cloak_set_cross_domain_cookie($name, $value, $expire)
|
||||||
{
|
{
|
||||||
$bottom_host_name = cloak_fingerprint_cookie_domain();
|
$cookieDomain = cloak_fingerprint_cookie_domain();
|
||||||
if ($bottom_host_name === '') {
|
$secure = cloak_request_is_https();
|
||||||
setcookie($name, $value, $expire, '/');
|
|
||||||
|
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;
|
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
@@ -107,19 +107,22 @@ if (!function_exists('cloak_visitor_log_fp_risk_explain')) {
|
|||||||
|
|
||||||
if (!function_exists('cloak_visitor_log_apply_result_filter')) {
|
if (!function_exists('cloak_visitor_log_apply_result_filter')) {
|
||||||
/**
|
/**
|
||||||
* 日志列表 result 筛选:默认排除 wait(检测中);显式选 wait/true/false 时精确匹配
|
* 日志列表 result 筛选:默认显示全部(含 wait/检测中);显式筛选时精确匹配
|
||||||
*
|
*
|
||||||
* @return string SQL 片段(含前导 AND)
|
* @return string SQL 片段(含前导 AND)
|
||||||
*/
|
*/
|
||||||
function cloak_visitor_log_apply_result_filter(mysqli $conn, $resultParam): string
|
function cloak_visitor_log_apply_result_filter(mysqli $conn, $resultParam): string
|
||||||
{
|
{
|
||||||
$resultParam = trim((string) $resultParam);
|
$resultParam = trim((string) $resultParam);
|
||||||
if ($resultParam === 'wait') {
|
if ($resultParam === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if ($resultParam === 'wait' || $resultParam === '检测中') {
|
||||||
return " AND `result` = 'wait'";
|
return " AND `result` = 'wait'";
|
||||||
}
|
}
|
||||||
if ($resultParam === 'true' || $resultParam === 'false') {
|
if ($resultParam === 'true' || $resultParam === 'false') {
|
||||||
return " AND `result` = '" . cloak_db_escape($conn, $resultParam) . "'";
|
return " AND `result` = '" . cloak_db_escape($conn, $resultParam) . "'";
|
||||||
}
|
}
|
||||||
return " AND (`result` IS NULL OR `result` <> 'wait')";
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,16 @@ require_once __DIR__ . '/Cloak/GeoIpCountryCache.php';
|
|||||||
require_once __DIR__ . '/Cloak/GeoIpCountryResolver.php';
|
require_once __DIR__ . '/Cloak/GeoIpCountryResolver.php';
|
||||||
require_once __DIR__ . '/Cloak/CountryCodeNormalizer.php';
|
require_once __DIR__ . '/Cloak/CountryCodeNormalizer.php';
|
||||||
require_once __DIR__ . '/Cloak/CountryAllowlist.php';
|
require_once __DIR__ . '/Cloak/CountryAllowlist.php';
|
||||||
|
require_once __DIR__ . '/Cloak/WhitelistGate.php';
|
||||||
require_once __DIR__ . '/Cloak/RiskSecondaryGate.php';
|
require_once __DIR__ . '/Cloak/RiskSecondaryGate.php';
|
||||||
|
require_once __DIR__ . '/Cloak/CloakJudgmentCache.php';
|
||||||
require_once __DIR__ . '/Cloak/RiskSecondaryCache.php';
|
require_once __DIR__ . '/Cloak/RiskSecondaryCache.php';
|
||||||
require_once __DIR__ . '/Cloak/VisitorLogSchema.php';
|
require_once __DIR__ . '/Cloak/VisitorLogSchema.php';
|
||||||
require_once __DIR__ . '/Cloak/VisitorApiAudit.php';
|
require_once __DIR__ . '/Cloak/VisitorApiAudit.php';
|
||||||
require_once __DIR__ . '/Cloak/VisitorLogQueue.php';
|
require_once __DIR__ . '/Cloak/VisitorLogQueue.php';
|
||||||
require_once __DIR__ . '/Cloak/VisitorLogWorker.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/Pipeline/CheckPipeline.php';
|
||||||
require_once __DIR__ . '/Cloak/Page/FingerprintRedirectRenderer.php';
|
require_once __DIR__ . '/Cloak/Page/FingerprintRedirectRenderer.php';
|
||||||
require_once __DIR__ . '/Cloak/Page/FpPageRenderer.php';
|
require_once __DIR__ . '/Cloak/Page/FpPageRenderer.php';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/** 虚拟设备检测规则引擎(IS_VIRTUAL 1=简单 2=标准 3=严格) */
|
/** 虚拟设备检测规则引擎(IS_VIRTUAL 1=简单 2=标准 3=严格) */
|
||||||
?>
|
?>
|
||||||
<script>
|
|
||||||
(function (window) {
|
(function (window) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@@ -239,4 +239,3 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
})(window);
|
})(window);
|
||||||
</script>
|
|
||||||
|
|||||||
Regular → Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
{"value":"__NONE__","expires_at":1784140663}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"value":"__NONE__","expires_at":1784140663}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"value":"__NONE__","expires_at":1784140663}
|
||||||
@@ -373,7 +373,7 @@ if(empty($_SESSION['password']) || $_SESSION['password'] != LOGIN_PASSWORD) {
|
|||||||
<div class="layui-col-md3">
|
<div class="layui-col-md3">
|
||||||
<div class="layui-input-wrap">
|
<div class="layui-input-wrap">
|
||||||
<select name="result">
|
<select name="result">
|
||||||
<option value="">全部(不含检测中)</option>
|
<option value="">全部</option>
|
||||||
<option value="true">TRUE(通过访问)</option>
|
<option value="true">TRUE(通过访问)</option>
|
||||||
<option value="false">FALSE(禁止访问)</option>
|
<option value="false">FALSE(禁止访问)</option>
|
||||||
<option value="wait">检测中</option>
|
<option value="wait">检测中</option>
|
||||||
|
|||||||
Executable
+30
@@ -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');
|
||||||
Executable
+30
@@ -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');
|
||||||
@@ -137,6 +137,11 @@ reg_assert('fp 模式配置生效', ($fpResult['mode'] ?? '') === 'fp', json_enc
|
|||||||
|
|
||||||
$zpResult = run_pipeline_subprocess($root, 'regression_zp.php');
|
$zpResult = run_pipeline_subprocess($root, 'regression_zp.php');
|
||||||
reg_assert('zp 模式配置生效', ($zpResult['mode'] ?? '') === 'zp', json_encode($zpResult, JSON_UNESCAPED_UNICODE));
|
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. 指纹跳转模板 ───────────────────────────────────────────
|
// ── 7. 指纹跳转模板 ───────────────────────────────────────────
|
||||||
echo "[7] 指纹跳转模板\n";
|
echo "[7] 指纹跳转模板\n";
|
||||||
@@ -158,12 +163,17 @@ reg_assert('cloak_fingerprint_cookies_ready', cloak_fingerprint_cookies_ready())
|
|||||||
$_SERVER['HTTP_HOST'] = 'shop.example.com';
|
$_SERVER['HTTP_HOST'] = 'shop.example.com';
|
||||||
reg_assert('cloak_fingerprint_cookie_domain', cloak_fingerprint_cookie_domain() === '.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'];
|
$_GET = ['c' => 'index'];
|
||||||
ob_start();
|
ob_start();
|
||||||
include $root . '/dist/index.php';
|
include $root . '/dist/index.php';
|
||||||
$distJs = ob_get_clean();
|
$distJs = ob_get_clean();
|
||||||
reg_assert('dist/index.php 输出 Cookies.set', strpos($distJs, "fpCookies.set('ctime'") !== false);
|
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 输出指纹完成回调', 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);
|
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');
|
$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);
|
$riskCacheJson = json_decode((string) $riskCacheOut, true);
|
||||||
reg_assert('二次风控 Session 缓存与 CORS', ($riskCacheJson['ok'] ?? false) === true, (string) $riskCacheOut);
|
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');
|
$datalistHelperOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_datalist_helper.php') . ' 2>/dev/null');
|
||||||
$datalistHelperJson = json_decode((string) $datalistHelperOut, true);
|
$datalistHelperJson = json_decode((string) $datalistHelperOut, true);
|
||||||
reg_assert('日志列表 VisitorLogListHelper', ($datalistHelperJson['ok'] ?? false) === true, (string) $datalistHelperOut);
|
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 / 宝塔部署文件 ───────────────────────────
|
// ── 12. ConfigLoader / 宝塔部署文件 ───────────────────────────
|
||||||
echo "[12] ConfigLoader 与宝塔部署\n";
|
echo "[12] ConfigLoader 与宝塔部署\n";
|
||||||
require_once $root . '/config/ConfigLoader.php';
|
require_once $root . '/config/ConfigLoader.php';
|
||||||
|
|||||||
Executable
+97
@@ -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);
|
||||||
@@ -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
@@ -23,6 +23,15 @@ if ($conn->connect_error) {
|
|||||||
$where = VisitorLogListHelper::buildWhere($conn, ['visit_date' => '2026-01-15']);
|
$where = VisitorLogListHelper::buildWhere($conn, ['visit_date' => '2026-01-15']);
|
||||||
$tests['date_range_where'] = strpos($where, 'visit_date') !== false && strpos($where, 'DATE_FORMAT') === false;
|
$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);
|
$sql = VisitorLogListHelper::listSelectSql('1', 0, 10, true);
|
||||||
$tests['lean_select_no_fp_hdata'] = strpos($sql, '`fp_hdata`,') === false && strpos($sql, 'has_fp_hdata') !== false;
|
$tests['lean_select_no_fp_hdata'] = strpos($sql, '`fp_hdata`,') === false && strpos($sql, 'has_fp_hdata') !== false;
|
||||||
|
|
||||||
|
|||||||
Executable
+82
@@ -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);
|
||||||
Executable
+84
@@ -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
+53
-26
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* 二次风控 Session 缓存回归(CLI 输出 JSON)
|
* CloakJudgmentCache / RiskSecondaryCache 回归(CLI JSON)
|
||||||
*/
|
*/
|
||||||
if (PHP_SAPI !== 'cli') {
|
if (PHP_SAPI !== 'cli') {
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -9,41 +9,69 @@ if (PHP_SAPI !== 'cli') {
|
|||||||
|
|
||||||
$root = dirname(__DIR__);
|
$root = dirname(__DIR__);
|
||||||
require_once $root . '/cong.php';
|
require_once $root . '/cong.php';
|
||||||
require_once $root . '/lib/CloakHttpHelpers.php';
|
|
||||||
require_once $root . '/lib/Cloak/ClientIpResolver.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';
|
require_once $root . '/lib/Cloak/RiskSecondaryCache.php';
|
||||||
|
|
||||||
|
if (!defined('CLOAK_RISK_NUMBER')) {
|
||||||
|
define('CLOAK_RISK_NUMBER', 51);
|
||||||
|
}
|
||||||
|
if (!defined('COSTM_IP_SCORE')) {
|
||||||
|
define('COSTM_IP_SCORE', 'test_campaign');
|
||||||
|
}
|
||||||
|
|
||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
|
$tests = [];
|
||||||
|
|
||||||
$tests = [];
|
CloakJudgmentCache::storeFromByApiRisk([
|
||||||
|
'ip' => '203.0.113.10',
|
||||||
RiskSecondaryCache::store([
|
'country' => 'US',
|
||||||
'ip' => '203.0.113.10',
|
'result' => 'true',
|
||||||
'result' => 'true',
|
'reason' => '',
|
||||||
'reason' => 'ok',
|
'fp_url' => 'https://fp.example/a',
|
||||||
'fp_url' => 'https://fp.example/a',
|
|
||||||
]);
|
]);
|
||||||
$hit = RiskSecondaryCache::get('203.0.113.10');
|
$hit = CloakJudgmentCache::getFinal('203.0.113.10');
|
||||||
$tests['same_ip_hit'] = is_array($hit) && ($hit['result'] ?? '') === 'true';
|
$tests['same_ip_final_hit'] = is_array($hit) && ($hit['result'] ?? '') === 'true' && ($hit['stage'] ?? '') === CloakJudgmentCache::STAGE_FINAL;
|
||||||
|
|
||||||
RiskSecondaryCache::applyToSession($hit);
|
if (is_array($hit)) {
|
||||||
$tests['apply_session'] = ($_SESSION['visit_to_3'] ?? '') == 3 && ($_SESSION['check_result'] ?? '') === 'true';
|
CloakJudgmentCache::applyToSession($hit);
|
||||||
|
}
|
||||||
|
$tests['apply_session_final'] = ($_SESSION['visit_to_3'] ?? '') == 3 && ($_SESSION['check_result'] ?? '') === 'true';
|
||||||
|
|
||||||
$miss = RiskSecondaryCache::get('198.51.100.1');
|
$hitNewIp = CloakJudgmentCache::get('198.51.100.5');
|
||||||
$tests['ip_change_miss'] = $miss === null && empty($_SESSION[RiskSecondaryCache::SESSION_KEY]);
|
$tests['ip_change_still_valid'] = is_array($hitNewIp) && ($hitNewIp['result'] ?? '') === 'true';
|
||||||
$tests['ip_change_resets_visit_to_3'] = ($_SESSION['visit_to_3'] ?? '') === '1';
|
$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');
|
||||||
'ip' => '203.0.113.10',
|
$tests['risk_cache_get_skips_partial'] = true;
|
||||||
'result' => 'false',
|
CloakJudgmentCache::clear();
|
||||||
'reason' => 'deny',
|
CloakJudgmentCache::storeFromByApi([
|
||||||
'fp_url' => 'https://zp.example/',
|
'ip' => '203.0.113.10',
|
||||||
|
'country' => 'US',
|
||||||
|
'result' => 'true',
|
||||||
|
'reason' => 'api ok',
|
||||||
]);
|
]);
|
||||||
$api = RiskSecondaryCache::toApiResponse(RiskSecondaryCache::get('203.0.113.10'));
|
$tests['byapi_stage_is_partial'] = CloakJudgmentCache::isPartial();
|
||||||
$tests['to_api_response'] = $api['result'] === false && $api['reason'] === RiskSecondaryCache::hitReason();
|
$tests['risk_cache_get_skips_partial'] = RiskSecondaryCache::get('203.0.113.10') === null;
|
||||||
|
|
||||||
RiskSecondaryCache::clear();
|
CloakJudgmentCache::storeFromByApiRisk([
|
||||||
$tests['clear'] = RiskSecondaryCache::get('203.0.113.10') === null;
|
'ip' => '203.0.113.10',
|
||||||
|
'country' => 'US',
|
||||||
|
'result' => 'false',
|
||||||
|
'reason' => 'deny',
|
||||||
|
'fp_url' => 'https://zp.example/',
|
||||||
|
]);
|
||||||
|
$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();
|
||||||
|
|
||||||
|
$_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;
|
$originOk = false;
|
||||||
$_SERVER['HTTP_HOST'] = 'cloak.example.com';
|
$_SERVER['HTTP_HOST'] = 'cloak.example.com';
|
||||||
@@ -59,6 +87,5 @@ $badOrigin = !(bool) $method->invoke(null, 'https://evil.other.com');
|
|||||||
$tests['cors_reject_foreign'] = $badOrigin;
|
$tests['cors_reject_foreign'] = $badOrigin;
|
||||||
|
|
||||||
$ok = !in_array(false, $tests, true);
|
$ok = !in_array(false, $tests, true);
|
||||||
|
|
||||||
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
echo json_encode(['ok' => $ok, 'tests' => $tests], JSON_UNESCAPED_UNICODE) . "\n";
|
||||||
exit($ok ? 0 : 1);
|
exit($ok ? 0 : 1);
|
||||||
|
|||||||
Executable
+32
@@ -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
@@ -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);
|
||||||
Executable
+72
@@ -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);
|
||||||
@@ -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);
|
||||||
Reference in New Issue
Block a user