新增 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。
62 lines
2.0 KiB
Go
62 lines
2.0 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/publicerror"
|
|
)
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, value any) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(value)
|
|
}
|
|
|
|
func writeError(w http.ResponseWriter, status int, message string, codes ...string) {
|
|
writeErrorWithDetails(w, status, message, nil, codes...)
|
|
}
|
|
|
|
func writeErrorWithDetails(w http.ResponseWriter, status int, message string, details map[string]any, codes ...string) {
|
|
code := ""
|
|
if len(codes) > 0 {
|
|
code = strings.TrimSpace(codes[0])
|
|
}
|
|
standard := publicerror.FromFields(code, message, status, status == http.StatusRequestTimeout || status == http.StatusTooManyRequests || status >= 500)
|
|
standard = publicErrorWithRetryAfter(standard, details)
|
|
publicerror.Observe(standard)
|
|
status = standard.HTTPStatus
|
|
errorPayload := map[string]any{
|
|
"message": standard.Message,
|
|
"status": status,
|
|
"code": standard.Code,
|
|
"category": standard.Category,
|
|
"httpStatus": standard.HTTPStatus,
|
|
"retryable": standard.Retryable,
|
|
"action": standard.Action,
|
|
"version": standard.Version,
|
|
}
|
|
if standard.Code == "invalid_parameter" || standard.Code == "unsupported_response_parameter" {
|
|
errorPayload["type"] = "invalid_request_error"
|
|
if _, ok := details["param"]; !ok {
|
|
errorPayload["param"] = nil
|
|
}
|
|
}
|
|
for key, value := range safePublicErrorDetails(details, standard, false) {
|
|
errorPayload[key] = value
|
|
}
|
|
writeJSON(w, status, map[string]any{"error": errorPayload})
|
|
}
|
|
|
|
func writeLocalUserRequired(w http.ResponseWriter) {
|
|
writeError(w, http.StatusForbidden, "该账号尚未开通 EasyAI Gateway", errorCodeGatewayUserNotProvisioned)
|
|
}
|
|
|
|
func sendSSE(w http.ResponseWriter, event string, payload any) {
|
|
bytes, _ := json.Marshal(payload)
|
|
_, _ = fmt.Fprintf(w, "event: %s\n", event)
|
|
_, _ = fmt.Fprintf(w, "data: %s\n\n", bytes)
|
|
}
|