Files
soul-yongping/soul-api/internal/oss/oss.go
卡若 76965adb23 chore: 清理敏感与开发文档,仅同步代码
- 永久忽略并从仓库移除 开发文档/
- 移除并忽略 .env 与小程序私有配置
- 同步小程序/管理端/API与脚本改动

Made-with: Cursor
2026-03-17 17:50:12 +08:00

125 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 去掉 schemeSDK 需要
cfg.Endpoint = strings.TrimPrefix(strings.TrimPrefix(cfg.Endpoint, "https://"), "http://")
return &cfg
}
// IsEnabled 是否已配置 OSS 且可用
func IsEnabled() bool {
return LoadConfig() != nil
}
// Upload 上传文件到 OSSobjectKey 如 "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
}
// 公网 URLhttps://{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)
}