新增maxmind数据库本地判断国家,并且将优先级调整至最高
This commit is contained in:
+5
-1
@@ -2,4 +2,8 @@
|
|||||||
.sidebar/
|
.sidebar/
|
||||||
.cursor/
|
.cursor/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
/storage/runtime/c7f2a9e1/
|
||||||
|
GeoLite2-Country.mmdb
|
||||||
|
/vendor/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"geoip2/geoip2": "^2.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+264
@@ -0,0 +1,264 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "f3faaeb99fa42083a584a66b046ae2c6",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "composer/ca-bundle",
|
||||||
|
"version": "1.5.12",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/ca-bundle.git",
|
||||||
|
"reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78",
|
||||||
|
"reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"phpunit/phpunit": "^8 || ^9",
|
||||||
|
"psr/log": "^1.0 || ^2.0 || ^3.0",
|
||||||
|
"symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\CaBundle\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jordi Boggiano",
|
||||||
|
"email": "j.boggiano@seld.be",
|
||||||
|
"homepage": "http://seld.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.",
|
||||||
|
"keywords": [
|
||||||
|
"cabundle",
|
||||||
|
"cacert",
|
||||||
|
"certificate",
|
||||||
|
"ssl",
|
||||||
|
"tls"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"irc": "irc://irc.freenode.org/composer",
|
||||||
|
"issues": "https://github.com/composer/ca-bundle/issues",
|
||||||
|
"source": "https://github.com/composer/ca-bundle/tree/1.5.12"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-19T11:26:22+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "geoip2/geoip2",
|
||||||
|
"version": "v2.13.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maxmind/GeoIP2-php.git",
|
||||||
|
"reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/6a41d8fbd6b90052bc34dff3b4252d0f88067b23",
|
||||||
|
"reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"maxmind-db/reader": "~1.8",
|
||||||
|
"maxmind/web-service-common": "~0.8",
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "3.*",
|
||||||
|
"phpstan/phpstan": "*",
|
||||||
|
"phpunit/phpunit": "^8.0 || ^9.0",
|
||||||
|
"squizlabs/php_codesniffer": "3.*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GeoIp2\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gregory J. Oschwald",
|
||||||
|
"email": "goschwald@maxmind.com",
|
||||||
|
"homepage": "https://www.maxmind.com/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "MaxMind GeoIP2 PHP API",
|
||||||
|
"homepage": "https://github.com/maxmind/GeoIP2-php",
|
||||||
|
"keywords": [
|
||||||
|
"IP",
|
||||||
|
"geoip",
|
||||||
|
"geoip2",
|
||||||
|
"geolocation",
|
||||||
|
"maxmind"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/maxmind/GeoIP2-php/issues",
|
||||||
|
"source": "https://github.com/maxmind/GeoIP2-php/tree/v2.13.0"
|
||||||
|
},
|
||||||
|
"time": "2022-08-05T20:32:58+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxmind-db/reader",
|
||||||
|
"version": "v1.13.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
|
||||||
|
"reference": "2194f58d0f024ce923e685cdf92af3daf9951908"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908",
|
||||||
|
"reference": "2194f58d0f024ce923e685cdf92af3daf9951908",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"ext-maxminddb": "<1.11.1 || >=2.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "3.*",
|
||||||
|
"phpstan/phpstan": "*",
|
||||||
|
"phpunit/phpunit": ">=8.0.0,<10.0.0",
|
||||||
|
"squizlabs/php_codesniffer": "4.*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||||
|
"ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder",
|
||||||
|
"ext-maxminddb": "A C-based database decoder that provides significantly faster lookups",
|
||||||
|
"maxmind-db/reader-ext": "C extension for significantly faster IP lookups (install via PIE: pie install maxmind-db/reader-ext)"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"MaxMind\\Db\\": "src/MaxMind/Db"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gregory J. Oschwald",
|
||||||
|
"email": "goschwald@maxmind.com",
|
||||||
|
"homepage": "https://www.maxmind.com/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "MaxMind DB Reader API",
|
||||||
|
"homepage": "https://github.com/maxmind/MaxMind-DB-Reader-php",
|
||||||
|
"keywords": [
|
||||||
|
"database",
|
||||||
|
"geoip",
|
||||||
|
"geoip2",
|
||||||
|
"geolocation",
|
||||||
|
"maxmind"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues",
|
||||||
|
"source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1"
|
||||||
|
},
|
||||||
|
"time": "2025-11-21T22:24:26+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "maxmind/web-service-common",
|
||||||
|
"version": "v0.11.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/maxmind/web-service-common-php.git",
|
||||||
|
"reference": "c309236b5a5555b96cf560089ec3cead12d845d2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/c309236b5a5555b96cf560089ec3cead12d845d2",
|
||||||
|
"reference": "c309236b5a5555b96cf560089ec3cead12d845d2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/ca-bundle": "^1.0.3",
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "3.*",
|
||||||
|
"phpstan/phpstan": "*",
|
||||||
|
"phpunit/phpunit": "^10.0",
|
||||||
|
"squizlabs/php_codesniffer": "4.*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"MaxMind\\Exception\\": "src/Exception",
|
||||||
|
"MaxMind\\WebService\\": "src/WebService"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Gregory Oschwald",
|
||||||
|
"email": "goschwald@maxmind.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Internal MaxMind Web Service API",
|
||||||
|
"homepage": "https://github.com/maxmind/web-service-common-php",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/maxmind/web-service-common-php/issues",
|
||||||
|
"source": "https://github.com/maxmind/web-service-common-php/tree/v0.11.1"
|
||||||
|
},
|
||||||
|
"time": "2026-01-13T17:56:03+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {},
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {},
|
||||||
|
"platform-dev": {},
|
||||||
|
"plugin-api-version": "2.9.0"
|
||||||
|
}
|
||||||
@@ -22,7 +22,11 @@ define('REDIS_PASSWORD', '');
|
|||||||
// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---
|
// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---
|
||||||
define('VISITOR_LOG_ASYNC', 'ON');
|
define('VISITOR_LOG_ASYNC', 'ON');
|
||||||
|
|
||||||
// --- Cloudflare(可选;留空则域名仅本地登记,不走 API)---
|
define('CLOAK_GEOIP_ENABLED', 'ON');
|
||||||
|
define('CLOAK_GEOIP_DB_PATH', __DIR__ . '/storage/runtime/c7f2a9e1/cc_idx.dat');
|
||||||
|
define('CLOAK_GEOIP_CACHE_TTL', 2592000);
|
||||||
|
|
||||||
|
// --- Cloudflare(必填;留空则域名仅本地登记,不走 API)---
|
||||||
define('SERVER_IP', '');
|
define('SERVER_IP', '');
|
||||||
define('CLOUDFLARE_API_TOKEN', '');
|
define('CLOUDFLARE_API_TOKEN', '');
|
||||||
define('CLOUDFLARE_ACCOUNT_ID', '');
|
define('CLOUDFLARE_ACCOUNT_ID', '');
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ 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
|
||||||
|
. '// --- GeoIP 本地国家库(MaxMind GeoLite2 Country)---' . PHP_EOL
|
||||||
|
. 'define(\'CLOAK_GEOIP_ENABLED\', \'ON\');' . PHP_EOL
|
||||||
|
. 'define(\'CLOAK_GEOIP_DB_PATH\', __DIR__ . \'/storage/runtime/c7f2a9e1/cc_idx.dat\');' . PHP_EOL
|
||||||
|
. 'define(\'CLOAK_GEOIP_CACHE_TTL\', 2592000);' . PHP_EOL
|
||||||
|
. PHP_EOL
|
||||||
. '// --- Cloudflare(可选;留空则域名仅本地登记,不走 API)---' . PHP_EOL
|
. '// --- Cloudflare(可选;留空则域名仅本地登记,不走 API)---' . PHP_EOL
|
||||||
. 'define(\'SERVER_IP\', ' . var_export($server_ip, true) . ');' . PHP_EOL
|
. 'define(\'SERVER_IP\', ' . var_export($server_ip, true) . ');' . PHP_EOL
|
||||||
. 'define(\'CLOUDFLARE_API_TOKEN\', ' . var_export($cf_token, true) . ');' . PHP_EOL
|
. 'define(\'CLOUDFLARE_API_TOKEN\', ' . var_export($cf_token, true) . ');' . PHP_EOL
|
||||||
|
|||||||
@@ -93,3 +93,7 @@
|
|||||||
2026-06-14 12:30:50 | 写入数据库 | | regression_test | async=1
|
2026-06-14 12:30:50 | 写入数据库 | | regression_test | async=1
|
||||||
2026-06-14 12:33:28 | 来源判断 | 黑名单|false | index | 127.0.0.1 |
|
2026-06-14 12:33:28 | 来源判断 | 黑名单|false | index | 127.0.0.1 |
|
||||||
2026-06-14 12:33:28 | 写入数据库 | false | index | async=1
|
2026-06-14 12:33:28 | 写入数据库 | false | index | async=1
|
||||||
|
2026-06-14 15:22:55 | 写入数据库 | | regression_test | async=1
|
||||||
|
2026-06-14 15:22:55 | 写入数据库 | | regression_test | async=1
|
||||||
|
2026-06-14 15:23:23 | 写入数据库 | | regression_test | async=1
|
||||||
|
2026-06-14 15:23:23 | 写入数据库 | | regression_test | async=1
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CDN / 反代场景下解析访客真实 IP
|
||||||
|
*/
|
||||||
|
class ClientIpResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function resolve()
|
||||||
|
{
|
||||||
|
static $resolved = null;
|
||||||
|
if ($resolved !== null) {
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
$candidates = [];
|
||||||
|
|
||||||
|
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
|
||||||
|
$candidates[] = (string) $_SERVER['HTTP_CF_CONNECTING_IP'];
|
||||||
|
}
|
||||||
|
if (!empty($_SERVER['HTTP_TRUE_CLIENT_IP'])) {
|
||||||
|
$candidates[] = (string) $_SERVER['HTTP_TRUE_CLIENT_IP'];
|
||||||
|
}
|
||||||
|
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
|
||||||
|
$candidates[] = (string) $_SERVER['HTTP_X_REAL_IP'];
|
||||||
|
}
|
||||||
|
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||||
|
foreach (explode(',', (string) $_SERVER['HTTP_X_FORWARDED_FOR']) as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
if ($part !== '' && strcasecmp($part, 'unknown') !== 0) {
|
||||||
|
$candidates[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($_SERVER['REMOTE_ADDR'])) {
|
||||||
|
$candidates[] = (string) $_SERVER['REMOTE_ADDR'];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
$ip = self::normalizeIp($candidate);
|
||||||
|
if ($ip === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (self::isPublicIp($ip)) {
|
||||||
|
$resolved = $ip;
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($candidates as $candidate) {
|
||||||
|
$ip = self::normalizeIp($candidate);
|
||||||
|
if ($ip !== null) {
|
||||||
|
$resolved = $ip;
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolved = '0.0.0.0';
|
||||||
|
return $resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
*/
|
||||||
|
public static function normalizeIp($ip)
|
||||||
|
{
|
||||||
|
$ip = trim($ip);
|
||||||
|
if ($ip === '' || strcasecmp($ip, 'unknown') === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
*/
|
||||||
|
private static function isPublicIp($ip)
|
||||||
|
{
|
||||||
|
return filter_var(
|
||||||
|
$ip,
|
||||||
|
FILTER_VALIDATE_IP,
|
||||||
|
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
|
||||||
|
) !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SHOW_SITE_COUNTRY 允许列表解析与比对
|
||||||
|
*/
|
||||||
|
class CountryAllowlist
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $config 逗号分隔国家码,如 US,GB,CA
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function parse($config)
|
||||||
|
{
|
||||||
|
$config = strtoupper(trim((string) $config));
|
||||||
|
if ($config === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
$parts = preg_split('/\s*,\s*/', $config);
|
||||||
|
$out = [];
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
if ($part !== '') {
|
||||||
|
$out[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values(array_unique($out));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $countryCode
|
||||||
|
* @param string[] $allowlist
|
||||||
|
*/
|
||||||
|
public static function isAllowed($countryCode, array $allowlist)
|
||||||
|
{
|
||||||
|
if ($allowlist === []) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($countryCode === null || $countryCode === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return in_array(strtoupper($countryCode), $allowlist, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* IP → 国家码跨请求缓存(Redis 优先,文件降级,默认 TTL 30 天)
|
||||||
|
*/
|
||||||
|
class GeoIpCountryCache
|
||||||
|
{
|
||||||
|
const REDIS_PREFIX = 'cloak:geoip:cc:';
|
||||||
|
const NONE_SENTINEL = '__NONE__';
|
||||||
|
|
||||||
|
/** @var bool|null */
|
||||||
|
private static $redisAvailable = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return string|false|null 命中返回国家码或 null(已知无国家);false=未命中
|
||||||
|
*/
|
||||||
|
public static function get($ip)
|
||||||
|
{
|
||||||
|
$ip = ClientIpResolver::normalizeIp($ip);
|
||||||
|
if ($ip === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$redis = self::redisClient();
|
||||||
|
if ($redis) {
|
||||||
|
try {
|
||||||
|
$value = $redis->get(self::REDIS_PREFIX . $ip);
|
||||||
|
if ($value === false || $value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $value === self::NONE_SENTINEL ? null : (string) $value;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
self::logError('Redis get failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::getFile($ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @param string|null $countryCode
|
||||||
|
*/
|
||||||
|
public static function set($ip, $countryCode)
|
||||||
|
{
|
||||||
|
$ip = ClientIpResolver::normalizeIp($ip);
|
||||||
|
if ($ip === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $countryCode !== null && $countryCode !== '' ? strtoupper($countryCode) : self::NONE_SENTINEL;
|
||||||
|
$ttl = self::cacheTtl();
|
||||||
|
|
||||||
|
$redis = self::redisClient();
|
||||||
|
if ($redis) {
|
||||||
|
try {
|
||||||
|
$redis->setex(self::REDIS_PREFIX . $ip, $ttl, $value);
|
||||||
|
return;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
self::logError('Redis set failed: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::setFile($ip, $value, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function cacheTtl()
|
||||||
|
{
|
||||||
|
$ttl = defined('CLOAK_GEOIP_CACHE_TTL') ? (int) CLOAK_GEOIP_CACHE_TTL : 2592000;
|
||||||
|
return max(60, $ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Redis|null
|
||||||
|
*/
|
||||||
|
private static function redisClient()
|
||||||
|
{
|
||||||
|
if (self::$redisAvailable === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!extension_loaded('redis') || !defined('REDIS_ENABLED') || strtoupper(REDIS_ENABLED) !== 'ON') {
|
||||||
|
self::$redisAvailable = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$r = new Redis();
|
||||||
|
$timeout = defined('REDIS_TIMEOUT') ? (float) REDIS_TIMEOUT : 1.0;
|
||||||
|
if (!@$r->connect(REDIS_HOST, (int) REDIS_PORT, $timeout)) {
|
||||||
|
self::$redisAvailable = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (defined('REDIS_PASSWORD') && REDIS_PASSWORD !== '') {
|
||||||
|
$r->auth(REDIS_PASSWORD);
|
||||||
|
}
|
||||||
|
self::$redisAvailable = true;
|
||||||
|
return $r;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
self::$redisAvailable = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function cacheDir()
|
||||||
|
{
|
||||||
|
$dir = dirname(__DIR__, 2) . '/storage/runtime/c7f2a9e1/cc_cache';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
@mkdir($dir, 0755, true);
|
||||||
|
}
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function cacheFilePath($ip)
|
||||||
|
{
|
||||||
|
return self::cacheDir() . '/' . hash('sha256', $ip) . '.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return string|false|null
|
||||||
|
*/
|
||||||
|
private static function getFile($ip)
|
||||||
|
{
|
||||||
|
$path = self::cacheFilePath($ip);
|
||||||
|
if (!is_file($path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$raw = @file_get_contents($path);
|
||||||
|
if ($raw === false || $raw === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$data = json_decode($raw, true);
|
||||||
|
if (!is_array($data) || empty($data['expires_at']) || empty($data['value'])) {
|
||||||
|
@unlink($path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((int) $data['expires_at'] < time()) {
|
||||||
|
@unlink($path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $data['value'] === self::NONE_SENTINEL ? null : (string) $data['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @param string $value
|
||||||
|
* @param int $ttl
|
||||||
|
*/
|
||||||
|
private static function setFile($ip, $value, $ttl)
|
||||||
|
{
|
||||||
|
$payload = json_encode([
|
||||||
|
'value' => $value,
|
||||||
|
'expires_at' => time() + $ttl,
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
if ($payload === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@file_put_contents(self::cacheFilePath($ip), $payload, LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function logError($message)
|
||||||
|
{
|
||||||
|
$handle = @fopen(dirname(__DIR__, 2) . '/err.txt', 'a+');
|
||||||
|
if ($handle) {
|
||||||
|
fwrite($handle, date('Y-m-d H:i:s') . ' | GeoIpCountryCache | ' . $message . "\n");
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
class GeoIpCountryResolver
|
||||||
|
{
|
||||||
|
/** @var GeoIp2\Database\Reader|null */
|
||||||
|
private static $reader = null;
|
||||||
|
|
||||||
|
/** @var array<string, string|null> */
|
||||||
|
private static $requestCache = [];
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private static $lastSource = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return array{code:?string, source:string}
|
||||||
|
*/
|
||||||
|
public static function resolveWithMeta($ip)
|
||||||
|
{
|
||||||
|
$ip = ClientIpResolver::normalizeIp($ip);
|
||||||
|
if ($ip === null) {
|
||||||
|
self::$lastSource = 'invalid_ip';
|
||||||
|
return ['code' => null, 'source' => 'invalid_ip'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($ip, self::$requestCache)) {
|
||||||
|
self::$lastSource = 'request';
|
||||||
|
return ['code' => self::$requestCache[$ip], 'source' => 'request'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$cached = GeoIpCountryCache::get($ip);
|
||||||
|
if ($cached !== false) {
|
||||||
|
self::$requestCache[$ip] = $cached;
|
||||||
|
self::$lastSource = 'cache';
|
||||||
|
return ['code' => $cached, 'source' => 'cache'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = self::lookupMmdb($ip);
|
||||||
|
GeoIpCountryCache::set($ip, $code);
|
||||||
|
self::$requestCache[$ip] = $code;
|
||||||
|
self::$lastSource = 'mmdb';
|
||||||
|
return ['code' => $code, 'source' => 'mmdb'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return string|null ISO 3166-1 alpha-2
|
||||||
|
*/
|
||||||
|
public static function resolve($ip)
|
||||||
|
{
|
||||||
|
$result = self::resolveWithMeta($ip);
|
||||||
|
return $result['code'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function lastSource()
|
||||||
|
{
|
||||||
|
return self::$lastSource ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function isAvailable()
|
||||||
|
{
|
||||||
|
return self::getReader() !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private static function lookupMmdb($ip)
|
||||||
|
{
|
||||||
|
$reader = self::getReader();
|
||||||
|
if ($reader === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$record = $reader->country($ip);
|
||||||
|
$code = $record->country->isoCode ?? null;
|
||||||
|
if ($code === null || $code === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return strtoupper((string) $code);
|
||||||
|
} catch (GeoIp2\Exception\AddressNotFoundException $e) {
|
||||||
|
return null;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
GeoIpCountryCache::logError('mmdb lookup failed: ' . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return GeoIp2\Database\Reader|null
|
||||||
|
*/
|
||||||
|
private static function getReader()
|
||||||
|
{
|
||||||
|
if (self::$reader !== null) {
|
||||||
|
return self::$reader;
|
||||||
|
}
|
||||||
|
if (!class_exists('GeoIp2\\Database\\Reader')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!defined('CLOAK_GEOIP_DB_PATH') || !is_file(CLOAK_GEOIP_DB_PATH)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
self::$reader = new GeoIp2\Database\Reader(CLOAK_GEOIP_DB_PATH);
|
||||||
|
return self::$reader;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
GeoIpCountryCache::logError('Reader init failed: ' . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
* 流量判定主流水线
|
* 流量判定主流水线
|
||||||
*
|
*
|
||||||
* 阶段顺序(不可调换):
|
* 阶段顺序(不可调换):
|
||||||
* 1. Session 快速路径 → 2. 黑白名单 → 3. 广告来源限制 → 4. 临时链接 → 5. API/指纹
|
* 1. Session 快速路径 → 2. GeoIP 国家 → 3. 黑白名单 → 4. 广告来源 → 5. 临时链接 → 6. API/指纹
|
||||||
*/
|
*/
|
||||||
class CheckPipeline
|
class CheckPipeline
|
||||||
{
|
{
|
||||||
@@ -34,6 +34,13 @@ class CheckPipeline
|
|||||||
cloak_dbg_step(__LINE__, 'Session 快速路径', 'skip', '未命中缓存,进入完整判定流程(DEBUG 已重置 visit_to_*)');
|
cloak_dbg_step(__LINE__, 'Session 快速路径', 'skip', '未命中缓存,进入完整判定流程(DEBUG 已重置 visit_to_*)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CloakPipelineTimer::stage('GeoIP国家判定');
|
||||||
|
include __DIR__ . '/stages/check_country_geoip.inc.php';
|
||||||
|
if (!empty($_SESSION['check_result'])) {
|
||||||
|
CloakPipelineTimer::finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CloakPipelineTimer::stage('黑白名单');
|
CloakPipelineTimer::stage('黑白名单');
|
||||||
include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php';
|
include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php';
|
||||||
CloakPipelineTimer::stage('广告来源限制');
|
CloakPipelineTimer::stage('广告来源限制');
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 屏蔽模式:GeoIP 国家判定(本地 MaxMind + 30 天 IP 缓存)
|
||||||
|
*
|
||||||
|
* 依赖全局:$v_info, $reason, $__cloak_debug_on
|
||||||
|
*/
|
||||||
|
if (!defined('SHOW_SITE_MODE_SWITCH') || SHOW_SITE_MODE_SWITCH !== 'ip_check') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!defined('CLOAK_GEOIP_ENABLED') || strtoupper(CLOAK_GEOIP_ENABLED) !== 'ON') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!empty($_SESSION['check_result'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = ClientIpResolver::resolve();
|
||||||
|
if ($ip !== '' && $ip !== '0.0.0.0') {
|
||||||
|
$v_info->v_ip = $ip;
|
||||||
|
$_SESSION['gcu_ip'] = $ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
$geoMeta = GeoIpCountryResolver::resolveWithMeta($ip);
|
||||||
|
$countryCode = $geoMeta['code'];
|
||||||
|
$geoSource = $geoMeta['source'];
|
||||||
|
|
||||||
|
if ($countryCode !== null && $countryCode !== '') {
|
||||||
|
$v_info->v_country = $countryCode;
|
||||||
|
$_SESSION['gcu_country'] = $countryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowlist = CountryAllowlist::parse(defined('SHOW_SITE_COUNTRY') ? SHOW_SITE_COUNTRY : '');
|
||||||
|
if (!CountryAllowlist::isAllowed($countryCode, $allowlist)) {
|
||||||
|
$_SESSION['check_result'] = 'false';
|
||||||
|
if ($countryCode === null || $countryCode === '') {
|
||||||
|
$reason = '无法识别访客国家';
|
||||||
|
} else {
|
||||||
|
$reason = '国家不符:' . $countryCode;
|
||||||
|
}
|
||||||
|
if (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||||
|
cloak_dbg_step(__LINE__, 'GeoIP国家判定', 'fail', $reason, [
|
||||||
|
'ip' => $ip,
|
||||||
|
'country' => $countryCode,
|
||||||
|
'allowlist' => $allowlist,
|
||||||
|
'geo_source' => $geoSource,
|
||||||
|
]);
|
||||||
|
cloak_dbg_record(__LINE__, 'false', $reason, ['stage' => 'geoip_country']);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($GLOBALS['__cloak_debug_on'])) {
|
||||||
|
cloak_dbg_step(__LINE__, 'GeoIP国家判定', 'pass', '国家允许或列表为空', [
|
||||||
|
'ip' => $ip,
|
||||||
|
'country' => $countryCode,
|
||||||
|
'allowlist' => $allowlist,
|
||||||
|
'geo_source' => $geoSource,
|
||||||
|
]);
|
||||||
|
}
|
||||||
+11
-35
@@ -32,10 +32,9 @@ class visitorInfo
|
|||||||
*/
|
*/
|
||||||
public function get_visitor_Info($costm_ip_score = '', $check_key = '', $check_country = '')
|
public function get_visitor_Info($costm_ip_score = '', $check_key = '', $check_country = '')
|
||||||
{
|
{
|
||||||
$this->v_country = !empty($_SESSION["gcu_country"])?$_SESSION["gcu_country"]:$check_country;
|
$this->v_country = !empty($_SESSION['gcu_country']) ? (string) $_SESSION['gcu_country'] : '';
|
||||||
$this->v_ip = $this->getIp();
|
$this->v_ip = $this->getIp();
|
||||||
$this->v_Browser = $this->getBrowser();
|
$this->v_Browser = $this->getBrowser();
|
||||||
//$this->v_country = $this->findCityByIp($this->v_ip);
|
|
||||||
$this->v_referer = $this->getFromPage();
|
$this->v_referer = $this->getFromPage();
|
||||||
$this->v_Client = $this->v_isMobile();
|
$this->v_Client = $this->v_isMobile();
|
||||||
$this->v_curPageURL = $this->curPageURL();
|
$this->v_curPageURL = $this->curPageURL();
|
||||||
@@ -45,8 +44,10 @@ class visitorInfo
|
|||||||
$this->costm_ip_score = $costm_ip_score;
|
$this->costm_ip_score = $costm_ip_score;
|
||||||
$this->check_key = $check_key;
|
$this->check_key = $check_key;
|
||||||
$this->check_country = $check_country;
|
$this->check_country = $check_country;
|
||||||
|
|
||||||
$_SESSION["gcu_country"] = $this->v_country;
|
if ($this->v_country !== '') {
|
||||||
|
$_SESSION['gcu_country'] = $this->v_country;
|
||||||
|
}
|
||||||
$_SESSION["gcu_Client"] = $this->v_Client;
|
$_SESSION["gcu_Client"] = $this->v_Client;
|
||||||
$_SESSION["gcu_ip"] = $this->v_ip;
|
$_SESSION["gcu_ip"] = $this->v_ip;
|
||||||
$_SESSION["gcu_Browser"] = $this->v_Browser;
|
$_SESSION["gcu_Browser"] = $this->v_Browser;
|
||||||
@@ -71,7 +72,9 @@ class visitorInfo
|
|||||||
$return = ['result' => false, 'reason' => 'API响应异常', 'country' => $this->v_country];
|
$return = ['result' => false, 'reason' => 'API响应异常', 'country' => $this->v_country];
|
||||||
}
|
}
|
||||||
$this->check_result = !empty($return['result']) ? "true" : "false";
|
$this->check_result = !empty($return['result']) ? "true" : "false";
|
||||||
$this->v_country = isset($return['country']) ? $return['country'] : $this->v_country;
|
if ($this->v_country === '' && !empty($return['country'])) {
|
||||||
|
$this->v_country = (string) $return['country'];
|
||||||
|
}
|
||||||
$this->v_reason = cloak_api_reason($return, $this->check_result);
|
$this->v_reason = cloak_api_reason($return, $this->check_result);
|
||||||
return $return;
|
return $return;
|
||||||
}
|
}
|
||||||
@@ -149,37 +152,10 @@ class visitorInfo
|
|||||||
//获取访客ip
|
//获取访客ip
|
||||||
public function getIp($type = 0)
|
public function getIp($type = 0)
|
||||||
{
|
{
|
||||||
$type = $type ? 1 : 0;
|
if ($type) {
|
||||||
static $ip = NULL;
|
return ClientIpResolver::resolve();
|
||||||
if ($ip !== NULL) return $ip[$type];
|
|
||||||
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) { // 使用cloudflare 转发的IP地址
|
|
||||||
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
|
|
||||||
} else {
|
|
||||||
if (getenv('HTTP_CLIENT_IP') && strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
|
|
||||||
$ip = getenv('HTTP_CLIENT_IP');
|
|
||||||
} elseif (getenv('HTTP_X_FORWARDED_FOR') && strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
|
|
||||||
$ip = getenv('HTTP_X_FORWARDED_FOR');
|
|
||||||
} elseif (getenv('REMOTE_ADDR') && strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
|
|
||||||
$ip = getenv('REMOTE_ADDR');
|
|
||||||
} elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] && strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
|
|
||||||
$ip = $_SERVER['REMOTE_ADDR'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
return ClientIpResolver::resolve();
|
||||||
// $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
|
||||||
// $pos = array_search('unknown',$arr);
|
|
||||||
// if(false !== $pos) unset($arr[$pos]);
|
|
||||||
// $ip = trim($arr[0]);
|
|
||||||
// }elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
|
|
||||||
// $ip = $_SERVER['HTTP_CLIENT_IP'];
|
|
||||||
// }elseif (isset($_SERVER['REMOTE_ADDR'])) {
|
|
||||||
// $ip = $_SERVER['REMOTE_ADDR'];
|
|
||||||
// }
|
|
||||||
// IP地址合法验证
|
|
||||||
//$long = sprintf("%u",ip2long($ip));
|
|
||||||
//$ip = $long ? array($ip, $long) : array('0.0.0.0', 0);
|
|
||||||
|
|
||||||
return $ip;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//客户当前浏览的页面 url(浏览器实际访问地址,含正确 scheme 与 query)
|
//客户当前浏览的页面 url(浏览器实际访问地址,含正确 scheme 与 query)
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
require_once __DIR__ . '/CloakPhpCompat.php';
|
require_once __DIR__ . '/CloakPhpCompat.php';
|
||||||
CloakPhpCompat::ensureRuntime();
|
CloakPhpCompat::ensureRuntime();
|
||||||
|
|
||||||
|
$__cloakComposerAutoload = dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
if (is_file($__cloakComposerAutoload)) {
|
||||||
|
require_once $__cloakComposerAutoload;
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/DbHelper.php';
|
require_once __DIR__ . '/DbHelper.php';
|
||||||
require_once __DIR__ . '/Cloak/DomainRepository.php';
|
require_once __DIR__ . '/Cloak/DomainRepository.php';
|
||||||
require_once __DIR__ . '/CloakHttpHelpers.php';
|
require_once __DIR__ . '/CloakHttpHelpers.php';
|
||||||
@@ -15,6 +20,10 @@ require_once __DIR__ . '/Cloak/VisitorInfo.php';
|
|||||||
require_once __DIR__ . '/CloakDebugPage.php';
|
require_once __DIR__ . '/CloakDebugPage.php';
|
||||||
require_once __DIR__ . '/CloakAdSourceGuard.php';
|
require_once __DIR__ . '/CloakAdSourceGuard.php';
|
||||||
require_once __DIR__ . '/Cloak/PipelineTimer.php';
|
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/CountryAllowlist.php';
|
||||||
require_once __DIR__ . '/Cloak/VisitorLogSchema.php';
|
require_once __DIR__ . '/Cloak/VisitorLogSchema.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';
|
||||||
|
|||||||
@@ -248,6 +248,14 @@ $cfDomainOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($roo
|
|||||||
$cfDomainJson = json_decode((string) $cfDomainOut, true);
|
$cfDomainJson = json_decode((string) $cfDomainOut, true);
|
||||||
reg_assert('Cloudflare 域名自动化 Mock 流转', ($cfDomainJson['ok'] ?? false) === true, (string) $cfDomainOut);
|
reg_assert('Cloudflare 域名自动化 Mock 流转', ($cfDomainJson['ok'] ?? false) === true, (string) $cfDomainOut);
|
||||||
|
|
||||||
|
$geoOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($root . '/tools/verify_geoip.php') . ' 2>/dev/null');
|
||||||
|
$geoJson = json_decode((string) $geoOut, true);
|
||||||
|
if (($geoJson['tests']['db_file_exists'] ?? false) === true) {
|
||||||
|
reg_assert('GeoIP 本地国家库', ($geoJson['ok'] ?? false) === true, (string) $geoOut);
|
||||||
|
} else {
|
||||||
|
reg_skip('GeoIP 本地国家库', 'mmdb 未安装,运行 php tools/setup_geoip_db.php');
|
||||||
|
}
|
||||||
|
|
||||||
// ── 9. 数据库连通(可选) ─────────────────────────────────────
|
// ── 9. 数据库连通(可选) ─────────────────────────────────────
|
||||||
echo "[9] 数据库\n";
|
echo "[9] 数据库\n";
|
||||||
if (defined('DB_NAME')) {
|
if (defined('DB_NAME')) {
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 将根目录 GeoLite2-Country.mmdb 移动并重命名到隐蔽路径
|
||||||
|
*
|
||||||
|
* php tools/setup_geoip_db.php
|
||||||
|
*/
|
||||||
|
$root = dirname(__DIR__);
|
||||||
|
require_once $root . '/cong.php';
|
||||||
|
|
||||||
|
$source = $root . '/GeoLite2-Country.mmdb';
|
||||||
|
$target = defined('CLOAK_GEOIP_DB_PATH') ? CLOAK_GEOIP_DB_PATH : $root . '/storage/runtime/c7f2a9e1/cc_idx.dat';
|
||||||
|
$targetDir = dirname($target);
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'ok' => false,
|
||||||
|
'source' => $source,
|
||||||
|
'target' => $target,
|
||||||
|
'msg' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (is_file($target)) {
|
||||||
|
$result['ok'] = true;
|
||||||
|
$result['msg'] = '目标文件已存在,跳过';
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_file($source)) {
|
||||||
|
$result['msg'] = '源文件不存在:' . $source;
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($targetDir)) {
|
||||||
|
if (!@mkdir($targetDir, 0755, true) && !is_dir($targetDir)) {
|
||||||
|
$result['msg'] = '无法创建目录:' . $targetDir;
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!@rename($source, $target)) {
|
||||||
|
if (!@copy($source, $target)) {
|
||||||
|
$result['msg'] = '移动/复制失败';
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
@unlink($source);
|
||||||
|
}
|
||||||
|
|
||||||
|
$htaccess = $targetDir . '/.htaccess';
|
||||||
|
if (!is_file($htaccess)) {
|
||||||
|
@file_put_contents($htaccess, "Deny from all\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['ok'] = true;
|
||||||
|
$result['msg'] = 'GeoIP 数据库已就绪';
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* GeoIP 本地国家库冒烟测试
|
||||||
|
*
|
||||||
|
* php tools/verify_geoip.php
|
||||||
|
*/
|
||||||
|
$root = dirname(__DIR__);
|
||||||
|
require_once $root . '/cong.php';
|
||||||
|
require_once $root . '/lib/bootstrap.php';
|
||||||
|
|
||||||
|
$tests = [];
|
||||||
|
$ok = true;
|
||||||
|
|
||||||
|
$dbPath = defined('CLOAK_GEOIP_DB_PATH') ? CLOAK_GEOIP_DB_PATH : '';
|
||||||
|
$tests['db_file_exists'] = $dbPath !== '' && is_file($dbPath);
|
||||||
|
if (!$tests['db_file_exists']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tests['sdk_available'] = class_exists('GeoIp2\\Database\\Reader');
|
||||||
|
if (!$tests['sdk_available']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tests['resolver_available'] = GeoIpCountryResolver::isAvailable();
|
||||||
|
if (!$tests['resolver_available']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SERVER['HTTP_CF_CONNECTING_IP'] = '203.0.113.50';
|
||||||
|
$resolvedIp = ClientIpResolver::resolve();
|
||||||
|
$tests['client_ip_cf'] = ($resolvedIp === '203.0.113.50');
|
||||||
|
unset($_SERVER['HTTP_CF_CONNECTING_IP']);
|
||||||
|
if (!$tests['client_ip_cf']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tests['resolver_available']) {
|
||||||
|
$first = GeoIpCountryResolver::resolveWithMeta('8.8.8.8');
|
||||||
|
$second = GeoIpCountryResolver::resolveWithMeta('8.8.8.8');
|
||||||
|
$tests['lookup_8_8_8_8'] = ($first['code'] === 'US');
|
||||||
|
$tests['cache_second_hit'] = ($second['source'] === 'cache' || $second['source'] === 'request');
|
||||||
|
if (!$tests['lookup_8_8_8_8']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allow = CountryAllowlist::isAllowed('US', CountryAllowlist::parse('US,GB'));
|
||||||
|
$deny = CountryAllowlist::isAllowed('CN', CountryAllowlist::parse('US,GB'));
|
||||||
|
$tests['allowlist_us'] = $allow === true;
|
||||||
|
$tests['allowlist_cn'] = $deny === false;
|
||||||
|
if (!$tests['allowlist_us'] || !$tests['allowlist_cn']) {
|
||||||
|
$ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'ok' => $ok,
|
||||||
|
'tests' => $tests,
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
|
||||||
|
|
||||||
|
exit($ok ? 0 : 1);
|
||||||
Reference in New Issue
Block a user