From 06d4844c8e0d949b28a899a9360a42496ff29948 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 25 Jul 2026 15:46:36 +0800 Subject: [PATCH] fix(ip-geo): fallback free lookup when Amap key platform mismatches --- .../src/schema/sections/extensions/ip.js | 4 +- src/AKSRT/Admin/ExtensionController.php | 22 +++++--- src/AKSRT/Admin/Schema/SchemaExtensionsIP.php | 4 +- src/AKSRT/Extensions/IPGeo.php | 50 +++++++++++++++++-- 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/assets/admin/src/schema/sections/extensions/ip.js b/assets/admin/src/schema/sections/extensions/ip.js index a058cf48..2436891c 100644 --- a/assets/admin/src/schema/sections/extensions/ip.js +++ b/assets/admin/src/schema/sections/extensions/ip.js @@ -46,8 +46,8 @@ export const ipSchema = { fields: [ { key: 'ip_addr_sdk_qq_appkey', label: '腾讯 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'qq' } }, { key: 'ip_addr_sdk_qq_secretkey', label: '腾讯 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'qq' } }, - { key: 'ip_addr_sdk_amap_appkey', label: '高德 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' } }, - { key: 'ip_addr_sdk_amap_secretkey', label: '高德 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' } }, + { key: 'ip_addr_sdk_amap_appkey', label: '高德 AppKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' }, help: '必须在高德开放平台创建「Web服务」类型 Key,并开通 IP 定位。选错平台会报 USERKEY_PLAT_NOMATCH。' }, + { key: 'ip_addr_sdk_amap_secretkey', label: '高德 SecretKey', type: 'input', default: '', dependsOn: { key: 'ip_addr_sdk', value: 'amap' }, help: '若控制台启用了「数字签名」,必填安全密钥;未启用可留空。' }, ], }, { diff --git a/src/AKSRT/Admin/ExtensionController.php b/src/AKSRT/Admin/ExtensionController.php index 44a3c267..7803eb4e 100644 --- a/src/AKSRT/Admin/ExtensionController.php +++ b/src/AKSRT/Admin/ExtensionController.php @@ -323,14 +323,20 @@ class ExtensionController extends ControllerBase { $message = __('当前服务商 AppKey 未填写,请在「扩展 → IP归属地」补全凭据。', 'aksrt'); break; case 'provider_error': - // e.g. INVALID_USER_KEY from Amap - $message = $detail !== '' - ? sprintf( - /* translators: %s: provider error code/message */ - __('服务商拒绝请求:%s。请检查 Key 是否开通「IP定位」且平台类型匹配(Web服务)。', 'aksrt'), - $detail - ) - : __('服务商返回错误,请检查 Key 权限与配额。', 'aksrt'); + // e.g. USERKEY_PLAT_NOMATCH / INVALID_USER_KEY from Amap + if (stripos($detail, 'USERKEY_PLAT_NOMATCH') !== false) { + $message = __('高德返回 USERKEY_PLAT_NOMATCH:当前 Key 平台类型不匹配。请在高德开放平台新建「Web服务」类型 Key,并开通 IP 定位,不要用 Web端(JS)/Android/iOS Key。', 'aksrt'); + } elseif (stripos($detail, 'INVALID_USER_KEY') !== false) { + $message = __('高德返回 INVALID_USER_KEY:AppKey 无效或未启用,请核对 Key 是否复制完整、是否已启用。', 'aksrt'); + } else { + $message = $detail !== '' + ? sprintf( + /* translators: %s: provider error code/message */ + __('服务商拒绝请求:%s。请检查 Key 权限(IP定位)与配额。', 'aksrt'), + $detail + ) + : __('服务商返回错误,请检查 Key 权限与配额。', 'aksrt'); + } break; case 'http_error': $message = $detail !== '' diff --git a/src/AKSRT/Admin/Schema/SchemaExtensionsIP.php b/src/AKSRT/Admin/Schema/SchemaExtensionsIP.php index 2df61483..6e72e6ae 100644 --- a/src/AKSRT/Admin/Schema/SchemaExtensionsIP.php +++ b/src/AKSRT/Admin/Schema/SchemaExtensionsIP.php @@ -29,8 +29,8 @@ class SchemaExtensionsIP { ['key' => 'providers', 'title' => '接口凭据', 'fields' => [ ['key' => 'ip_addr_sdk_qq_appkey', 'label' => '腾讯 AppKey', 'type' => 'input', 'default' => ''], ['key' => 'ip_addr_sdk_qq_secretkey', 'label' => '腾讯 SecretKey', 'type' => 'input', 'default' => ''], - ['key' => 'ip_addr_sdk_amap_appkey', 'label' => '高德 AppKey', 'type' => 'input', 'default' => ''], - ['key' => 'ip_addr_sdk_amap_secretkey', 'label' => '高德 SecretKey', 'type' => 'input', 'default' => ''], + ['key' => 'ip_addr_sdk_amap_appkey', 'label' => '高德 AppKey', 'type' => 'input', 'default' => '', 'help' => '须为「Web服务」Key,并开通 IP 定位'], + ['key' => 'ip_addr_sdk_amap_secretkey', 'label' => '高德 SecretKey', 'type' => 'input', 'default' => '', 'help' => '启用数字签名时必填'], ]], ['key' => 'display', 'title' => '展示设置', 'fields' => [ ['key' => 'ip_geo_display', 'label' => '展示位置', 'type' => 'checkbox', 'default' => ['comment', 'user_home', 'user_center', 'post'], 'sanitize_type' => 'array', 'options' => [ diff --git a/src/AKSRT/Extensions/IPGeo.php b/src/AKSRT/Extensions/IPGeo.php index a3ddabf5..d1c5578e 100644 --- a/src/AKSRT/Extensions/IPGeo.php +++ b/src/AKSRT/Extensions/IPGeo.php @@ -134,31 +134,71 @@ class IPGeo { case 'qq': return $this->query_qq($ip, $config); case 'amap': - return $this->query_amap($ip, $config); + $amap = $this->query_amap($ip, $config); + if ($this->has_location($amap)) { + return $amap; + } + // Credential / platform mismatch: free path so front still works. + if ($this->should_fallback_free($amap)) { + $free = $this->query_pconline($ip); + if ($this->has_location($free)) { + $free['detail'] = 'amap_failed_fallback_free:' . (string) ($amap['detail'] ?? $amap['reason'] ?? ''); + return $free; + } + } + return $amap; case 'polling': $last = $this->empty_result('provider_empty'); foreach (['qq', 'amap', 'null'] as $fallback) { - // Skip providers that clearly lack keys. if ($fallback === 'qq' && empty($config['ip_addr_sdk_qq_appkey'])) { continue; } if ($fallback === 'amap' && empty($config['ip_addr_sdk_amap_appkey'])) { continue; } - $result = $this->query_provider($fallback, $ip, $config); + // Avoid re-entering amap→free→… recursion: call leaf methods. + if ($fallback === 'amap') { + $result = $this->query_amap($ip, $config); + } elseif ($fallback === 'qq') { + $result = $this->query_qq($ip, $config); + } else { + $result = $this->query_pconline($ip); + } if ($this->has_location($result)) { return $result; } $last = $result; } - return $last; + // Last resort free path. + $free = $this->query_pconline($ip); + return $this->has_location($free) ? $free : $last; case 'null': default: - // Canonical free provider: 太平洋 (pconline). Label matches admin UI. + // Admin label「太平洋公共接口」: pconline, with ip-api if blocked. return $this->query_pconline($ip); } } + /** + * Whether a paid-provider failure should fall back to free IP lookup. + */ + private function should_fallback_free(array $result): bool { + $reason = (string) ($result['reason'] ?? ''); + if (in_array($reason, ['missing_key', 'http_error'], true)) { + return true; + } + if ($reason !== 'provider_error') { + return false; + } + $detail = strtoupper((string) ($result['detail'] ?? '')); + foreach (['USERKEY_PLAT_NOMATCH', 'INVALID_USER_KEY', 'INVALID_USER_SCODE', 'DAILY_QUERY_OVER_LIMIT', 'ACCESS_TOO_FREQUENT', 'CUQPS_HAS_EXCEEDED_THE_LIMIT'] as $code) { + if (str_contains($detail, $code)) { + return true; + } + } + return false; + } + private function query_qq(string $ip, array $config): array { $appkey = trim((string) ($config['ip_addr_sdk_qq_appkey'] ?? '')); $secretkey = trim((string) ($config['ip_addr_sdk_qq_secretkey'] ?? ''));