fix(ip-geo): fallback free lookup when Amap key platform mismatches

This commit is contained in:
2026-07-25 15:46:36 +08:00
parent 62478a02fd
commit 06d4844c8e
4 changed files with 63 additions and 17 deletions
@@ -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: '若控制台启用了「数字签名」,必填安全密钥;未启用可留空。' },
],
},
{
+14 -8
View File
@@ -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 !== ''
@@ -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' => [
+45 -5
View File
@@ -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'] ?? ''));