Version: 0.2.6.dev.260211
feat: 🕒 为 schedule_events 表新增 start_time 与 end_time 字段 - 新增 start_time 与 end_time 两列 - 支持最近已完成任务列表接口 - 为后续获取当前正在进行的任务接口做准备 🚧 feat: ✅ 新增最近已完成任务列表接口并通过测试 - 完成接口实现与测试 🧪 - 当前 sv 层使用测试时间进行逻辑验证 - ⚠️ 生产环境需改回使用当前时间
This commit is contained in:
@@ -79,3 +79,30 @@ func (s *ScheduleAPI) DeleteScheduleEvent(c *gin.Context) {
|
||||
//4.返回删除成功的响应给前端
|
||||
c.JSON(http.StatusOK, respond.Ok)
|
||||
}
|
||||
|
||||
func (s *ScheduleAPI) GetUserRecentCompletedSchedules(c *gin.Context) {
|
||||
// 1. 从请求上下文中获取用户ID以及其他查询参数(如 index 和 limit)
|
||||
userID := c.GetInt("user_id")
|
||||
index := c.Query("index")
|
||||
limit := c.Query("limit")
|
||||
intIndex, err := strconv.Atoi(index)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, respond.WrongParamType)
|
||||
return
|
||||
}
|
||||
intLimit, err := strconv.Atoi(limit)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, respond.WrongParamType)
|
||||
return
|
||||
}
|
||||
//2.调用服务层方法获取用户最近完成的日程事件
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second)
|
||||
defer cancel() // 记得释放资源
|
||||
completedSchedules, err := s.scheduleService.GetUserRecentCompletedSchedules(ctx, userID, intIndex, intLimit)
|
||||
if err != nil {
|
||||
respond.DealWithError(c, err)
|
||||
return
|
||||
}
|
||||
//3.返回数据给前端
|
||||
c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, completedSchedules))
|
||||
}
|
||||
|
||||
@@ -71,87 +71,6 @@ var sectionTimeMap = map[int][2]string{
|
||||
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{}
|
||||
@@ -342,3 +261,20 @@ func SchedulesToUserWeeklySchedule(schedules []model.Schedule) []model.UserWeekS
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func SchedulesToRecentCompletedSchedules(schedules []model.Schedule) model.UserRecentCompletedScheduleResponse {
|
||||
var result model.UserRecentCompletedScheduleResponse
|
||||
for _, s := range schedules {
|
||||
strTime := s.Event.EndTime.Format("2006-01-02 15:04:05")
|
||||
temp := model.RecentCompletedEventBrief{
|
||||
ID: s.ID,
|
||||
Name: s.Event.Name,
|
||||
Type: s.Event.Type,
|
||||
// CompletedTime 需要从 ScheduleEvent 的 CompletedTime 字段获取
|
||||
CompletedTime: strTime,
|
||||
}
|
||||
result.Events = append(result.Events, temp)
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/LoveLosita/smartflow/backend/model"
|
||||
"github.com/LoveLosita/smartflow/backend/respond"
|
||||
@@ -444,3 +445,21 @@ func (d *ScheduleDAO) DeleteScheduleEventByTaskItemID(ctx context.Context, taskI
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ScheduleDAO) GetUserRecentCompletedSchedules(ctx context.Context, nowTime time.Time, userID int, index, limit int) ([]model.Schedule, error) {
|
||||
var schedules []model.Schedule
|
||||
err := d.db.WithContext(ctx).
|
||||
Preload("Event").
|
||||
Preload("EmbeddedTask").
|
||||
Joins("JOIN schedule_events ON schedule_events.id = schedules.event_id").
|
||||
Where("schedules.user_id = ? AND schedule_events.type = ? AND schedule_events.end_time < ?",
|
||||
userID, "task", nowTime).
|
||||
Order("schedule_events.end_time DESC"). // 命中索引
|
||||
Offset(index).
|
||||
Limit(limit).
|
||||
Find(&schedules).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return schedules, nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type ScheduleEvent struct {
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int `gorm:"column:user_id;index:idx_user_events;not null" json:"user_id"`
|
||||
Name string `gorm:"column:name;type:varchar(255);not null;comment:课程或任务名称" json:"name"`
|
||||
Location *string `gorm:"column:location;type:varchar(255);default:'';comment:地点 (教学楼/会议室)" json:"location"`
|
||||
Type string `gorm:"column:type;type:enum('course','task');not null;comment:日程类型" json:"type"`
|
||||
RelID *int `gorm:"column:rel_id;comment:关联原始数据ID (如教务系统的课程ID)" json:"rel_id"`
|
||||
CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;default:0;comment:是否允许在此时段嵌入其他任务" json:"can_be_embedded"`
|
||||
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int `gorm:"column:user_id;index:idx_user_events;not null" json:"user_id"`
|
||||
Name string `gorm:"column:name;type:varchar(255);not null;comment:课程或任务名称" json:"name"`
|
||||
Location *string `gorm:"column:location;type:varchar(255);default:'';comment:地点 (教学楼/会议室)" json:"location"`
|
||||
Type string `gorm:"column:type;type:enum('course','task');not null;comment:日程类型" json:"type"`
|
||||
RelID *int `gorm:"column:rel_id;comment:关联原始数据ID (如教务系统的课程ID)" json:"rel_id"`
|
||||
CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;default:0;comment:是否允许在此时段嵌入其他任务" json:"can_be_embedded"`
|
||||
StartTime time.Time `gorm:"column:start_time;type:time;comment:开始时间" json:"start_time"`
|
||||
EndTime time.Time `gorm:"column:end_time;type:time;comment:结束时间" json:"end_time"`
|
||||
}
|
||||
|
||||
type Schedule struct {
|
||||
@@ -92,6 +96,17 @@ type UserDeleteScheduleEvent struct {
|
||||
DeleteEmbeddedTask bool `json:"delete_embedded_task"`
|
||||
}
|
||||
|
||||
type UserRecentCompletedScheduleResponse struct {
|
||||
Events []RecentCompletedEventBrief `json:"events"`
|
||||
}
|
||||
|
||||
type RecentCompletedEventBrief struct {
|
||||
ID int `json:"id"` //如果是嵌入的任务事件,这个ID是TaskClassItem的ID;如果是课程事件,这个ID是ScheduleEvent的ID
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CompletedTime string `json:"completed_time"`
|
||||
}
|
||||
|
||||
func (ScheduleEvent) TableName() string { return "schedule_events" }
|
||||
|
||||
func (Schedule) TableName() string { return "schedules" }
|
||||
|
||||
@@ -78,6 +78,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO, limiter *pk
|
||||
scheduleGroup.GET("/today", handlers.ScheduleHandler.GetUserTodaySchedule)
|
||||
scheduleGroup.GET("/week", handlers.ScheduleHandler.GetUserWeeklySchedule)
|
||||
scheduleGroup.DELETE("/delete", middleware.IdempotencyMiddleware(cache), handlers.ScheduleHandler.DeleteScheduleEvent)
|
||||
scheduleGroup.GET("/recent-completed", middleware.IdempotencyMiddleware(cache), handlers.ScheduleHandler.GetUserRecentCompletedSchedules)
|
||||
}
|
||||
}
|
||||
// 初始化Gin引擎
|
||||
|
||||
@@ -236,3 +236,17 @@ func (ss *ScheduleService) DeleteScheduleEvent(ctx context.Context, requests []m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *ScheduleService) GetUserRecentCompletedSchedules(ctx context.Context, userID, index, limit int) (model.UserRecentCompletedScheduleResponse, error) {
|
||||
//1.查询用户最近完成的日程安排
|
||||
//获取现在的时间
|
||||
/*nowTime := time.Now()*/
|
||||
nowTime := time.Date(2026, 6, 15, 12, 0, 0, 0, time.Local) //测试数据
|
||||
schedules, err := ss.scheduleDAO.GetUserRecentCompletedSchedules(ctx, nowTime, userID, index, limit)
|
||||
if err != nil {
|
||||
return model.UserRecentCompletedScheduleResponse{}, err
|
||||
}
|
||||
//2.转换为前端需要的格式
|
||||
result := conv.SchedulesToRecentCompletedSchedules(schedules)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user