125 lines
3.5 KiB
Go
125 lines
3.5 KiB
Go
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)
|
||
}
|