package plant import ( "context" "errors" "sundynix-go/global" common "sundynix-go/model/commom/request" "sundynix-go/model/plant" plantReq "sundynix-go/model/plant/request" plantRes "sundynix-go/model/plant/response" "sundynix-go/model/system" "sundynix-go/utils/timer" "time" "go.uber.org/zap" "gorm.io/gorm" ) type MyPlantService struct{} var MyPlantServiceApp = new(MyPlantService) // AddPlant 添加植物 func (s *MyPlantService) AddPlant(req plantReq.CreateMyPlant, userId string) error { err := global.DB.Transaction(func(tx *gorm.DB) error { //1. 验证oss是否存在 var ossList []*system.Oss err := tx.Where("id in ?", req.OssIds).Find(&ossList).Error if err != nil { return err } bgTime := timer.GetZeroTime() //2. 处理养护计划 var carePlans []*plant.CarePlan for _, v := range req.CarePlans { carePlans = append(carePlans, &plant.CarePlan{ UserId: userId, Name: v.Name, Icon: v.Icon, Period: v.Period, TargetAction: v.TargetAction, }) } //3.保存数据 myPlant钩子函数自动处理创建careTask myPlant := plant.MyPlant{ UserId: userId, Name: req.Name, PlantTime: bgTime, Status: 1, Placement: req.Placement, PotMaterial: req.PotMaterial, PotSize: req.PotSize, Sunlight: req.Sunlight, PlantingMaterial: req.PlantingMaterial, CarePlans: carePlans, } err = tx.Create(&myPlant).Error if err != nil { return err } //4.处理图片关系 if len(ossList) > 0 { var relations []map[string]interface{} for _, oss := range ossList { relations = append(relations, map[string]interface{}{ "my_plant_id": myPlant.Id, "oss_id": oss.Id, }) } err = tx.Table("sundynix_my_plant_oss").Create(relations).Error if err != nil { return err } } //5.更新用户中心数据 err = tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId). Update("plant_count", gorm.Expr("plant_count + 1")).Error if err != nil { return err } return nil }) return err } // PlantPage 植物列表 func (s *MyPlantService) PlantPage(req common.PageInfo, userId string) (list interface{}, total int64, err error) { limit := req.PageSize offset := req.PageSize * (req.Current - 1) db := global.DB.Model(&plant.MyPlant{}).Preload("ImgList", func(db *gorm.DB) *gorm.DB { return db.Order("created_at desc") }) var myPlants []*plant.MyPlant db = db.Where("user_id = ?", userId) err = db.Count(&total).Error if err != nil { return } err = db.Limit(limit).Offset(offset).Order("created_at desc").Find(&myPlants).Error return myPlants, total, err } // PlantDetail 植物详情 func (s *MyPlantService) PlantDetail(id string) (p plant.MyPlant, err error) { var res plant.MyPlant err = global.DB.Where("id = ?", id). Preload("ImgList", func(db *gorm.DB) *gorm.DB { return db.Order("created_at desc") }). Preload("CarePlans"). Preload("CareRecords", func(db *gorm.DB) *gorm.DB { return db.Order("created_at desc") }). Preload("GrowthRecords", func(db *gorm.DB) *gorm.DB { return db.Preload("ImgList", func(db *gorm.DB) *gorm.DB { return db.Order("created_at desc") }).Order("created_at desc") }). First(&res).Error //不存在的时候不要返回错误,而是返回nil if err != nil { return res, err } return res, nil } func (s *MyPlantService) UpdatePlant(req plantReq.UpdateMyPlant) error { return global.DB.Transaction(func(tx *gorm.DB) error { var myPlant plant.MyPlant err := tx.Where("id = ?", req.Id).First(&myPlant).Error if err != nil { return err } // 以map形式更新 先构建map updateMap := map[string]interface{}{ "name": req.Name, "placement": req.Placement, "planting_material": req.PlantingMaterial, "pot_material": req.PotMaterial, "pot_size": req.PotSize, "sunlight": req.Sunlight, } //1.修改基本信息 err = tx.Model(&plant.MyPlant{}).Where("id = ?", req.Id).Updates(updateMap).Error if err != nil { return err } //2.修改计划 if len(req.CarePlans) > 0 { today := timer.GetZeroTime() for _, plan := range req.CarePlans { err = tx.Model(&plant.CarePlan{}).Where("id = ?", plan.Id).Updates(map[string]interface{}{ "icon": plan.Icon, "name": plan.Name, "period": plan.Period, "target_action": plan.TargetAction, }).Error if err != nil { return err } //3.重新生成任务 //3.1 删除旧任务 err = tx.Where("plan_id = ?", plan.Id).Unscoped().Delete(&plant.CareTask{}).Error if err != nil { return err } //3.2 创建新任务 dueDate := today.AddDate(0, 0, plan.Period) task := plant.CareTask{ UserId: myPlant.UserId, PlantId: myPlant.Id, PlanId: plan.Id, Name: plan.Name, Icon: plan.Icon, DueDate: dueDate, Status: 1, TargetAction: plan.TargetAction, } err = tx.Create(&task).Error if err != nil { return err } } } return nil }) } // TodayTask 今日任务 func (s *MyPlantService) TodayTask(userId string) ([]plantRes.PlantTaskVO, error) { today := timer.GetZeroTime() endOfToday := today.Add(24 * time.Hour) var tasks []*plant.CareTask // 查询条件:1.未完成的任务(包含今天和以前逾期的) 2.今天已经完成的任务 err := global.DB.Where("user_id = ? AND ("+ "(status = 1 AND due_date < ?) OR "+ "(status = 2 AND completed_at >= ? AND completed_at < ?)"+ ")", userId, endOfToday, today, endOfToday). Order("due_date ASC"). Find(&tasks).Error if err != nil { return nil, err } // 2.内存处理:聚合数据 plantTaskMap := make(map[string][]*plant.CareTask) expiredMap := make(map[string]bool) var plantIds []string for _, t := range tasks { plantIds = append(plantIds, t.PlantId) plantTaskMap[t.PlantId] = append(plantTaskMap[t.PlantId], t) // 判定右上角红标:状态为待办 且 应做时间早于今天 if t.Status == 1 && t.DueDate.Before(today) { expiredMap[t.PlantId] = true } } //3.批量查询植物 var myPlants []*plant.MyPlant err = global.DB.Where("id in ?", plantIds).Preload("ImgList", func(db *gorm.DB) *gorm.DB { return db.Order("created_at desc") }).Find(&myPlants).Error if err != nil { return nil, err } //4.组装结果 var res []plantRes.PlantTaskVO for _, p := range myPlants { plantTaskVO := plantRes.PlantTaskVO{ MyPlant: p, HasExpired: expiredMap[p.Id], Tasks: plantTaskMap[p.Id], } res = append(res, plantTaskVO) } return res, nil } // CompleteTask 完成任务 func (s *MyPlantService) CompleteTask(req plantReq.CompleteTask, userId string) (*plantRes.TaskCompletionResult, error) { var result plantRes.TaskCompletionResult err := global.DB.Transaction(func(tx *gorm.DB) error { var task plant.CareTask if err := tx.Where("id = ?", req.TaskId).First(&task).Error; err != nil { return err } //1.更新当前任务为完成 updateData := map[string]interface{}{ "status": 2, "completed_at": time.Now(), } if err := tx.Model(&task).Where("id = ?", req.TaskId).Updates(updateData).Error; err != nil { return err } //2.获取计划模版 var plan plant.CarePlan if err := tx.Where("id = ?", task.PlanId).First(&plan).Error; err != nil { return err } //3.生成下个周期的任务 today := timer.GetZeroTime() nextDueDate := today.AddDate(0, 0, plan.Period) newTask := plant.CareTask{ UserId: plan.UserId, PlantId: plan.PlantId, PlanId: plan.Id, Name: plan.Name, Icon: plan.Icon, TargetAction: plan.TargetAction, DueDate: nextDueDate, Status: 1, } if err := tx.Create(&newTask).Error; err != nil { return err } //4.保存养护记录 record := plant.CareRecord{ UserId: plan.UserId, PlantId: plan.PlantId, PlanId: plan.Id, Name: plan.Name, Icon: plan.Icon, Remark: req.Remark, } if err := tx.Create(&record).Error; err != nil { return err } //4.等级与阳光计算 var profile plant.UserProfile if err := tx.Set("gorm:query_option", "FOR UPDATE").Where("user_id = ?", 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", } column, ok := fieldMap[task.TargetAction] if !ok { column = "care_count" // 默认备用 } const TaskReward = 50 newTotalSunlight := profile.TotalSunlight + TaskReward newCurrentSunlight := profile.CurrentSunlight + TaskReward // 5. 等级判定 (根据累计阳光查出当前应有的等级) var latestLevel plant.LevelConfig if err := tx.Where("min_sunlight <= ?", newTotalSunlight). Order("min_sunlight DESC").First(&latestLevel).Error; err != nil { return errors.New("等级配置异常") } result.CurrentLevel = &latestLevel result.IsLevelUp = latestLevel.Id != profile.LevelId // 6. 执行 Profile 更新 (任务阳光 + 计数自增 + 等级同步) profileData := map[string]interface{}{ column: gorm.Expr(column + " + 1"), "current_sunlight": newCurrentSunlight, "total_sunlight": newTotalSunlight, "level_id": latestLevel.Id, } if err := tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId).Updates(profileData).Error; err != nil { return err } // 7. 徽章判定逻辑 // 7.1 计算当前动作的最新逻辑数值 var currentActionVal int64 switch task.TargetAction { case "ACT_WATER": currentActionVal = profile.WaterCount + 1 case "ACT_FERTILIZE": currentActionVal = profile.FertilizeCount + 1 case "ACT_REPOT": currentActionVal = profile.RepotCount + 1 case "ACT_PRUNE": currentActionVal = profile.PruneCount + 1 default: currentActionVal = 0 } // 7.2 查询已拥有的徽章 ID (用于去重) var ownedBadgeIds []string tx.Model(&plant.UserBadge{}).Where("user_id = ?", userId).Pluck("badge_id", &ownedBadgeIds) ownedMap := make(map[string]bool) for _, id := range ownedBadgeIds { ownedMap[id] = true } // 7.3 筛选当前 Action 下满足条件的最高级徽章 var badgeConfigs []plant.BadgeConfig tx.Where("target_action = ?", task.TargetAction).Preload("Icon").Find(&badgeConfigs) for i := range badgeConfigs { conf := &badgeConfigs[i] // 如果没拿过且数值达标 if !ownedMap[conf.Id] && currentActionVal >= conf.Threshold { // 寻找 Tier 最大的那个 if result.NewBadge == nil || conf.Tier > result.NewBadge.Tier { result.NewBadge = conf result.IsGetBadge = true } } } // 8. 奖励入库:如果产生了新徽章 if result.NewBadge != nil { // 写入获得记录 ub := plant.UserBadge{ UserId: userId, BadgeId: result.NewBadge.Id, // 注意这里 Id 是 string } if err := tx.Create(&ub).Error; err != nil { return err } // 发放徽章额外奖励阳光 if err := tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId). Update("current_sunlight", gorm.Expr("current_sunlight + ?", result.NewBadge.RewardSunlight)).Error; err != nil { return err } } return nil }) return &result, err } // DeletePlants 删除植物 func (s *MyPlantService) DeletePlants(req common.IdsReq) error { return global.DB.Transaction(func(tx *gorm.DB) error { var imgIds []string tx.Table("sundynix_my_plant_oss").Where("my_plant_id IN ?", req.Ids).Pluck("oss_id", &imgIds) // 2. 清理中间表记录 (解开多对多关系) // 使用 Exec 直接操作中间表比循环 Clear 快得多 if err := tx.Exec("DELETE FROM sundynix_my_plant_oss WHERE my_plant_id IN ?", req.Ids).Error; err != nil { return err } // 3. 物理删除图片记录本身 if len(imgIds) > 0 { if err := tx.Unscoped().Where("id IN ?", imgIds).Delete(&system.Oss{}).Error; err != nil { return err } } //4.批量删除养护计划 养护任务 养护记录 成长记录 if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CarePlan{}).Error; err != nil { return err } if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CareTask{}).Error; err != nil { return err } if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.CareRecord{}).Error; err != nil { return err } if err := tx.Unscoped().Where("plant_id in ?", req.Ids).Delete(&plant.GrowthRecord{}).Error; err != nil { return err } //5.删除植物本身 if err := tx.Unscoped().Where("id in ?", req.Ids).Delete(&plant.MyPlant{}).Error; err != nil { return err } return nil }) } // DeletePlans 删除任务 func (s *MyPlantService) DeletePlans(req common.IdsReq) error { return global.DB.Transaction(func(tx *gorm.DB) error { var plans []plant.CarePlan if err := tx.Where("id in ?", req.Ids).Find(&plans).Error; err != nil { return err } var tasks []plant.CareTask if err := tx.Where("plan_id in ?", req.Ids).Find(&tasks).Error; err != nil { return err } //1.删除计划 err := tx.Unscoped().Delete(&plans).Error if err != nil { return err } //2.删除任务 err = tx.Unscoped().Delete(&tasks).Error if err != nil { return err } return nil }) } // AddCarePlan 添加CarePlan func (s *MyPlantService) AddCarePlan(req plantReq.AddPlans) error { return global.DB.Transaction(func(tx *gorm.DB) error { for _, plan := range req.CarePlan { var myPlant plant.MyPlant err := tx.Where("id = ?", plan.PlantId).First(&myPlant).Error if err != nil { return err } //1.新增计划 newPlan := plant.CarePlan{ UserId: myPlant.UserId, PlantId: myPlant.Id, Name: plan.Name, Icon: plan.Icon, Period: plan.Period, TargetAction: plan.TargetAction, } err = tx.Create(&newPlan).Error if err != nil { return err } //2.新增任务 today := timer.GetZeroTime() dueDate := today.AddDate(0, 0, plan.Period) task := plant.CareTask{ UserId: myPlant.UserId, PlantId: myPlant.Id, PlanId: newPlan.Id, Name: plan.Name, Icon: plan.Icon, TargetAction: plan.TargetAction, DueDate: dueDate, Status: 1, } err = tx.Create(&task).Error if err != nil { return err } } return nil }) } // DeletePlan 删除CarePlan func (s *MyPlantService) DeletePlan(id string) error { return global.DB.Transaction(func(tx *gorm.DB) error { var plan plant.CarePlan if err := tx.Where("id = ?", id).First(&plan).Error; err != nil { return err } var tasks []plant.CareTask if err := tx.Where("plan_id = ?", plan.Id).Find(&tasks).Error; err != nil { return err } err := tx.Unscoped().Delete(&plan).Error if err != nil { return err } err = tx.Unscoped().Delete(&tasks).Error if err != nil { return err } return nil }) } // AddGrowthRecord 添加成长记录 func (s *MyPlantService) AddGrowthRecord(req plantReq.CreateGrowthRecord, userId string) error { return global.DB.Transaction(func(tx *gorm.DB) error { //1.验证图片是否存在 var ossList []*system.Oss err := tx.Where("id in ?", req.OssIds).Find(&ossList).Error if err != nil { return err } //2.保存记录 record := plant.GrowthRecord{ UserId: userId, PlantId: req.PlantId, Name: req.Name, Content: req.Content, Desc: req.Desc, Tag: req.Tag, } err = tx.Create(&record).Error if err != nil { return err } //3.保存图片关系 if len(ossList) > 0 { var relations []map[string]interface{} for _, oss := range ossList { relations = append(relations, map[string]interface{}{ "growth_record_id": record.Id, "oss_id": oss.Id, }) } err = tx.Table("sundynix_growth_record_oss").Create(relations).Error if err != nil { return err } } return nil }) } // QuickCare 快捷养护记录(无需预设任务,直接创建养护记录) func (s *MyPlantService) QuickCare(req plantReq.QuickCare, userId string) error { return global.DB.Transaction(func(tx *gorm.DB) error { // 1.验证植物存在且属于该用户 var myPlant plant.MyPlant if err := tx.Where("id = ? AND user_id = ?", req.PlantId, userId).First(&myPlant).Error; err != nil { return errors.New("植物不存在") } // 2.直接创建养护记录 record := plant.CareRecord{ UserId: userId, PlantId: req.PlantId, Name: req.Name, Icon: req.Icon, Remark: req.Remark, } if err := tx.Create(&record).Error; err != nil { return err } // 3.更新用户 profile 对应计数(尝试从 icon JSON 解析 targetAction) column := "care_count" if req.Icon != "" { // 解析 icon JSON 中的 id 字段来判断动作类型 actionMap := map[string]string{ "water": "water_count", "fertilize": "fertilize_count", "prune": "prune_count", "repot": "repot_count", } // 简单提取: 不引入 encoding/json 解析,用名称匹配 nameMap := map[string]string{ "浇水": "water_count", "施肥": "fertilize_count", "修剪": "prune_count", "换盆": "repot_count", } if col, ok := nameMap[req.Name]; ok { column = col } else { // 尝试从 actionMap (icon.id) 匹配 for key, col := range actionMap { if len(req.Icon) > 0 && contains(req.Icon, key) { column = col break } } } } if err := tx.Model(&plant.UserProfile{}).Where("user_id = ?", userId). Update(column, gorm.Expr(column+" + 1")).Error; err != nil { return err } return nil }) } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStr(s, substr)) } func containsStr(s, sub string) bool { for i := 0; i <= len(s)-len(sub); i++ { if s[i:i+len(sub)] == sub { return true } } return false } // 完成任务后异步执行 更新用户等级 sunlight func (s *MyPlantService) handleCompleteTaskThenUpdateProfile(ctx context.Context, userId string) { //5.更新用户profile var profile plant.UserProfile if asyncErr := global.DB.Where("user_id = ?", userId).First(&profile).Error; asyncErr != nil { global.Logger.Error("完成任务异步操作-----查询用户profile失败", zap.Error(asyncErr)) } totalSunlight := profile.TotalSunlight + 50 updateData := map[string]interface{}{ "care_count": profile.CareCount + 1, "current_sunlight": profile.CurrentSunlight + 50, "total_sunlight": totalSunlight, } //5.1 判断是否到达下个等级 var currentLevel plant.LevelConfig levelErr := global.DB.Where("min_sunlight <= ?", totalSunlight).Order("min_sunlight DESC").First(¤tLevel).Error if levelErr != nil { global.Logger.Error("完成任务异步操作-----查询用户等级失败", zap.Error(levelErr)) } updateData["level_id"] = currentLevel.Id //5.2 更新用户profile updateProfileErr := global.DB.Model(&plant.UserProfile{}).Where("user_id = ?", userId).Updates(updateData).Error if updateProfileErr != nil { global.Logger.Error("完成任务异步操作-----更新用户profile失败", zap.Error(updateProfileErr)) } } // todo 完成任务后异步执行 处理徽章 func (s *MyPlantService) handleCompleteTaskThenHandleBadge(ctx context.Context, taskId, userId string) { //1.查询任务 var task plant.CareTask if asyncErr := global.DB.Where("id = ?", taskId).First(&task).Error; asyncErr != nil { global.Logger.Error("完成任务异步操作-----查询任务失败", zap.Error(asyncErr)) } //2.查询徽章 var badges []plant.BadgeConfig if asyncErr := global.DB.Where("target_action = ?", task.TargetAction).Order("tier ASC").Find(&badges).Error; asyncErr != nil { global.Logger.Error("完成任务异步操作-----查询徽章失败", zap.Error(asyncErr)) } //3.用户的徽章 var userBadges []plant.UserBadge if asyncErr := global.DB.Where("user_id = ?", userId).Find(&userBadges).Error; asyncErr != nil { global.Logger.Error("完成任务异步操作-----查询用户徽章失败", zap.Error(asyncErr)) } //4.用户个人资料 }