Version:0.0.4.dev.260203

feat: 📚 完成课程导入与校验接口(赶在 token 过期前…)

- 实现课程导入接口 
- 实现课程信息校验接口 🔍
- 测试通过 🧪

fix: 🐛 修了一堆 bug,15 分钟 accessToken 默默见证了时间的流逝 😭
This commit is contained in:
LoveLosita
2026-02-03 22:05:11 +08:00
parent a59bcfbc5e
commit f554d9bd06
8 changed files with 212 additions and 4 deletions

View File

@@ -3,4 +3,5 @@ package api
type ApiHandlers struct {
UserHandler *UserHandler
TaskHandler *TaskHandler
ScheduleHandler *ScheduleHandler
}

66
backend/api/schedule.go Normal file
View File

@@ -0,0 +1,66 @@
package api
import (
"errors"
"net/http"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
"github.com/LoveLosita/smartflow/backend/service"
"github.com/gin-gonic/gin"
)
type ScheduleHandler struct {
// 伸出手:准备接住 Service
service *service.ScheduleService
}
// NewScheduleHandler 创建 ScheduleHandler 实例
func NewScheduleHandler(service *service.ScheduleService) *ScheduleHandler {
return &ScheduleHandler{
service: service,
}
}
func (sa *ScheduleHandler) CheckUserCourse(c *gin.Context) {
//1.从请求中获取课程信息
var req model.UserCheckCourseRequest
err := c.ShouldBindJSON(&req)
if err != nil {
c.JSON(http.StatusBadRequest, respond.WrongParamType)
return
}
//2.调用 service 层的 CheckSingleCourse 方法进行校验
result := service.CheckSingleCourse(req)
//3.根据校验结果返回响应
if result {
c.JSON(http.StatusOK, respond.Ok)
} else {
c.JSON(http.StatusBadRequest, respond.WrongCourseInfo)
}
}
func (sa *ScheduleHandler) AddUserCourses(c *gin.Context) {
//1.从请求中获取课程信息
var req model.UserImportCoursesRequest
err := c.ShouldBindJSON(&req)
if err != nil {
c.JSON(http.StatusBadRequest, respond.WrongParamType)
return
}
//2.从上下文获取用户ID
userIDInterface := c.GetInt("user_id")
//3.调用 service 层的 AddUserCourses 方法添加课程
err = sa.service.AddUserCourses(req, userIDInterface)
if err != nil {
switch {
case errors.Is(err, respond.WrongParamType), errors.Is(err, respond.WrongCourseInfo):
c.JSON(http.StatusBadRequest, err)
default:
c.JSON(http.StatusInternalServerError, respond.InternalError(err))
}
return
}
//4.返回成功响应
c.JSON(http.StatusOK, respond.Ok)
}

View File

@@ -43,16 +43,20 @@ func Start() {
userRepo := dao.NewUserDAO(db)
cacheRepo := dao.NewCacheDAO(rdb)
taskRepo := dao.NewTaskDAO(db)
scheduleRepo := dao.NewScheduleDAO(db)
//service 层
userService := service.NewUserService(userRepo, cacheRepo)
taskSv := service.NewTaskService(taskRepo)
scheduleService := service.NewScheduleService(scheduleRepo)
//api 层
userApi := api.NewUserHandler(userService)
taskApi := api.NewTaskHandler(taskSv)
scheduleApi := api.NewScheduleHandler(scheduleService)
handlers := &api.ApiHandlers{
UserHandler: userApi,
TaskHandler: taskApi,
ScheduleHandler: scheduleApi,
}
r := routers.RegisterRouters(handlers, cacheRepo)
routers.StartEngine(r)

24
backend/dao/schedule.go Normal file
View File

@@ -0,0 +1,24 @@
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
}

31
backend/model/schedule.go Normal file
View File

@@ -0,0 +1,31 @@
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"`
}

View File

@@ -133,4 +133,9 @@ var ( //请求相关的响应
Status: "40018",
Info: "invalid priority",
}
WrongCourseInfo = Response{ //课程信息错误
Status: "40019",
Info: "wrong course info",
}
)

View File

@@ -54,6 +54,12 @@ 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")
{
scheduleGroup.Use(middleware.JWTTokenAuth(cache))
scheduleGroup.POST("/validate", handlers.ScheduleHandler.CheckUserCourse)
scheduleGroup.POST("/import-courses", handlers.ScheduleHandler.AddUserCourses)
}
}
// 初始化Gin引擎
log.Println("Routes setup completed")

View File

@@ -0,0 +1,71 @@
package service
import (
"fmt"
"github.com/LoveLosita/smartflow/backend/dao"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/LoveLosita/smartflow/backend/respond"
)
type ScheduleService struct {
// 伸出手:准备接住 DAO
dao *dao.ScheduleDAO
}
// NewScheduleService 创建 ScheduleService 实例
func NewScheduleService(dao *dao.ScheduleDAO) *ScheduleService {
return &ScheduleService{
dao: dao,
}
}
func CheckSingleCourse(req model.UserCheckCourseRequest) bool {
for _, arrangement := range req.Arrangements {
if arrangement.StartWeek > arrangement.EndWeek ||
arrangement.DayOfWeek < 1 || arrangement.DayOfWeek > 7 ||
arrangement.StartSection < 1 || arrangement.EndSection < arrangement.StartSection ||
arrangement.EndSection > 12 || arrangement.StartWeek < 1 || arrangement.EndWeek > 24 {
return false
}
}
return true
}
// AddUserCourses 添加用户课程表
func (ss *ScheduleService) AddUserCourses(req model.UserImportCoursesRequest, userID int) error {
//1.先校验参数是否正确
for _, course := range req.Courses {
result := CheckSingleCourse(course)
if !result {
return respond.WrongCourseInfo
}
}
//2.转换为 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,
}
schedules = append(schedules, schedule)
}
}
//3.调用 DAO 方法添加课程
err := ss.dao.AddUserCourses(schedules)
if err != nil {
return err
}
}
//4.返回结果
return nil
}