diff --git a/backend/api/schedule.go b/backend/api/schedule.go index 89d01d5..320effe 100644 --- a/backend/api/schedule.go +++ b/backend/api/schedule.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net/http" + "strconv" "time" "github.com/LoveLosita/smartflow/backend/respond" @@ -42,3 +43,30 @@ func (s *ScheduleAPI) GetUserTodaySchedule(c *gin.Context) { //3.返回日程安排数据给前端 c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, todaySchedules)) } + +func (s *ScheduleAPI) GetUserWeeklySchedule(c *gin.Context) { + // 1. 从请求上下文中获取用户ID + userID := c.GetInt("user_id") + // 2. 从查询参数中获取 week 参数 + week, err := strconv.Atoi(c.Query("week")) + if err != nil { + c.JSON(http.StatusBadRequest, respond.WrongParamType) + return + } + //3.调用服务层方法获取用户当周的日程安排 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + weeklySchedules, err := s.scheduleService.GetUserWeeklySchedule(ctx, userID, week) + if err != nil { + switch { + case errors.Is(err, respond.WrongUserID), errors.Is(err, respond.WeekOutOfRange): + c.JSON(http.StatusBadRequest, err) + return + default: + c.JSON(http.StatusInternalServerError, respond.InternalError(err)) + return + } + } + //4.返回日程安排数据给前端 + c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, weeklySchedules)) +} diff --git a/backend/conv/schedule.go b/backend/conv/schedule.go index 0177b0e..518aa85 100644 --- a/backend/conv/schedule.go +++ b/backend/conv/schedule.go @@ -156,35 +156,29 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS if len(schedules) == 0 { return []model.UserTodaySchedule{} } - // 1. 数据预处理:按 Week-Day 分组 dayGroups := make(map[string][]model.Schedule) for _, s := range schedules { dayKey := fmt.Sprintf("%d-%d", s.Week, s.DayOfWeek) dayGroups[dayKey] = append(dayGroups[dayKey], s) } - var result []model.UserTodaySchedule - for _, daySchedules := range dayGroups { todayDTO := model.UserTodaySchedule{ Week: daySchedules[0].Week, DayOfWeek: daySchedules[0].DayOfWeek, Events: []model.EventBrief{}, } - // 💡 关键点:建立一个 Section 查找表,方便 O(1) 确定某节课是什么 sectionMap := make(map[int]model.Schedule) for _, s := range daySchedules { sectionMap[s.Section] = s } - order := 1 // 💡 线性扫描:从第 1 节巡检到第 12 节 for curr := 1; curr <= 12; { if slot, ok := sectionMap[curr]; ok { // === A 场景:当前节次有课 === - // 1. 寻找该事件的连续范围(比如 9-12 节连上) // 我们向后探测,直到 EventID 变化或节次断开 end := curr @@ -195,7 +189,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS break } } - // 2. 封装 EventBrief brief := model.EventBrief{ ID: slot.EventID, @@ -207,7 +200,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS EndTime: sectionTimeMap[end][1], Span: end - curr + 1, } - // 3. 处理嵌入任务 // 只要这几个连续节次里有一个有任务,就带上 for i := curr; i <= end; i++ { @@ -220,16 +212,12 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS break } } - todayDTO.Events = append(todayDTO.Events, brief) - // 💡 指针跳跃:直接跳过已处理的节次 curr = end + 1 order++ - } else { // === B 场景:当前节次没课(Type = "empty") === - // 逻辑:按照学校标准大节(1-2, 3-4...)进行空位合并 // 如果当前是奇数节(1, 3, 5...)且下一节也没课,就合并成一个空块 emptyEnd := curr @@ -238,7 +226,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS emptyEnd = curr + 1 } } - todayDTO.Events = append(todayDTO.Events, model.EventBrief{ ID: 0, // 空课 ID 为 0 Order: order, @@ -248,14 +235,110 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS EndTime: sectionTimeMap[emptyEnd][1], Location: "休息时间", }) - curr = emptyEnd + 1 order++ } } - result = append(result, todayDTO) } - + return result +} + +func SchedulesToUserWeeklySchedule(schedules []model.Schedule) []model.UserWeekSchedule { + if len(schedules) == 0 { + return []model.UserWeekSchedule{} + } + // 1. 数据预处理:按 Week 分组 + weekGroups := make(map[int][]model.Schedule) + for _, s := range schedules { + weekGroups[s.Week] = append(weekGroups[s.Week], s) + } + var result []model.UserWeekSchedule + // 2. 遍历每一个周 + for week, weekSchedules := range weekGroups { + weekDTO := model.UserWeekSchedule{ + Week: week, + Events: []model.WeeklyEventBrief{}, + } + // 💡 核心优化:建立 [天][节次] 的快速索引地图 + // indexMap[day][section] -> model.Schedule + indexMap := make(map[int]map[int]model.Schedule) + for d := 1; d <= 7; d++ { + indexMap[d] = make(map[int]model.Schedule) + } + for _, s := range weekSchedules { + indexMap[s.DayOfWeek][s.Section] = s + } + // 3. 线性扫描 1-7 天 + for day := 1; day <= 7; day++ { + order := 1 // 每一天开始时,Order 重置 + // 4. 线性扫描 1-12 节 + for curr := 1; curr <= 12; { + // 检查当前槽位是否有课 + if slot, hasClass := indexMap[day][curr]; hasClass { + // === A 场景:有课,寻找连续边界 (Span 计算) === + end := curr + // 探测逻辑:只要 EventID 相同且在同一天,就视为同一个块 + for next := curr + 1; next <= 12; next++ { + if nextSlot, exist := indexMap[day][next]; exist && nextSlot.EventID == slot.EventID { + end = next + } else { + break + } + } + span := end - curr + 1 + brief := model.WeeklyEventBrief{ + ID: slot.EventID, + Order: order, + DayOfWeek: day, + Name: slot.Event.Name, + Location: *slot.Event.Location, + Type: slot.Event.Type, + StartTime: sectionTimeMap[curr][0], + EndTime: sectionTimeMap[end][1], + Span: span, + } + // 提取嵌入任务信息 (如果有) + for i := curr; i <= end; i++ { + if s, exist := indexMap[day][i]; exist && s.EmbeddedTask != nil { + brief.EmbeddedTaskInfo = model.TaskBrief{ + ID: s.EmbeddedTask.ID, + Name: *s.EmbeddedTask.Content, + Type: "task", + } + break + } + } + weekDTO.Events = append(weekDTO.Events, brief) + curr = end + 1 // 指针跳跃 + order++ + } else { + // === B 场景:无课 (Type="empty") === + // 逻辑:默认按“大节”合并空位(1-2, 3-4...) + emptyEnd := curr + // 如果是奇数节且下一节也没课,则合并为一个 2 节的大空块 + if curr%2 != 0 && curr < 12 { + if _, nextHasClass := indexMap[day][curr+1]; !nextHasClass { + emptyEnd = curr + 1 + } + } + weekDTO.Events = append(weekDTO.Events, model.WeeklyEventBrief{ + ID: 0, + Order: order, + DayOfWeek: day, + Name: "无课", + Type: "empty", + StartTime: sectionTimeMap[curr][0], + EndTime: sectionTimeMap[emptyEnd][1], + Span: emptyEnd - curr + 1, + Location: "", + }) + curr = emptyEnd + 1 + order++ + } + } + } + result = append(result, weekDTO) + } return result } diff --git a/backend/dao/schedule.go b/backend/dao/schedule.go index 015959f..0f07a67 100644 --- a/backend/dao/schedule.go +++ b/backend/dao/schedule.go @@ -284,3 +284,20 @@ func (d *ScheduleDAO) GetUserTodaySchedule(ctx context.Context, userID, week, da return schedules, nil } + +func (d *ScheduleDAO) GetUserWeeklySchedule(ctx context.Context, userID, week int) ([]model.Schedule, error) { + var schedules []model.Schedule + + err := d.db.WithContext(ctx). + Preload("Event"). + Preload("EmbeddedTask"). + Where("user_id = ? AND week = ?", userID, week). + Order("day_of_week ASC, section ASC"). + Find(&schedules).Error + + if err != nil { + return nil, err + } + + return schedules, nil +} diff --git a/backend/model/schedule.go b/backend/model/schedule.go index 359c110..f61de80 100644 --- a/backend/model/schedule.go +++ b/backend/model/schedule.go @@ -56,7 +56,7 @@ type EventBrief struct { EndTime string `json:"end_time"` Location string `json:"location"` Type string `json:"type"` - Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度 + Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度/高度 EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"` } @@ -68,6 +68,24 @@ type TaskBrief struct { Type string `json:"type"` } +type UserWeekSchedule struct { + Week int `json:"week"` + Events []WeeklyEventBrief `json:"events"` +} + +type WeeklyEventBrief struct { + ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID,不是 Schedule 的 ID + Order int `json:"order"` // order 用于区分它们在一天中的显示顺序 + DayOfWeek int `json:"day_of_week"` + Name string `json:"name"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + Location string `json:"location"` + Type string `json:"type"` + Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度/高度 + EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"` +} + func (ScheduleEvent) TableName() string { return "schedule_events" } func (Schedule) TableName() string { return "schedules" } diff --git a/backend/respond/respond.go b/backend/respond/respond.go index 61f6b00..bff1f31 100644 --- a/backend/respond/respond.go +++ b/backend/respond/respond.go @@ -188,4 +188,9 @@ var ( //请求相关的响应 Status: "40029", Info: "insert course twice", } + + WeekOutOfRange = Response{ //周数超出范围 + Status: "40030", + Info: "week out of range", + } ) diff --git a/backend/routers/routers.go b/backend/routers/routers.go index 14195ff..5534052 100644 --- a/backend/routers/routers.go +++ b/backend/routers/routers.go @@ -73,6 +73,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO) *gin.Engine { scheduleGroup.Use(middleware.JWTTokenAuth(cache)) scheduleGroup.GET("/today", handlers.ScheduleHandler.GetUserTodaySchedule) + scheduleGroup.GET("/week", handlers.ScheduleHandler.GetUserWeeklySchedule) } } // 初始化Gin引擎 diff --git a/backend/service/schedule.go b/backend/service/schedule.go index 1519795..8bfc4a9 100644 --- a/backend/service/schedule.go +++ b/backend/service/schedule.go @@ -8,6 +8,7 @@ import ( "github.com/LoveLosita/smartflow/backend/conv" "github.com/LoveLosita/smartflow/backend/dao" + "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" "gorm.io/gorm" ) @@ -24,7 +25,7 @@ func NewScheduleService(scheduleDAO *dao.ScheduleDAO, userDAO *dao.UserDAO) *Sch } } -func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int) (interface{}, error) { +func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int) ([]model.UserTodaySchedule, error) { //1.先检查用户id是否存在 _, err := ss.userDAO.GetUserByID(userID) if err != nil { @@ -50,3 +51,35 @@ func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int) todaySchedules := conv.SchedulesToUserTodaySchedule(schedules) return todaySchedules, nil } + +func (ss *ScheduleService) GetUserWeeklySchedule(ctx context.Context, userID, week int) ([]model.UserWeekSchedule, error) { + //1.先检查 week 参数是否合法 + if week < 0 || week > 25 { + return nil, respond.WeekOutOfRange + } + //2.先检查用户id是否存在 + _, err := ss.userDAO.GetUserByID(userID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, respond.WrongUserID + } + return nil, err + } + //2.查询用户每周的日程安排 + //如果没有传入 week 参数,则默认查询当前周的日程安排 + if week == 0 { + curTime := time.Now().Format("2006-01-02") + var err error + week, _, err = conv.RealDateToRelativeDate(curTime) + if err != nil { + return nil, err + } + } + schedules, err := ss.scheduleDAO.GetUserWeeklySchedule(ctx, userID, week) + if err != nil { + return nil, err + } + //3.转换为前端需要的格式 + weeklySchedules := conv.SchedulesToUserWeeklySchedule(schedules) + return weeklySchedules, nil +}