161 lines
4.2 KiB
Go
161 lines
4.2 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
|
|
"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
|
|
|
|
"git.beifan.cn/trace-system/backend-go/config"
|
|
)
|
|
|
|
// OSSService 封装阿里云 OSS 上传。
|
|
type OSSService struct {
|
|
cfg config.OSSConfig
|
|
}
|
|
|
|
func NewOSSService() OSSService {
|
|
return OSSService{cfg: config.GetAppConfig().OSS}
|
|
}
|
|
|
|
func (s OSSService) UploadObject(objectKey string, reader io.Reader, contentType string) error {
|
|
if err := s.validateConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
request := &oss.PutObjectRequest{
|
|
Bucket: oss.Ptr(s.cfg.Bucket),
|
|
Key: oss.Ptr(objectKey),
|
|
Body: reader,
|
|
}
|
|
if contentType != "" {
|
|
request.ContentType = oss.Ptr(contentType)
|
|
}
|
|
|
|
if _, err := s.newClient().PutObject(context.TODO(), request); err != nil {
|
|
return fmt.Errorf("上传现场图片失败: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s OSSService) validateConfig() error {
|
|
if strings.TrimSpace(s.cfg.Endpoint) == "" ||
|
|
strings.TrimSpace(s.cfg.Bucket) == "" ||
|
|
strings.TrimSpace(s.cfg.AccessKeyID) == "" ||
|
|
strings.TrimSpace(s.cfg.AccessKeySecret) == "" {
|
|
return fmt.Errorf("OSS 配置未完整,请检查 endpoint、bucket、access_key_id、access_key_secret")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s OSSService) publicURL(objectKey string) string {
|
|
if base := strings.TrimRight(strings.TrimSpace(s.cfg.PublicBaseURL), "/"); base != "" {
|
|
return base + "/" + objectKey
|
|
}
|
|
endpoint := normalizeOSSEndpoint(s.cfg.Endpoint)
|
|
u, err := url.Parse(endpoint)
|
|
if err != nil || u.Host == "" {
|
|
return fmt.Sprintf("https://%s.%s/%s", s.cfg.Bucket, strings.TrimPrefix(endpoint, "https://"), objectKey)
|
|
}
|
|
return fmt.Sprintf("%s://%s.%s/%s", u.Scheme, s.cfg.Bucket, u.Host, objectKey)
|
|
}
|
|
|
|
func (s OSSService) AccessURL(objectRef string) string {
|
|
objectKey := s.objectKeyFromRef(objectRef)
|
|
if objectKey == "" {
|
|
return objectRef
|
|
}
|
|
if strings.TrimSpace(s.cfg.PublicBaseURL) != "" {
|
|
return s.publicURL(objectKey)
|
|
}
|
|
url, err := s.SignedURL(objectKey)
|
|
if err != nil {
|
|
return s.publicURL(objectKey)
|
|
}
|
|
return url
|
|
}
|
|
|
|
func (s OSSService) SignedURL(objectKey string) (string, error) {
|
|
if err := s.validateConfig(); err != nil {
|
|
return "", err
|
|
}
|
|
expires := s.cfg.SignedURLExpire
|
|
if expires <= 0 {
|
|
expires = 3600
|
|
}
|
|
result, err := s.newClient().Presign(
|
|
context.TODO(),
|
|
&oss.GetObjectRequest{
|
|
Bucket: oss.Ptr(s.cfg.Bucket),
|
|
Key: oss.Ptr(objectKey),
|
|
},
|
|
oss.PresignExpires(time.Duration(expires)*time.Second),
|
|
)
|
|
if err != nil {
|
|
return "", fmt.Errorf("生成 OSS 签名 URL 失败: %w", err)
|
|
}
|
|
return result.URL, nil
|
|
}
|
|
|
|
func (s OSSService) objectKeyFromRef(objectRef string) string {
|
|
objectRef = strings.TrimSpace(objectRef)
|
|
if objectRef == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(objectRef, "http://") && !strings.HasPrefix(objectRef, "https://") {
|
|
return strings.TrimLeft(objectRef, "/")
|
|
}
|
|
u, err := url.Parse(objectRef)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
host := strings.ToLower(u.Host)
|
|
bucketHostPrefix := strings.ToLower(s.cfg.Bucket) + "."
|
|
if strings.HasPrefix(host, bucketHostPrefix) {
|
|
return strings.TrimLeft(u.Path, "/")
|
|
}
|
|
if base := strings.TrimSpace(s.cfg.PublicBaseURL); base != "" {
|
|
baseURL, err := url.Parse(base)
|
|
if err == nil && strings.EqualFold(baseURL.Host, u.Host) {
|
|
return strings.TrimLeft(strings.TrimPrefix(u.Path, baseURL.Path), "/")
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func normalizeOSSEndpoint(endpoint string) string {
|
|
endpoint = strings.TrimSpace(endpoint)
|
|
if endpoint == "" {
|
|
return endpoint
|
|
}
|
|
if strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://") {
|
|
return endpoint
|
|
}
|
|
return "https://" + endpoint
|
|
}
|
|
|
|
func (s OSSService) newClient() *oss.Client {
|
|
cfg := oss.LoadDefaultConfig().
|
|
WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
|
s.cfg.AccessKeyID,
|
|
s.cfg.AccessKeySecret,
|
|
)).
|
|
WithRegion(normalizeOSSRegion(s.cfg.Region)).
|
|
WithEndpoint(strings.TrimPrefix(strings.TrimPrefix(normalizeOSSEndpoint(s.cfg.Endpoint), "https://"), "http://"))
|
|
return oss.NewClient(cfg)
|
|
}
|
|
|
|
func normalizeOSSRegion(region string) string {
|
|
region = strings.TrimSpace(region)
|
|
if strings.HasPrefix(region, "oss-") {
|
|
return strings.TrimPrefix(region, "oss-")
|
|
}
|
|
return region
|
|
}
|