refactor(autoload): migrate theme classes to Composer PSR-4
Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Extensions;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class IPGeo {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) self::$instance = new self();
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询 IP 归属地
|
||||
*
|
||||
* @param string $ip IPv4 or IPv6
|
||||
* @param bool $force 管理端测试用:跳过「启用」开关与缓存,仍拒绝私有 IP
|
||||
* @return array{country: string, province: string, city: string, raw: array, reason?: string}
|
||||
*/
|
||||
public function lookup(string $ip, bool $force = false): array {
|
||||
$config = $this->get_config();
|
||||
|
||||
if (!$force && empty($config['ip_geo_enabled'])) {
|
||||
return $this->empty_result('disabled');
|
||||
}
|
||||
|
||||
// 本地/私有 IP 跳过查询
|
||||
if ($this->is_private_ip($ip)) {
|
||||
return $this->empty_result('private_ip');
|
||||
}
|
||||
|
||||
$cache_key = 'aksrt_ip_geo_' . md5($ip);
|
||||
$ttl = max(3600, (int) ($config['ip_geo_cache_ttl'] ?? 86400));
|
||||
if (!$force) {
|
||||
$cached = get_transient($cache_key);
|
||||
if ($cached !== false && is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$provider = sanitize_key((string) ($config['ip_addr_sdk'] ?? ($config['ip_geo_provider'] ?? 'null')));
|
||||
$result = $this->query_provider($provider, $ip, $config);
|
||||
|
||||
if (!empty($result['province']) || !empty($result['city']) || !empty($result['country'])) {
|
||||
if (!$force) {
|
||||
set_transient($cache_key, $result, $ttl);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (empty($result['reason'])) {
|
||||
$result['reason'] = 'provider_empty';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务运行时状态
|
||||
*/
|
||||
public function get_status(): array {
|
||||
$config = $this->get_config();
|
||||
$provider = sanitize_key((string) ($config['ip_addr_sdk'] ?? ($config['ip_geo_provider'] ?? 'null')));
|
||||
$enabled = !empty($config['ip_geo_enabled']);
|
||||
|
||||
$missing = [];
|
||||
if ($enabled && $provider === 'qq') {
|
||||
if (empty($config['ip_addr_sdk_qq_appkey'])) $missing[] = 'ip_addr_sdk_qq_appkey';
|
||||
if (empty($config['ip_addr_sdk_qq_secretkey'])) $missing[] = 'ip_addr_sdk_qq_secretkey';
|
||||
}
|
||||
if ($enabled && $provider === 'amap') {
|
||||
if (empty($config['ip_addr_sdk_amap_appkey'])) $missing[] = 'ip_addr_sdk_amap_appkey';
|
||||
if (empty($config['ip_addr_sdk_amap_secretkey'])) $missing[] = 'ip_addr_sdk_amap_secretkey';
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'provider' => $provider,
|
||||
'configured' => $enabled && empty($missing),
|
||||
'available' => $enabled,
|
||||
'verified' => !empty($config['ip_geo_verified']),
|
||||
'transport' => 'http_api',
|
||||
'missing_fields' => $missing,
|
||||
'cache_ttl' => (int) ($config['ip_geo_cache_ttl'] ?? 86400),
|
||||
'display' => (array) ($config['ip_geo_display'] ?? ['comment']),
|
||||
'format' => (string) ($config['ip_geo_format'] ?? 'province_city'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化归属地显示文本
|
||||
*/
|
||||
public function format(array $geo, string $format = ''): string {
|
||||
if ($format === '') {
|
||||
$config = $this->get_config();
|
||||
$format = (string) ($config['ip_geo_format'] ?? 'province_city');
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
case 'province':
|
||||
return $geo['province'] ?? '';
|
||||
case 'country_province':
|
||||
return trim(($geo['country'] ?? '') . ' ' . ($geo['province'] ?? ''));
|
||||
case 'province_city':
|
||||
default:
|
||||
$parts = array_filter([$geo['province'] ?? '', $geo['city'] ?? '']);
|
||||
return implode(' ', $parts) ?: ($geo['country'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
private function get_config(): array {
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
return \AKSRT\Settings\Settings::get_ip_geo_settings();
|
||||
}
|
||||
return ['ip_geo_enabled' => false];
|
||||
}
|
||||
|
||||
private function query_provider(string $provider, string $ip, array $config): array {
|
||||
switch ($provider) {
|
||||
case 'qq':
|
||||
return $this->query_qq($ip, $config);
|
||||
case 'amap':
|
||||
return $this->query_amap($ip, $config);
|
||||
case 'polling':
|
||||
foreach (['qq', 'amap', 'null'] as $fallback) {
|
||||
$result = $this->query_provider($fallback, $ip, $config);
|
||||
if (!empty($result['province']) || !empty($result['city']) || !empty($result['country'])) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return $this->empty_result();
|
||||
case 'null':
|
||||
default:
|
||||
return $this->query_ip_api($ip);
|
||||
}
|
||||
}
|
||||
|
||||
private function query_qq(string $ip, array $config): array {
|
||||
$appkey = trim((string) ($config['ip_addr_sdk_qq_appkey'] ?? ''));
|
||||
if ($appkey === '') {
|
||||
return $this->empty_result();
|
||||
}
|
||||
|
||||
$url = add_query_arg([
|
||||
'ip' => $ip,
|
||||
'key' => $appkey,
|
||||
], 'https://apis.map.qq.com/ws/location/v1/ip');
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 8,
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
if (is_wp_error($response)) return $this->empty_result();
|
||||
|
||||
$data = json_decode(wp_remote_retrieve_body($response), true);
|
||||
if (!is_array($data) || (int) ($data['status'] ?? 1) !== 0) return $this->empty_result();
|
||||
|
||||
$info = $data['result']['ad_info'] ?? [];
|
||||
return [
|
||||
'country' => '中国',
|
||||
'province' => (string) ($info['province'] ?? ''),
|
||||
'city' => (string) ($info['city'] ?? ''),
|
||||
'raw' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
private function query_amap(string $ip, array $config): array {
|
||||
$appkey = trim((string) ($config['ip_addr_sdk_amap_appkey'] ?? ''));
|
||||
if ($appkey === '') {
|
||||
return $this->empty_result();
|
||||
}
|
||||
|
||||
$url = add_query_arg([
|
||||
'ip' => $ip,
|
||||
'key' => $appkey,
|
||||
], 'https://restapi.amap.com/v3/ip');
|
||||
|
||||
$response = wp_remote_get($url, [
|
||||
'timeout' => 8,
|
||||
'headers' => ['Accept' => 'application/json'],
|
||||
]);
|
||||
if (is_wp_error($response)) return $this->empty_result();
|
||||
|
||||
$data = json_decode(wp_remote_retrieve_body($response), true);
|
||||
if (!is_array($data) || (string) ($data['status'] ?? '0') !== '1') return $this->empty_result();
|
||||
|
||||
return [
|
||||
'country' => '中国',
|
||||
'province' => (string) ($data['province'] ?? ''),
|
||||
'city' => (string) ($data['city'] ?? ''),
|
||||
'raw' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
private function query_ip_api(string $ip): array {
|
||||
$response = wp_remote_get("http://ip-api.com/json/{$ip}?lang=zh-CN", [
|
||||
'timeout' => 5,
|
||||
]);
|
||||
if (is_wp_error($response)) return $this->empty_result();
|
||||
|
||||
$data = json_decode(wp_remote_retrieve_body($response), true);
|
||||
if (!is_array($data) || ($data['status'] ?? '') !== 'success') return $this->empty_result();
|
||||
|
||||
return [
|
||||
'country' => (string) ($data['country'] ?? ''),
|
||||
'province' => (string) ($data['regionName'] ?? ''),
|
||||
'city' => (string) ($data['city'] ?? ''),
|
||||
'raw' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
private function is_private_ip(string $ip): bool {
|
||||
return !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
|
||||
}
|
||||
|
||||
private function empty_result(string $reason = ''): array {
|
||||
$out = ['country' => '', 'province' => '', 'city' => '', 'raw' => []];
|
||||
if ($reason !== '') {
|
||||
$out['reason'] = $reason;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user