Files
smartmate/backend/conv/schedule.go
LoveLosita 79b6be5f40 Version: 0.1.3.dev.260208
refactor: ♻️ 重命名部分接口接收器以避免与 dao 层包名冲突

- 调整部分接口接收器命名,避免与 dao 层包名重名 🧩

feat: 📅 新增获取用户今日日程接口并完成实现

fix: 🐛 修复现实日期与相对日期转换逻辑中的初始化时序问题

- 修复 conv/time.go 中日期转换函数的一个 bug ⏱️
- 解决 viper 在包级变量初始化时机过早的问题
- 避免因过早初始化导致无法读取配置中的学期开学/结束时间 📆
2026-02-08 19:09:40 +08:00

262 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package conv
import (
"fmt"
"github.com/LoveLosita/smartflow/backend/model"
)
import "sort"
func SchedulesToScheduleConflictDetail(schedules []model.Schedule) []model.ScheduleConflictDetail {
if len(schedules) == 0 {
return []model.ScheduleConflictDetail{}
}
// 1. 使用 Map 进行逻辑分组
// Key 格式: EventID-Week-Day (防止同一事件在不同天出现时被混为一谈)
groups := make(map[string]*model.ScheduleConflictDetail)
for _, s := range schedules {
key := fmt.Sprintf("%d-%d-%d", s.EventID, s.Week, s.DayOfWeek)
if _, ok := groups[key]; !ok {
// 初始化该分组
groups[key] = &model.ScheduleConflictDetail{
EventID: s.EventID,
Name: s.Event.Name,
Location: *s.Event.Location, // 假设字段是 *string
Type: s.Event.Type,
Week: s.Week,
DayOfWeek: s.DayOfWeek,
}
}
// 将当前节次加入数组
groups[key].Sections = append(groups[key].Sections, s.Section)
}
// 2. 处理每个分组的区间逻辑
res := make([]model.ScheduleConflictDetail, 0, len(groups))
for _, detail := range groups {
// 排序节次,例如把 [3, 1, 2] 变成 [1, 2, 3]
sort.Ints(detail.Sections)
// 最小值即起始,最大值即结束
detail.StartSection = detail.Sections[0]
detail.EndSection = detail.Sections[len(detail.Sections)-1]
res = append(res, *detail)
}
// 3. 可选:对结果集按时间排序,让前端收到的 DTO 也是有序的
sort.Slice(res, func(i, j int) bool {
if res[i].Week != res[j].Week {
return res[i].Week < res[j].Week
}
if res[i].DayOfWeek != res[j].DayOfWeek {
return res[i].DayOfWeek < res[j].DayOfWeek
}
return res[i].StartSection < res[j].StartSection
})
return res
}
// SectionToTime 映射表:将原子节次转为起始/结束时间点
// 此处以重邮为例
var sectionTimeMap = map[int][2]string{
1: {"08:00", "08:45"}, 2: {"08:55", "09:40"},
3: {"10:15", "11:00"}, 4: {"11:10", "11:55"},
5: {"14:00", "14:45"}, 6: {"14:55", "15:40"},
7: {"16:15", "17:00"}, 8: {"17:10", "17:55"},
9: {"19:00", "19:45"}, 10: {"19:55", "20:40"},
11: {"20:50", "21:35"}, 12: {"21:45", "22:30"},
}
/*func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodaySchedule {
if len(schedules) == 0 {
return []model.UserTodaySchedule{}
}
// 1. 按周次和星期进行第一层分组 (map[week][day]schedules)
// 虽然是“今日日程”,但传入的切片可能包含多天,这样写兼容性更好
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{},
}
// 2. 在每一天内部,按 EventID 进行第二层聚合(把连续的节次拼成一个 Event
eventGroups := make(map[int][]model.Schedule)
for _, s := range daySchedules {
eventGroups[s.EventID] = append(eventGroups[s.EventID], s)
}
// 3. 遍历每个 Event 组,转化为 EventBrief
var tempEvents []model.EventBrief
for eventID, slots := range eventGroups {
// 对当前 Event 的所有原子槽位按节次排序
sort.Slice(slots, func(i, j int) bool {
return slots[i].Section < slots[j].Section
})
firstSlot := slots[0]
lastSlot := slots[len(slots)-1]
brief := model.EventBrief{
ID: eventID,
Name: firstSlot.Event.Name,
Location: *firstSlot.Event.Location,
Type: firstSlot.Event.Type,
StartTime: sectionTimeMap[firstSlot.Section][0], // 取第一节的开始时间
EndTime: sectionTimeMap[lastSlot.Section][1], // 取最后一节的结束时间
}
// 💡 4. 检查是否有嵌入任务 (Embedded Task)
// 逻辑:只要这个时间块中任何一个原子槽位带了 EmbeddedTaskID就提取它
// 注意:这里假设你在 DAO 层也 Preload 了 Task 信息,或者在此处仅填 ID
for _, slot := range slots {
if slot.EmbeddedTaskID != nil {
brief.EmbeddedTaskInfo = model.TaskBrief{
ID: *slot.EmbeddedTaskID,
Name: *slot.EmbeddedTask.Content,
Type: "task",
}
break // 找到一个即代表该块已占用
}
}
tempEvents = append(tempEvents, brief)
}
// 5. 对结果进行排序(按开始时间),并分配 Order
sort.Slice(tempEvents, func(i, j int) bool {
return tempEvents[i].StartTime < tempEvents[j].StartTime
})
for i := range tempEvents {
tempEvents[i].Order = i + 1
}
todayDTO.Events = tempEvents
result = append(result, todayDTO)
}
return result
}*/
func SchedulesToUserTodaySchedule(schedules []model.Schedule) []model.UserTodaySchedule {
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
for next := curr + 1; next <= 12; next++ {
if nextSlot, exist := sectionMap[next]; exist && nextSlot.EventID == slot.EventID {
end = next
} else {
break
}
}
// 2. 封装 EventBrief
brief := model.EventBrief{
ID: slot.EventID,
Order: order,
Name: slot.Event.Name,
Location: *slot.Event.Location,
Type: slot.Event.Type,
StartTime: sectionTimeMap[curr][0],
EndTime: sectionTimeMap[end][1],
Span: end - curr + 1,
}
// 3. 处理嵌入任务
// 只要这几个连续节次里有一个有任务,就带上
for i := curr; i <= end; i++ {
if s, exist := sectionMap[i]; exist && s.EmbeddedTask != nil {
brief.EmbeddedTaskInfo = model.TaskBrief{
ID: s.EmbeddedTask.ID,
Name: *s.EmbeddedTask.Content,
Type: "task",
}
break
}
}
todayDTO.Events = append(todayDTO.Events, brief)
// 💡 指针跳跃:直接跳过已处理的节次
curr = end + 1
order++
} else {
// === B 场景当前节次没课Type = "empty" ===
// 逻辑按照学校标准大节1-2, 3-4...)进行空位合并
// 如果当前是奇数节1, 3, 5...)且下一节也没课,就合并成一个空块
emptyEnd := curr
if curr%2 != 0 && curr < 12 {
if _, nextHasClass := sectionMap[curr+1]; !nextHasClass {
emptyEnd = curr + 1
}
}
todayDTO.Events = append(todayDTO.Events, model.EventBrief{
ID: 0, // 空课 ID 为 0
Order: order,
Name: "无课",
Type: "empty",
StartTime: sectionTimeMap[curr][0],
EndTime: sectionTimeMap[emptyEnd][1],
Location: "休息时间",
})
curr = emptyEnd + 1
order++
}
}
result = append(result, todayDTO)
}
return result
}