新增maxmind数据库本地判断国家,并且将优先级调整至最高

This commit is contained in:
root
2026-06-14 15:29:29 +08:00
parent 72d388f642
commit eab59c1c18
17 changed files with 912 additions and 38 deletions
+4
View File
@@ -3,3 +3,7 @@
.cursor/ .cursor/
.vscode/ .vscode/
*.log *.log
/storage/runtime/c7f2a9e1/
GeoLite2-Country.mmdb
/vendor/
+5
View File
@@ -0,0 +1,5 @@
{
"require": {
"geoip2/geoip2": "^2.13"
}
}
Generated
+264
View File
@@ -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"
}
+5 -1
View File
@@ -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', '');
+5
View File
@@ -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
+4
View File
@@ -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
+89
View File
@@ -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;
}
}
+42
View File
@@ -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);
}
}
+168
View File
@@ -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);
}
}
}
+111
View File
@@ -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;
}
}
}
+8 -1
View File
@@ -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,
]);
}
+10 -34
View File
@@ -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();
@@ -46,7 +45,9 @@ class visitorInfo
$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'];
} }
} return ClientIpResolver::resolve();
// if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// $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
+9
View File
@@ -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';
+8
View File
@@ -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')) {
+58
View File
@@ -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";
+61
View File
@@ -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);