package grpc import ( "context" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/growth" service "hyapp/services/activity-service/internal/service/growth" ) // GrowthLevelServer 把 App 等级查询和内部等级事件消费适配为 gRPC。 type GrowthLevelServer struct { activityv1.UnimplementedGrowthLevelServiceServer svc *service.Service } func NewGrowthLevelServer(svc *service.Service) *GrowthLevelServer { return &GrowthLevelServer{svc: svc} } func (s *GrowthLevelServer) GetMyLevelOverview(ctx context.Context, req *activityv1.GetMyLevelOverviewRequest) (*activityv1.GetMyLevelOverviewResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) overview, err := s.svc.GetMyLevelOverview(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.GetMyLevelOverviewResponse{ Tracks: make([]*activityv1.LevelTrackOverview, 0, len(overview.Tracks)), ServerTimeMs: overview.ServerTimeMS, } for _, item := range overview.Tracks { resp.Tracks = append(resp.Tracks, levelTrackOverviewToProto(item)) } return resp, nil } func (s *GrowthLevelServer) GetLevelTrack(ctx context.Context, req *activityv1.GetLevelTrackRequest) (*activityv1.GetLevelTrackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) detail, err := s.svc.GetLevelTrack(ctx, req.GetUserId(), req.GetTrack()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.GetLevelTrackResponse{ Overview: levelTrackOverviewToProto(detail.Overview), Rules: make([]*activityv1.LevelRule, 0, len(detail.Rules)), Tiers: make([]*activityv1.LevelTier, 0, len(detail.Tiers)), ServerTimeMs: detail.ServerTimeMS, } for _, rule := range detail.Rules { resp.Rules = append(resp.Rules, levelRuleToProto(rule)) } for _, tier := range detail.Tiers { resp.Tiers = append(resp.Tiers, levelTierToProto(tier)) } return resp, nil } func (s *GrowthLevelServer) BatchGetUserLevelDisplayProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) profiles, err := s.svc.BatchGetUserLevelDisplayProfiles(ctx, req.GetUserIds()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.BatchGetUserLevelDisplayProfilesResponse{ Profiles: make([]*activityv1.UserLevelDisplayProfile, 0, len(profiles.Profiles)), ServerTimeMs: profiles.ServerTimeMS, } for _, item := range profiles.Profiles { resp.Profiles = append(resp.Profiles, userLevelDisplayProfileToProto(item)) } return resp, nil } func (s *GrowthLevelServer) ListLevelRewards(ctx context.Context, req *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) rewards, total, err := s.svc.ListLevelRewards(ctx, domain.RewardQuery{ UserID: req.GetUserId(), Track: req.GetTrack(), Status: req.GetStatus(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.ListLevelRewardsResponse{Rewards: make([]*activityv1.LevelRewardJob, 0, len(rewards)), Total: total} for _, reward := range rewards { resp.Rewards = append(resp.Rewards, levelRewardToProto(reward)) } return resp, nil } func (s *GrowthLevelServer) ConsumeLevelEvent(ctx context.Context, req *activityv1.ConsumeLevelEventRequest) (*activityv1.ConsumeLevelEventResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.ConsumeLevelEvent(ctx, domain.ValueEvent{ EventID: req.GetEventId(), SourceEventID: req.GetSourceEventId(), SourceService: req.GetSourceService(), SourceEventType: req.GetSourceEventType(), UserID: req.GetUserId(), Track: req.GetTrack(), MetricType: req.GetMetricType(), ValueDelta: req.GetValueDelta(), OccurredAtMS: req.GetOccurredAtMs(), DimensionsJSON: req.GetDimensionsJson(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.ConsumeLevelEventResponse{ EventId: result.EventID, Status: result.Status, LevelChanged: result.LevelChanged, PreviousLevel: result.PreviousLevel, NewLevel: result.NewLevel, RewardJobCount: result.RewardJobCount, }, nil } func (s *GrowthLevelServer) IssueRegistrationLevelBadges(ctx context.Context, req *activityv1.IssueRegistrationLevelBadgesRequest) (*activityv1.IssueRegistrationLevelBadgesResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) grants, err := s.svc.IssueRegistrationLevelBadges(ctx, req.GetUserId(), req.GetCommandId()) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.IssueRegistrationLevelBadgesResponse{ Grants: make([]*activityv1.RegistrationLevelBadgeGrant, 0, len(grants.Grants)), ServerTimeMs: grants.ServerTimeMS, } for _, item := range grants.Grants { resp.Grants = append(resp.Grants, registrationLevelBadgeGrantToProto(item)) } return resp, nil } // AdminGrowthLevelServer 暴露后台等级配置入口,业务约束仍由 service/repository 校验。 type AdminGrowthLevelServer struct { activityv1.UnimplementedAdminGrowthLevelServiceServer svc *service.Service } func NewAdminGrowthLevelServer(svc *service.Service) *AdminGrowthLevelServer { return &AdminGrowthLevelServer{svc: svc} } func (s *AdminGrowthLevelServer) ListLevelConfig(ctx context.Context, req *activityv1.ListLevelConfigRequest) (*activityv1.ListLevelConfigResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) config, err := s.svc.ListLevelConfig(ctx, domain.ConfigQuery{ Track: req.GetTrack(), Status: req.GetStatus(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.ListLevelConfigResponse{ Tracks: make([]*activityv1.LevelTrack, 0, len(config.Tracks)), Rules: make([]*activityv1.LevelRule, 0, len(config.Rules)), Tiers: make([]*activityv1.LevelTier, 0, len(config.Tiers)), ServerTimeMs: config.ServerTimeMS, } for _, track := range config.Tracks { resp.Tracks = append(resp.Tracks, levelTrackToProto(track)) } for _, rule := range config.Rules { resp.Rules = append(resp.Rules, levelRuleToProto(rule)) } for _, tier := range config.Tiers { resp.Tiers = append(resp.Tiers, levelTierToProto(tier)) } return resp, nil } func (s *AdminGrowthLevelServer) UpsertLevelTrack(ctx context.Context, req *activityv1.UpsertLevelTrackRequest) (*activityv1.UpsertLevelTrackResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertLevelTrack(ctx, domain.TrackCommand{ Track: req.GetTrack(), Name: req.GetName(), Status: req.GetStatus(), SortOrder: req.GetSortOrder(), DisplayConfigJSON: req.GetDisplayConfigJson(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.UpsertLevelTrackResponse{Track: levelTrackToProto(item), Created: created}, nil } func (s *AdminGrowthLevelServer) UpsertLevelRule(ctx context.Context, req *activityv1.UpsertLevelRuleRequest) (*activityv1.UpsertLevelRuleResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertLevelRule(ctx, domain.RuleCommand{ Track: req.GetTrack(), Level: req.GetLevel(), RequiredValue: req.GetRequiredValue(), Name: req.GetName(), Status: req.GetStatus(), RewardResourceGroupID: req.GetRewardResourceGroupId(), SortOrder: req.GetSortOrder(), DisplayConfigJSON: req.GetDisplayConfigJson(), OperatorAdminID: req.GetOperatorAdminId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.UpsertLevelRuleResponse{Rule: levelRuleToProto(item), Created: created}, nil } func (s *AdminGrowthLevelServer) UpsertLevelTier(ctx context.Context, req *activityv1.UpsertLevelTierRequest) (*activityv1.UpsertLevelTierResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertLevelTier(ctx, domain.TierCommand{ TierID: req.GetTierId(), Track: req.GetTrack(), MinLevel: req.GetMinLevel(), MaxLevel: req.GetMaxLevel(), Name: req.GetName(), DisplayAvatarFrameResourceID: req.GetDisplayAvatarFrameResourceId(), DisplayBadgeResourceID: req.GetDisplayBadgeResourceId(), RewardResourceGroupID: req.GetRewardResourceGroupId(), Status: req.GetStatus(), DisplayConfigJSON: req.GetDisplayConfigJson(), OperatorAdminID: req.GetOperatorAdminId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.UpsertLevelTierResponse{Tier: levelTierToProto(item), Created: created}, nil } func levelTrackToProto(item domain.Track) *activityv1.LevelTrack { return &activityv1.LevelTrack{ Track: item.Track, Name: item.Name, Status: item.Status, SortOrder: item.SortOrder, DisplayConfigJson: item.DisplayConfigJSON, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func levelRuleToProto(item domain.Rule) *activityv1.LevelRule { return &activityv1.LevelRule{ Track: item.Track, Level: item.Level, RequiredValue: item.RequiredValue, Name: item.Name, Status: item.Status, RewardResourceGroupId: item.RewardResourceGroupID, SortOrder: item.SortOrder, DisplayConfigJson: item.DisplayConfigJSON, CreatedByAdminId: item.CreatedByAdminID, UpdatedByAdminId: item.UpdatedByAdminID, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func levelTierToProto(item domain.Tier) *activityv1.LevelTier { return &activityv1.LevelTier{ TierId: item.TierID, Track: item.Track, MinLevel: item.MinLevel, MaxLevel: item.MaxLevel, Name: item.Name, DisplayAvatarFrameResourceId: item.DisplayAvatarFrameResourceID, DisplayBadgeResourceId: item.DisplayBadgeResourceID, RewardResourceGroupId: item.RewardResourceGroupID, Status: item.Status, DisplayConfigJson: item.DisplayConfigJSON, CreatedByAdminId: item.CreatedByAdminID, UpdatedByAdminId: item.UpdatedByAdminID, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func levelTrackOverviewToProto(item domain.TrackOverview) *activityv1.LevelTrackOverview { return &activityv1.LevelTrackOverview{ Track: item.Track, Name: item.Name, Level: item.Level, TierId: item.TierID, TotalValue: item.TotalValue, CurrentLevelRequiredValue: item.CurrentLevelRequiredValue, NextLevel: item.NextLevel, NextLevelRequiredValue: item.NextLevelRequiredValue, DisplayAvatarFrameResourceId: item.DisplayAvatarFrameResourceID, DisplayBadgeResourceId: item.DisplayBadgeResourceID, DisplayBadgeSourceLevel: item.DisplayBadgeSourceLevel, RewardPendingCount: item.RewardPendingCount, SortOrder: item.SortOrder, } } func userLevelDisplayProfileToProto(item domain.UserLevelDisplayProfile) *activityv1.UserLevelDisplayProfile { return &activityv1.UserLevelDisplayProfile{ UserId: item.UserID, Wealth: levelDisplayTrackProfileToProto(item.Wealth), Game: levelDisplayTrackProfileToProto(item.Game), Charm: levelDisplayTrackProfileToProto(item.Charm), } } func levelDisplayTrackProfileToProto(item domain.LevelDisplayTrackProfile) *activityv1.LevelDisplayTrackProfile { return &activityv1.LevelDisplayTrackProfile{ Track: item.Track, Level: item.Level, TierId: item.TierID, AvatarFrameResourceId: item.AvatarFrameResourceID, BadgeResourceId: item.BadgeResourceID, BadgeSourceLevel: item.BadgeSourceLevel, UpdatedAtMs: item.UpdatedAtMS, } } func levelRewardToProto(item domain.RewardJob) *activityv1.LevelRewardJob { return &activityv1.LevelRewardJob{ RewardJobId: item.RewardJobID, UserId: item.UserID, Track: item.Track, RewardSourceType: item.RewardSourceType, RewardSourceId: item.RewardSourceID, ResourceGroupId: item.ResourceGroupID, WalletCommandId: item.WalletCommandID, WalletGrantId: item.WalletGrantID, Status: item.Status, AttemptCount: item.AttemptCount, FailureReason: item.FailureReason, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } } func registrationLevelBadgeGrantToProto(item domain.RegistrationLevelBadgeGrant) *activityv1.RegistrationLevelBadgeGrant { return &activityv1.RegistrationLevelBadgeGrant{ Track: item.Track, Level: item.Level, ResourceId: item.ResourceID, GrantId: item.GrantID, } }