feat(storage): 统一二进制对象存储与公开错误
新增 Aliyun OSS 与 S3 协议、通道内重试和按优先级跨通道切换,保留 server-main 兼容与环境 OSS 内存通道。 将请求及结果中的 Base64、Data URI、Buffer、multipart 和内联二进制统一对象化,生产路径不再写入本机静态目录,历史本地资源仅保留只读兼容。 引入 PublicErrorV1 并统一 API、异步查询、兼容协议和失败回调的安全错误输出,同时补充迁移、管理端、指标、OpenAPI 与本地模拟验收。 验证:go test ./... -count=1;go vet ./...;pnpm lint;pnpm test;pnpm build;pnpm openapi;tests/ci/migrations-test.sh。
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/publicerror"
|
||||
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
||||
)
|
||||
|
||||
func publicGatewayTask(task store.GatewayTask) store.GatewayTask {
|
||||
if task.Status == "failed" || task.Status == "cancelled" || task.ErrorCode != "" || task.ErrorMessage != "" {
|
||||
value := publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error), storedTaskErrorStatus(task.ErrorCode), false)
|
||||
if task.PublicError != nil && task.PublicError.Code != "" {
|
||||
value = *task.PublicError
|
||||
}
|
||||
value = publicerror.WithIDs(value, task.RequestID, task.ID)
|
||||
publicerror.Observe(value)
|
||||
task.PublicError = &value
|
||||
task.ErrorCode = value.Code
|
||||
task.ErrorMessage = value.Message
|
||||
task.Error = value.Message
|
||||
}
|
||||
for index := range task.Attempts {
|
||||
attempt := &task.Attempts[index]
|
||||
if attempt.ErrorCode == "" && attempt.ErrorMessage == "" {
|
||||
continue
|
||||
}
|
||||
status := attempt.StatusCode
|
||||
if status <= 0 {
|
||||
status = http.StatusBadGateway
|
||||
}
|
||||
value := publicerror.FromFields(attempt.ErrorCode, attempt.ErrorMessage, status, attempt.Retryable)
|
||||
if attempt.PublicError != nil && attempt.PublicError.Code != "" {
|
||||
value = *attempt.PublicError
|
||||
}
|
||||
value = publicerror.WithIDs(value, attempt.RequestID, task.ID)
|
||||
attempt.PublicError = &value
|
||||
attempt.ErrorCode = value.Code
|
||||
attempt.ErrorMessage = value.Message
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
func publicTaskError(task store.GatewayTask) publicerror.Error {
|
||||
if task.PublicError != nil && task.PublicError.Code != "" {
|
||||
return publicerror.WithIDs(*task.PublicError, task.RequestID, task.ID)
|
||||
}
|
||||
status := storedTaskErrorStatus(task.ErrorCode)
|
||||
if status <= 0 {
|
||||
status = http.StatusBadGateway
|
||||
}
|
||||
return publicerror.WithIDs(publicerror.FromFields(task.ErrorCode, firstNonEmpty(task.ErrorMessage, task.Error, task.Message), status, false), task.RequestID, task.ID)
|
||||
}
|
||||
|
||||
func publicTaskList(items []store.GatewayTask) []store.GatewayTask {
|
||||
out := make([]store.GatewayTask, len(items))
|
||||
for index, task := range items {
|
||||
out[index] = publicGatewayTask(task)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func publicErrorMap(value publicerror.Error) map[string]any {
|
||||
out := map[string]any{
|
||||
"code": value.Code, "message": value.Message, "category": value.Category,
|
||||
"httpStatus": value.HTTPStatus, "retryable": value.Retryable, "action": value.Action,
|
||||
"version": value.Version,
|
||||
}
|
||||
if value.RetryAfterSeconds > 0 {
|
||||
out["retryAfterSeconds"] = value.RetryAfterSeconds
|
||||
}
|
||||
if requestID := strings.TrimSpace(value.RequestID); requestID != "" {
|
||||
out["requestId"] = requestID
|
||||
}
|
||||
if taskID := strings.TrimSpace(value.TaskID); taskID != "" {
|
||||
out["taskId"] = taskID
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func publicErrorWithRetryAfter(value publicerror.Error, details map[string]any) publicerror.Error {
|
||||
if value.RetryAfterSeconds > 0 || details == nil {
|
||||
return value
|
||||
}
|
||||
switch typed := details["retryAfterSeconds"].(type) {
|
||||
case int:
|
||||
value.RetryAfterSeconds = typed
|
||||
case int32:
|
||||
value.RetryAfterSeconds = int(typed)
|
||||
case int64:
|
||||
value.RetryAfterSeconds = int(typed)
|
||||
case float64:
|
||||
value.RetryAfterSeconds = int(typed)
|
||||
}
|
||||
if value.RetryAfterSeconds < 0 {
|
||||
value.RetryAfterSeconds = 0
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func safePublicErrorDetails(details map[string]any, value publicerror.Error, includePublicError bool) map[string]any {
|
||||
out := map[string]any{}
|
||||
for _, key := range []string{"param", "retryAfterSeconds", "recoveryAt", "rateLimit", "pricing"} {
|
||||
if item, ok := details[key]; ok && item != nil {
|
||||
out[key] = item
|
||||
}
|
||||
}
|
||||
if value.Category == "request" {
|
||||
for _, key := range []string{"reason", "diagnosticId"} {
|
||||
if item, ok := details[key]; ok && item != nil {
|
||||
out[key] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
if includePublicError {
|
||||
out["publicError"] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user