topfans/backend/services/assetService/provider/material_provider.go
2026-05-17 19:03:34 +08:00

285 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}