Files
smartmate/backend/model/task-class.go
LoveLosita 132b7095ac Version:0.1.0.dev.260205
feat: 🆕 完善course模块功能并优化批量导入接口

- 调整 task-class 模型代码并增加注释,使其更简洁易读 ✍️
- 结构调整:将课程相关接口从 schedule 分类移至独立的 course 分类 🚀
  - 修改接口 URL 从 /schedule 更改为 /course 🔄

fix: 🐛 修复批量导入课程接口重复导入相同课程的 bug
- 通过在数据库添加唯一约束解决此问题 🔐
- 这只是初步修复,后续会在 sv 层增加重复/时间冲突检测逻辑 ⚠️
- 引导用户修改课表与任务块时间冲突的机制待实现 

refactor: 🔨 重构 schedule 表单结构
- 将节次管理策略从字符串存储改为原子化存储(如 1-2 节更改为单独两条记录)
- 为后续冲突检查与智能排课做准备 🧠

perf: 🚀 优化批量导入课程接口性能
- 通过数据暂存内存中减少数据插入 MySQL 的次数 
2026-02-05 16:51:15 +08:00

134 lines
4.9 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 model
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
// TaskClass 用于和数据库中的 task_classes 表进行映射
type TaskClass struct {
//section 1
ID int `gorm:"column:id;primaryKey;autoIncrement"`
UserID *int `gorm:"column:user_id;index:idx_task_classes_user_id"`
//section 2
Name *string `gorm:"column:name;size:255"`
Mode *string `gorm:"column:mode;type:enum('auto','manual')"`
StartDate *time.Time `gorm:"column:start_date"`
EndDate *time.Time `gorm:"column:end_date"`
//section 3
TotalSlots *int `gorm:"column:total_slots;comment:分配的总节数"`
AllowFillerCourse *bool `gorm:"column:allow_filler_course;default:true"`
Strategy *string `gorm:"column:strategy;type:enum('steady','rapid')"`
ExcludedSlots *string `gorm:"column:excluded_slots;type:json;comment:不想要的时段切片"`
Items []TaskClassItem `gorm:"foreignKey:CategoryID;references:ID"` // 一对多关联:一个 TaskClass 有多个 TaskClassItem
}
// TaskClassItem 用于和数据库中的 task_items 表进行映射
type TaskClassItem struct {
//section 1
ID int `gorm:"column:id;primaryKey;autoIncrement"`
CategoryID *int `gorm:"column:category_id"` //对应 TaskClass 的 ID
//section 2
Order *int `gorm:"column:order"`
Content *string `gorm:"column:content;type:text"`
EmbeddedTime *TargetTime `gorm:"column:embedded_time;type:json;comment:目标时间{date,section_from,section_to}"`
Status *int `gorm:"column:status;comment:1:未安排, 2:已应用"`
}
// UserAddTaskClassRequest 用于处理用户添加任务类别的请求
type UserAddTaskClassRequest struct {
Name string `json:"name" binding:"required"`
StartDate string `json:"start_date" binding:"required"` // YYYY-MM-DD
EndDate string `json:"end_date" binding:"required"` // YYYY-MM-DD
Mode string `json:"mode" binding:"required,oneof=auto manual"`
Config UserAddTaskClassConfig `json:"config" binding:"required"`
Items []UserAddTaskClassItemRequest `json:"items" binding:"required"`
}
// UserAddTaskClassConfig 用于处理用户添加任务类别时的配置部分
type UserAddTaskClassConfig struct {
TotalSlots int `json:"total_slots" binding:"required,min=1"`
AllowFillerCourse bool `json:"allow_filler_course"`
Strategy string `json:"strategy" binding:"required,oneof=steady rapid"`
ExcludedSlots []int `json:"excluded_slots"`
}
// UserAddTaskClassItemRequest 用于处理用户添加任务类别时的任务块部分
type UserAddTaskClassItemRequest struct {
Order int `json:"order" binding:"required,min=1"`
Content string `json:"content" binding:"required"`
EmbeddedTime *TargetTime `json:"embedded_time"` // 例: 2025-12-22 1-2节; nil 表示未安排
}
// TargetTime 表示任务块的目标时间
type TargetTime struct {
Date string `json:"date"` // 例: 2025-12-22
SectionFrom int `json:"section_from"` // 起始节次
SectionTo int `json:"section_to"` // 结束节次
}
// UserGetTaskClassesResponse 用于返回用户的任务类列表,展示简要信息
type UserGetTaskClassesResponse struct {
TaskClasses []TaskClassSummary `json:"task_classes"`
}
// TaskClassSummary 提供任务类别的简要信息
type TaskClassSummary struct {
ID int `json:"id"`
Name string `json:"name"`
Mode string `json:"mode"`
Strategy string `json:"strategy"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
TotalSlots int `json:"total_slots"`
}
// Value 实现 driver.Valuer 接口,负责将 TargetTime 转换为数据库存储的格式
func (t *TargetTime) Value() (driver.Value, error) {
if t == nil {
return nil, nil
}
// 💡 关键:调用 json.Marshal 将结构体转为 []byte
// 这样 GORM 就能把这一串 JSON 存进数据库的 text/json 字段了
return json.Marshal(t)
}
// Scan 实现 sql.Scanner 接口,负责将数据库中的值转换为 TargetTime 结构体
func (t *TargetTime) Scan(value any) error {
if value == nil {
// 如果数据库是 NULL保持指针对应的对象为零值即可
// 或者在业务层判断 nil
return nil
}
var data []byte
switch v := value.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("TargetTime: 不支持的扫描类型: %T", value)
}
return json.Unmarshal(data, t)
}
// TableName 指定 TaskClass 对应的数据库表名
func (TaskClass) TableName() string {
return "task_classes"
}
// TableName 指定 TaskClassItem 对应的数据库表名
func (TaskClassItem) TableName() string {
return "task_items"
}
// 任务块状态常量
const (
TaskItemStatusUnscheduled = 1 // 未安排
TaskItemStatusApplied = 2 // 已应用
)