topfans/backend/services/galleryService/provider/gallery_provider.go
zerosaturation c5bf9df955 feat: 经济系统重构 - 任务服务与画廊服务解耦
- galleryService 独立启动,配置 task-service-url
- 新增 taskService 到 galleryService 的 RPC 调用
- 更新 proto 文件并重新生成代码
- 新增展览唯一约束迁移脚本
- 修复多个 service 的配置和依赖关系

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 23:12:02 +08:00

535 lines
15 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"
"dubbo.apache.org/dubbo-go/v3/common/constant"
appErrors "github.com/topfans/backend/pkg/errors"
"github.com/topfans/backend/pkg/logger"
pbCommon "github.com/topfans/backend/pkg/proto/common"
pb "github.com/topfans/backend/pkg/proto/gallery"
"github.com/topfans/backend/services/galleryService/service"
"go.uber.org/zap"
)
// GalleryProvider 展馆服务Provider实现
// 实现 Triple 协议生成的 GalleryServiceHandler 接口
type GalleryProvider struct {
galleryService service.GalleryService
slotService service.SlotService
exhibitionService service.ExhibitionService
}
// 确保 GalleryProvider 实现了 GalleryServiceHandler 接口
var _ pb.GalleryServiceHandler = (*GalleryProvider)(nil)
// NewGalleryProvider 创建展馆服务Provider实例
func NewGalleryProvider(
galleryService service.GalleryService,
slotService service.SlotService,
exhibitionService service.ExhibitionService,
) *GalleryProvider {
return &GalleryProvider{
galleryService: galleryService,
slotService: slotService,
exhibitionService: exhibitionService,
}
}
// GetMyGallery 获取我的展馆
func (p *GalleryProvider) GetMyGallery(ctx context.Context, req *pb.GetMyGalleryRequest) (*pb.GetMyGalleryResponse, error) {
logger.Logger.Info("Received GetMyGallery request")
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.GetMyGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
galleryData, err := p.galleryService.GetMyGallery(userID, starID)
if err != nil {
logger.Logger.Error("GetMyGallery failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Error(err),
)
return &pb.GetMyGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Debug("GetMyGallery successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int32("slot_total", galleryData.SlotTotal),
)
return &pb.GetMyGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
Data: galleryData,
}, nil
}
// GetUserGallery 获取他人展馆
func (p *GalleryProvider) GetUserGallery(ctx context.Context, req *pb.GetUserGalleryRequest) (*pb.GetUserGalleryResponse, error) {
logger.Logger.Info("Received GetUserGallery request",
zap.Int64("target_uid", req.TargetUid),
)
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.GetUserGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
galleryData, err := p.galleryService.GetUserGallery(userID, starID, req.TargetUid)
if err != nil {
logger.Logger.Error("GetUserGallery failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("target_uid", req.TargetUid),
zap.Error(err),
)
return &pb.GetUserGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Debug("GetUserGallery successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("target_uid", req.TargetUid),
zap.Int32("slot_total", galleryData.SlotTotal),
)
return &pb.GetUserGalleryResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
Data: galleryData,
}, nil
}
// PlaceAsset 在展位展示藏品
func (p *GalleryProvider) PlaceAsset(ctx context.Context, req *pb.PlaceAssetRequest) (*pb.PlaceAssetResponse, error) {
logger.Logger.Info("Received PlaceAsset request",
zap.Int64("asset_id", req.AssetId),
zap.Int64("gallery_owner_id", req.GalleryOwnerId),
zap.Int64("slot_id", req.SlotId),
)
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.PlaceAssetResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
placeData, err := p.exhibitionService.PlaceAsset(userID, starID, req)
if err != nil {
errorMsg := err.Error()
if errorMsg == "" {
errorMsg = "未知错误"
}
logger.Logger.Error("PlaceAsset failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("asset_id", req.AssetId),
zap.Int64("slot_id", req.SlotId),
zap.String("error_message", errorMsg),
zap.Error(err),
)
return &pb.PlaceAssetResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: errorMsg,
Timestamp: 0,
},
}, err
}
logger.Logger.Info("PlaceAsset successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("asset_id", req.AssetId),
zap.Int64("slot_id", req.SlotId),
)
return &pb.PlaceAssetResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
Data: placeData,
}, nil
}
// RemoveFromSlot 从展位移除资产(统一接口,支持展位所有者踢走和占位者下架)
func (p *GalleryProvider) RemoveFromSlot(ctx context.Context, req *pb.RemoveFromSlotRequest) (*pb.RemoveFromSlotResponse, error) {
logger.Logger.Info("Received RemoveFromSlot request",
zap.Int64("slot_id", req.SlotId),
)
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.RemoveFromSlotResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "用户认证失败",
Timestamp: 0,
},
}, err
}
// 调用Service层
err = p.exhibitionService.RemoveFromSlot(userID, starID, req)
if err != nil {
logger.Logger.Error("RemoveFromSlot failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("slot_id", req.SlotId),
zap.Error(err),
)
return &pb.RemoveFromSlotResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Info("RemoveFromSlot successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("slot_id", req.SlotId),
)
return &pb.RemoveFromSlotResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "移除成功",
Timestamp: 0,
},
}, nil
}
// UnlockSlot 解锁/购买新展位
func (p *GalleryProvider) UnlockSlot(ctx context.Context, req *pb.UnlockSlotRequest) (*pb.UnlockSlotResponse, error) {
logger.Logger.Info("Received UnlockSlot request")
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.UnlockSlotResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
unlockData, err := p.slotService.UnlockSlot(userID, starID)
if err != nil {
logger.Logger.Error("UnlockSlot failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Error(err),
)
return &pb.UnlockSlotResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Info("UnlockSlot successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int32("slot_total", unlockData.SlotTotal),
)
return &pb.UnlockSlotResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
Data: unlockData,
}, nil
}
// GetMyExhibitedAssets 获取我展出的作品列表
func (p *GalleryProvider) GetMyExhibitedAssets(ctx context.Context, req *pb.GetMyExhibitedAssetsRequest) (*pb.GetMyExhibitedAssetsResponse, error) {
logger.Logger.Info("Received GetMyExhibitedAssets request")
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.GetMyExhibitedAssetsResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
return p.exhibitionService.GetMyExhibitedAssets(ctx, userID, starID, req)
}
// GetInspirationFlow 获取灵感瀑布藏品列表
func (p *GalleryProvider) GetInspirationFlow(ctx context.Context, req *pb.GetInspirationFlowRequest) (*pb.GetInspirationFlowResponse, error) {
logger.Logger.Info("Received GetInspirationFlow request",
zap.String("cursor", req.Cursor),
zap.String("direction", req.Direction),
zap.Int32("limit", req.Limit),
zap.String("type", req.Type),
)
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.GetInspirationFlowResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
data, _, err := p.galleryService.GetInspirationFlow(userID, starID, req.Cursor, req.Direction, req.Limit, req.Type, req.SessionId)
if err != nil {
logger.Logger.Error("GetInspirationFlow failed",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.String("direction", req.Direction),
zap.Error(err),
)
return &pb.GetInspirationFlowResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Info("GetInspirationFlow successful",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int("item_count", len(data.Items)),
)
return &pb.GetInspirationFlowResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
Data: data,
}, nil
}
// GetUserExhibitedAssets 获取他人展出的作品列表
func (p *GalleryProvider) GetUserExhibitedAssets(ctx context.Context, req *pb.GetUserExhibitedAssetsRequest) (*pb.GetUserExhibitedAssetsResponse, error) {
logger.Logger.Info("Received GetUserExhibitedAssets request",
zap.Int64("target_user_id", req.UserId),
)
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
if err != nil {
logger.Logger.Error("Failed to extract user info from attachments",
zap.Error(err),
)
return &pb.GetUserExhibitedAssetsResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
Message: "user authentication required",
Timestamp: 0,
},
}, err
}
// 调用Service层
return p.exhibitionService.GetUserExhibitedAssets(ctx, userID, starID, req)
}
// RemoveExhibitionByAsset 根据资产ID移除展品内部RPC用于领取收益后下架
func (p *GalleryProvider) RemoveExhibitionByAsset(ctx context.Context, req *pb.RemoveExhibitionByAssetRequest) (*pb.RemoveExhibitionByAssetResponse, error) {
logger.Logger.Info("Received RemoveExhibitionByAsset request",
zap.Int64("asset_id", req.AssetId),
)
// 调用Service层
err := p.exhibitionService.RemoveExhibitionByAsset(ctx, req.AssetId)
if err != nil {
logger.Logger.Error("RemoveExhibitionByAsset failed",
zap.Int64("asset_id", req.AssetId),
zap.Error(err),
)
return &pb.RemoveExhibitionByAssetResponse{
Base: &pbCommon.BaseResponse{
Code: appErrors.ToStatusCode(err),
Message: err.Error(),
Timestamp: 0,
},
}, err
}
logger.Logger.Info("RemoveExhibitionByAsset successful",
zap.Int64("asset_id", req.AssetId),
)
return &pb.RemoveExhibitionByAssetResponse{
Base: &pbCommon.BaseResponse{
Code: pbCommon.StatusCode_STATUS_OK,
Message: "success",
Timestamp: 0,
},
}, nil
}
// ==================== 辅助函数 ====================
// extractUserInfoFromDubboAttachments 从Dubbo attachments提取用户信息
func extractUserInfoFromDubboAttachments(ctx context.Context) (int64, int64, error) {
logger.Logger.Debug("Extracting user info from Dubbo attachments")
// 使用正确的 constant.AttachmentKey 获取 Dubbo attachments
if attachments := ctx.Value(constant.AttachmentKey); attachments != nil {
logger.Logger.Debug("Found attachments in context",
zap.Any("attachments", attachments),
)
if attMap, ok := attachments.(map[string]interface{}); ok {
userID, starID := extractUserInfoFromMap(attMap)
if userID > 0 && starID > 0 {
logger.Logger.Debug("Successfully extracted user info",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
)
return userID, starID, nil
}
logger.Logger.Warn("Extracted zero user_id or star_id",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
)
} else {
logger.Logger.Warn("Attachments is not a map[string]interface{}",
zap.String("type", fmt.Sprintf("%T", attachments)),
)
}
} else {
logger.Logger.Warn("No attachments found in context")
}
return 0, 0, fmt.Errorf("failed to extract user info from Dubbo attachments")
}
// extractUserInfoFromMap 从map中提取用户信息
func extractUserInfoFromMap(attMap map[string]interface{}) (int64, int64) {
var userID, starID int64
// 提取 user_id
if v, ok := attMap["user_id"]; ok {
userID = parseIntValue(v)
}
// 提取 star_id
if v, ok := attMap["star_id"]; ok {
starID = parseIntValue(v)
}
return userID, starID
}
// parseIntValue 解析各种类型的值为 int64
func parseIntValue(v interface{}) int64 {
switch val := v.(type) {
case int64:
return val
case int:
return int64(val)
case float64:
return int64(val)
case string:
var result int64
fmt.Sscanf(val, "%d", &result)
return result
case []string:
if len(val) > 0 {
var result int64
fmt.Sscanf(val[0], "%d", &result)
return result
}
}
return 0
}