- galleryService 独立启动,配置 task-service-url - 新增 taskService 到 galleryService 的 RPC 调用 - 更新 proto 文件并重新生成代码 - 新增展览唯一约束迁移脚本 - 修复多个 service 的配置和依赖关系 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
535 lines
15 KiB
Go
535 lines
15 KiB
Go
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
|
||
}
|