285 lines
8.9 KiB
Go
285 lines
8.9 KiB
Go
package provider
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"net/url"
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||
"github.com/aliyun/credentials-go/credentials"
|
||
pb "github.com/topfans/backend/pkg/proto/asset"
|
||
pbCommon "github.com/topfans/backend/pkg/proto/common"
|
||
"github.com/topfans/backend/pkg/models"
|
||
"github.com/topfans/backend/pkg/logger"
|
||
"github.com/topfans/backend/services/assetService/service"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// UploadMaterial 上传素材
|
||
func (p *AssetProvider) UploadMaterial(ctx context.Context, req *pb.UploadMaterialRequest) (*pb.UploadMaterialResponse, error) {
|
||
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
|
||
if err != nil {
|
||
return &pb.UploadMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"},
|
||
}, err
|
||
}
|
||
|
||
material := &models.Material{
|
||
OssKey: req.OssKey,
|
||
OriginalName: req.OriginalName,
|
||
FileSize: req.FileSize,
|
||
MimeType: req.MimeType,
|
||
Hash: req.Hash,
|
||
CreatedBy: userID,
|
||
StarID: starID,
|
||
}
|
||
if req.Width > 0 {
|
||
w := int(req.Width)
|
||
material.Width = &w
|
||
}
|
||
if req.Height > 0 {
|
||
h := int(req.Height)
|
||
material.Height = &h
|
||
}
|
||
|
||
result, err := p.materialService.UploadMaterial(material)
|
||
if err != nil {
|
||
logger.Logger.Error("UploadMaterial failed", zap.Error(err))
|
||
return &pb.UploadMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: err.Error()},
|
||
}, err
|
||
}
|
||
|
||
return &pb.UploadMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"},
|
||
Material: &pb.Material{
|
||
MaterialId: result.ID,
|
||
OssKey: result.OssKey,
|
||
OriginalName: result.OriginalName,
|
||
FileSize: result.FileSize,
|
||
MimeType: result.MimeType,
|
||
Hash: result.Hash,
|
||
CreatedBy: result.CreatedBy,
|
||
StarId: result.StarID,
|
||
CreatedAt: result.CreatedAt,
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// BindAssetMaterials 绑定资产素材
|
||
func (p *AssetProvider) BindAssetMaterials(ctx context.Context, req *pb.BindAssetMaterialsRequest) (*pb.BindAssetMaterialsResponse, error) {
|
||
if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil {
|
||
return &pb.BindAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"},
|
||
}, err
|
||
}
|
||
|
||
items := make([]service.BindMaterialItem, 0, len(req.Materials))
|
||
for _, m := range req.Materials {
|
||
items = append(items, service.BindMaterialItem{
|
||
MaterialID: m.MaterialId,
|
||
MaterialType: m.MaterialType,
|
||
LayerOrder: m.LayerOrder,
|
||
PosX: doublePtr(m.PosX),
|
||
PosY: doublePtr(m.PosY),
|
||
Opacity: doublePtr(m.Opacity),
|
||
Rotation: doublePtr(m.Rotation),
|
||
ScaleX: doublePtr(m.ScaleX),
|
||
ScaleY: doublePtr(m.ScaleY),
|
||
})
|
||
}
|
||
|
||
if _, err := p.materialService.BindMaterials(req.AssetId, items); err != nil {
|
||
logger.Logger.Error("BindAssetMaterials failed", zap.Error(err))
|
||
return &pb.BindAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: err.Error()},
|
||
}, err
|
||
}
|
||
|
||
return &pb.BindAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"},
|
||
}, nil
|
||
}
|
||
|
||
// GetAssetMaterials 获取资产素材列表
|
||
func (p *AssetProvider) GetAssetMaterials(ctx context.Context, req *pb.GetAssetMaterialsRequest) (*pb.GetAssetMaterialsResponse, error) {
|
||
if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil {
|
||
return &pb.GetAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"},
|
||
}, err
|
||
}
|
||
|
||
relations, err := p.materialService.GetAssetMaterials(req.AssetId)
|
||
if err != nil {
|
||
return &pb.GetAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: err.Error()},
|
||
}, err
|
||
}
|
||
|
||
materials := make([]*pb.AssetMaterialRelation, 0, len(relations))
|
||
for _, rel := range relations {
|
||
pbRel := &pb.AssetMaterialRelation{
|
||
RelationId: rel.ID,
|
||
AssetId: rel.AssetID,
|
||
MaterialId: rel.MaterialID,
|
||
MaterialType: rel.MaterialType,
|
||
LayerOrder: int32(rel.LayerOrder),
|
||
}
|
||
// 如果关联的 Material 有 OssKey,生成预签名 URL
|
||
if rel.Material.OssKey != "" {
|
||
signedURL, err := generatePresignedURL(rel.Material.OssKey, 3600)
|
||
if err == nil {
|
||
pbRel.MaterialUrlSigned = signedURL
|
||
}
|
||
}
|
||
if rel.PosX != nil {
|
||
pbRel.PosX = *rel.PosX
|
||
}
|
||
if rel.PosY != nil {
|
||
pbRel.PosY = *rel.PosY
|
||
}
|
||
if rel.Opacity != nil {
|
||
pbRel.Opacity = *rel.Opacity
|
||
}
|
||
if rel.Rotation != nil {
|
||
pbRel.Rotation = *rel.Rotation
|
||
}
|
||
if rel.ScaleX != nil {
|
||
pbRel.ScaleX = *rel.ScaleX
|
||
}
|
||
if rel.ScaleY != nil {
|
||
pbRel.ScaleY = *rel.ScaleY
|
||
}
|
||
materials = append(materials, pbRel)
|
||
}
|
||
|
||
return &pb.GetAssetMaterialsResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"},
|
||
Materials: materials,
|
||
}, nil
|
||
}
|
||
|
||
// UpdateMaterialLayerOrder 更新图层顺序
|
||
func (p *AssetProvider) UpdateMaterialLayerOrder(ctx context.Context, req *pb.UpdateMaterialLayerOrderRequest) (*pb.UpdateMaterialLayerOrderResponse, error) {
|
||
if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil {
|
||
return &pb.UpdateMaterialLayerOrderResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"},
|
||
}, err
|
||
}
|
||
|
||
orders := make(map[int64]int, len(req.Orders))
|
||
for _, o := range req.Orders {
|
||
orders[o.RelationId] = int(o.LayerOrder)
|
||
}
|
||
|
||
if err := p.materialService.UpdateLayerOrder(req.AssetId, orders); err != nil {
|
||
return &pb.UpdateMaterialLayerOrderResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: err.Error()},
|
||
}, err
|
||
}
|
||
|
||
return &pb.UpdateMaterialLayerOrderResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"},
|
||
}, nil
|
||
}
|
||
|
||
// UnbindAssetMaterial 解绑素材
|
||
func (p *AssetProvider) UnbindAssetMaterial(ctx context.Context, req *pb.UnbindAssetMaterialRequest) (*pb.UnbindAssetMaterialResponse, error) {
|
||
if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil {
|
||
return &pb.UnbindAssetMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"},
|
||
}, err
|
||
}
|
||
|
||
if err := p.materialService.UnbindMaterial(req.RelationId); err != nil {
|
||
return &pb.UnbindAssetMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR, Message: err.Error()},
|
||
}, err
|
||
}
|
||
|
||
return &pb.UnbindAssetMaterialResponse{
|
||
Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"},
|
||
}, nil
|
||
}
|
||
|
||
func doublePtr(v float64) *float64 {
|
||
if v == 0 {
|
||
return nil
|
||
}
|
||
return &v
|
||
}
|
||
|
||
// generatePresignedURL 生成预签名 URL
|
||
func generatePresignedURL(filePath string, expiresInSeconds int64) (string, error) {
|
||
region := os.Getenv("OSS_REGION")
|
||
bucketName := os.Getenv("OSS_BUCKET_NAME")
|
||
roleArn := os.Getenv("OSS_STS_ROLE_ARN")
|
||
accessKeyID := os.Getenv("OSS_ACCESS_KEY_ID")
|
||
accessKeySecret := os.Getenv("OSS_ACCESS_KEY_SECRET")
|
||
|
||
if region == "" || bucketName == "" || roleArn == "" || accessKeyID == "" || accessKeySecret == "" {
|
||
return "", fmt.Errorf("OSS 配置不完整")
|
||
}
|
||
|
||
credConfig := new(credentials.Config).
|
||
SetType("ram_role_arn").
|
||
SetAccessKeyId(accessKeyID).
|
||
SetAccessKeySecret(accessKeySecret).
|
||
SetRoleArn(roleArn).
|
||
SetRoleSessionName("topfans-download-session").
|
||
SetPolicy("").
|
||
SetRoleSessionExpiration(int(expiresInSeconds))
|
||
|
||
provider, err := credentials.NewCredential(credConfig)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建凭证提供器失败: %w", err)
|
||
}
|
||
|
||
cred, err := provider.GetCredential()
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取临时凭证失败: %w", err)
|
||
}
|
||
|
||
endpoint := fmt.Sprintf("https://oss-%s.aliyuncs.com", region)
|
||
client, err := oss.New(endpoint, *cred.AccessKeyId, *cred.AccessKeySecret,
|
||
oss.SecurityToken(*cred.SecurityToken))
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建OSS客户端失败: %w", err)
|
||
}
|
||
|
||
bucket, err := client.Bucket(bucketName)
|
||
if err != nil {
|
||
return "", fmt.Errorf("获取Bucket失败: %w", err)
|
||
}
|
||
|
||
ossKey := filePath
|
||
if strings.HasPrefix(filePath, "https://") {
|
||
parts := strings.SplitN(filePath, ".oss-", 2)
|
||
if len(parts) == 2 {
|
||
keyParts := strings.SplitN(parts[1], "/", 2)
|
||
if len(keyParts) == 2 {
|
||
ossKey = keyParts[1]
|
||
}
|
||
}
|
||
}
|
||
|
||
signedURL, err := bucket.SignURL(ossKey, oss.HTTPGet, expiresInSeconds)
|
||
if err != nil {
|
||
return "", fmt.Errorf("生成预签名URL失败: %w", err)
|
||
}
|
||
|
||
if idx := strings.Index(signedURL, "?"); idx >= 0 {
|
||
signedURL = strings.ReplaceAll(signedURL[:idx], "%2F", "/") + signedURL[idx:]
|
||
} else {
|
||
signedURL = strings.ReplaceAll(signedURL, "%2F", "/")
|
||
}
|
||
|
||
if !strings.Contains(signedURL, "security-token") && cred.SecurityToken != nil && *cred.SecurityToken != "" {
|
||
signedURL = signedURL + "&security-token=" + url.QueryEscape(*cred.SecurityToken)
|
||
}
|
||
|
||
return signedURL, nil
|
||
}
|