150 lines
5.9 KiB
Go
150 lines
5.9 KiB
Go
// 资源对应的对象存储配置解析:系统存储、用户自有存储与历史位置。
|
||
|
||
package app
|
||
|
||
import (
|
||
"errors"
|
||
"strings"
|
||
|
||
"gorm.io/gorm"
|
||
"infinite-canvas/backend/internal/model"
|
||
)
|
||
|
||
func (s *Service) activeOSSSetting() (ossSettingValue, error) {
|
||
_, setting, err := s.readOSSSetting()
|
||
if err != nil {
|
||
return ossSettingValue{}, err
|
||
}
|
||
return validateActiveOSSSetting(setting, "管理员尚未启用 OSS", "平台 OSS 配置不完整,请联系管理员")
|
||
}
|
||
|
||
func (s *Service) activeResourceOSSSetting(userID string) (ossSettingValue, string, bool, error) {
|
||
userSetting, value, err := s.readUserOSSSetting(userID)
|
||
if err != nil {
|
||
return ossSettingValue{}, "", false, err
|
||
}
|
||
_, systemValue, err := s.readOSSSetting()
|
||
if err != nil {
|
||
return ossSettingValue{}, "", false, err
|
||
}
|
||
userAllowed := value.Provider != s3Provider || systemValue.AllowUserS3
|
||
if userSetting != nil && value.Enabled && userAllowed {
|
||
value, err = validateActiveOSSSetting(value, "用户 OSS 尚未启用", "你的 OSS 配置不完整")
|
||
return value, firstNonEmpty(value.StorageLocationID, userSetting.ID), true, err
|
||
}
|
||
if !systemValue.Enabled {
|
||
return ossSettingValue{}, "", false, nil
|
||
}
|
||
systemValue, err = validateActiveOSSSetting(systemValue, "管理员尚未启用 OSS", "平台 OSS 配置不完整,请联系管理员")
|
||
return systemValue, systemValue.StorageLocationID, true, err
|
||
}
|
||
|
||
func (s *Service) ossSettingForResource(userID string, resource *model.Resource) (ossSettingValue, error) {
|
||
var setting ossSettingValue
|
||
var err error
|
||
if resource.StorageSettingID != "" {
|
||
_, setting, err = s.storageLocationValue(resource.StorageSettingID)
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
_, setting, err = s.readUserOSSSettingByID(userID, resource.StorageSettingID)
|
||
}
|
||
if err == nil {
|
||
_, current, currentErr := s.readUserOSSSetting(userID)
|
||
if currentErr != nil {
|
||
return ossSettingValue{}, currentErr
|
||
}
|
||
// 密钥固定在资源绑定的历史版本;只有存储位置完全一致时,才允许沿用当前 CDN。
|
||
if resourceStorageMatches(current, resource) {
|
||
setting.CDNBaseURL = current.CDNBaseURL
|
||
// CDN 的鉴权方式和回退策略属于分发配置,而不是资源创建时的
|
||
// 凭据。存储位置未变时沿用当前策略,避免历史资源因管理员
|
||
// 刚补齐 CDN 鉴权配置而继续回源。
|
||
setting.Delivery = current.Delivery
|
||
}
|
||
}
|
||
} else {
|
||
// 早期资源没有 StorageSettingID,但资源本身仍记录了 provider/endpoint/bucket。
|
||
// 先从用户 OSS 历史版本中按存储位置反查,不能把当前七牛配置猜给历史阿里云对象。
|
||
setting, err = s.userOSSSettingForResource(userID, resource)
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
_, setting, err = s.readOSSSetting()
|
||
}
|
||
}
|
||
if err != nil {
|
||
return ossSettingValue{}, err
|
||
}
|
||
resourceProvider := strings.ToLower(strings.TrimSpace(resource.Provider))
|
||
resourceMatchesSetting := resourceStorageMatches(setting, resource)
|
||
setting, err = ossSettingForProvider(setting, firstNonEmpty(resource.Provider, setting.Provider))
|
||
if err != nil {
|
||
return ossSettingValue{}, err
|
||
}
|
||
setting.Endpoint = firstNonEmpty(resource.Endpoint, setting.Endpoint)
|
||
setting.Bucket = firstNonEmpty(resource.Bucket, setting.Bucket)
|
||
// CDN 是具体存储位置的出口,不得在 provider/endpoint/bucket 不匹配时继续沿用,
|
||
// 否则切换到七牛后会把历史阿里云 objectKey 拼成七牛域名。
|
||
if !resourceMatchesSetting || (resourceProvider != "" && resourceProvider != setting.Provider) {
|
||
setting.CDNBaseURL = ""
|
||
}
|
||
if setting.AccessKeyID == "" || setting.AccessKeySecret == "" {
|
||
return ossSettingValue{}, errors.New("对象存储访问密钥不可用")
|
||
}
|
||
return setting, nil
|
||
}
|
||
|
||
func (s *Service) userOSSSettingForResource(userID string, resource *model.Resource) (ossSettingValue, error) {
|
||
settings, err := s.repo.UserOSSSettingsForUser(userID)
|
||
if err != nil {
|
||
return ossSettingValue{}, err
|
||
}
|
||
for index := range settings {
|
||
value, valueErr := s.userOSSSettingValue(&settings[index])
|
||
if valueErr != nil {
|
||
return ossSettingValue{}, valueErr
|
||
}
|
||
if resourceStorageMatches(value, resource) {
|
||
return value, nil
|
||
}
|
||
}
|
||
return ossSettingValue{}, gorm.ErrRecordNotFound
|
||
}
|
||
|
||
func resourceStorageMatches(setting ossSettingValue, resource *model.Resource) bool {
|
||
if resource == nil {
|
||
return false
|
||
}
|
||
setting = normalizeOSSSetting(setting)
|
||
return setting.Provider == strings.ToLower(strings.TrimSpace(resource.Provider)) &&
|
||
setting.Endpoint == strings.TrimRight(strings.TrimSpace(resource.Endpoint), "/") &&
|
||
setting.Bucket == strings.TrimSpace(resource.Bucket)
|
||
}
|
||
|
||
func ossSettingForProvider(setting ossSettingValue, provider string) (ossSettingValue, error) {
|
||
setting = normalizeOSSSetting(setting)
|
||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||
if provider == "" || provider == setting.Provider {
|
||
return setting, nil
|
||
}
|
||
credentials, ok := setting.ArchivedCredentials[provider]
|
||
if !ok || credentials.AccessKeyID == "" || credentials.AccessKeySecret == "" {
|
||
return ossSettingValue{}, errors.New("历史对象存储访问密钥不可用")
|
||
}
|
||
setting.Provider = provider
|
||
setting.AccessKeyID = credentials.AccessKeyID
|
||
setting.AccessKeySecret = credentials.AccessKeySecret
|
||
return setting, nil
|
||
}
|
||
|
||
func validateActiveOSSSetting(setting ossSettingValue, disabledMessage string, incompleteMessage string) (ossSettingValue, error) {
|
||
setting = normalizeOSSSetting(setting)
|
||
if !setting.Enabled {
|
||
return ossSettingValue{}, BadAuthRequest(disabledMessage)
|
||
}
|
||
if setting.Provider != aliyunOSSProvider && setting.Provider != tencentCOSProvider && setting.Provider != qiniuKodoProvider && setting.Provider != s3Provider {
|
||
return ossSettingValue{}, BadAuthRequest("仅支持阿里云 OSS、腾讯云 COS、七牛云 Kodo 和通用 S3")
|
||
}
|
||
if setting.Bucket == "" || setting.Endpoint == "" || setting.AccessKeyID == "" || setting.AccessKeySecret == "" {
|
||
return ossSettingValue{}, BadAuthRequest(incompleteMessage)
|
||
}
|
||
return setting, nil
|
||
}
|