topfans/backend/services/taskService/service/daily_task_service.go
2026-05-16 02:42:32 +08:00

369 lines
13 KiB
Go

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
}