Version: 0.1.4.dev.260208
feat: 📆 新增查看用户整周日程接口
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/LoveLosita/smartflow/backend/respond"
|
"github.com/LoveLosita/smartflow/backend/respond"
|
||||||
@@ -42,3 +43,30 @@ func (s *ScheduleAPI) GetUserTodaySchedule(c *gin.Context) {
|
|||||||
//3.返回日程安排数据给前端
|
//3.返回日程安排数据给前端
|
||||||
c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, todaySchedules))
|
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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,35 +156,29 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
if len(schedules) == 0 {
|
if len(schedules) == 0 {
|
||||||
return []model.UserTodaySchedule{}
|
return []model.UserTodaySchedule{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 数据预处理:按 Week-Day 分组
|
// 1. 数据预处理:按 Week-Day 分组
|
||||||
dayGroups := make(map[string][]model.Schedule)
|
dayGroups := make(map[string][]model.Schedule)
|
||||||
for _, s := range schedules {
|
for _, s := range schedules {
|
||||||
dayKey := fmt.Sprintf("%d-%d", s.Week, s.DayOfWeek)
|
dayKey := fmt.Sprintf("%d-%d", s.Week, s.DayOfWeek)
|
||||||
dayGroups[dayKey] = append(dayGroups[dayKey], s)
|
dayGroups[dayKey] = append(dayGroups[dayKey], s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var result []model.UserTodaySchedule
|
var result []model.UserTodaySchedule
|
||||||
|
|
||||||
for _, daySchedules := range dayGroups {
|
for _, daySchedules := range dayGroups {
|
||||||
todayDTO := model.UserTodaySchedule{
|
todayDTO := model.UserTodaySchedule{
|
||||||
Week: daySchedules[0].Week,
|
Week: daySchedules[0].Week,
|
||||||
DayOfWeek: daySchedules[0].DayOfWeek,
|
DayOfWeek: daySchedules[0].DayOfWeek,
|
||||||
Events: []model.EventBrief{},
|
Events: []model.EventBrief{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 关键点:建立一个 Section 查找表,方便 O(1) 确定某节课是什么
|
// 💡 关键点:建立一个 Section 查找表,方便 O(1) 确定某节课是什么
|
||||||
sectionMap := make(map[int]model.Schedule)
|
sectionMap := make(map[int]model.Schedule)
|
||||||
for _, s := range daySchedules {
|
for _, s := range daySchedules {
|
||||||
sectionMap[s.Section] = s
|
sectionMap[s.Section] = s
|
||||||
}
|
}
|
||||||
|
|
||||||
order := 1
|
order := 1
|
||||||
// 💡 线性扫描:从第 1 节巡检到第 12 节
|
// 💡 线性扫描:从第 1 节巡检到第 12 节
|
||||||
for curr := 1; curr <= 12; {
|
for curr := 1; curr <= 12; {
|
||||||
if slot, ok := sectionMap[curr]; ok {
|
if slot, ok := sectionMap[curr]; ok {
|
||||||
// === A 场景:当前节次有课 ===
|
// === A 场景:当前节次有课 ===
|
||||||
|
|
||||||
// 1. 寻找该事件的连续范围(比如 9-12 节连上)
|
// 1. 寻找该事件的连续范围(比如 9-12 节连上)
|
||||||
// 我们向后探测,直到 EventID 变化或节次断开
|
// 我们向后探测,直到 EventID 变化或节次断开
|
||||||
end := curr
|
end := curr
|
||||||
@@ -195,7 +189,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 封装 EventBrief
|
// 2. 封装 EventBrief
|
||||||
brief := model.EventBrief{
|
brief := model.EventBrief{
|
||||||
ID: slot.EventID,
|
ID: slot.EventID,
|
||||||
@@ -207,7 +200,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
EndTime: sectionTimeMap[end][1],
|
EndTime: sectionTimeMap[end][1],
|
||||||
Span: end - curr + 1,
|
Span: end - curr + 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 处理嵌入任务
|
// 3. 处理嵌入任务
|
||||||
// 只要这几个连续节次里有一个有任务,就带上
|
// 只要这几个连续节次里有一个有任务,就带上
|
||||||
for i := curr; i <= end; i++ {
|
for i := curr; i <= end; i++ {
|
||||||
@@ -220,16 +212,12 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
todayDTO.Events = append(todayDTO.Events, brief)
|
todayDTO.Events = append(todayDTO.Events, brief)
|
||||||
|
|
||||||
// 💡 指针跳跃:直接跳过已处理的节次
|
// 💡 指针跳跃:直接跳过已处理的节次
|
||||||
curr = end + 1
|
curr = end + 1
|
||||||
order++
|
order++
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// === B 场景:当前节次没课(Type = "empty") ===
|
// === B 场景:当前节次没课(Type = "empty") ===
|
||||||
|
|
||||||
// 逻辑:按照学校标准大节(1-2, 3-4...)进行空位合并
|
// 逻辑:按照学校标准大节(1-2, 3-4...)进行空位合并
|
||||||
// 如果当前是奇数节(1, 3, 5...)且下一节也没课,就合并成一个空块
|
// 如果当前是奇数节(1, 3, 5...)且下一节也没课,就合并成一个空块
|
||||||
emptyEnd := curr
|
emptyEnd := curr
|
||||||
@@ -238,7 +226,6 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
emptyEnd = curr + 1
|
emptyEnd = curr + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
todayDTO.Events = append(todayDTO.Events, model.EventBrief{
|
todayDTO.Events = append(todayDTO.Events, model.EventBrief{
|
||||||
ID: 0, // 空课 ID 为 0
|
ID: 0, // 空课 ID 为 0
|
||||||
Order: order,
|
Order: order,
|
||||||
@@ -248,14 +235,110 @@ func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodayS
|
|||||||
EndTime: sectionTimeMap[emptyEnd][1],
|
EndTime: sectionTimeMap[emptyEnd][1],
|
||||||
Location: "休息时间",
|
Location: "休息时间",
|
||||||
})
|
})
|
||||||
|
|
||||||
curr = emptyEnd + 1
|
curr = emptyEnd + 1
|
||||||
order++
|
order++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, todayDTO)
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,3 +284,20 @@ func (d *ScheduleDAO) GetUserTodaySchedule(ctx context.Context, userID, week, da
|
|||||||
|
|
||||||
return schedules, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ type EventBrief struct {
|
|||||||
EndTime string `json:"end_time"`
|
EndTime string `json:"end_time"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度
|
Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度/高度
|
||||||
EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"`
|
EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +68,24 @@ type TaskBrief struct {
|
|||||||
Type string `json:"type"`
|
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 (ScheduleEvent) TableName() string { return "schedule_events" }
|
||||||
|
|
||||||
func (Schedule) TableName() string { return "schedules" }
|
func (Schedule) TableName() string { return "schedules" }
|
||||||
|
|||||||
@@ -188,4 +188,9 @@ var ( //请求相关的响应
|
|||||||
Status: "40029",
|
Status: "40029",
|
||||||
Info: "insert course twice",
|
Info: "insert course twice",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeekOutOfRange = Response{ //周数超出范围
|
||||||
|
Status: "40030",
|
||||||
|
Info: "week out of range",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO) *gin.Engine
|
|||||||
{
|
{
|
||||||
scheduleGroup.Use(middleware.JWTTokenAuth(cache))
|
scheduleGroup.Use(middleware.JWTTokenAuth(cache))
|
||||||
scheduleGroup.GET("/today", handlers.ScheduleHandler.GetUserTodaySchedule)
|
scheduleGroup.GET("/today", handlers.ScheduleHandler.GetUserTodaySchedule)
|
||||||
|
scheduleGroup.GET("/week", handlers.ScheduleHandler.GetUserWeeklySchedule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 初始化Gin引擎
|
// 初始化Gin引擎
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/LoveLosita/smartflow/backend/conv"
|
"github.com/LoveLosita/smartflow/backend/conv"
|
||||||
"github.com/LoveLosita/smartflow/backend/dao"
|
"github.com/LoveLosita/smartflow/backend/dao"
|
||||||
|
"github.com/LoveLosita/smartflow/backend/model"
|
||||||
"github.com/LoveLosita/smartflow/backend/respond"
|
"github.com/LoveLosita/smartflow/backend/respond"
|
||||||
"gorm.io/gorm"
|
"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是否存在
|
//1.先检查用户id是否存在
|
||||||
_, err := ss.userDAO.GetUserByID(userID)
|
_, err := ss.userDAO.GetUserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,3 +51,35 @@ func (ss *ScheduleService) GetUserTodaySchedule(ctx context.Context, userID int)
|
|||||||
todaySchedules := conv.SchedulesToUserTodaySchedule(schedules)
|
todaySchedules := conv.SchedulesToUserTodaySchedule(schedules)
|
||||||
return todaySchedules, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user