175 lines
5.8 KiB
Go
175 lines
5.8 KiB
Go
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
|
|
}
|