369 lines
13 KiB
Go
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
|
|
}
|