Files
smartmate/backend/service/task-class.go
LoveLosita f4bea0576c feat: 🗓️ 新增任务块排期能力并完善课程与日程模型
Version: 0.1.1.dev.260207

- 新增并测试通过将任务块排进日程接口 
- 批量导入课程接口增加单双周功能,支持只在单双周上课的课程 📚
- 任务块时间定位逻辑调整为「第几周-周几」模式 🧭

refactor: 🔨 重构时间与日程数据结构

- 完成绝对日期与相对时间的转换逻辑 🔄
  - 后续可根据需求灵活决定时间的传入与输出类型
- 再次重构 schedule 表单结构
  - 拆分为 schedule_event(单)与 schedule(多)
  - 建立前者对后者的一对多关系 🧩

fix: 🐛 大幅调整表结构与业务逻辑,修复大量历史遗留 bug 🔥
2026-02-07 16:33:30 +08:00

203 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"errors"
"log"
"github.com/LoveLosita/smartflow/backend/conv"
"github.com/LoveLosita/smartflow/backend/dao"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/go-redis/redis/v8"
)
type TaskClassService struct {
// 这里可以添加数据库连接或其他依赖
taskClassRepo *dao.TaskClassDAO
cacheRepo *dao.CacheDAO
scheduleRepo *dao.ScheduleDAO
}
func NewTaskClassService(taskClassRepo *dao.TaskClassDAO, cacheRepo *dao.CacheDAO, scheduleRepo *dao.ScheduleDAO) *TaskClassService {
return &TaskClassService{
taskClassRepo: taskClassRepo,
cacheRepo: cacheRepo,
scheduleRepo: scheduleRepo,
}
}
// AddOrUpdateTaskClass 为指定用户添加任务类
func (sv *TaskClassService) AddOrUpdateTaskClass(ctx context.Context, req *model.UserAddTaskClassRequest, userID int, method int, targetTaskClassID int) error {
// 1) 先写数据库(事务内)
if err := sv.taskClassRepo.Transaction(func(txDAO *dao.TaskClassDAO) error {
taskClass, items, err := conv.ProcessUserAddTaskClassRequest(req, userID)
if err != nil {
return err
}
if method == 1 { // 更新操作
taskClass.ID = targetTaskClassID
}
taskClassID, err := txDAO.AddOrUpdateTaskClass(userID, taskClass)
if err != nil {
return err
}
for i := range items {
items[i].CategoryID = &taskClassID
}
if err := txDAO.AddOrUpdateTaskClassItems(userID, items); err != nil {
return err
}
return nil
}); err != nil {
return err
}
// 2) 事务提交成功后删除该用户缓存(如果有)
err := sv.cacheRepo.DeleteTaskClassList(ctx, userID)
if err != nil {
log.Printf("redis删除任务分类列表缓存失败: userID=%d err=%v", userID, err)
}
return nil
}
func (sv *TaskClassService) GetUserTaskClassInfos(ctx context.Context, userID int) (*model.UserGetTaskClassesResponse, error) {
//1.先查询redis
list, err := sv.cacheRepo.GetTaskClassList(ctx, userID)
if err == nil {
//命中缓存
return &list, nil
} else if !errors.Is(err, redis.Nil) { //不是缓存未命中错误说明redis可能炸了照常放行
log.Println("redis获取任务分类列表失败:", err)
}
//2.缓存未命中,查询数据库
taskClasses, err := sv.taskClassRepo.GetUserTaskClasses(userID)
if err != nil {
return nil, err
}
resp := conv.TaskClassModelToResponse(taskClasses)
//3.写入缓存
err = sv.cacheRepo.AddTaskClassList(ctx, userID, resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (sv *TaskClassService) GetUserCompleteTaskClass(ctx context.Context, userID int, taskClassID int) (*model.UserAddTaskClassRequest, error) {
//1.查询数据库
taskClass, err := sv.taskClassRepo.GetCompleteTaskClassByID(ctx, taskClassID, userID)
if err != nil {
return nil, err
}
//2.转换为响应结构体
resp, err := conv.ProcessUserGetCompleteTaskClassRequest(taskClass)
if err != nil {
return nil, err
}
return resp, nil
}
func (sv *TaskClassService) AddTaskClassItemIntoSchedule(ctx context.Context, req *model.UserInsertTaskClassItemToScheduleRequest, userID int, taskID int) error {
//1.先验证任务块归属
taskClassID, err := sv.taskClassRepo.GetTaskClassIDByTaskItemID(ctx, taskID) //通过任务块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.取出任务块信息
taskItem, err := sv.taskClassRepo.GetTaskClassItemByID(ctx, taskID) //通过任务块ID获取任务块信息
if err != nil {
return err
}
//更新TaskClassItem的embedded_time字段
taskItem.EmbeddedTime = &model.TargetTime{
DayOfWeek: req.DayOfWeek,
Week: req.Week,
SectionFrom: req.StartSection,
SectionTo: req.EndSection,
}
//3.判断是否嵌入课程
if req.EmbedCourseEventID != 0 {
//先检查看课程是否存在、是否归属该用户以及是否已经被嵌入了其他任务块
courseOwnerID, err := sv.scheduleRepo.GetCourseUserIDByID(ctx, req.EmbedCourseEventID)
if err != nil {
return err
}
if courseOwnerID != userID {
return respond.CourseNotBelongToUser
}
//再检查用户给的时间是否和课程的时间匹配(目前逻辑是给的区间必须完全匹配)
match, err := sv.scheduleRepo.IsCourseTimeMatch(ctx, req.EmbedCourseEventID, req.Week, req.DayOfWeek, req.StartSection, req.EndSection)
if err != nil {
return err
}
if !match {
return respond.CourseTimeNotMatch
}
//查询对应时段的课程是否已被其他任务块嵌入了(目前业务限制:一个课程只能被一个任务块嵌入,但是目前设计是支持多个任务块嵌入一节课的,只要放得下)
isEmbedded, err := sv.scheduleRepo.IsCourseEmbeddedByOtherTaskBlock(ctx, req.EmbedCourseEventID, req.StartSection, req.EndSection)
if err != nil {
return err
}
if isEmbedded {
return respond.CourseAlreadyEmbeddedByOtherTaskBlock
}
//嵌入课程,直接更新日程表对应时段的 embedded_task_id 字段
err = sv.scheduleRepo.EmbedTaskIntoSchedule(req.StartSection, req.EndSection, req.DayOfWeek, req.Week, userID, taskID)
if err != nil {
return err
}
//更新任务块的 embedded_time 字段
err = sv.taskClassRepo.UpdateTaskClassItemEmbeddedTime(ctx, taskID, taskItem.EmbeddedTime)
if err != nil {
return err
}
return nil
}
//4.否则构造Schedule模型
sections := make([]int, 0, req.EndSection-req.StartSection+1)
schedules, scheduleEvent := conv.UserInsertTaskItemRequestToModel(req, taskItem, taskID, userID, req.StartSection, req.EndSection)
//4.1 统一检查冲突(避免逐条查库)
conflict, err := sv.scheduleRepo.HasUserScheduleConflict(ctx, userID, req.Week, req.DayOfWeek, sections)
if err != nil {
return err
}
if conflict {
return respond.ScheduleConflict
}
//5.写入数据库(事务)
if err := sv.taskClassRepo.Transaction(func(txDAO *dao.TaskClassDAO) error {
//5.1 先将任务块插入scheduleEvent表获取生成的ID后再插入schedule表因为schedule表有外键关联
id, err := sv.scheduleRepo.AddScheduleEvent(scheduleEvent)
if err != nil {
return err
}
//5.2 将生成的 ScheduleEvent ID 赋值给对应的 Schedule 的 EventID 字段
for i := range schedules {
schedules[i].EventID = id
}
//5.3 插入 Schedule 表
_, err = sv.scheduleRepo.AddSchedules(schedules)
if err != nil {
return err
}
//5.4 更新任务块的 embedded_time 字段
if err := txDAO.UpdateTaskClassItemEmbeddedTime(ctx, taskID, taskItem.EmbeddedTime); err != nil {
return err
}
return nil
}); err != nil {
return err
}
return nil
}