package grpc import ( "context" "time" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" achievementservice "hyapp/services/activity-service/internal/service/achievement" agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening" cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank" growthservice "hyapp/services/activity-service/internal/service/growth" messageservice "hyapp/services/activity-service/internal/service/message" roomturnoverrewardservice "hyapp/services/activity-service/internal/service/roomturnoverreward" weeklystarservice "hyapp/services/activity-service/internal/service/weeklystar" ) // CronServer exposes activity-service owned background batches to cron-service. type CronServer struct { activityv1.UnimplementedActivityCronServiceServer message *messageservice.Service growth *growthservice.Service achievement *achievementservice.Service weeklyStar *weeklystarservice.Service roomReward *roomturnoverrewardservice.Service agencyOpen *agencyopeningservice.Service cpWeekly *cpweeklyrankservice.Service } // NewCronServer creates the internal cron adapter without exposing message storage. func NewCronServer(messageSvc *messageservice.Service, growthSvc *growthservice.Service, achievementSvc *achievementservice.Service, weeklyStarSvc *weeklystarservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, cpWeeklyRankSvc *cpweeklyrankservice.Service) *CronServer { server := &CronServer{message: messageSvc} server.growth = growthSvc server.achievement = achievementSvc server.weeklyStar = weeklyStarSvc server.roomReward = roomRewardSvc server.agencyOpen = agencyOpeningSvc server.cpWeekly = cpWeeklyRankSvc return server } // ProcessMessageFanoutBatch claims and processes one message fanout page. func (s *CronServer) ProcessMessageFanoutBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.message == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "message service is not configured")) } processed, err := s.message.ProcessNextFanoutJob(ctx, messageservice.FanoutWorkerOptions{ WorkerID: req.GetWorkerId(), BatchSize: int(req.GetBatchSize()), LockTTL: durationFromMillis(req.GetLockTtlMs()), }) if err != nil { return nil, xerr.ToGRPCError(err) } if !processed { return &activityv1.CronBatchResponse{}, nil } return &activityv1.CronBatchResponse{ ClaimedCount: 1, ProcessedCount: 1, SuccessCount: 1, HasMore: true, }, nil } // ProcessLevelRewardBatch claims and grants pending permanent level rewards. func (s *CronServer) ProcessLevelRewardBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.growth == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "growth service is not configured")) } claimed, processed, success, failure, hasMore, err := s.growth.ProcessLevelRewardBatch(ctx, req.GetRunId(), req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs())) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore, }, nil } // ProcessAchievementRewardBatch claims and grants pending achievement badge/resource rewards. func (s *CronServer) ProcessAchievementRewardBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.achievement == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "achievement service is not configured")) } claimed, processed, success, failure, hasMore, err := s.achievement.ProcessAchievementRewardBatch(ctx, req.GetRunId(), req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs())) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore, }, nil } // ProcessWeeklyStarSettlementBatch claims ended weekly-star cycles and grants Top rewards. func (s *CronServer) ProcessWeeklyStarSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.weeklyStar == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "weekly star service is not configured")) } claimed, processed, success, failure, hasMore, err := s.weeklyStar.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs())) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore, }, nil } // ProcessCPWeeklyRankSettlementBatch closes the previous UTC CP week and grants configured Top rewards. func (s *CronServer) ProcessCPWeeklyRankSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.cpWeekly == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp weekly rank service is not configured")) } claimed, processed, success, failure, hasMore, err := s.cpWeekly.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize()) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore, }, nil } // ProcessRoomTurnoverRewardSettlementBatch closes the previous UTC week and grants pending room rewards. func (s *CronServer) ProcessRoomTurnoverRewardSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { // cron-service 只负责调度,activity-service 才拥有上一 UTC 周期认领 settlement 和发奖状态机。 ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.roomReward == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room turnover reward service is not configured")) } result, err := s.roomReward.ProcessSettlementBatch(ctx, int(req.GetBatchSize())) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: result.ClaimedCount, ProcessedCount: result.ProcessedCount, SuccessCount: result.SuccessCount, FailureCount: result.FailureCount, HasMore: result.HasMore, }, nil } // ProcessAgencyOpeningSettlementBatch claims ended agency-opening cycles and grants leaderboard COIN rewards. func (s *CronServer) ProcessAgencyOpeningSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) if s.agencyOpen == nil { return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "agency opening service is not configured")) } claimed, processed, success, failure, hasMore, err := s.agencyOpen.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs())) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.CronBatchResponse{ ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore, }, nil } func durationFromMillis(value int64) time.Duration { if value <= 0 { return 0 } return time.Duration(value) * time.Millisecond }