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:
2026-08-04 08:14:39 +08:00
parent d129bcccbd
commit 0f0998cbcf
55 changed files with 3649 additions and 1008 deletions
+6 -8
View File
@@ -1154,7 +1154,7 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
body, err := s.decodeTaskRequestBody(r.Context(), w, r, kind)
if err != nil {
status := http.StatusBadRequest
if code := clients.ErrorCode(err); strings.HasPrefix(code, "upload_") || code == "request_asset_upload_failed" || code == "request_asset_public_url_required" {
if code := clients.ErrorCode(err); strings.HasPrefix(code, "upload_") || strings.HasPrefix(code, "storage_") || code == "request_asset_upload_failed" || code == "request_asset_public_url_required" {
status = http.StatusBadGateway
}
writeTaskError(status, err.Error(), nil, clients.ErrorCode(err))
@@ -1195,7 +1195,7 @@ func (s *Server) createTask(kind string, compatible bool) http.Handler {
if err != nil {
s.logger.Warn("prepare task request failed", "kind", kind, "error", err)
status := http.StatusBadRequest
if code := clients.ErrorCode(err); strings.HasPrefix(code, "upload_") || code == "request_asset_upload_failed" {
if code := clients.ErrorCode(err); strings.HasPrefix(code, "upload_") || strings.HasPrefix(code, "storage_") || code == "request_asset_upload_failed" {
status = http.StatusBadGateway
}
writeTaskError(status, err.Error(), nil, clients.ErrorCode(err))
@@ -1556,10 +1556,6 @@ func writeProtocolCompatibleTaskResponse(runCtx context.Context, w http.Response
return
}
applyRunErrorHeaders(w, runErr)
if wire := clients.ErrorWireResponse(runErr); wireResponseMatches(wire, targetProtocol) {
writeWireResponse(w, wire)
return
}
if targetProtocol != "" {
writeProtocolError(w, targetProtocol, statusFromRunError(runErr), runErrorMessage(runErr), runErrorDetails(runErr), runErrorCode(runErr))
return
@@ -1673,6 +1669,8 @@ func modelNameFromValue(value any) string {
func statusFromRunError(err error) int {
switch {
case clients.ErrorCode(err) == "storage_write_failed" || clients.ErrorCode(err) == "storage_read_failed" || clients.ErrorCode(err) == "storage_config_invalid" || clients.ErrorCode(err) == "storage_auth_failed":
return http.StatusServiceUnavailable
case clients.ErrorCode(err) == "binary_result_expired":
return http.StatusGone
case clients.ErrorCode(err) == "binary_result_corrupted" || clients.ErrorCode(err) == "result_binary_not_materialized":
@@ -1949,7 +1947,7 @@ func (s *Server) listTasks(w http.ResponseWriter, r *http.Request) {
return
}
writeJSON(w, http.StatusOK, map[string]any{
"items": result.Items,
"items": publicTaskList(result.Items),
"total": result.Total,
"page": result.Page,
"pageSize": result.PageSize,
@@ -2034,7 +2032,7 @@ func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
writeStoredBinaryResultError(w, err)
return
}
writeJSON(w, http.StatusOK, task)
writeJSON(w, http.StatusOK, publicGatewayTask(task))
return
}
if store.IsNotFound(err) {