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:
@@ -161,6 +161,9 @@ func run(ctx context.Context, opts options) error {
|
||||
if err := ensureAcceptanceAccessRules(ctx, database, groupID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureAcceptanceObjectStorage(ctx, database, opts.emulatorBaseURL); err != nil {
|
||||
return err
|
||||
}
|
||||
geminiModel, videoModel, err := selectedModels(ctx, database)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -240,6 +243,73 @@ func run(ctx context.Context, opts options) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureAcceptanceObjectStorage(ctx context.Context, database *store.Store, emulatorBaseURL string) error {
|
||||
baseURL := strings.TrimRight(strings.TrimSpace(emulatorBaseURL), "/")
|
||||
if baseURL == "" {
|
||||
return errors.New("acceptance object storage requires the emulator URL")
|
||||
}
|
||||
channels := []struct {
|
||||
key string
|
||||
name string
|
||||
provider string
|
||||
endpoint string
|
||||
priority int
|
||||
pathStyle bool
|
||||
}{
|
||||
{
|
||||
key: "local-acceptance-oss", name: "Local Acceptance Aliyun OSS", provider: "aliyun_oss",
|
||||
endpoint: baseURL + "/storage/oss/bucket", priority: 10, pathStyle: true,
|
||||
},
|
||||
{
|
||||
key: "local-acceptance-s3", name: "Local Acceptance S3", provider: "s3",
|
||||
endpoint: baseURL + "/storage/s3", priority: 20, pathStyle: true,
|
||||
},
|
||||
}
|
||||
for _, channel := range channels {
|
||||
_, err := database.Pool().Exec(ctx, `
|
||||
INSERT INTO file_storage_channels (
|
||||
channel_key, name, provider, credentials, config, retry_policy, priority, status
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3,
|
||||
'{"accessKeyId":"local-acceptance","accessKeySecret":"local-acceptance-secret"}'::jsonb,
|
||||
jsonb_build_object(
|
||||
'endpoint', $4::text,
|
||||
'region', 'local-acceptance-1',
|
||||
'bucket', 'bucket',
|
||||
'objectPrefix', 'acceptance/media',
|
||||
'accessScope', 'private',
|
||||
'forcePathStyle', $5::boolean,
|
||||
'scenes', jsonb_build_array('upload', 'image_result', 'request_asset'),
|
||||
'acceptanceEmulatorOnly', true
|
||||
),
|
||||
'{"enabled":true,"maxRetries":2,"backoffSeconds":[0.25,1],"strategy":"exponential"}'::jsonb,
|
||||
$6, 'enabled'
|
||||
)
|
||||
ON CONFLICT (channel_key) DO UPDATE
|
||||
SET name = EXCLUDED.name,
|
||||
provider = EXCLUDED.provider,
|
||||
credentials = EXCLUDED.credentials,
|
||||
config = EXCLUDED.config,
|
||||
retry_policy = EXCLUDED.retry_policy,
|
||||
priority = EXCLUDED.priority,
|
||||
status = 'enabled',
|
||||
deleted_at = NULL,
|
||||
last_error = NULL,
|
||||
updated_at = now()`, channel.key, channel.name, channel.provider, channel.endpoint, channel.pathStyle, channel.priority)
|
||||
if err != nil {
|
||||
return fmt.Errorf("configure %s object storage channel: %w", channel.provider, err)
|
||||
}
|
||||
}
|
||||
_, err := database.Pool().Exec(ctx, `
|
||||
INSERT INTO system_settings (setting_key, value)
|
||||
VALUES ('file_storage', '{"resultUploadPolicy":"default"}'::jsonb)
|
||||
ON CONFLICT (setting_key) DO UPDATE
|
||||
SET value = jsonb_set(COALESCE(system_settings.value, '{}'::jsonb), '{resultUploadPolicy}', '"default"'::jsonb, true),
|
||||
updated_at = now()`)
|
||||
return err
|
||||
}
|
||||
|
||||
func resetPreviousLocalRun(ctx context.Context, database *store.Store, opts options) error {
|
||||
mode, err := database.GetGatewayTrafficMode(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user