Version:0.0.5.dev.260204

feat: 🏗️ 完成任务分类创建与列表查询接口并通过测试

- 历经复杂嵌套逻辑处理 🌀
- 实现创建任务分类接口 
- 实现获取任务分类列表接口 📋
- 接口测试全部通过 🧪

perf: 🚀 下个版本将为任务分类列表接口加入 Redis 缓存以提升查询速度 
This commit is contained in:
LoveLosita
2026-02-04 19:26:22 +08:00
parent f554d9bd06
commit af8e8bd804
8 changed files with 384 additions and 6 deletions

120
backend/model/task-class.go Normal file
View File

@@ -0,0 +1,120 @@
package model
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
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:不想要的时段切片"`
}
// TableName 设定 TaskClass 的表名为 task_classes
func (TaskClass) TableName() string {
return "task_classes"
}
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"`
}
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"`
}
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 表示未安排
}
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:已应用"`
}
type TargetTime struct {
Date string `json:"date"` // 例: 2025-12-22
SectionFrom int `json:"section_from"` // 起始节次
SectionTo int `json:"section_to"` // 结束节次
}
func (t *TargetTime) Value() (driver.Value, error) {
if t == nil {
return nil, nil
}
// 💡 关键:调用 json.Marshal 将结构体转为 []byte
// 这样 GORM 就能把这一串 JSON 存进数据库的 text/json 字段了
return json.Marshal(t)
}
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)
}
func (TaskClassItem) TableName() string {
return "task_items"
}
const (
TaskItemStatusUnscheduled = 1
TaskItemStatusApplied = 2
)
type UserGetTaskClassesResponse struct {
TaskClasses []TaskClassSummary `json:"task_classes"`
}
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"`
}