From 0bc06963ee35c15ae54ac17227e8574c0f0ce39b Mon Sep 17 00:00:00 2001 From: LoveLosita <2810873701@qq.com> Date: Tue, 10 Feb 2026 22:05:59 +0800 Subject: [PATCH] =?UTF-8?q?Version:=200.2.2.dev.260210=20feat:=20?= =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20=E6=96=B0=E5=A2=9E=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=9D=97=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 schedules、schedule_events 与 task_items 三表的联动删除 - 保证数据一致性,避免遗留脏数据 ✅ --- backend/api/task-class.go | 20 +++++++++++++++ backend/dao/schedule.go | 47 +++++++++++++++++++++++++++++++++++ backend/dao/task-class.go | 19 +++++++++++--- backend/respond/respond.go | 10 ++++++++ backend/routers/routers.go | 1 + backend/service/schedule.go | 4 +-- backend/service/task-class.go | 47 +++++++++++++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 6 deletions(-) diff --git a/backend/api/task-class.go b/backend/api/task-class.go index 01f4f8d..56528b8 100644 --- a/backend/api/task-class.go +++ b/backend/api/task-class.go @@ -131,3 +131,23 @@ func (api *TaskClassHandler) UserAddTaskClassItemIntoSchedule(c *gin.Context) { } c.JSON(http.StatusOK, respond.Ok) } + +func (api *TaskClassHandler) DeleteTaskClassItem(c *gin.Context) { + taskID := c.Query("task_item_id") + //将taskID转换为int + intTaskID, err := strconv.Atoi(taskID) + if err != nil { + c.JSON(http.StatusBadRequest, respond.WrongParamType) + return + } + userID := c.GetInt("user_id") + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + err = api.svc.DeleteTaskClassItem(ctx, userID, intTaskID) + if err != nil { + respond.DealWithError(c, err) + return + } + c.JSON(http.StatusOK, respond.Ok) +} diff --git a/backend/dao/schedule.go b/backend/dao/schedule.go index 3a6232c..da2204c 100644 --- a/backend/dao/schedule.go +++ b/backend/dao/schedule.go @@ -397,3 +397,50 @@ func (d *ScheduleDAO) SetScheduleEmbeddedTaskIDToNull(ctx context.Context, event } return embeddedTaskID, nil } + +func (d *ScheduleDAO) FindEmbeddedTaskIDAndDeleteIt(ctx context.Context, taskID int) (int, error) { + // 1. 先找到 schedules 表中 embedded_task_id = taskID 的记录,获取对应的 event_id + type row struct { + EventID *int `gorm:"column:event_id"` + } + var r row + err := d.db.WithContext(ctx). + Table("schedules"). + Select("event_id"). + Where("embedded_task_id = ?", taskID). + Order("id ASC"). + Limit(1). + Scan(&r).Error + if err != nil { + return 0, err + } + if r.EventID == nil { + return 0, respond.TargetTaskNotEmbeddedInAnySchedule + } + eventID := *r.EventID + + // 2. 删除该 event_id 对应的课程事件(通过级联删除实现) + res := d.db.WithContext(ctx). + Table("schedule_events"). + Where("id = ?", eventID). + Delete(&model.ScheduleEvent{}) + if res.Error != nil { + return 0, res.Error + } + if res.RowsAffected == 0 { + return 0, respond.TargetTaskNotEmbeddedInAnySchedule + } + return eventID, nil +} + +func (d *ScheduleDAO) DeleteScheduleEventByTaskItemID(ctx context.Context, taskItemID int) error { + //直接找schedule_events表中type=task且rel_id=taskItemID的记录,删除它(级联删schedules) + res := d.db.WithContext(ctx). + Table("schedule_events"). + Where("type = ? AND rel_id = ?", "task", taskItemID). + Delete(&model.ScheduleEvent{}) + if res.Error != nil { + return res.Error + } + return nil +} diff --git a/backend/dao/task-class.go b/backend/dao/task-class.go index 8bd2de0..0be5339 100644 --- a/backend/dao/task-class.go +++ b/backend/dao/task-class.go @@ -2,6 +2,7 @@ package dao import ( "context" + "errors" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -158,12 +159,15 @@ func (dao *TaskClassDAO) GetTaskClassItemByID(ctx context.Context, id int) (*mod func (dao *TaskClassDAO) GetTaskClassIDByTaskItemID(ctx context.Context, itemID int) (int, error) { var item model.TaskClassItem - err := dao.db.WithContext(ctx). + res := dao.db.WithContext(ctx). Select("category_id"). Where("id = ?", itemID). - First(&item).Error - if err != nil { - return 0, err + First(&item) + if res.Error != nil { + if errors.Is(res.Error, gorm.ErrRecordNotFound) { + return 0, respond.TaskClassItemNotFound + } + return 0, res.Error } return *item.CategoryID, nil } @@ -207,3 +211,10 @@ func (dao *TaskClassDAO) IfTaskClassItemArranged(ctx context.Context, taskID int } return item.EmbeddedTime != nil, nil } + +func (dao *TaskClassDAO) DeleteTaskClassItemByID(ctx context.Context, id int) error { + err := dao.db.WithContext(ctx). + Where("id = ?", id). + Delete(&model.TaskClassItem{}).Error + return err +} diff --git a/backend/respond/respond.go b/backend/respond/respond.go index 82e1cac..bb42b64 100644 --- a/backend/respond/respond.go +++ b/backend/respond/respond.go @@ -230,4 +230,14 @@ var ( //请求相关的响应 Status: "40034", Info: "task class item already arranged", } + + TargetTaskNotEmbeddedInAnySchedule = Response{ //目标任务未嵌入任何日程 + Status: "40035", + Info: "target task not embedded in any schedule", + } + + TaskClassItemNotFound = Response{ //任务类项目未找到 + Status: "40036", + Info: "task class item not found", + } ) diff --git a/backend/routers/routers.go b/backend/routers/routers.go index 97d7d32..c825820 100644 --- a/backend/routers/routers.go +++ b/backend/routers/routers.go @@ -69,6 +69,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO, limiter *pk taskClassGroup.GET("/get", handlers.TaskClassHandler.UserGetCompleteTaskClass) taskClassGroup.PUT("/update", handlers.TaskClassHandler.UserUpdateTaskClass) taskClassGroup.POST("/insert-into-schedule", handlers.TaskClassHandler.UserAddTaskClassItemIntoSchedule) + taskClassGroup.DELETE("/delete-item", handlers.TaskClassHandler.DeleteTaskClassItem) } scheduleGroup := apiGroup.Group("/schedule") { diff --git a/backend/service/schedule.go b/backend/service/schedule.go index b4bee3e..01b506c 100644 --- a/backend/service/schedule.go +++ b/backend/service/schedule.go @@ -37,8 +37,8 @@ func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int) return nil, err }*/ //2.获取当前日期 - curTime := time.Now().Format("2006-01-02") - /*curTime := "2026-03-02" //测试数据*/ + /*curTime := time.Now().Format("2006-01-02")*/ + curTime := "2026-03-02" //测试数据 week, dayOfWeek, err := conv.RealDateToRelativeDate(curTime) if err != nil { return nil, err diff --git a/backend/service/task-class.go b/backend/service/task-class.go index 14f3a0e..b81794b 100644 --- a/backend/service/task-class.go +++ b/backend/service/task-class.go @@ -242,3 +242,50 @@ func (sv *TaskClassService) AddTaskClassItemIntoSchedule(ctx context.Context, re } return nil } + +func (sv *TaskClassService) DeleteTaskClassItem(ctx context.Context, userID int, taskItemID int) error { + //1.先验证任务块归属 + taskClassID, err := sv.taskClassRepo.GetTaskClassIDByTaskItemID(ctx, taskItemID) //通过任务块ID获取所属任务类ID + if err != nil { + return err + } + ownerID, err := sv.taskClassRepo.GetTaskClassUserIDByID(ctx, taskClassID) //通过任务类ID获取所属用户ID + if err != nil { + return err + } + if ownerID != userID { + return respond.TaskClassItemNotBelongToUser + } + //2.如果该任务块已经被安排了,先解除安排,再删除任务块(事务) + if err := sv.repoManager.Transaction(ctx, func(txM *dao.RepoManager) error { + //2.1.先检查该任务块是否已经被安排了 + arranged, err := txM.TaskClass.IfTaskClassItemArranged(ctx, taskItemID) + if err != nil { + return err + } + if arranged { + //2.2.如果已经被安排了,先解除安排 + //先扫schedules找到该task_item_id并删除 + _, txErr := txM.Schedule.FindEmbeddedTaskIDAndDeleteIt(ctx, taskItemID) + //2.3.再将task_items表的embedded_time字段设置为null + txErr = txM.TaskClass.DeleteTaskClassItemEmbeddedTime(ctx, taskItemID) + if txErr != nil { + return txErr + } + //再删除schedule_event表中对应的事件 + txErr = txM.Schedule.DeleteScheduleEventByTaskItemID(ctx, taskItemID) + if txErr != nil { + return txErr + } + } + //2.4.最后删除任务块 + err = txM.TaskClass.DeleteTaskClassItemByID(ctx, taskItemID) + if err != nil { + return err + } + return nil + }); err != nil { + return err + } + return nil +}