Files
lone-services/pkg/oss/oss.go
T
2026-10-28 16:10:10 +08:00

179 lines
4.3 KiB
Go

package oss
import (
"bytes"
"fmt"
"path"
"regexp"
"strings"
"time"
"lone-services/pkg/utils"
alioss "github.com/aliyun/aliyun-oss-go-sdk/oss"
)
const MaxUploadBytes = 128 << 20
var unsafeFileChars = regexp.MustCompile(`[^\w.\-\p{Han}]+`)
type Config struct {
AccessKeyId string
AccessKeySecret string
BucketName string
Endpoint string
Host string
Dir string
}
type Result struct {
URL string `json:"url"`
Path string `json:"path"`
Host string `json:"host"`
}
type UploadInput struct {
Filename string
Content []byte
ContentType string
Dir string
}
func LoadConfig() Config {
cfg := Config{
AccessKeyId: utils.GetConfigString("oss.accessKeyId"),
AccessKeySecret: utils.GetConfigString("oss.accessKeySecret"),
BucketName: utils.GetConfigString("oss.bucketName"),
Endpoint: utils.GetConfigString("oss.endpoint"),
Host: utils.GetConfigString("oss.host"),
Dir: utils.GetConfigString("oss.dir"),
}
if cfg.BucketName == utils.StringEmpty {
cfg.BucketName = "lone-images"
}
if cfg.Endpoint == utils.StringEmpty {
cfg.Endpoint = "https://oss-accelerate.aliyuncs.com"
}
if cfg.Host == utils.StringEmpty {
cfg.Host = "https://images.ailuowan.com"
}
if cfg.Dir == utils.StringEmpty {
cfg.Dir = "test/chore/files/"
}
if !strings.HasPrefix(cfg.Host, "http") {
cfg.Host = "https://" + cfg.Host
}
return cfg
}
func (c Config) Validate() error {
if c.AccessKeyId == utils.StringEmpty || c.AccessKeySecret == utils.StringEmpty {
return fmt.Errorf("oss access key missing")
}
return nil
}
func Upload(in UploadInput) (*Result, error) {
cfg := LoadConfig()
if err := cfg.Validate(); err != nil {
return nil, err
}
if len(in.Content) == 0 {
return nil, fmt.Errorf("empty file content")
}
if len(in.Content) > MaxUploadBytes {
return nil, fmt.Errorf("file too large, max %d bytes", MaxUploadBytes)
}
dir := cfg.Dir
if strings.TrimSpace(in.Dir) != "" {
dir = in.Dir
}
objectKey := buildObjectKey(dir, in.Filename)
endpoint := normalizeEndpoint(cfg.Endpoint, cfg.BucketName)
client, err := alioss.New(endpoint, cfg.AccessKeyId, cfg.AccessKeySecret)
if err != nil {
return nil, fmt.Errorf("create oss client: %w", err)
}
bucket, err := client.Bucket(cfg.BucketName)
if err != nil {
return nil, fmt.Errorf("get oss bucket: %w", err)
}
opts := make([]alioss.Option, 0, 1)
if ct := strings.TrimSpace(in.ContentType); ct != "" {
opts = append(opts, alioss.ContentType(ct))
}
if err = bucket.PutObject(objectKey, bytes.NewReader(in.Content), opts...); err != nil {
return nil, fmt.Errorf("put object: %w", err)
}
host := strings.TrimRight(cfg.Host, "/")
return &Result{
URL: host + "/" + strings.TrimLeft(objectKey, "/"),
Path: objectKey,
Host: host,
}, nil
}
func normalizeEndpoint(endpoint, bucket string) string {
ep := strings.TrimSpace(endpoint)
ep = strings.TrimPrefix(ep, "https://")
ep = strings.TrimPrefix(ep, "http://")
ep = strings.TrimSuffix(ep, "/")
prefix := bucket + "."
if bucket != "" && strings.HasPrefix(ep, prefix) {
ep = strings.TrimPrefix(ep, prefix)
}
return ep
}
func buildObjectKey(configDir, filename string) string {
base := strings.Trim(sanitizeDir(configDir), "/")
if base != "" {
base += "/"
}
dateDir := time.Now().In(utils.AsiaShanghai).Format(utils.YMD)
name := sanitizeFilename(filename)
return base + dateDir + "/" + name
}
func sanitizeDir(dir string) string {
dir = strings.TrimSpace(dir)
dir = strings.ReplaceAll(dir, "\\", "/")
dir = strings.Trim(dir, "/")
if dir == "" {
return ""
}
segs := strings.Split(dir, "/")
clean := make([]string, 0, len(segs))
for _, s := range segs {
s = strings.TrimSpace(s)
if s == "" || s == "." || s == ".." {
continue
}
clean = append(clean, s)
}
return strings.Join(clean, "/")
}
func sanitizeFilename(filename string) string {
name := path.Base(strings.ReplaceAll(filename, "\\", "/"))
name = strings.TrimSpace(name)
name = unsafeFileChars.ReplaceAllString(name, "_")
name = strings.Trim(name, "._")
if name == "" || name == "." || name == ".." {
return "file"
}
if len(name) > 200 {
ext := path.Ext(name)
base := strings.TrimSuffix(name, ext)
if len(base) > 180 {
base = base[:180]
}
name = base + ext
}
return name
}