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:
+182
-16
@@ -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"
|
||||
},
|
||||
|
||||
+122
-12
@@ -481,19 +481,41 @@ definitions:
|
||||
type: object
|
||||
httpapi.ErrorPayload:
|
||||
properties:
|
||||
action:
|
||||
example: retry_after
|
||||
type: string
|
||||
category:
|
||||
example: rate_limit
|
||||
type: string
|
||||
code:
|
||||
example: rate_limit
|
||||
type: string
|
||||
httpStatus:
|
||||
example: 429
|
||||
type: integer
|
||||
message:
|
||||
example: invalid json body
|
||||
type: string
|
||||
param: {}
|
||||
requestId:
|
||||
type: string
|
||||
retryAfterSeconds:
|
||||
example: 2
|
||||
type: integer
|
||||
retryable:
|
||||
example: true
|
||||
type: boolean
|
||||
status:
|
||||
example: 400
|
||||
type: integer
|
||||
taskId:
|
||||
type: string
|
||||
type:
|
||||
example: invalid_request_error
|
||||
type: string
|
||||
version:
|
||||
example: v1
|
||||
type: string
|
||||
type: object
|
||||
httpapi.FileStorageChannelListResponse:
|
||||
properties:
|
||||
@@ -502,6 +524,19 @@ definitions:
|
||||
$ref: '#/definitions/store.FileStorageChannel'
|
||||
type: array
|
||||
type: object
|
||||
httpapi.FileStorageChannelTestResponse:
|
||||
properties:
|
||||
deleteSucceeded:
|
||||
type: boolean
|
||||
durationMs:
|
||||
type: integer
|
||||
headSucceeded:
|
||||
type: boolean
|
||||
provider:
|
||||
type: string
|
||||
putSucceeded:
|
||||
type: boolean
|
||||
type: object
|
||||
httpapi.FileUploadData:
|
||||
properties:
|
||||
assetStorage:
|
||||
@@ -651,6 +686,8 @@ definitions:
|
||||
example: 0
|
||||
type: integer
|
||||
data: {}
|
||||
error:
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
message:
|
||||
example: SUCCEED
|
||||
type: string
|
||||
@@ -775,6 +812,8 @@ definitions:
|
||||
message:
|
||||
example: invalid parameter
|
||||
type: string
|
||||
public_error:
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
request_id:
|
||||
type: string
|
||||
type: object
|
||||
@@ -1602,16 +1641,7 @@ definitions:
|
||||
httpapi.VolcesErrorEnvelope:
|
||||
properties:
|
||||
error:
|
||||
$ref: '#/definitions/httpapi.VolcesErrorPayload'
|
||||
type: object
|
||||
httpapi.VolcesErrorPayload:
|
||||
properties:
|
||||
code:
|
||||
example: invalid_parameter
|
||||
type: string
|
||||
message:
|
||||
example: model is required
|
||||
type: string
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
type: object
|
||||
httpapi.WalletAdjustmentResponse:
|
||||
properties:
|
||||
@@ -1953,6 +1983,29 @@ definitions:
|
||||
- RevisionActive
|
||||
- RevisionSuperseded
|
||||
- RevisionFailed
|
||||
publicerror.Error:
|
||||
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
|
||||
type: object
|
||||
runner.PortraitAssetCapability:
|
||||
properties:
|
||||
availablePlatformIds:
|
||||
@@ -2297,6 +2350,8 @@ definitions:
|
||||
pricingSnapshot:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
publicError:
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
remoteTaskId:
|
||||
type: string
|
||||
request:
|
||||
@@ -2929,6 +2984,12 @@ definitions:
|
||||
type: object
|
||||
store.FileStorageChannelInput:
|
||||
properties:
|
||||
accessKey:
|
||||
type: string
|
||||
accessKeyId:
|
||||
type: string
|
||||
accessKeySecret:
|
||||
type: string
|
||||
apiKey:
|
||||
type: string
|
||||
channelKey:
|
||||
@@ -2949,6 +3010,10 @@ definitions:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
secretKey:
|
||||
type: string
|
||||
sessionToken:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
uploadUrl:
|
||||
@@ -3064,6 +3129,8 @@ definitions:
|
||||
pricingSnapshot:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
publicError:
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
remoteTaskId:
|
||||
type: string
|
||||
request:
|
||||
@@ -4125,6 +4192,8 @@ definitions:
|
||||
type: string
|
||||
providerModelName:
|
||||
type: string
|
||||
publicError:
|
||||
$ref: '#/definitions/publicerror.Error'
|
||||
queueKey:
|
||||
type: string
|
||||
requestFingerprint:
|
||||
@@ -6358,7 +6427,7 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 创建文件存储通道,当前主要用于配置 server-main OpenAPI 上传通道。
|
||||
description: 创建 server-main OpenAPI、阿里云 OSS 或 S3 兼容文件存储通道。
|
||||
parameters:
|
||||
- description: 文件存储通道
|
||||
in: body
|
||||
@@ -6485,6 +6554,47 @@ paths:
|
||||
summary: 更新文件存储通道
|
||||
tags:
|
||||
- system
|
||||
/api/admin/system/file-storage/channels/{channelID}/test:
|
||||
post:
|
||||
description: 对指定 OSS 或 S3 通道执行隔离的 Put、Head、Delete 探针,不返回凭据或对象键。
|
||||
parameters:
|
||||
- description: 文件存储通道 ID
|
||||
in: path
|
||||
name: channelID
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
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'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 测试对象存储通道
|
||||
tags:
|
||||
- system
|
||||
/api/admin/system/file-storage/settings:
|
||||
get:
|
||||
description: 返回文件存储系统设置;数据库对象尚未创建时返回默认设置。
|
||||
@@ -8652,7 +8762,7 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: 上传文件到配置的文件存储通道;没有启用通道时回退到本地静态上传目录。单文件最大 256MiB。
|
||||
description: 上传文件到配置的对象存储通道;所有通道失败时返回标准化存储错误,不写入本机静态目录。单文件最大 256MiB。
|
||||
parameters:
|
||||
- description: 要上传的文件
|
||||
in: formData
|
||||
|
||||
Reference in New Issue
Block a user