Version: 0.1.1.dev.260207 - 新增并测试通过将任务块排进日程接口 ✅ - 批量导入课程接口增加单双周功能,支持只在单双周上课的课程 📚 - 任务块时间定位逻辑调整为「第几周-周几」模式 🧭 refactor: 🔨 重构时间与日程数据结构 - 完成绝对日期与相对时间的转换逻辑 🔄 - 后续可根据需求灵活决定时间的传入与输出类型 - 再次重构 schedule 表单结构 - 拆分为 schedule_event(单)与 schedule(多) - 建立前者对后者的一对多关系 🧩 fix: 🐛 大幅调整表结构与业务逻辑,修复大量历史遗留 bug 🔥
203 lines
6.7 KiB
Go
203 lines
6.7 KiB
Go
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
|
||
}
|