88 lines
2.2 KiB
PHP
88 lines
2.2 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
namespace app\common\service;
|
|||
|
|
|
|||
|
|
use GeoIp2\Database\Reader;
|
|||
|
|
use GeoIp2\Exception\AddressNotFoundException;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 基于 GeoLite2-Country.mmdb 的 IP 国家查询(MaxMind GeoIP2)
|
|||
|
|
*/
|
|||
|
|
class SplitGeoIpService
|
|||
|
|
{
|
|||
|
|
/** 项目根目录下的 MaxMind 国家库文件名 */
|
|||
|
|
private const DB_FILENAME = 'GeoLite2-Country.mmdb';
|
|||
|
|
|
|||
|
|
private static ?Reader $reader = null;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析 IP 对应 ISO 3166-1 alpha-2 国家代码;无法解析时返回 null
|
|||
|
|
*/
|
|||
|
|
public static function getCountryIso2(string $ip): ?string
|
|||
|
|
{
|
|||
|
|
$ip = trim($ip);
|
|||
|
|
if ($ip === '' || filter_var($ip, FILTER_VALIDATE_IP) === false) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::bootstrapLibrary();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$record = self::getReader()->country($ip);
|
|||
|
|
$code = $record->country->isoCode ?? null;
|
|||
|
|
if (!is_string($code) || $code === '') {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return strtoupper($code);
|
|||
|
|
} catch (AddressNotFoundException $e) {
|
|||
|
|
return null;
|
|||
|
|
} catch (\Throwable $e) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* MMDB 绝对路径
|
|||
|
|
*/
|
|||
|
|
public static function getDatabasePath(): string
|
|||
|
|
{
|
|||
|
|
return rtrim((string) ROOT_PATH, '/\\') . DIRECTORY_SEPARATOR . self::DB_FILENAME;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function isDatabaseAvailable(): bool
|
|||
|
|
{
|
|||
|
|
$path = self::getDatabasePath();
|
|||
|
|
|
|||
|
|
return is_file($path) && is_readable($path);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static function bootstrapLibrary(): void
|
|||
|
|
{
|
|||
|
|
static $bootstrapped = false;
|
|||
|
|
if ($bootstrapped) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
$bootstrapped = true;
|
|||
|
|
$loader = ROOT_PATH . 'patches/third_party/load_geoip2.php';
|
|||
|
|
if (is_file($loader)) {
|
|||
|
|
require_once $loader;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static function getReader(): Reader
|
|||
|
|
{
|
|||
|
|
if (self::$reader instanceof Reader) {
|
|||
|
|
return self::$reader;
|
|||
|
|
}
|
|||
|
|
if (!self::isDatabaseAvailable()) {
|
|||
|
|
throw new \RuntimeException('GeoLite2-Country.mmdb not readable at project root');
|
|||
|
|
}
|
|||
|
|
self::$reader = new Reader(self::getDatabasePath());
|
|||
|
|
|
|||
|
|
return self::$reader;
|
|||
|
|
}
|
|||
|
|
}
|