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;
|
||
}
|
||
}
|