179 lines
4.3 KiB
Go
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
|
|
}
|