滑动续期、网络错误归类、API Key 激活乐观锁与生效提示

This commit is contained in:
2026-08-11 11:45:03 +08:00
parent 23b2820101
commit e63af49831
21 changed files with 679 additions and 15 deletions
+69
View File
@@ -1,7 +1,11 @@
package oci
import (
"context"
"errors"
"fmt"
"net"
"net/url"
"regexp"
"strings"
@@ -16,6 +20,7 @@ var ociErrorHints = map[string]string{
"QuotaExceeded": "compartment 配额不足",
"OutOfHostCapacity": "该可用域容量不足,可稍后重试或改用抢机任务",
"TooManyRequests": "请求过于频繁,请稍后再试",
"NotAuthenticated": "OCI 拒绝了请求签名:密钥无效或尚未生效。若刚替换过 API Key,全球生效需数分钟,请稍后重试",
"InvalidParameter": "请求参数无效",
"InternalError": "OCI 服务内部错误,请稍后重试",
"Conflict": "资源正在变更中,请稍后重试",
@@ -24,6 +29,9 @@ var ociErrorHints = map[string]string{
// ocidRe 匹配消息中的完整 OCID(unique 段 20 位以上才压缩,避免误伤短标识)。
var ocidRe = regexp.MustCompile(`ocid1\.([a-z0-9]+)\.[a-z0-9]*\.[a-z0-9-]*\.{1,2}[a-z0-9]{20,}`)
// proxyUserinfoRe 只匹配网络错误文本里位于 token / URL 起点的 userinfo@。
var proxyUserinfoRe = regexp.MustCompile(`(^|[\s/("'=])[^@\s/]+@`)
// shortenOcids 把消息里的长 OCID 压缩为「ocid1.<类型>…<尾6位>」,
// 保留资源类型与可比对的尾部,避免整条错误被 OCID 撑爆。
func shortenOcids(s string) string {
@@ -68,6 +76,67 @@ func ErrorHint(err error) string {
return ociErrorHints[svcErr.GetCode()]
}
// UpstreamNetworkError 判定错误链是否为上游连接层失败(代理拨号 / SOCKS 握手 /
// 超时 / DNS 等),命中返回给用户看的精简描述。已拿到 OCI 响应的 ServiceError
// 不在此列;判定保守:宁可漏归 500,不把业务错误误标为网络问题。
func UpstreamNetworkError(err error) (string, bool) {
var svcErr common.ServiceError
if errors.As(err, &svcErr) {
return "", false
}
var uerr *url.Error
if errors.As(err, &uerr) {
return classifyNetDetail(redactURLError(uerr)), true
}
if errors.Is(err, context.DeadlineExceeded) {
return "上游连接失败: 请求超时", true
}
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
return "上游连接失败: 连接超时", true
}
return "", false
}
// redactURLError 把 url.Error 的完整 URL 压缩为主机,并递归处理内层 url.Error。
func redactURLError(uerr *url.Error) string {
host := "<redacted-url>"
if u, err := url.Parse(uerr.URL); err == nil && u.Host != "" {
host = u.Host
}
return fmt.Sprintf("%s %s: %s", uerr.Op, host, redactURLCause(uerr.Err))
}
// redactURLCause 保留普通包装前缀,但用脱敏文本替换其中嵌套的 url.Error。
func redactURLCause(err error) string {
if err == nil {
return "unknown network error"
}
var nested *url.Error
if !errors.As(err, &nested) {
return redactProxyUserinfo(err.Error())
}
raw, nestedRaw := err.Error(), nested.Error()
if strings.Contains(raw, nestedRaw) {
raw = strings.Replace(raw, nestedRaw, redactURLError(nested), 1)
return redactProxyUserinfo(raw)
}
return redactURLError(nested)
}
// redactProxyUserinfo 清除存量非法代理 host 可能写入普通错误文本的 userinfo。
func redactProxyUserinfo(detail string) string {
return proxyUserinfoRe.ReplaceAllString(detail, "$1")
}
// classifyNetDetail 按细节特征加中文分类前缀:代理链路失败与一般上游失败分开表述。
func classifyNetDetail(detail string) string {
if strings.Contains(detail, "proxyconnect") || strings.Contains(detail, "socks connect") {
return "代理连接失败: " + detail
}
return "上游连接失败: " + detail
}
// ServiceStatus 返回错误链中 OCI ServiceError 的 HTTP 状态码;非服务端错误 ok=false。
func ServiceStatus(err error) (int, bool) {
var svcErr common.ServiceError