package grpc import ( "context" userv1 "hyapp.local/api/proto/user/v1" "hyapp/pkg/xerr" cpdomain "hyapp/services/user-service/internal/domain/cp" ) // ListCPApplications 返回当前用户收到或发出的 CP/兄弟/姐妹申请。 func (s *Server) ListCPApplications(ctx context.Context, req *userv1.ListCPApplicationsRequest) (*userv1.ListCPApplicationsResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } // gRPC 层只透传分页、方向和状态,pending 过期固化由 service/repository 统一处理。 applications, total, serverTimeMS, err := s.cpSvc.ListApplications(ctx, req.GetUserId(), req.GetDirection(), req.GetStatus(), req.GetPage(), req.GetPageSize()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &userv1.ListCPApplicationsResponse{ Applications: make([]*userv1.CPApplication, 0, len(applications)), Total: total, ServerTimeMs: serverTimeMS, } for _, application := range applications { resp.Applications = append(resp.Applications, toProtoCPApplication(application)) } return resp, nil } // AcceptCPApplication 同意申请并返回建立后的关系。 func (s *Server) AcceptCPApplication(ctx context.Context, req *userv1.AcceptCPApplicationRequest) (*userv1.AcceptCPApplicationResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } application, relationship, err := s.cpSvc.AcceptApplication(ctx, req.GetUserId(), req.GetApplicationId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.AcceptCPApplicationResponse{ Application: toProtoCPApplication(application), Relationship: toProtoCPRelationship(relationship), }, nil } // RejectCPApplication 拒绝申请;拒绝原因只进入 outbox payload,不改变关系主事实。 func (s *Server) RejectCPApplication(ctx context.Context, req *userv1.RejectCPApplicationRequest) (*userv1.RejectCPApplicationResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } application, err := s.cpSvc.RejectApplication(ctx, req.GetUserId(), req.GetApplicationId(), req.GetReason()) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.RejectCPApplicationResponse{Application: toProtoCPApplication(application)}, nil } // ListCPRelationships 返回用户 active 关系;当前每个用户最多同时拥有一条关系。 func (s *Server) ListCPRelationships(ctx context.Context, req *userv1.ListCPRelationshipsRequest) (*userv1.ListCPRelationshipsResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } relationships, total, err := s.cpSvc.ListRelationships(ctx, req.GetUserId(), req.GetRelationType(), req.GetPage(), req.GetPageSize()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &userv1.ListCPRelationshipsResponse{ Relationships: make([]*userv1.CPRelationship, 0, len(relationships)), Total: total, } for _, relationship := range relationships { resp.Relationships = append(resp.Relationships, toProtoCPRelationship(relationship)) } return resp, nil } // PrepareBreakCPRelationship 创建解除关系占位;gateway 随后按返回费用调用 wallet-service 扣金币。 func (s *Server) PrepareBreakCPRelationship(ctx context.Context, req *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } breakup, relationship, err := s.cpSvc.PrepareBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.PrepareBreakCPRelationshipResponse{ Relationship: toProtoCPRelationship(relationship), CommandId: breakup.CommandID, Status: breakup.Status, BreakupCostCoins: breakup.BreakupCostCoins, WalletTransactionId: breakup.WalletTransactionID, CoinBalanceAfter: breakup.CoinBalanceAfter, }, nil } // ConfirmBreakCPRelationship 在钱包扣费成功后确认解除,关系状态和 outbox 在同一事务提交。 func (s *Server) ConfirmBreakCPRelationship(ctx context.Context, req *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } breakup, relationship, err := s.cpSvc.ConfirmBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId(), req.GetWalletTransactionId(), req.GetPaidCoinAmount(), req.GetCoinBalanceAfter()) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.ConfirmBreakCPRelationshipResponse{ Relationship: toProtoCPRelationship(relationship), BreakupCostCoins: breakup.BreakupCostCoins, WalletTransactionId: breakup.WalletTransactionID, CoinBalanceAfter: breakup.CoinBalanceAfter, }, nil } // CancelBreakCPRelationship 取消还未扣费成功的解除占位,避免钱包失败后关系被其他请求长期阻塞。 func (s *Server) CancelBreakCPRelationship(ctx context.Context, req *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } breakup, err := s.cpSvc.CancelBreakRelationship(ctx, req.GetUserId(), req.GetRelationshipId(), req.GetCommandId(), req.GetReason()) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.CancelBreakCPRelationshipResponse{ CommandId: breakup.CommandID, Status: breakup.Status, }, nil } // ConsumeRoomGiftCPEvent 由 user-service 内部 MQ worker 调用,把 RoomGiftSent 转为 CP 申请或亲密值增长。 func (s *Server) ConsumeRoomGiftCPEvent(ctx context.Context, req *userv1.ConsumeRoomGiftCPEventRequest) (*userv1.ConsumeRoomGiftCPEventResponse, error) { ctx = contextWithApp(ctx, req.GetMeta()) if s.cpSvc == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured")) } event := req.GetEvent() result, err := s.cpSvc.ConsumeGiftEvent(ctx, cpdomain.GiftEvent{ EventID: event.GetEventId(), RoomID: event.GetRoomId(), RoomVersion: event.GetRoomVersion(), OccurredAtMS: event.GetOccurredAtMs(), SenderUserID: event.GetSenderUserId(), TargetUserID: event.GetTargetUserId(), GiftID: event.GetGiftId(), GiftCount: event.GetGiftCount(), GiftValue: event.GetGiftValue(), BillingReceiptID: event.GetBillingReceiptId(), VisibleRegionID: event.GetVisibleRegionId(), CommandID: event.GetCommandId(), GiftTypeCode: event.GetGiftTypeCode(), CPRelationType: event.GetCpRelationType(), GiftName: event.GetGiftName(), GiftIconURL: event.GetGiftIconUrl(), GiftAnimationURL: event.GetGiftAnimationUrl(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &userv1.ConsumeRoomGiftCPEventResponse{ Consumed: result.Consumed, Application: toProtoCPApplication(result.Application), Relationship: toProtoCPRelationship(result.Relationship), }, nil } func toProtoCPApplication(application cpdomain.Application) *userv1.CPApplication { if application.ApplicationID == "" { return nil } return &userv1.CPApplication{ ApplicationId: application.ApplicationID, RelationType: application.RelationType, Status: application.Status, Requester: toProtoCPUserProfile(application.Requester), Target: toProtoCPUserProfile(application.Target), RoomId: application.RoomID, RoomRegionId: application.RoomRegionID, Gift: toProtoCPGiftSnapshot(application.Gift), CreatedAtMs: application.CreatedAtMS, UpdatedAtMs: application.UpdatedAtMS, ExpiresAtMs: application.ExpiresAtMS, DecidedAtMs: application.DecidedAtMS, } } func toProtoCPRelationship(relationship cpdomain.Relationship) *userv1.CPRelationship { if relationship.RelationshipID == "" { return nil } return &userv1.CPRelationship{ RelationshipId: relationship.RelationshipID, RelationType: relationship.RelationType, Status: relationship.Status, Me: toProtoCPUserProfile(relationship.Me), Partner: toProtoCPUserProfile(relationship.Partner), IntimacyValue: relationship.IntimacyValue, Level: relationship.Level, FormedAtMs: relationship.FormedAtMS, UpdatedAtMs: relationship.UpdatedAtMS, CurrentLevelThreshold: relationship.CurrentLevelThreshold, NextLevelThreshold: relationship.NextLevelThreshold, NeededForNextLevel: relationship.NeededForNextLevel, LevelProgressPercent: relationship.LevelProgressPercent, MaxLevel: relationship.MaxLevel, BreakupCostCoins: relationship.BreakupCostCoins, EndedAtMs: relationship.EndedAtMS, } } func toProtoCPUserProfile(profile cpdomain.UserProfile) *userv1.CPUserProfile { if profile.UserID <= 0 { return nil } return &userv1.CPUserProfile{ UserId: profile.UserID, DisplayUserId: profile.DisplayUserID, Username: profile.Username, Avatar: profile.Avatar, } } func toProtoCPGiftSnapshot(gift cpdomain.GiftSnapshot) *userv1.CPGiftSnapshot { if gift.GiftID == "" && gift.GiftName == "" { return nil } return &userv1.CPGiftSnapshot{ GiftId: gift.GiftID, GiftName: gift.GiftName, GiftIconUrl: gift.GiftIconURL, GiftAnimationUrl: gift.GiftAnimationURL, GiftCount: gift.GiftCount, GiftValue: gift.GiftValue, BillingReceiptId: gift.BillingReceiptID, } }