新增maxmind数据库本地判断国家,并且将优先级调整至最高
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user