feat(storage): 完善对象存储配置与过期策略
补齐 OSS/S3 的 Endpoint、Region、Bucket、CDN、对象前缀和签名有效期配置,并为生成结果与请求素材自动维护分级生命周期规则。普通上传继续保持永久,私有资源按配置生成限时签名 URL,管理端连接测试覆盖生命周期、上传、读取和删除。\n\n新增可重复的真实 OSS 验收脚本,凭据仅从本地环境读取,接口响应继续保持脱敏。\n\n验证:Go 全量测试、迁移安全检查、pnpm lint、pnpm test、pnpm build、本地阿里云 OSS 真实上传下载删除验收。
This commit is contained in:
@@ -280,7 +280,7 @@ func (s *Server) deleteFileStorageChannel(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// testFileStorageChannel godoc
|
||||
// @Summary 测试对象存储通道
|
||||
// @Description 对指定 OSS 或 S3 通道执行隔离的 Put、Head、Delete 探针,不返回凭据或对象键。
|
||||
// @Description 对指定 OSS 或 S3 通道检查并初始化临时文件生命周期规则,然后执行隔离的 Put、Head、Delete 探针;不返回凭据或对象键。
|
||||
// @Tags system
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
@@ -358,9 +358,26 @@ func validateFileStorageChannelInput(input store.FileStorageChannelInput, existi
|
||||
if !validFileStorageBaseURL(endpoint) {
|
||||
return "object storage config.endpoint must be an http or https URL without embedded credentials"
|
||||
}
|
||||
if publicBaseURL := firstNonEmpty(fileStorageConfigString(input.Config, "publicBaseUrl"), fileStorageConfigString(input.Config, "publicBaseURL")); publicBaseURL != "" && !validFileStorageBaseURL(publicBaseURL) {
|
||||
if publicBaseURL := firstNonEmpty(
|
||||
fileStorageConfigString(input.Config, "publicBaseUrl"),
|
||||
fileStorageConfigString(input.Config, "publicBaseURL"),
|
||||
fileStorageConfigString(input.Config, "cdnDomain"),
|
||||
fileStorageConfigString(input.Config, "publicDomain"),
|
||||
); publicBaseURL != "" && !validFileStorageBaseURL(publicBaseURL) {
|
||||
return "object storage config.publicBaseUrl must be an http or https URL without embedded credentials"
|
||||
}
|
||||
if policy := firstNonEmpty(
|
||||
fileStorageConfigString(input.Config, "temporaryFileExpirePolicy"),
|
||||
fileStorageConfigString(input.Config, "apiReturnExpirePolicy"),
|
||||
); policy != "" && !validFileStorageExpirationPolicy(policy) {
|
||||
return "object storage config.temporaryFileExpirePolicy must be never, 1d, 1m, 3m or 6m"
|
||||
}
|
||||
if value, exists := input.Config["signedUrlExpiresSeconds"]; exists {
|
||||
seconds, ok := fileStorageConfigInteger(value)
|
||||
if !ok || seconds < 60 || seconds > 7*24*60*60 {
|
||||
return "object storage config.signedUrlExpiresSeconds must be an integer between 60 and 604800"
|
||||
}
|
||||
}
|
||||
accessKeyID := input.AccessKeyID
|
||||
if accessKeyID == nil {
|
||||
accessKeyID = input.AccessKey
|
||||
@@ -378,6 +395,34 @@ func validateFileStorageChannelInput(input store.FileStorageChannelInput, existi
|
||||
return ""
|
||||
}
|
||||
|
||||
func validFileStorageExpirationPolicy(value string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(value)) {
|
||||
case "never", "1d", "1m", "3m", "6m":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func fileStorageConfigInteger(value any) (int64, bool) {
|
||||
switch typed := value.(type) {
|
||||
case int:
|
||||
return int64(typed), true
|
||||
case int32:
|
||||
return int64(typed), true
|
||||
case int64:
|
||||
return typed, true
|
||||
case float64:
|
||||
converted := int64(typed)
|
||||
return converted, float64(converted) == typed
|
||||
case float32:
|
||||
converted := int64(typed)
|
||||
return converted, float32(converted) == typed
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func validFileStorageBaseURL(value string) bool {
|
||||
parsed, err := url.Parse(strings.TrimSpace(value))
|
||||
return err == nil && parsed.User == nil && parsed.Host != "" && (parsed.Scheme == "http" || parsed.Scheme == "https")
|
||||
|
||||
Reference in New Issue
Block a user