Files
lone-services/pkg/oss/oss.go
T
zzw b77a1f1b95
CI / changes (push) Successful in 1m31s
CI / ad (push) Successful in 32s
CI / admin (push) Successful in 26s
CI / analysis (push) Successful in 28s
CI / bff (push) Successful in 23s
CI / chore (push) Successful in 24s
CI / equipment (push) Successful in 21s
CI / express (push) Successful in 21s
CI / factory (push) Successful in 18s
CI / message (push) Successful in 18s
CI / order (push) Successful in 21s
CI / payment (push) Successful in 20s
CI / percutaneous (push) Successful in 19s
CI / product (push) Successful in 21s
CI / record (push) Successful in 21s
CI / sale (push) Successful in 20s
CI / store (push) Successful in 25s
CI / support (push) Successful in 21s
CI / task (push) Successful in 20s
CI / user (push) Successful in 22s
CI / water (push) Successful in 20s
CI / wecom (push) Successful in 21s
CI / ws (push) Successful in 8s
feat: user improve
2026-10-27 13:01:24 +08:00

179 lines
4.4 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 // 128MB
var unsafeFileChars = regexp.MustCompile(`[^\w.\-\p{Han}]+`)
type Config struct {
AccessKeyId string
AccessKeySecret string
BucketName string
Endpoint string
Host string
Dir string // 固定前缀,如 test/chore/files/
}
type Result struct {
URL string `json:"url"`
Path string `json:"path"` // object key,如 test/chore/files/20260403/a.zip
Host string `json:"host"`
}
type UploadInput struct {
Filename string
Content []byte
ContentType string
Dir string // 可选,覆盖配置 oss.dir
}
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
}