Files
sundynix-plant-be/service/plant/my_plant.go
T

602 lines
18 KiB
Go

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
})
}
// 完成任务后异步执行 更新用户等级 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(&currentLevel).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.用户个人资料
}