feat(m8): P1 MinIO 对象存储与文件管理
- 迁移 000023 gateway.file_objects(personal/system 归属隔离 + 部分索引) - internal/platform/storage:minio-go 适配(端点 scheme 剥离、流式 PutObject/Open/Delete) - internal/workbench/files.go:FileService(sha256 校验、PutObject-then-insert 回滚、delete 先删行再删对象) - admin /api/v1/admin/files + portal /api/v1/portal/files 处理器(流式上传下载、Content-Disposition) - RBAC file:read/file:manage;菜单加文件管理 + 门户文件仓库 - compose 增 minio 服务(S3_* anchor、不暴露端口);nginx client_max_body_size 32m→256m - 管理端文件管理页 + 门户个人文件仓;集成测试 TestFileObjectLifecycle 连真 MinIO 通过 - healthz object_storage:true;README/PRODUCTION/进展文档同步 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,18 +11,19 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Environment string
|
||||
Server Server
|
||||
Database Database
|
||||
Redis Redis
|
||||
Security Security
|
||||
Auth Auth
|
||||
Credentials Credentials
|
||||
Upstream Upstream
|
||||
Audit Audit
|
||||
Outbox Outbox
|
||||
RuntimeData RuntimeData
|
||||
Shadow Shadow
|
||||
Environment string
|
||||
Server Server
|
||||
Database Database
|
||||
Redis Redis
|
||||
Security Security
|
||||
Auth Auth
|
||||
Credentials Credentials
|
||||
Upstream Upstream
|
||||
Audit Audit
|
||||
Outbox Outbox
|
||||
RuntimeData RuntimeData
|
||||
Shadow Shadow
|
||||
ObjectStorage ObjectStorage
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
@@ -114,6 +115,16 @@ type Shadow struct {
|
||||
MaxConcurrent int
|
||||
}
|
||||
|
||||
type ObjectStorage struct {
|
||||
Endpoint string
|
||||
AccessKeyID string
|
||||
SecretAccessKey string
|
||||
Bucket string
|
||||
Region string
|
||||
UseSSL bool
|
||||
MaxFileBytes int64
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
cfg := Config{
|
||||
Environment: env("APP_ENV", "local"),
|
||||
@@ -182,6 +193,15 @@ func Load() (Config, error) {
|
||||
Timeout: duration("SHADOW_TIMEOUT", 20*time.Second), MaxBodyBytes: int64Value("SHADOW_MAX_BODY_BYTES", 2<<20),
|
||||
MaxConcurrent: intValue("SHADOW_MAX_CONCURRENT", 16),
|
||||
},
|
||||
ObjectStorage: ObjectStorage{
|
||||
Endpoint: strings.TrimRight(env("S3_ENDPOINT", "http://minio:9000"), "/"),
|
||||
AccessKeyID: env("S3_ACCESS_KEY_ID", "gateway"),
|
||||
SecretAccessKey: env("S3_SECRET_ACCESS_KEY", "gateway-secret"),
|
||||
Bucket: env("S3_BUCKET", "gateway-files"),
|
||||
Region: env("S3_REGION", "us-east-1"),
|
||||
UseSSL: boolValue("S3_USE_SSL", false),
|
||||
MaxFileBytes: int64Value("S3_MAX_FILE_BYTES", 128<<20),
|
||||
},
|
||||
}
|
||||
|
||||
return cfg, cfg.Validate()
|
||||
@@ -236,6 +256,21 @@ func (c Config) Validate() error {
|
||||
errs = append(errs, errors.New("SHADOW_API_KEY is required when SHADOW_BASE_URL is set"))
|
||||
}
|
||||
}
|
||||
if err := validateHTTPURL(c.ObjectStorage.Endpoint); err != nil {
|
||||
errs = append(errs, fmt.Errorf("S3_ENDPOINT: %w", err))
|
||||
} else if endpointURL, parseErr := url.Parse(c.ObjectStorage.Endpoint); parseErr == nil && endpointURL.Path != "" {
|
||||
// minio-go 的 Endpoint 不接受带路径的完整 URL(报 "fully qualified paths")。
|
||||
errs = append(errs, errors.New("S3_ENDPOINT must not contain a path"))
|
||||
}
|
||||
if c.ObjectStorage.AccessKeyID == "" || c.ObjectStorage.SecretAccessKey == "" {
|
||||
errs = append(errs, errors.New("S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are required"))
|
||||
}
|
||||
if c.ObjectStorage.Bucket == "" || len(c.ObjectStorage.Bucket) > 63 {
|
||||
errs = append(errs, errors.New("S3_BUCKET must be a non-empty bucket name of at most 63 characters"))
|
||||
}
|
||||
if c.ObjectStorage.MaxFileBytes < 1<<20 || c.ObjectStorage.MaxFileBytes > 512<<20 {
|
||||
errs = append(errs, errors.New("S3_MAX_FILE_BYTES must be between 1 MiB and 512 MiB"))
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user