package logic import ( "context" "errors" "time" "github.com/zeromicro/go-zero/core/logx" "gorm.io/gorm" plantModel "sundynix-micro-go/app/plant/model" "sundynix-micro-go/app/plant/rpc/internal/svc" "sundynix-micro-go/app/plant/rpc/plant" ) type CompleteTaskLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCompleteTaskLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CompleteTaskLogic { return &CompleteTaskLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)} } const taskReward = int64(50) func (l *CompleteTaskLogic) CompleteTask(in *plant.CompleteTaskReq) (*plant.TaskCompletionResult, error) { var result plant.TaskCompletionResult err := l.svcCtx.DB.Transaction(func(tx *gorm.DB) error { // 1. 查任务 var task plantModel.CareTask if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("id = ? AND user_id = ?", in.TaskId, in.UserId).First(&task).Error; err != nil { return err } if task.Status != 1 { return errors.New("任务已完成或已过期,不能重复完成") } // 2. 标记完成 now := time.Now() if err := tx.Model(&task).Updates(map[string]interface{}{ "status": 2, "completed_at": now, }).Error; err != nil { return err } // 3. 查计划,生成下个周期任务 var plan plantModel.CarePlan if err := tx.Where("id = ?", task.PlanID).First(&plan).Error; err != nil { return err } today := time.Now() todayZero := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()) nextDue := todayZero.AddDate(0, 0, plan.Period) // 检查是否已经存在该计划的待办任务,避免重复生成相同事项的多个待办任务 var activeCount int64 if err := tx.Model(&plantModel.CareTask{}). Where("plan_id = ? AND status = 1", plan.ID). Count(&activeCount).Error; err != nil { return err } if activeCount == 0 { newTask := plantModel.CareTask{ UserID: plan.UserID, PlantID: plan.PlantID, PlanID: plan.ID, Name: plan.Name, Icon: plan.Icon, TargetAction: plan.TargetAction, DueDate: nextDue, Status: 1, } if err := tx.Create(&newTask).Error; err != nil { return err } } // 4. 保存养护记录 record := plantModel.CareRecord{ UserID: plan.UserID, PlantID: plan.PlantID, PlanID: plan.ID, Name: plan.Name, Icon: plan.Icon, Remark: in.Remark, } if err := tx.Create(&record).Error; err != nil { return err } // 5. 查用户 profile(加锁) var profile plantModel.UserProfile if err := tx.Set("gorm:query_option", "FOR UPDATE"). Where("user_id = ?", in.UserId).First(&profile).Error; err != nil { return err } fieldMap := map[string]string{ "ACT_WATER": "water_count", "ACT_FERTILIZE": "fertilize_count", "ACT_REPOT": "repot_count", "ACT_PRUNE": "prune_count", } col, ok := fieldMap[task.TargetAction] if !ok { col = "care_count" } newTotal := profile.TotalSunlight + taskReward // 5.1 等级判定 var latestLevel plantModel.LevelConfig levelErr := tx.Where("min_sunlight <= ?", newTotal). Order("min_sunlight DESC").First(&latestLevel).Error if levelErr != nil { if errors.Is(levelErr, gorm.ErrRecordNotFound) { // 没有等级配置,跳过等级相关逻辑 result.IsLevelUp = false result.RewardSunlight = taskReward // 仍要更新阳光值 profileData := map[string]interface{}{ col: gorm.Expr(col + " + 1"), "current_sunlight": profile.CurrentSunlight + taskReward, "total_sunlight": newTotal, } if err := tx.Model(&plantModel.UserProfile{}). Where("user_id = ?", in.UserId).Updates(profileData).Error; err != nil { return err } return nil // 跳过后续等级/徽章逻辑 } return errors.New("等级配置异常") } result.IsLevelUp = (latestLevel.ID != profile.LevelID) result.CurrentLevel = &plant.LevelConfigInfo{ Id: latestLevel.ID, Level: int32(latestLevel.Level), Title: latestLevel.Title, MinSunlight: latestLevel.MinSunlight, } result.RewardSunlight = taskReward // 5.2 更新 profile profileData := map[string]interface{}{ col: gorm.Expr(col + " + 1"), "current_sunlight": profile.CurrentSunlight + taskReward, "total_sunlight": newTotal, "level_id": latestLevel.ID, } if err := tx.Model(&plantModel.UserProfile{}). Where("user_id = ?", in.UserId).Updates(profileData).Error; err != nil { return err } // 6. 徽章判定 actionValMap := map[string]int64{ "ACT_WATER": profile.WaterCount + 1, "ACT_FERTILIZE": profile.FertilizeCount + 1, "ACT_REPOT": profile.RepotCount + 1, "ACT_PRUNE": profile.PruneCount + 1, } currentVal := actionValMap[task.TargetAction] var ownedIds []string tx.Model(&plantModel.UserBadge{}).Where("user_id = ?", in.UserId).Pluck("badge_id", &ownedIds) ownedMap := make(map[string]bool) for _, id := range ownedIds { ownedMap[id] = true } var badgeConfigs []plantModel.BadgeConfig tx.Where("target_action = ?", task.TargetAction).Find(&badgeConfigs) var bestBadge *plantModel.BadgeConfig for i := range badgeConfigs { bc := &badgeConfigs[i] if !ownedMap[bc.ID] && currentVal >= bc.Threshold { if bestBadge == nil || bc.Tier > bestBadge.Tier { bestBadge = bc } } } if bestBadge != nil { ub := plantModel.UserBadge{UserID: in.UserId, BadgeID: bestBadge.ID} if err := tx.Create(&ub).Error; err != nil { return err } tx.Model(&plantModel.UserProfile{}).Where("user_id = ?", in.UserId). UpdateColumn("current_sunlight", gorm.Expr("current_sunlight + ?", bestBadge.RewardSunlight)) result.IsGetBadge = true result.NewBadge = &plant.BadgeConfigInfo{ Id: bestBadge.ID, Name: bestBadge.Name, Dimension: bestBadge.Dimension, Tier: int32(bestBadge.Tier), RewardSunlight: bestBadge.RewardSunlight, } } return nil }) return &result, err }