2026-06-08 19:00:23 +08:00

370 lines
14 KiB
Go

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) SetUserLevel(ctx context.Context, req *activityv1.SetUserLevelRequest) (*activityv1.SetUserLevelResponse, error) {
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
result, err := s.svc.SetUserLevel(ctx, domain.SetUserLevelCommand{
CommandID: req.GetCommandId(),
UserID: req.GetUserId(),
Track: req.GetTrack(),
Level: req.GetLevel(),
OperatorUserID: req.GetOperatorUserId(),
Reason: req.GetReason(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &activityv1.SetUserLevelResponse{
EventId: result.EventID,
Status: result.Status,
Track: result.Track,
PreviousLevel: result.PreviousLevel,
NewLevel: result.NewLevel,
TotalValue: result.TotalValue,
RewardJobCount: result.RewardJobCount,
ServerTimeMs: result.ServerTimeMS,
}, 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,
}
}