101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package uploader
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"image/png"
|
||
"math/rand"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||
"github.com/topfans/laserCompositor/config"
|
||
)
|
||
|
||
// OSSUploader OSS 上传器
|
||
type OSSUploader struct {
|
||
cfg config.OSSConfig
|
||
}
|
||
|
||
// NewOSSUploader 创建 OSS 上传器
|
||
func NewOSSUploader(cfg config.OSSConfig) *OSSUploader {
|
||
return &OSSUploader{cfg: cfg}
|
||
}
|
||
|
||
// UploadResult 上传结果
|
||
type UploadResult struct {
|
||
OSSKey string `json:"oss_key"`
|
||
SignedURL string `json:"signed_url"`
|
||
}
|
||
|
||
// UploadPNG 上传 PNG 到 OSS,返回 oss_key 和 signed_url
|
||
func (u *OSSUploader) UploadPNG(data []byte, dir, filename string) (*UploadResult, error) {
|
||
bucket, err := u.getBucket()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if dir == "" {
|
||
dir = "laser-card/"
|
||
}
|
||
if filename == "" {
|
||
filename = fmt.Sprintf("%d_%d.png", time.Now().UnixMilli(), rand.Intn(10000))
|
||
}
|
||
ossKey := strings.TrimRight(dir, "/") + "/" + filename
|
||
|
||
err = bucket.PutObject(ossKey, bytes.NewReader(data))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("OSS upload failed: %w", err)
|
||
}
|
||
|
||
// 生成签名 URL(1 小时有效)
|
||
signedURL, err := bucket.SignURL(ossKey, oss.HTTPGet, 3600)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("sign URL failed: %w", err)
|
||
}
|
||
|
||
return &UploadResult{
|
||
OSSKey: ossKey,
|
||
SignedURL: signedURL,
|
||
}, nil
|
||
}
|
||
|
||
// UploadPNG 变体:上传并返回带签名的 URL
|
||
func (u *OSSUploader) getBucket() (*oss.Bucket, error) {
|
||
ak := strings.TrimSpace(u.cfg.AccessKeyID)
|
||
sk := strings.TrimSpace(u.cfg.AccessKeySecret)
|
||
if ak == "" || sk == "" {
|
||
return nil, fmt.Errorf("OSS credentials not configured")
|
||
}
|
||
|
||
endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", u.cfg.Region)
|
||
client, err := oss.New(endpoint, ak, sk)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("create OSS client: %w", err)
|
||
}
|
||
|
||
bucket, err := client.Bucket(u.cfg.BucketName)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("get bucket: %w", err)
|
||
}
|
||
|
||
return bucket, nil
|
||
}
|
||
|
||
// EncodePNG 将 compositor.ComposeResult 编码为 PNG bytes
|
||
func EncodePNG(width, height int, data []byte) ([]byte, error) {
|
||
// 如果 data 已经是完整的 PNG 数据,直接返回
|
||
if len(data) > 4 && data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4e && data[3] == 0x47 {
|
||
return data, nil
|
||
}
|
||
|
||
var buf bytes.Buffer
|
||
// 这里 data 预期是 raw RGBA pixels
|
||
// 但实际上 compositor 返回的是 NRGBA image
|
||
// 暂时返回空,由调用方在 handler 层处理
|
||
if err := png.Encode(&buf, nil); err != nil {
|
||
return nil, err
|
||
}
|
||
return buf.Bytes(), nil
|
||
}
|