package grpc import ( "context" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/pkg/appcode" "hyapp/pkg/xerr" taskdomain "hyapp/services/activity-service/internal/domain/task" taskservice "hyapp/services/activity-service/internal/service/task" ) // TaskServer 把任务查询、事件消费和领奖用例适配为 activity-service gRPC。 type TaskServer struct { activityv1.UnimplementedTaskServiceServer svc *taskservice.Service } // NewTaskServer 创建 App 任务服务 gRPC adapter。 func NewTaskServer(svc *taskservice.Service) *TaskServer { return &TaskServer{svc: svc} } // ListUserTasks 返回当前用户 daily/exclusive 任务页。 func (s *TaskServer) ListUserTasks(ctx context.Context, req *activityv1.ListUserTasksRequest) (*activityv1.ListUserTasksResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.ListUserTasks(ctx, req.GetUserId()) if err != nil { return nil, xerr.ToGRPCError(err) } return taskListToProto(result), nil } // ClaimTaskReward 领取已完成任务奖励,金币入账仍由 wallet-service 完成。 func (s *TaskServer) ClaimTaskReward(ctx context.Context, req *activityv1.ClaimTaskRewardRequest) (*activityv1.ClaimTaskRewardResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) claim, err := s.svc.ClaimTaskReward(ctx, taskdomain.ClaimCommand{ UserID: req.GetUserId(), TaskID: req.GetTaskId(), TaskType: req.GetTaskType(), CycleKey: req.GetTaskDay(), CommandID: req.GetCommandId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return claimToProto(claim), nil } // ConsumeTaskEvent 消费由 outbox worker 投递的服务端事实事件。 func (s *TaskServer) ConsumeTaskEvent(ctx context.Context, req *activityv1.ConsumeTaskEventRequest) (*activityv1.ConsumeTaskEventResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) result, err := s.svc.ConsumeTaskEvent(ctx, taskdomain.Event{ EventID: req.GetEventId(), EventType: req.GetEventType(), SourceService: req.GetSourceService(), UserID: req.GetUserId(), MetricType: req.GetMetricType(), Value: req.GetValue(), OccurredAtMS: req.GetOccurredAtMs(), DimensionsJSON: req.GetDimensionsJson(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.ConsumeTaskEventResponse{ EventId: result.EventID, Status: result.Status, MatchedTaskCount: result.MatchedTaskCount, }, nil } // AdminTaskServer 暴露后台任务配置管理入口。 type AdminTaskServer struct { activityv1.UnimplementedAdminTaskServiceServer svc *taskservice.Service } // NewAdminTaskServer 创建后台任务配置 gRPC adapter。 func NewAdminTaskServer(svc *taskservice.Service) *AdminTaskServer { return &AdminTaskServer{svc: svc} } // ListTaskDefinitions 返回后台任务定义列表。 func (s *AdminTaskServer) ListTaskDefinitions(ctx context.Context, req *activityv1.ListTaskDefinitionsRequest) (*activityv1.ListTaskDefinitionsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) items, total, err := s.svc.ListTaskDefinitions(ctx, taskdomain.DefinitionQuery{ TaskType: req.GetTaskType(), Category: req.GetCategory(), Status: req.GetStatus(), Keyword: req.GetKeyword(), Page: req.GetPage(), PageSize: req.GetPageSize(), }) if err != nil { return nil, xerr.ToGRPCError(err) } resp := &activityv1.ListTaskDefinitionsResponse{Tasks: make([]*activityv1.TaskDefinition, 0, len(items)), Total: total} for _, item := range items { resp.Tasks = append(resp.Tasks, definitionToProto(item)) } return resp, nil } // UpsertTaskDefinition 创建或编辑任务配置。 func (s *AdminTaskServer) UpsertTaskDefinition(ctx context.Context, req *activityv1.UpsertTaskDefinitionRequest) (*activityv1.UpsertTaskDefinitionResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, created, err := s.svc.UpsertTaskDefinition(ctx, taskdomain.DefinitionCommand{ TaskID: req.GetTaskId(), TaskType: req.GetTaskType(), Category: req.GetCategory(), MetricType: req.GetMetricType(), Title: req.GetTitle(), Description: req.GetDescription(), AudienceType: req.GetAudienceType(), IconKey: req.GetIconKey(), IconURL: req.GetIconUrl(), ActionType: req.GetActionType(), ActionParam: req.GetActionParam(), ActionPayloadJSON: req.GetActionPayloadJson(), DimensionFilterJSON: req.GetDimensionFilterJson(), TargetValue: req.GetTargetValue(), TargetUnit: req.GetTargetUnit(), RewardCoinAmount: req.GetRewardCoinAmount(), RewardAssetType: req.GetRewardAssetType(), Status: req.GetStatus(), SortOrder: req.GetSortOrder(), EffectiveFromMS: req.GetEffectiveFromMs(), EffectiveToMS: req.GetEffectiveToMs(), OperatorAdminID: req.GetOperatorAdminId(), }) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.UpsertTaskDefinitionResponse{Task: definitionToProto(item), Created: created}, nil } // SetTaskDefinitionStatus 启停或归档任务。 func (s *AdminTaskServer) SetTaskDefinitionStatus(ctx context.Context, req *activityv1.SetTaskDefinitionStatusRequest) (*activityv1.SetTaskDefinitionStatusResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) item, err := s.svc.SetTaskDefinitionStatus(ctx, req.GetTaskId(), req.GetStatus(), req.GetOperatorAdminId()) if err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.SetTaskDefinitionStatusResponse{Task: definitionToProto(item)}, nil } // PublishTaskRewardPolicy 写入 policy instance 编译后的 activity 运行侧任务奖励默认资产。 func (s *AdminTaskServer) PublishTaskRewardPolicy(ctx context.Context, req *activityv1.PublishTaskRewardPolicyRequest) (*activityv1.PublishTaskRewardPolicyResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) command := taskdomain.RewardPolicyCommand{ InstanceCode: req.GetInstanceCode(), TemplateCode: req.GetTemplateCode(), TemplateVersion: req.GetTemplateVersion(), Status: req.GetStatus(), RewardAssetType: req.GetRewardAssetType(), RuleJSON: req.GetRuleJson(), OperatorAdminID: int64(req.GetOperatorAdminId()), PublishedAtMS: req.GetPublishedAtMs(), } if err := s.svc.PublishTaskRewardPolicy(ctx, command); err != nil { return nil, xerr.ToGRPCError(err) } return &activityv1.PublishTaskRewardPolicyResponse{ AppCode: appcode.FromContext(ctx), InstanceCode: command.InstanceCode, Status: command.Status, RewardAssetType: command.RewardAssetType, PublishedAtMs: command.PublishedAtMS, }, nil } func taskListToProto(result taskdomain.ListResult) *activityv1.ListUserTasksResponse { resp := &activityv1.ListUserTasksResponse{ Sections: make([]*activityv1.TaskSection, 0, len(result.Sections)), ServerTimeMs: result.ServerTimeMS, NextRefreshAtMs: result.NextRefreshAtMS, } for _, section := range result.Sections { target := &activityv1.TaskSection{ Section: section.Section, Items: make([]*activityv1.TaskItem, 0, len(section.Items)), ServerTimeMs: section.ServerTimeMS, NextRefreshAtMs: section.NextRefreshAtMS, } for _, item := range section.Items { target.Items = append(target.Items, itemToProto(item)) } resp.Sections = append(resp.Sections, target) } return resp } func itemToProto(item taskdomain.Item) *activityv1.TaskItem { return &activityv1.TaskItem{ TaskId: item.TaskID, TaskType: item.TaskType, Category: item.Category, MetricType: item.MetricType, Title: item.Title, Description: item.Description, AudienceType: item.AudienceType, IconKey: item.IconKey, IconUrl: item.IconURL, ActionType: item.ActionType, ActionParam: item.ActionParam, ActionPayloadJson: item.ActionPayloadJSON, DimensionFilterJson: item.DimensionFilterJSON, TargetValue: item.TargetValue, TargetUnit: item.TargetUnit, ProgressValue: item.ProgressValue, RewardCoinAmount: item.RewardCoinAmount, RewardAssetType: item.RewardAssetType, Status: item.UserStatus, Claimable: item.Claimable, TaskDay: item.TaskDay, ServerTimeMs: item.ServerTimeMS, NextRefreshAtMs: item.NextRefreshAtMS, SortOrder: item.SortOrder, Version: item.Version, } } func claimToProto(claim taskdomain.Claim) *activityv1.ClaimTaskRewardResponse { return &activityv1.ClaimTaskRewardResponse{ ClaimId: claim.ClaimID, TaskId: claim.TaskID, TaskType: claim.TaskType, TaskDay: claim.CycleKey, RewardCoinAmount: claim.RewardCoinAmount, RewardAssetType: claim.RewardAssetType, Status: claim.Status, WalletTransactionId: claim.WalletTransactionID, GrantedAtMs: claim.UpdatedAtMS, Claimed: claim.Status == taskdomain.ClaimStatusGranted, } } func definitionToProto(item taskdomain.Definition) *activityv1.TaskDefinition { return &activityv1.TaskDefinition{ TaskId: item.TaskID, TaskType: item.TaskType, Category: item.Category, MetricType: item.MetricType, Title: item.Title, Description: item.Description, AudienceType: item.AudienceType, IconKey: item.IconKey, IconUrl: item.IconURL, ActionType: item.ActionType, ActionParam: item.ActionParam, ActionPayloadJson: item.ActionPayloadJSON, DimensionFilterJson: item.DimensionFilterJSON, TargetValue: item.TargetValue, TargetUnit: item.TargetUnit, RewardCoinAmount: item.RewardCoinAmount, RewardAssetType: item.RewardAssetType, Status: item.Status, SortOrder: item.SortOrder, Version: item.Version, EffectiveFromMs: item.EffectiveFromMS, EffectiveToMs: item.EffectiveToMS, CreatedByAdminId: item.CreatedByAdminID, UpdatedByAdminId: item.UpdatedByAdminID, CreatedAtMs: item.CreatedAtMS, UpdatedAtMs: item.UpdatedAtMS, } }