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
+182 -16
View File
@@ -3157,7 +3157,7 @@
"BearerAuth": []
}
],
"description": "创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。",
"description": "创建 server-main OpenAPI、阿里云 OSS 或 S3 兼容文件存储通道。",
"consumes": [
"application/json"
],
@@ -3354,6 +3354,70 @@
}
}
},
"/api/admin/system/file-storage/channels/{channelID}/test": {
"post": {
"security": [
{
"BearerAuth": []
}
],
"description": "对指定 OSS 或 S3 通道执行隔离的 Put、Head、Delete 探针,不返回凭据或对象键。",
"produces": [
"application/json"
],
"tags": [
"system"
],
"summary": "测试对象存储通道",
"parameters": [
{
"type": "string",
"description": "文件存储通道 ID",
"name": "channelID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httpapi.FileStorageChannelTestResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"403": {
"description": "Forbidden",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"503": {
"description": "Service Unavailable",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
}
}
}
},
"/api/admin/system/file-storage/settings": {
"get": {
"security": [
@@ -6712,7 +6776,7 @@
"BearerAuth": []
}
],
"description": "上传文件到配置的文件存储通道;没有启用通道时回退到本地静态上传目录。单文件最大 256MiB。",
"description": "上传文件到配置的对象存储通道;所有通道失败时返回标准化存储错误,不写入本机静态目录。单文件最大 256MiB。",
"consumes": [
"multipart/form-data"
],
@@ -11207,22 +11271,52 @@
"httpapi.ErrorPayload": {
"type": "object",
"properties": {
"action": {
"type": "string",
"example": "retry_after"
},
"category": {
"type": "string",
"example": "rate_limit"
},
"code": {
"type": "string",
"example": "rate_limit"
},
"httpStatus": {
"type": "integer",
"example": 429
},
"message": {
"type": "string",
"example": "invalid json body"
},
"param": {},
"requestId": {
"type": "string"
},
"retryAfterSeconds": {
"type": "integer",
"example": 2
},
"retryable": {
"type": "boolean",
"example": true
},
"status": {
"type": "integer",
"example": 400
},
"taskId": {
"type": "string"
},
"type": {
"type": "string",
"example": "invalid_request_error"
},
"version": {
"type": "string",
"example": "v1"
}
}
},
@@ -11237,6 +11331,26 @@
}
}
},
"httpapi.FileStorageChannelTestResponse": {
"type": "object",
"properties": {
"deleteSucceeded": {
"type": "boolean"
},
"durationMs": {
"type": "integer"
},
"headSucceeded": {
"type": "boolean"
},
"provider": {
"type": "string"
},
"putSucceeded": {
"type": "boolean"
}
}
},
"httpapi.FileUploadData": {
"type": "object",
"properties": {
@@ -11443,6 +11557,9 @@
"example": 0
},
"data": {},
"error": {
"$ref": "#/definitions/publicerror.Error"
},
"message": {
"type": "string",
"example": "SUCCEED"
@@ -11620,6 +11737,9 @@
"type": "string",
"example": "invalid parameter"
},
"public_error": {
"$ref": "#/definitions/publicerror.Error"
},
"request_id": {
"type": "string"
}
@@ -12809,20 +12929,7 @@
"type": "object",
"properties": {
"error": {
"$ref": "#/definitions/httpapi.VolcesErrorPayload"
}
}
},
"httpapi.VolcesErrorPayload": {
"type": "object",
"properties": {
"code": {
"type": "string",
"example": "invalid_parameter"
},
"message": {
"type": "string",
"example": "model is required"
"$ref": "#/definitions/publicerror.Error"
}
}
},
@@ -13323,6 +13430,41 @@
"RevisionFailed"
]
},
"publicerror.Error": {
"type": "object",
"properties": {
"action": {
"type": "string"
},
"category": {
"type": "string"
},
"code": {
"type": "string"
},
"httpStatus": {
"type": "integer"
},
"message": {
"type": "string"
},
"requestId": {
"type": "string"
},
"retryAfterSeconds": {
"type": "integer"
},
"retryable": {
"type": "boolean"
},
"taskId": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"runner.PortraitAssetCapability": {
"type": "object",
"properties": {
@@ -13835,6 +13977,9 @@
"type": "object",
"additionalProperties": {}
},
"publicError": {
"$ref": "#/definitions/publicerror.Error"
},
"remoteTaskId": {
"type": "string"
},
@@ -14771,6 +14916,15 @@
"store.FileStorageChannelInput": {
"type": "object",
"properties": {
"accessKey": {
"type": "string"
},
"accessKeyId": {
"type": "string"
},
"accessKeySecret": {
"type": "string"
},
"apiKey": {
"type": "string"
},
@@ -14800,6 +14954,12 @@
"type": "string"
}
},
"secretKey": {
"type": "string"
},
"sessionToken": {
"type": "string"
},
"status": {
"type": "string"
},
@@ -14971,6 +15131,9 @@
"type": "object",
"additionalProperties": {}
},
"publicError": {
"$ref": "#/definitions/publicerror.Error"
},
"remoteTaskId": {
"type": "string"
},
@@ -16540,6 +16703,9 @@
"providerModelName": {
"type": "string"
},
"publicError": {
"$ref": "#/definitions/publicerror.Error"
},
"queueKey": {
"type": "string"
},