package provider import ( "context" "fmt" "time" "dubbo.apache.org/dubbo-go/v3/common/constant" appErrors "github.com/topfans/backend/pkg/errors" "github.com/topfans/backend/pkg/logger" "github.com/topfans/backend/pkg/models" pb "github.com/topfans/backend/pkg/proto/asset" pbCommon "github.com/topfans/backend/pkg/proto/common" "github.com/topfans/backend/services/assetService/service" "go.uber.org/zap" ) // AssetProvider 资产服务Provider实现 // 实现 Triple 协议生成的 AssetServiceHandler 接口 type AssetProvider struct { assetService service.AssetService mintService service.MintService assetLikeService *service.AssetLikeService } // 确保 AssetProvider 实现了 AssetServiceHandler 接口 var _ pb.AssetServiceHandler = (*AssetProvider)(nil) // NewAssetProvider 创建资产服务Provider实例 func NewAssetProvider(assetService service.AssetService, mintService service.MintService, assetLikeService *service.AssetLikeService) *AssetProvider { return &AssetProvider{ assetService: assetService, mintService: mintService, assetLikeService: assetLikeService, } } // InitMintOrder 阶段一:初始化订单(仅落库 order_id) func (p *AssetProvider) InitMintOrder(ctx context.Context, req *pb.InitMintOrderRequest) (*pb.InitMintOrderResponse, error) { userID, starID, err := extractUserInfoFromDubboAttachments(ctx) if err != nil { return &pb.InitMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } resp, err := p.mintService.InitMintOrder(req.OrderId, userID, starID) if err != nil { if resp == nil { resp = &pb.InitMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } return resp, nil } // PreCreateMintOrder 阶段一:预创建铸造订单(生成 order_id) func (p *AssetProvider) PreCreateMintOrder(ctx context.Context, req *pb.PreCreateMintOrderRequest) (*pb.PreCreateMintOrderResponse, error) { logger.Logger.Info("Received PreCreateMintOrder request", zap.String("material_url", req.MaterialUrl), ) userID, starID, err := extractUserInfoFromDubboAttachments(ctx) if err != nil { return &pb.PreCreateMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } resp, err := p.mintService.PreCreateMintOrder(req, userID, starID) if err != nil { logger.Logger.Error("PreCreateMintOrder failed", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) if resp == nil { resp = &pb.PreCreateMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Info("PreCreateMintOrder successful", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("order_id", resp.Order.OrderId), ) return resp, nil } // CreateMintOrder 创建铸造订单 func (p *AssetProvider) CreateMintOrder(ctx context.Context, req *pb.CreateMintOrderRequest) (*pb.CreateMintOrderResponse, error) { // 记录请求日志 logger.Logger.Info("Received CreateMintOrder request", zap.String("name", req.Name), ) // 从 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.CreateMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 resp, err := p.mintService.CreateMintOrder(req, userID, starID) if err != nil { logger.Logger.Error("CreateMintOrder failed", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.CreateMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Info("CreateMintOrder successful", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("order_id", resp.Order.OrderId), ) return resp, nil } // GetMyAssets 获取我的藏品列表 func (p *AssetProvider) GetMyAssets(ctx context.Context, req *pb.GetMyAssetsRequest) (*pb.GetMyAssetsResponse, error) { // 记录请求日志 logger.Logger.Info("Received GetMyAssets request", zap.Int32("page", req.Page), zap.Int32("page_size", req.PageSize), ) // 从 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.GetMyAssetsResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 resp, err := p.assetService.GetMyAssets(req, userID, starID) if err != nil { logger.Logger.Error("GetMyAssets failed", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.GetMyAssetsResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Debug("GetMyAssets successful", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int("count", len(resp.Data.Groups)), ) return resp, nil } // GetAsset 获取资产详情 func (p *AssetProvider) GetAsset(ctx context.Context, req *pb.GetAssetRequest) (*pb.GetAssetResponse, error) { // 记录请求日志 logger.Logger.Info("Received GetAsset request", zap.Int64("asset_id", req.AssetId), ) // 从 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.GetAssetResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 resp, err := p.assetService.GetAsset(req, userID, starID) if err != nil { logger.Logger.Error("GetAsset failed", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("asset_id", req.AssetId), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.GetAssetResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Debug("GetAsset successful", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), ) return resp, nil } // GetAssetStatus 查询上链状态 func (p *AssetProvider) GetAssetStatus(ctx context.Context, req *pb.GetAssetStatusRequest) (*pb.GetAssetStatusResponse, error) { // 记录请求日志 logger.Logger.Info("Received GetAssetStatus request", zap.Int64("asset_id", req.AssetId), ) // 从 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.GetAssetStatusResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 resp, err := p.assetService.GetAssetStatus(req, userID, starID) if err != nil { logger.Logger.Error("GetAssetStatus failed", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("asset_id", req.AssetId), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.GetAssetStatusResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Debug("GetAssetStatus successful", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), ) return resp, nil } // GetAssetForRPC 获取资产信息(内部RPC调用) func (p *AssetProvider) GetAssetForRPC(ctx context.Context, req *pb.GetAssetForRPCRequest) (*pb.GetAssetForRPCResponse, error) { logger.Logger.Info("Received GetAssetForRPC request", zap.Int64("asset_id", req.AssetId), ) // 调用Service层 resp, err := p.assetService.GetAssetForRPC(req) if err != nil { logger.Logger.Error("GetAssetForRPC failed", zap.Int64("asset_id", req.AssetId), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.GetAssetForRPCResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Info("GetAssetForRPC successful", zap.Int64("asset_id", req.AssetId), ) return resp, nil } // LikeAsset 点赞资产(内部RPC调用) func (p *AssetProvider) LikeAsset(ctx context.Context, req *pb.LikeAssetRequest) (*pb.LikeAssetResponse, error) { logger.Logger.Info("Received LikeAsset request", zap.Int64("asset_id", req.AssetId), ) // 从 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.LikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 likeCount, err := p.assetLikeService.LikeAsset(ctx, req.AssetId, userID, starID) if err != nil { logger.Logger.Error("LikeAsset failed", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Error(err), ) return &pb.LikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, }, err } logger.Logger.Info("LikeAsset successful", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Int32("like_count", likeCount), ) return &pb.LikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "Asset liked successfully", Timestamp: 0, }, LikeCount: likeCount, }, nil } // UnlikeAsset 取消点赞资产(内部RPC调用) func (p *AssetProvider) UnlikeAsset(ctx context.Context, req *pb.UnlikeAssetRequest) (*pb.UnlikeAssetResponse, error) { logger.Logger.Info("Received UnlikeAsset request", zap.Int64("asset_id", req.AssetId), ) // 从 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.UnlikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 likeCount, err := p.assetLikeService.UnlikeAsset(ctx, req.AssetId, userID, starID) if err != nil { logger.Logger.Error("UnlikeAsset failed", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Error(err), ) return &pb.UnlikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, }, err } logger.Logger.Info("UnlikeAsset successful", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Int32("like_count", likeCount), ) return &pb.UnlikeAssetResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "Asset unliked successfully", Timestamp: 0, }, LikeCount: likeCount, }, nil } // CheckAssetLike 检查是否已点赞(内部RPC调用) func (p *AssetProvider) CheckAssetLike(ctx context.Context, req *pb.CheckAssetLikeRequest) (*pb.CheckAssetLikeResponse, error) { logger.Logger.Info("Received CheckAssetLike request", zap.Int64("asset_id", req.AssetId), ) // 从 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.CheckAssetLikeResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 isLiked, err := p.assetLikeService.CheckAssetLike(ctx, req.AssetId, userID, starID) if err != nil { logger.Logger.Error("CheckAssetLike failed", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Error(err), ) return &pb.CheckAssetLikeResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, }, err } logger.Logger.Info("CheckAssetLike successful", zap.Int64("user_id", userID), zap.Int64("asset_id", req.AssetId), zap.Bool("is_liked", isLiked), ) return &pb.CheckAssetLikeResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "Success", Timestamp: 0, }, IsLiked: isLiked, }, nil } // GetAssetLikes 获取资产点赞列表(内部RPC调用) func (p *AssetProvider) GetAssetLikes(ctx context.Context, req *pb.GetAssetLikesRequest) (*pb.GetAssetLikesResponse, error) { logger.Logger.Info("Received GetAssetLikes request", zap.Int64("asset_id", req.AssetId), zap.Int32("page", req.Page), zap.Int32("page_size", req.PageSize), ) // 调用Service层 likes, total, err := p.assetLikeService.GetAssetLikes(ctx, req.AssetId, req.Page, req.PageSize) if err != nil { logger.Logger.Error("GetAssetLikes failed", zap.Int64("asset_id", req.AssetId), zap.Error(err), ) return &pb.GetAssetLikesResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, }, err } // 转换为 pb.AssetLike pbLikes := make([]*pb.AssetLike, 0, len(likes)) for _, like := range likes { pbLikes = append(pbLikes, &pb.AssetLike{ Id: like.ID, AssetId: like.AssetID, UserId: like.UserID, StarId: like.StarID, CreatedAt: like.CreatedAt, }) } // 计算分页信息 pageSize := req.PageSize if pageSize <= 0 { pageSize = 20 } hasMore := total > int64(req.Page*pageSize) logger.Logger.Info("GetAssetLikes successful", zap.Int64("asset_id", req.AssetId), zap.Int("count", len(pbLikes)), zap.Int64("total", total), ) return &pb.GetAssetLikesResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "Success", Timestamp: 0, }, Likes: pbLikes, Total: total, Page: req.Page, PageSize: pageSize, HasMore: hasMore, }, nil } // extractUserInfoFromDubboAttachments 从 Dubbo attachments 中提取用户信息 func extractUserInfoFromDubboAttachments(ctx context.Context) (int64, int64, error) { logger.Logger.Info("Extracting user info from Dubbo attachments") // 使用正确的 constant.AttachmentKey 获取 Dubbo attachments if attachments := ctx.Value(constant.AttachmentKey); attachments != nil { logger.Logger.Info("Found attachments in context", zap.Any("attachments", attachments), zap.String("key", fmt.Sprintf("%v", constant.AttachmentKey)), ) if attMap, ok := attachments.(map[string]interface{}); ok { logger.Logger.Info("Attachments is a map", zap.Any("map", attMap)) userID, starID := extractUserInfoFromMap(attMap) if userID > 0 && starID > 0 { logger.Logger.Info("Successfully extracted user info", zap.Int64("user_id", userID), zap.Int64("star_id", starID), ) return userID, starID, nil } else { 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", zap.String("key", fmt.Sprintf("%v", constant.AttachmentKey)), ) } logger.Logger.Error("Failed to extract user info from Dubbo attachments") return 0, 0, fmt.Errorf("user info not found in Dubbo attachments (expected user_id and star_id from gateway)") } // extractUserInfoFromMap 从 map 中提取 user_id 和 star_id 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 } case []interface{}: if len(val) > 0 { return parseIntValue(val[0]) } } return 0 } // ClearAssetLikeRecords 清除资产点赞记录(内部RPC调用,供Gallery Service在下架时调用) func (p *AssetProvider) ClearAssetLikeRecords(ctx context.Context, req *pb.ClearAssetLikeRecordsRequest) (*pb.ClearAssetLikeRecordsResponse, error) { logger.Logger.Info("Received ClearAssetLikeRecords request", zap.Int64("asset_id", req.AssetId), ) if err := p.assetLikeService.ClearAssetLikeRecords(ctx, req.AssetId); err != nil { logger.Logger.Error("ClearAssetLikeRecords failed", zap.Int64("asset_id", req.AssetId), zap.Error(err), ) return &pb.ClearAssetLikeRecordsResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), }, }, err } logger.Logger.Info("ClearAssetLikeRecords successful", zap.Int64("asset_id", req.AssetId), ) return &pb.ClearAssetLikeRecordsResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "点赞记录已清除", }, }, nil } // GetMintOrder 查询铸造订单状态 func (p *AssetProvider) GetMintOrder(ctx context.Context, req *pb.GetMintOrderRequest) (*pb.GetMintOrderResponse, error) { logger.Logger.Info("Received GetMintOrder request", zap.String("order_id", req.OrderId), ) // 从 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.GetMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "user authentication required", Timestamp: 0, }, }, err } // 调用Service层 resp, err := p.mintService.GetMintOrder(req.OrderId, userID, starID) if err != nil { logger.Logger.Error("GetMintOrder failed", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) // 如果响应为空,构建错误响应 if resp == nil { resp = &pb.GetMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: appErrors.ToStatusCode(err), Message: err.Error(), Timestamp: 0, }, } } return resp, err } logger.Logger.Info("GetMintOrder successful", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.String("status", resp.Order.Status), ) return resp, nil } // CancelMintOrder 取消铸造订单 func (p *AssetProvider) CancelMintOrder(ctx context.Context, req *pb.CancelMintOrderRequest) (*pb.CancelMintOrderResponse, error) { // 记录请求日志 logger.Logger.Info("Received CancelMintOrder request", zap.String("order_id", req.OrderId), ) // 从 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.CancelMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "未授权", Timestamp: time.Now().UnixMilli(), }, }, err } // 调用服务层取消订单 err = p.mintService.CancelMintOrder(req.OrderId, userID, starID) // 构建响应 resp := &pb.CancelMintOrderResponse{ Base: &pbCommon.BaseResponse{ Code: pbCommon.StatusCode_STATUS_OK, Message: "", Timestamp: time.Now().UnixMilli(), }, OrderId: req.OrderId, Status: models.MintOrderStatusCancelled, } if err != nil { logger.Logger.Error("CancelMintOrder failed", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err), ) resp.Base.Code = appErrors.ToStatusCode(err) resp.Base.Message = err.Error() return resp, err } logger.Logger.Debug("CancelMintOrder successful", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.Int64("star_id", starID), ) return resp, nil }