修复判断入口文件ip_check.php以及新增复制配置选项

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