diff --git a/backend/api/container.go b/backend/api/container.go index 4669cb9..e4b0046 100644 --- a/backend/api/container.go +++ b/backend/api/container.go @@ -3,6 +3,6 @@ package api type ApiHandlers struct { UserHandler *UserHandler TaskHandler *TaskHandler - ScheduleHandler *ScheduleHandler + ScheduleHandler *CourseHandler TaskClassHandler *TaskClassHandler } diff --git a/backend/api/schedule.go b/backend/api/course.go similarity index 82% rename from backend/api/schedule.go rename to backend/api/course.go index bad3d7c..bc77df7 100644 --- a/backend/api/schedule.go +++ b/backend/api/course.go @@ -12,19 +12,19 @@ import ( "github.com/gin-gonic/gin" ) -type ScheduleHandler struct { +type CourseHandler struct { // 伸出手:准备接住 Service - service *service.ScheduleService + service *service.CourseService } -// NewScheduleHandler 创建 ScheduleHandler 实例 -func NewScheduleHandler(service *service.ScheduleService) *ScheduleHandler { - return &ScheduleHandler{ +// NewCourseHandler 创建 CourseHandler 实例 +func NewCourseHandler(service *service.CourseService) *CourseHandler { + return &CourseHandler{ service: service, } } -func (sa *ScheduleHandler) CheckUserCourse(c *gin.Context) { +func (sa *CourseHandler) CheckUserCourse(c *gin.Context) { //1.从请求中获取课程信息 var req model.UserCheckCourseRequest err := c.ShouldBindJSON(&req) @@ -42,7 +42,7 @@ func (sa *ScheduleHandler) CheckUserCourse(c *gin.Context) { } } -func (sa *ScheduleHandler) AddUserCourses(c *gin.Context) { +func (sa *CourseHandler) AddUserCourses(c *gin.Context) { //1.从请求中获取课程信息 var req model.UserImportCoursesRequest err := c.ShouldBindJSON(&req) diff --git a/backend/cmd/start.go b/backend/cmd/start.go index d205dc7..daa269b 100644 --- a/backend/cmd/start.go +++ b/backend/cmd/start.go @@ -43,23 +43,23 @@ func Start() { userRepo := dao.NewUserDAO(db) cacheRepo := dao.NewCacheDAO(rdb) taskRepo := dao.NewTaskDAO(db) - scheduleRepo := dao.NewScheduleDAO(db) + courseRepo := dao.NewCourseDAO(db) taskClassRepo := dao.NewTaskClassDAO(db) //service 层 userService := service.NewUserService(userRepo, cacheRepo) taskSv := service.NewTaskService(taskRepo) - scheduleService := service.NewScheduleService(scheduleRepo) + courseService := service.NewCourseService(courseRepo) taskClassService := service.NewTaskClassService(taskClassRepo, cacheRepo) //api 层 userApi := api.NewUserHandler(userService) taskApi := api.NewTaskHandler(taskSv) - scheduleApi := api.NewScheduleHandler(scheduleService) + courseApi := api.NewCourseHandler(courseService) taskClassApi := api.NewTaskClassHandler(taskClassService) handlers := &api.ApiHandlers{ UserHandler: userApi, TaskHandler: taskApi, - ScheduleHandler: scheduleApi, + ScheduleHandler: courseApi, TaskClassHandler: taskClassApi, } r := routers.RegisterRouters(handlers, cacheRepo) diff --git a/backend/dao/course.go b/backend/dao/course.go new file mode 100644 index 0000000..705c95b --- /dev/null +++ b/backend/dao/course.go @@ -0,0 +1,24 @@ +package dao + +import ( + "github.com/LoveLosita/smartflow/backend/model" + "gorm.io/gorm" +) + +type CourseDAO struct { + db *gorm.DB +} + +// NewCourseDAO 创建ScheduleDAO实例 +func NewCourseDAO(db *gorm.DB) *CourseDAO { + return &CourseDAO{ + db: db, + } +} + +func (dao *CourseDAO) AddUserCourses(courses []model.Schedule) error { + if err := dao.db.Create(&courses).Error; err != nil { + return err + } + return nil +} diff --git a/backend/dao/schedule.go b/backend/dao/schedule.go deleted file mode 100644 index 0764e25..0000000 --- a/backend/dao/schedule.go +++ /dev/null @@ -1,24 +0,0 @@ -package dao - -import ( - "github.com/LoveLosita/smartflow/backend/model" - "gorm.io/gorm" -) - -type ScheduleDAO struct { - db *gorm.DB -} - -// NewScheduleDAO 创建ScheduleDAO实例 -func NewScheduleDAO(db *gorm.DB) *ScheduleDAO { - return &ScheduleDAO{ - db: db, - } -} - -func (dao *ScheduleDAO) AddUserCourses(courses []model.Schedule) error { - if err := dao.db.Create(&courses).Error; err != nil { - return err - } - return nil -} diff --git a/backend/model/course.go b/backend/model/course.go new file mode 100644 index 0000000..b04c360 --- /dev/null +++ b/backend/model/course.go @@ -0,0 +1,18 @@ +package model + +type UserImportCoursesRequest struct { + Courses []UserCheckCourseRequest `json:"courses"` +} + +type UserCheckCourseRequest struct { + CourseName string `json:"course_name"` + Location string `json:"location"` + IsAllowTasks bool `json:"is_allow_tasks"` + Arrangements []struct { + StartWeek int `json:"start_week"` + EndWeek int `json:"end_week"` + DayOfWeek int `json:"day_of_week"` + StartSection int `json:"start_section"` + EndSection int `json:"end_section"` + } `json:"arrangements"` +} diff --git a/backend/model/schedule.go b/backend/model/schedule.go index 18967c5..55ce168 100644 --- a/backend/model/schedule.go +++ b/backend/model/schedule.go @@ -3,29 +3,12 @@ package model type Schedule struct { ID int `gorm:"primaryKey;autoIncrement" json:"id"` UserID int `gorm:"column:user_id;index" json:"user_id"` - Type string `gorm:"type:enum('course','task');comment:'course / task'" json:"type"` - RelID int `gorm:"column:rel_id;comment:'关联 course_id 或 task_item_id'" json:"rel_id"` - CanBeEmbedded bool `gorm:"column:can_be_embedded;comment:'是否允许嵌入水课'" json:"can_be_embedded"` - EmbeddedTaskID *uint `gorm:"column:embedded_task_id;comment:'若为水课嵌入,记录任务ID'" json:"embedded_task_id"` - Week int `gorm:"column:week" json:"week"` - DayOfWeek int `gorm:"column:day_of_week" json:"day_of_week"` - Sections string `gorm:"type:varchar(255)" json:"sections"` - Status string `gorm:"type:enum('normal','interrupted');default:'normal'" json:"status"` -} - -type UserImportCoursesRequest struct { - Courses []UserCheckCourseRequest `json:"courses"` -} - -type UserCheckCourseRequest struct { - CourseName string `json:"course_name"` - Location string `json:"location"` - IsAllowTasks bool `json:"is_allow_tasks"` - Arrangements []struct { - StartWeek int `json:"start_week"` - EndWeek int `json:"end_week"` - DayOfWeek int `json:"day_of_week"` - StartSection int `json:"start_section"` - EndSection int `json:"end_section"` - } `json:"arrangements"` + Type string `gorm:"type:enum('course','task');comment:course / task" json:"type"` + RelID int `gorm:"column:rel_id;comment:关联 course_id 或 task_item_id" json:"rel_id"` + EmbeddedTaskID *int `gorm:"column:embedded_task_id;index;comment:若为水课嵌入,记录任务ID" json:"embedded_task_id"` + Week int `gorm:"column:week;uniqueIndex:idx_user_slot_atomic,priority:2;comment:周次 (1-25)" json:"week"` + DayOfWeek int `gorm:"column:day_of_week;uniqueIndex:idx_user_slot_atomic,priority:3;comment:星期 (1-7)" json:"day_of_week"` + Section int `gorm:"column:section;uniqueIndex:idx_user_slot_atomic,priority:4;comment:原子化节次 (1-12)" json:"section"` + Status string `gorm:"type:enum('normal','interrupted');default:normal" json:"status"` + CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;comment:是否允许嵌入任务" json:"can_be_embedded"` } diff --git a/backend/model/task-class.go b/backend/model/task-class.go index 80cbde3..f7dec23 100644 --- a/backend/model/task-class.go +++ b/backend/model/task-class.go @@ -7,6 +7,7 @@ import ( "time" ) +// TaskClass 用于和数据库中的 task_classes 表进行映射 type TaskClass struct { //section 1 ID int `gorm:"column:id;primaryKey;autoIncrement"` @@ -24,33 +25,7 @@ type TaskClass struct { Items []TaskClassItem `gorm:"foreignKey:CategoryID;references:ID"` // 一对多关联:一个 TaskClass 有多个 TaskClassItem } -// TableName 设定 TaskClass 的表名为 task_classes -func (TaskClass) TableName() string { - return "task_classes" -} - -type UserAddTaskClassRequest struct { - Name string `json:"name" binding:"required"` - StartDate string `json:"start_date" binding:"required"` // YYYY-MM-DD - EndDate string `json:"end_date" binding:"required"` // YYYY-MM-DD - Mode string `json:"mode" binding:"required,oneof=auto manual"` - Config UserAddTaskClassConfig `json:"config" binding:"required"` - Items []UserAddTaskClassItemRequest `json:"items" binding:"required"` -} - -type UserAddTaskClassConfig struct { - TotalSlots int `json:"total_slots" binding:"required,min=1"` - AllowFillerCourse bool `json:"allow_filler_course"` - Strategy string `json:"strategy" binding:"required,oneof=steady rapid"` - ExcludedSlots []int `json:"excluded_slots"` -} - -type UserAddTaskClassItemRequest struct { - Order int `json:"order" binding:"required,min=1"` - Content string `json:"content" binding:"required"` - EmbeddedTime *TargetTime `json:"embedded_time"` // 例: 2025-12-22 1-2节; nil 表示未安排 -} - +// TaskClassItem 用于和数据库中的 task_items 表进行映射 type TaskClassItem struct { //section 1 ID int `gorm:"column:id;primaryKey;autoIncrement"` @@ -62,12 +37,55 @@ type TaskClassItem struct { Status *int `gorm:"column:status;comment:1:未安排, 2:已应用"` } +// UserAddTaskClassRequest 用于处理用户添加任务类别的请求 +type UserAddTaskClassRequest struct { + Name string `json:"name" binding:"required"` + StartDate string `json:"start_date" binding:"required"` // YYYY-MM-DD + EndDate string `json:"end_date" binding:"required"` // YYYY-MM-DD + Mode string `json:"mode" binding:"required,oneof=auto manual"` + Config UserAddTaskClassConfig `json:"config" binding:"required"` + Items []UserAddTaskClassItemRequest `json:"items" binding:"required"` +} + +// UserAddTaskClassConfig 用于处理用户添加任务类别时的配置部分 +type UserAddTaskClassConfig struct { + TotalSlots int `json:"total_slots" binding:"required,min=1"` + AllowFillerCourse bool `json:"allow_filler_course"` + Strategy string `json:"strategy" binding:"required,oneof=steady rapid"` + ExcludedSlots []int `json:"excluded_slots"` +} + +// UserAddTaskClassItemRequest 用于处理用户添加任务类别时的任务块部分 +type UserAddTaskClassItemRequest struct { + Order int `json:"order" binding:"required,min=1"` + Content string `json:"content" binding:"required"` + EmbeddedTime *TargetTime `json:"embedded_time"` // 例: 2025-12-22 1-2节; nil 表示未安排 +} + +// TargetTime 表示任务块的目标时间 type TargetTime struct { Date string `json:"date"` // 例: 2025-12-22 SectionFrom int `json:"section_from"` // 起始节次 SectionTo int `json:"section_to"` // 结束节次 } +// UserGetTaskClassesResponse 用于返回用户的任务类列表,展示简要信息 +type UserGetTaskClassesResponse struct { + TaskClasses []TaskClassSummary `json:"task_classes"` +} + +// TaskClassSummary 提供任务类别的简要信息 +type TaskClassSummary struct { + ID int `json:"id"` + Name string `json:"name"` + Mode string `json:"mode"` + Strategy string `json:"strategy"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` + TotalSlots int `json:"total_slots"` +} + +// Value 实现 driver.Valuer 接口,负责将 TargetTime 转换为数据库存储的格式 func (t *TargetTime) Value() (driver.Value, error) { if t == nil { return nil, nil @@ -77,6 +95,7 @@ func (t *TargetTime) Value() (driver.Value, error) { return json.Marshal(t) } +// Scan 实现 sql.Scanner 接口,负责将数据库中的值转换为 TargetTime 结构体 func (t *TargetTime) Scan(value any) error { if value == nil { // 如果数据库是 NULL,保持指针对应的对象为零值即可 @@ -97,25 +116,18 @@ func (t *TargetTime) Scan(value any) error { return json.Unmarshal(data, t) } +// TableName 指定 TaskClass 对应的数据库表名 +func (TaskClass) TableName() string { + return "task_classes" +} + +// TableName 指定 TaskClassItem 对应的数据库表名 func (TaskClassItem) TableName() string { return "task_items" } +// 任务块状态常量 const ( - TaskItemStatusUnscheduled = 1 - TaskItemStatusApplied = 2 + TaskItemStatusUnscheduled = 1 // 未安排 + TaskItemStatusApplied = 2 // 已应用 ) - -type UserGetTaskClassesResponse struct { - TaskClasses []TaskClassSummary `json:"task_classes"` -} - -type TaskClassSummary struct { - ID int `json:"id"` - Name string `json:"name"` - Mode string `json:"mode"` - Strategy string `json:"strategy"` - StartDate time.Time `json:"start_date"` - EndDate time.Time `json:"end_date"` - TotalSlots int `json:"total_slots"` -} diff --git a/backend/routers/routers.go b/backend/routers/routers.go index 2c74740..c82bf88 100644 --- a/backend/routers/routers.go +++ b/backend/routers/routers.go @@ -54,11 +54,11 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO) *gin.Engine taskGroup.POST("/create", handlers.TaskHandler.AddTask) taskGroup.GET("/get", handlers.TaskHandler.GetUserTasks) } - scheduleGroup := apiGroup.Group("/schedule") + courseGroup := apiGroup.Group("/course") { - scheduleGroup.Use(middleware.JWTTokenAuth(cache)) - scheduleGroup.POST("/validate", handlers.ScheduleHandler.CheckUserCourse) - scheduleGroup.POST("/import-courses", handlers.ScheduleHandler.AddUserCourses) + courseGroup.Use(middleware.JWTTokenAuth(cache)) + courseGroup.POST("/validate", handlers.ScheduleHandler.CheckUserCourse) + courseGroup.POST("/import", handlers.ScheduleHandler.AddUserCourses) } taskClassGroup := apiGroup.Group("/task-class") { diff --git a/backend/service/schedule.go b/backend/service/course.go similarity index 56% rename from backend/service/schedule.go rename to backend/service/course.go index 4de6fa1..1b831d3 100644 --- a/backend/service/schedule.go +++ b/backend/service/course.go @@ -2,21 +2,20 @@ package service import ( "context" - "fmt" "github.com/LoveLosita/smartflow/backend/dao" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" ) -type ScheduleService struct { +type CourseService struct { // 伸出手:准备接住 DAO - dao *dao.ScheduleDAO + dao *dao.CourseDAO } -// NewScheduleService 创建 ScheduleService 实例 -func NewScheduleService(dao *dao.ScheduleDAO) *ScheduleService { - return &ScheduleService{ +// NewCourseService 创建 CourseService 实例 +func NewCourseService(dao *dao.CourseDAO) *CourseService { + return &CourseService{ dao: dao, } } @@ -34,7 +33,7 @@ func CheckSingleCourse(req model.UserCheckCourseRequest) bool { } // AddUserCourses 添加用户课程表 -func (ss *ScheduleService) AddUserCourses(ctx context.Context, req model.UserImportCoursesRequest, userID int) error { +func (ss *CourseService) AddUserCourses(ctx context.Context, req model.UserImportCoursesRequest, userID int) error { //1.先校验参数是否正确 for _, course := range req.Courses { result := CheckSingleCourse(course) @@ -43,30 +42,32 @@ func (ss *ScheduleService) AddUserCourses(ctx context.Context, req model.UserImp } } //2.转换为 Schedule 切片 + var finalSchedules []model.Schedule for _, course := range req.Courses { var schedules []model.Schedule for _, arrangement := range course.Arrangements { for week := arrangement.StartWeek; week <= arrangement.EndWeek; week++ { - sections := fmt.Sprintf("%d-%d", arrangement.StartSection, arrangement.EndSection) - schedule := model.Schedule{ - Type: "course", - Week: week, - DayOfWeek: arrangement.DayOfWeek, - Sections: sections, - Status: "normal", - UserID: userID, - CanBeEmbedded: course.IsAllowTasks, + for section := arrangement.StartSection; section <= arrangement.EndSection; section++ { + schedule := model.Schedule{ + Type: "course", + Week: week, + DayOfWeek: arrangement.DayOfWeek, + Section: section, + Status: "normal", + UserID: userID, + CanBeEmbedded: course.IsAllowTasks, + } + schedules = append(schedules, schedule) } - schedules = append(schedules, schedule) } } - //3.调用 DAO 方法添加课程 - err := ss.dao.AddUserCourses(schedules) - if err != nil { - return err - } + finalSchedules = append(finalSchedules, schedules...) + } + //3.调用 DAO 方法添加课程 + err := ss.dao.AddUserCourses(finalSchedules) + if err != nil { + return err } - //4.返回结果 return nil }