package oss import ( "encoding/json" "io" "log" "net/url" "strings" "soul-api/internal/database" "soul-api/internal/model" alioss "github.com/aliyun/aliyun-oss-go-sdk/oss" ) // Config 阿里云 OSS 配置,与管理端 ossConfig 字段对应 type Config struct { Endpoint string `json:"endpoint"` Bucket string `json:"bucket"` Region string `json:"region"` AccessKeyID string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` } // LoadConfig 从 system_config 读取 oss_config,配置不完整时返回 nil func LoadConfig() *Config { var row model.SystemConfig if err := database.DB().Where("config_key = ?", "oss_config").First(&row).Error; err != nil { return nil } var m map[string]interface{} if err := json.Unmarshal(row.ConfigValue, &m); err != nil { return nil } var cfg Config if v, ok := m["endpoint"].(string); ok && v != "" { cfg.Endpoint = strings.TrimSpace(v) } if v, ok := m["bucket"].(string); ok && v != "" { cfg.Bucket = strings.TrimSpace(v) } if v, ok := m["accessKeyId"].(string); ok && v != "" { cfg.AccessKeyID = v } if v, ok := m["accessKeySecret"].(string); ok && v != "" { cfg.AccessKeySecret = v } if cfg.Endpoint == "" || cfg.Bucket == "" || cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" { return nil } // endpoint 去掉 scheme,SDK 需要 cfg.Endpoint = strings.TrimPrefix(strings.TrimPrefix(cfg.Endpoint, "https://"), "http://") return &cfg } // IsEnabled 是否已配置 OSS 且可用 func IsEnabled() bool { return LoadConfig() != nil } // Upload 上传文件到 OSS,objectKey 如 "uploads/avatars/xxx.jpg" // 返回公网访问 URL,如 https://bucket.oss-cn-hangzhou.aliyuncs.com/uploads/avatars/xxx.jpg func Upload(objectKey string, reader io.Reader, options ...alioss.Option) (string, error) { cfg := LoadConfig() if cfg == nil { return "", nil // 未配置,调用方需回退本地 } client, err := alioss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret) if err != nil { log.Printf("oss: client init failed: %v", err) return "", err } bucket, err := client.Bucket(cfg.Bucket) if err != nil { log.Printf("oss: bucket %s failed: %v", cfg.Bucket, err) return "", err } if err := bucket.PutObject(objectKey, reader, options...); err != nil { log.Printf("oss: PutObject %s failed: %v", objectKey, err) return "", err } // 公网 URL:https://{bucket}.{endpoint}/{objectKey} u := "https://" + cfg.Bucket + "." + cfg.Endpoint + "/" + objectKey return u, nil } // Delete 从 OSS 删除对象,objectKey 如 "uploads/avatars/xxx.jpg" func Delete(objectKey string) error { cfg := LoadConfig() if cfg == nil { return nil } client, err := alioss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret) if err != nil { return err } bucket, err := client.Bucket(cfg.Bucket) if err != nil { return err } return bucket.DeleteObject(objectKey) } // ParseObjectKeyFromURL 从 OSS 公网 URL 解析出 objectKey // 格式: https://bucket.oss-cn-xxx.aliyuncs.com/uploads/avatars/xxx.jpg func ParseObjectKeyFromURL(rawURL string) string { u, err := url.Parse(rawURL) if err != nil { return "" } path := strings.TrimPrefix(u.Path, "/") return path } // IsOSSURL 判断是否为 OSS 公网 URL(用于删除时区分本地/OSS) func IsOSSURL(rawURL string) bool { cfg := LoadConfig() if cfg == nil { return false } // 格式: https://{bucket}.{endpoint}/... prefix := "https://" + cfg.Bucket + "." + cfg.Endpoint + "/" return strings.HasPrefix(rawURL, prefix) }