package service import ( "context" "fmt" "strconv" "time" pbCommon "github.com/topfans/backend/pkg/proto/common" "github.com/topfans/backend/pkg/logger" pb "github.com/topfans/backend/pkg/proto/task" "github.com/topfans/backend/services/taskService/client" "github.com/topfans/backend/services/taskService/model" "github.com/topfans/backend/services/taskService/repository" "go.uber.org/zap" ) // DailyTaskService 每日任务Service接口 type DailyTaskService interface { GetDailyTasks(ctx context.Context, userID, starID int64) (*pb.GetDailyTasksResponse, error) ReportEvent(ctx context.Context, userID, starID int64, eventType string) (*pb.ReportEventResponse, error) ClaimDailyTask(ctx context.Context, userID, starID int64, taskKey string) (*pb.ClaimDailyTaskResponse, error) ClaimAllDailyTasks(ctx context.Context, userID, starID int64) (*pb.ClaimAllDailyTasksResponse, error) } // dailyTaskService 每日任务Service实现 type dailyTaskService struct { dailyRepo repository.DailyTaskRepository userRPCClient client.UserServiceClient } // NewDailyTaskService 创建每日任务Service实例 func NewDailyTaskService(dailyRepo repository.DailyTaskRepository, userRPCClient client.UserServiceClient) DailyTaskService { return &dailyTaskService{ dailyRepo: dailyRepo, userRPCClient: userRPCClient, } } // GetDailyTasks 获取用户的每日任务列表 func (s *dailyTaskService) GetDailyTasks(ctx context.Context, userID, starID int64) (*pb.GetDailyTasksResponse, error) { // 获取用户所有每日任务进度 progressList, err := s.dailyRepo.ListDailyTasksByUser(userID, starID) if err != nil { logger.Logger.Error("GetDailyTasks: failed to list user daily tasks", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err)) return &pb.GetDailyTasksResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Tasks: []*pb.DailyTaskItem{}}, nil } // 获取所有活跃的每日任务定义 definitions, err := s.dailyRepo.ListActiveDailyTaskDefinitions(starID) if err != nil { logger.Logger.Error("GetDailyTasks: failed to list active definitions", zap.Int64("star_id", starID), zap.Error(err)) return &pb.GetDailyTasksResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Tasks: []*pb.DailyTaskItem{}}, nil } // 构建 taskKey -> definition 映射 defMap := make(map[string]*model.TaskDefinition) for _, def := range definitions { defMap[def.TaskKey] = def } // 构建 taskKey -> progress 映射 progressMap := make(map[string]*model.UserDailyTaskProgress) for _, p := range progressList { progressMap[p.TaskKey] = p } // 转换为 pb.DailyTaskItem items := make([]*pb.DailyTaskItem, 0, len(definitions)) for _, def := range definitions { progress := progressMap[def.TaskKey] item := &pb.DailyTaskItem{ TaskKey: def.TaskKey, Name: def.Name, Description: def.Description, Status: "pending", CrystalReward: def.CrystalReward, } if progress != nil { item.Status = progress.Status item.CanClaim = progress.Status == "completed" } items = append(items, item) } return &pb.GetDailyTasksResponse{ Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK}, StarId: starID, Tasks: items, }, nil } // ReportEvent 处理用户事件上报 func (s *dailyTaskService) ReportEvent(ctx context.Context, userID, starID int64, eventType string) (*pb.ReportEventResponse, error) { logger.Logger.Info("ReportEvent", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("event_type", eventType)) // 获取所有活跃的每日任务定义,查找匹配的任务 definitions, err := s.dailyRepo.ListActiveDailyTaskDefinitions(starID) if err != nil { return &pb.ReportEventResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Success: false}, err } success := true for _, def := range definitions { // 检查事件类型是否匹配任务要求 // TODO: 根据实际业务逻辑判断事件与任务的匹配关系 // 这里简化处理:假设 taskKey 就是事件类型 if def.TaskKey != eventType { continue } // 获取或创建进度 progress, err := s.dailyRepo.GetOrCreateDailyProgress(userID, starID, def.TaskKey, def) if err != nil { logger.Logger.Error("ReportEvent: failed to get or create progress", zap.Int64("user_id", userID), zap.String("task_key", def.TaskKey), zap.Error(err)) success = false continue } // 如果已完成或已领取,跳过 if progress.Status == "completed" || progress.Status == "claimed" { continue } // 更新进度为完成 now := time.Now().UnixMilli() progress.Status = "completed" progress.CompletedAt = &now if err := s.dailyRepo.UpdateDailyProgress(progress); err != nil { logger.Logger.Error("ReportEvent: failed to update progress", zap.Int64("user_id", userID), zap.String("task_key", def.TaskKey), zap.Error(err)) success = false continue } logger.Logger.Info("ReportEvent: task completed", zap.Int64("user_id", userID), zap.String("task_key", def.TaskKey)) return &pb.ReportEventResponse{ Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK}, Success: true, TaskKey: def.TaskKey, TaskCompleted: true, Message: "任务完成", }, nil } return &pb.ReportEventResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK}, Success: success}, nil } // ClaimDailyTask 领取单个每日任务奖励 func (s *dailyTaskService) ClaimDailyTask(ctx context.Context, userID, starID int64, taskKey string) (*pb.ClaimDailyTaskResponse, error) { logger.Logger.Info("ClaimDailyTask", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("task_key", taskKey)) // 获取任务进度 progress, err := s.dailyRepo.GetUserDailyProgress(userID, starID, taskKey) if err != nil { logger.Logger.Error("ClaimDailyTask: failed to get progress", zap.Int64("user_id", userID), zap.String("task_key", taskKey), zap.Error(err)) return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Success: false}, err } if progress == nil { logger.Logger.Warn("ClaimDailyTask: progress not found", zap.Int64("user_id", userID), zap.String("task_key", taskKey)) return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_NOT_FOUND}, Success: false}, nil } if progress.Status != "completed" { logger.Logger.Warn("ClaimDailyTask: task not completed", zap.Int64("user_id", userID), zap.String("task_key", taskKey), zap.String("status", progress.Status)) return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_BAD_REQUEST}, Success: false}, nil } // 获取任务定义以获取奖励信息 definitions, err := s.dailyRepo.ListActiveDailyTaskDefinitions(starID) if err != nil { return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Success: false}, err } var def *model.TaskDefinition for _, d := range definitions { if d.TaskKey == taskKey { def = d break } } if def == nil { logger.Logger.Error("ClaimDailyTask: task definition not found", zap.String("task_key", taskKey)) return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_NOT_FOUND}, Success: false}, nil } // 获取当前用户余额 currentProfile, errProfile := s.userRPCClient.GetFanProfile(ctx, userID, starID) var currentCrystal int64 if errProfile != nil { logger.Logger.Warn("ClaimDailyTask: failed to get fan profile", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(errProfile)) } else if currentProfile != nil { currentCrystal = currentProfile.CrystalBalance logger.Logger.Info("ClaimDailyTask: got current balance", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("current_crystal", currentCrystal)) } else { logger.Logger.Warn("ClaimDailyTask: fan profile is nil", zap.Int64("user_id", userID), zap.Int64("star_id", starID)) } // 发放水晶奖励 var newCrystalBalance int64 if def.CrystalReward > 0 { newCrystalBalance, err = s.userRPCClient.UpdateCrystalBalance(ctx, userID, starID, def.CrystalReward, "task_reward", strconv.FormatInt(def.ID, 10), fmt.Sprintf("每日任务奖励: %s", def.Name)) if err != nil { logger.Logger.Error("ClaimDailyTask: failed to update crystal balance", zap.Int64("user_id", userID), zap.String("task_key", taskKey), zap.Error(err)) // 如果更新失败,使用当前余额 newCrystalBalance = currentCrystal } } else { newCrystalBalance = currentCrystal } // 更新进度为已领取 now := time.Now().UnixMilli() progress.Status = "claimed" progress.ClaimedAt = &now if err := s.dailyRepo.UpdateDailyProgress(progress); err != nil { logger.Logger.Error("ClaimDailyTask: failed to update progress", zap.Int64("user_id", userID), zap.String("task_key", taskKey), zap.Error(err)) return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, Success: false}, err } return &pb.ClaimDailyTaskResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK}, Success: true, CrystalBalance: newCrystalBalance}, nil } // ClaimAllDailyTasks 一键领取所有已完成但未领取的每日任务 func (s *dailyTaskService) ClaimAllDailyTasks(ctx context.Context, userID, starID int64) (*pb.ClaimAllDailyTasksResponse, error) { logger.Logger.Info("ClaimAllDailyTasks", zap.Int64("user_id", userID), zap.Int64("star_id", starID)) // 获取所有已完成但未领取的任务进度 completedProgress, err := s.dailyRepo.ListCompletedDailyTasks(userID, starID) if err != nil { logger.Logger.Error("ClaimAllDailyTasks: failed to list completed tasks", zap.Int64("user_id", userID), zap.Error(err)) return &pb.ClaimAllDailyTasksResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, ClaimedCount: 0}, err } // 获取所有活跃的每日任务定义 definitions, err := s.dailyRepo.ListActiveDailyTaskDefinitions(starID) if err != nil { logger.Logger.Error("ClaimAllDailyTasks: failed to list definitions", zap.Int64("star_id", starID), zap.Error(err)) return &pb.ClaimAllDailyTasksResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}, ClaimedCount: 0}, err } // 构建 taskKey -> definition 映射 defMap := make(map[string]*model.TaskDefinition) for _, def := range definitions { defMap[def.TaskKey] = def } claimedCount := 0 now := time.Now().UnixMilli() var totalCrystal int64 var claimedTaskKeys []string for _, progress := range completedProgress { def, ok := defMap[progress.TaskKey] if !ok { logger.Logger.Warn("ClaimAllDailyTasks: definition not found", zap.String("task_key", progress.TaskKey)) continue } // 发放水晶奖励 if def.CrystalReward > 0 { var newBalance int64 newBalance, err = s.userRPCClient.UpdateCrystalBalance(ctx, userID, starID, def.CrystalReward, "task_reward", strconv.FormatInt(def.ID, 10), fmt.Sprintf("每日任务奖励: %s", def.Name)) if err != nil { logger.Logger.Error("ClaimAllDailyTasks: failed to update crystal", zap.Int64("user_id", userID), zap.String("task_key", progress.TaskKey), zap.Error(err)) // 继续处理其他任务 } else { totalCrystal = newBalance } } // 更新进度 progress.Status = "claimed" progress.ClaimedAt = &now if err := s.dailyRepo.UpdateDailyProgress(progress); err != nil { logger.Logger.Error("ClaimAllDailyTasks: failed to update progress", zap.Int64("user_id", userID), zap.String("task_key", progress.TaskKey), zap.Error(err)) continue } claimedCount++ claimedTaskKeys = append(claimedTaskKeys, progress.TaskKey) } logger.Logger.Info("ClaimAllDailyTasks: done", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int("claimed_count", claimedCount)) return &pb.ClaimAllDailyTasksResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK}, ClaimedCount: int32(claimedCount), CrystalBalance: totalCrystal, ClaimedTaskKeys: claimedTaskKeys}, nil } // ensureDailyTasksInitialized 确保用户的每日任务已初始化 func (s *dailyTaskService) ensureDailyTasksInitialized(userID, starID int64) error { // 尝试获取用户的任务进度,如果为空则初始化 progressList, err := s.dailyRepo.ListDailyTasksByUser(userID, starID) if err != nil { return fmt.Errorf("failed to list daily tasks: %w", err) } if len(progressList) == 0 { if err := s.dailyRepo.InitDailyTasksForUser(userID, starID); err != nil { return fmt.Errorf("failed to init daily tasks: %w", err) } } return nil }