From eab59c1c18a8b7aed50cd7189ad215eb1c147b75 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 14 Jun 2026 15:29:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Emaxmind=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=9C=AC=E5=9C=B0=E5=88=A4=E6=96=AD=E5=9B=BD=E5=AE=B6?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E4=B8=94=E5=B0=86=E4=BC=98=E5=85=88=E7=BA=A7?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E8=87=B3=E6=9C=80=E9=AB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- composer.json | 5 + composer.lock | 264 ++++++++++++++++++ cong.php | 6 +- install.php | 5 + jump.txt | 4 + lib/Cloak/ClientIpResolver.php | 89 ++++++ lib/Cloak/CountryAllowlist.php | 42 +++ lib/Cloak/GeoIpCountryCache.php | 168 +++++++++++ lib/Cloak/GeoIpCountryResolver.php | 111 ++++++++ lib/Cloak/Pipeline/CheckPipeline.php | 9 +- .../stages/check_country_geoip.inc.php | 59 ++++ lib/Cloak/VisitorInfo.php | 46 +-- lib/bootstrap.php | 9 + tools/regression_test.php | 8 + tools/setup_geoip_db.php | 58 ++++ tools/verify_geoip.php | 61 ++++ 17 files changed, 912 insertions(+), 38 deletions(-) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 lib/Cloak/ClientIpResolver.php create mode 100644 lib/Cloak/CountryAllowlist.php create mode 100644 lib/Cloak/GeoIpCountryCache.php create mode 100644 lib/Cloak/GeoIpCountryResolver.php create mode 100644 lib/Cloak/Pipeline/stages/check_country_geoip.inc.php create mode 100644 tools/setup_geoip_db.php create mode 100644 tools/verify_geoip.php diff --git a/.gitignore b/.gitignore index 72ea8d3..de83b4f 100755 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ .sidebar/ .cursor/ .vscode/ -*.log \ No newline at end of file +*.log + +/storage/runtime/c7f2a9e1/ +GeoLite2-Country.mmdb +/vendor/ \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4427135 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "geoip2/geoip2": "^2.13" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..c0eb3cc --- /dev/null +++ b/composer.lock @@ -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" +} diff --git a/cong.php b/cong.php index eb9008c..5ec1ac7 100755 --- a/cong.php +++ b/cong.php @@ -22,7 +22,11 @@ define('REDIS_PASSWORD', ''); // --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)--- 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('CLOUDFLARE_API_TOKEN', ''); define('CLOUDFLARE_ACCOUNT_ID', ''); diff --git a/install.php b/install.php index 0a76ec2..5ed33c3 100755 --- a/install.php +++ b/install.php @@ -135,6 +135,11 @@ if (!$isLocked && !empty($_POST['action']) && $_POST['action'] === 'install') { . '// --- 访客日志异步写入(ON=队列落库,OFF=同步 INSERT)---' . PHP_EOL . 'define(\'VISITOR_LOG_ASYNC\', \'ON\');' . 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 . 'define(\'SERVER_IP\', ' . var_export($server_ip, true) . ');' . PHP_EOL . 'define(\'CLOUDFLARE_API_TOKEN\', ' . var_export($cf_token, true) . ');' . PHP_EOL diff --git a/jump.txt b/jump.txt index bc7a4ad..bc48087 100755 --- a/jump.txt +++ b/jump.txt @@ -93,3 +93,7 @@ 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 | 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 diff --git a/lib/Cloak/ClientIpResolver.php b/lib/Cloak/ClientIpResolver.php new file mode 100644 index 0000000..64815b5 --- /dev/null +++ b/lib/Cloak/ClientIpResolver.php @@ -0,0 +1,89 @@ +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); + } + } +} diff --git a/lib/Cloak/GeoIpCountryResolver.php b/lib/Cloak/GeoIpCountryResolver.php new file mode 100644 index 0000000..660b860 --- /dev/null +++ b/lib/Cloak/GeoIpCountryResolver.php @@ -0,0 +1,111 @@ + */ + 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; + } + } +} diff --git a/lib/Cloak/Pipeline/CheckPipeline.php b/lib/Cloak/Pipeline/CheckPipeline.php index a0fd8ad..01ff6c5 100755 --- a/lib/Cloak/Pipeline/CheckPipeline.php +++ b/lib/Cloak/Pipeline/CheckPipeline.php @@ -3,7 +3,7 @@ * 流量判定主流水线 * * 阶段顺序(不可调换): - * 1. Session 快速路径 → 2. 黑白名单 → 3. 广告来源限制 → 4. 临时链接 → 5. API/指纹 + * 1. Session 快速路径 → 2. GeoIP 国家 → 3. 黑白名单 → 4. 广告来源 → 5. 临时链接 → 6. API/指纹 */ class CheckPipeline { @@ -34,6 +34,13 @@ class CheckPipeline 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('黑白名单'); include __DIR__ . '/stages/apply_whitelist_blacklist.inc.php'; CloakPipelineTimer::stage('广告来源限制'); diff --git a/lib/Cloak/Pipeline/stages/check_country_geoip.inc.php b/lib/Cloak/Pipeline/stages/check_country_geoip.inc.php new file mode 100644 index 0000000..dde832c --- /dev/null +++ b/lib/Cloak/Pipeline/stages/check_country_geoip.inc.php @@ -0,0 +1,59 @@ +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, + ]); +} diff --git a/lib/Cloak/VisitorInfo.php b/lib/Cloak/VisitorInfo.php index c978d10..b80fbd2 100755 --- a/lib/Cloak/VisitorInfo.php +++ b/lib/Cloak/VisitorInfo.php @@ -32,10 +32,9 @@ class visitorInfo */ 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_Browser = $this->getBrowser(); - //$this->v_country = $this->findCityByIp($this->v_ip); $this->v_referer = $this->getFromPage(); $this->v_Client = $this->v_isMobile(); $this->v_curPageURL = $this->curPageURL(); @@ -45,8 +44,10 @@ class visitorInfo $this->costm_ip_score = $costm_ip_score; $this->check_key = $check_key; $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_ip"] = $this->v_ip; $_SESSION["gcu_Browser"] = $this->v_Browser; @@ -71,7 +72,9 @@ class visitorInfo $return = ['result' => false, 'reason' => 'API响应异常', 'country' => $this->v_country]; } $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); return $return; } @@ -149,37 +152,10 @@ class visitorInfo //获取访客ip public function getIp($type = 0) { - $type = $type ? 1 : 0; - static $ip = NULL; - 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 ($type) { + 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; + return ClientIpResolver::resolve(); } //客户当前浏览的页面 url(浏览器实际访问地址,含正确 scheme 与 query) diff --git a/lib/bootstrap.php b/lib/bootstrap.php index e9e13a0..0b541d4 100755 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -5,6 +5,11 @@ require_once __DIR__ . '/CloakPhpCompat.php'; CloakPhpCompat::ensureRuntime(); +$__cloakComposerAutoload = dirname(__DIR__) . '/vendor/autoload.php'; +if (is_file($__cloakComposerAutoload)) { + require_once $__cloakComposerAutoload; +} + require_once __DIR__ . '/DbHelper.php'; require_once __DIR__ . '/Cloak/DomainRepository.php'; require_once __DIR__ . '/CloakHttpHelpers.php'; @@ -15,6 +20,10 @@ require_once __DIR__ . '/Cloak/VisitorInfo.php'; require_once __DIR__ . '/CloakDebugPage.php'; require_once __DIR__ . '/CloakAdSourceGuard.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/VisitorLogQueue.php'; require_once __DIR__ . '/Cloak/VisitorLogWorker.php'; diff --git a/tools/regression_test.php b/tools/regression_test.php index f037ca2..0c8fe3d 100755 --- a/tools/regression_test.php +++ b/tools/regression_test.php @@ -248,6 +248,14 @@ $cfDomainOut = shell_exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg($roo $cfDomainJson = json_decode((string) $cfDomainOut, true); 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. 数据库连通(可选) ───────────────────────────────────── echo "[9] 数据库\n"; if (defined('DB_NAME')) { diff --git a/tools/setup_geoip_db.php b/tools/setup_geoip_db.php new file mode 100644 index 0000000..5c40c57 --- /dev/null +++ b/tools/setup_geoip_db.php @@ -0,0 +1,58 @@ + 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"; diff --git a/tools/verify_geoip.php b/tools/verify_geoip.php new file mode 100644 index 0000000..609328a --- /dev/null +++ b/tools/verify_geoip.php @@ -0,0 +1,61 @@ + $ok, + 'tests' => $tests, +], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n"; + +exit($ok ? 0 : 1);