生产真实 key 为 `YYYY/MM/<UnixNano>.<ext>`(无前缀、无 day 层), 与 route/question/page.go:uploadImage 一致;internal/storage 里三个 上传函数仍用旧的 `picture/ 前缀 + YYYY/MM/DD + 15hex + 无扩展名`,从未上线。 - PictureKeyPrefix 置空,统一由 uploadKey 生成 key - 扩展名跟随最终 ContentType(webp 转成功用 .webp,回退原图用原格式) - OSS 原图直传通道取原文件名扩展名 - 新增 picture_test.go 锁定 key 格式,防回归
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
// Copyright 2022 E99p1ant. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package storage
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/wuhan005/gadget"
|
|
|
|
"github.com/tamakyi/TamaBox/internal/conf"
|
|
)
|
|
|
|
// UploadPictureToOSS upload user's avatar or background image to OSS.
|
|
// It returns the uploaded asset URL.
|
|
func UploadPictureToOSS(file multipart.File, fileHeader *multipart.FileHeader) (string, error) {
|
|
// 读取文件头,校验是否为图片
|
|
header := make([]byte, 512)
|
|
n, err := file.Read(header)
|
|
if err != nil && err != io.EOF {
|
|
return "", errors.Wrap(err, "read file header")
|
|
}
|
|
if n == 0 {
|
|
return "", errors.New("empty file")
|
|
}
|
|
if !isSupportedImage(header[:n]) {
|
|
return "", errors.New("不支持的文件格式,仅允许 JPEG、PNG、GIF、WebP 图片")
|
|
}
|
|
// 重置文件指针
|
|
if _, err := file.Seek(0, io.SeekStart); err != nil {
|
|
return "", errors.Wrap(err, "seek file")
|
|
}
|
|
|
|
// 获取共享 OSS bucket
|
|
bucket, err := GetOSSBucket()
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "get OSS bucket")
|
|
}
|
|
|
|
// OSS 通道原样上传原图(不转 WebP),扩展名沿用原始文件名。
|
|
ext := ""
|
|
if fileHeader != nil {
|
|
ext = originalExt(fileHeader.Filename)
|
|
}
|
|
key := uploadKey(ext)
|
|
|
|
if err := gadget.Retry(5, func() error {
|
|
if err := bucket.PutObject(key, file); err != nil {
|
|
return errors.Wrap(err, "put object")
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return "", errors.Wrap(err, "retry 5 times")
|
|
}
|
|
|
|
if conf.Upload.AliyunBucketCDNHost != "" {
|
|
return fmt.Sprintf("https://%s/%s", conf.Upload.AliyunBucketCDNHost, key), nil
|
|
}
|
|
return fmt.Sprintf("https://%s.%s/%s", conf.Upload.AliyunBucket, conf.Upload.AliyunEndpoint, key), nil
|
|
}
|