feat(material): add material handling and routing functionality
- Introduced MaterialHandler for managing learning materials and subjects. - Updated router to include routes for public and admin material management. - Enhanced database initialization with material subject and file models. - Implemented seeding logic for material subjects and files. - Updated wire generation to include MaterialHandler and related services.
This commit is contained in:
@@ -159,6 +159,10 @@ func autoMigrate(db *gorm.DB) error {
|
||||
&Role{},
|
||||
&UserRole{},
|
||||
&CasbinRule{},
|
||||
|
||||
// 学习资料相关
|
||||
&MaterialSubject{},
|
||||
&MaterialFile{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -184,6 +188,11 @@ func autoMigrate(db *gorm.DB) error {
|
||||
return fmt.Errorf("failed to seed channels: %w", err)
|
||||
}
|
||||
|
||||
// 初始化学习资料学科种子数据
|
||||
if err := seedMaterialSubjects(db); err != nil {
|
||||
return fmt.Errorf("failed to seed material subjects: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -321,8 +330,18 @@ func seedPermissions(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedChannels 初始化频道种子数据(按 slug 幂等)
|
||||
// seedChannels 初始化频道种子数据(仅在表为空时)
|
||||
func seedChannels(db *gorm.DB) error {
|
||||
// 检查是否已有频道数据
|
||||
var count int64
|
||||
if err := db.Model(&Channel{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
zap.L().Info("Channels already exist, skipping seed", zap.Int64("count", count))
|
||||
return nil
|
||||
}
|
||||
|
||||
defaultChannels := []Channel{
|
||||
{Name: "跳蚤市场", Slug: "market", Description: "二手交易与闲置发布", SortOrder: 10, IsActive: true},
|
||||
{Name: "课程交流", Slug: "courses", Description: "课程资料、选课与学习讨论", SortOrder: 20, IsActive: true},
|
||||
@@ -331,33 +350,45 @@ func seedChannels(db *gorm.DB) error {
|
||||
{Name: "保研出国", Slug: "graduate-abroad", Description: "保研、留学申请与经验分享", SortOrder: 50, IsActive: true},
|
||||
}
|
||||
|
||||
for _, item := range defaultChannels {
|
||||
var existing Channel
|
||||
err := db.Where("slug = ?", item.Slug).First(&existing).Error
|
||||
if err == nil {
|
||||
updates := map[string]any{
|
||||
"name": item.Name,
|
||||
"description": item.Description,
|
||||
"sort_order": item.SortOrder,
|
||||
"is_active": item.IsActive,
|
||||
}
|
||||
if uerr := db.Model(&existing).Updates(updates).Error; uerr != nil {
|
||||
return uerr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return err
|
||||
}
|
||||
if cerr := db.Create(&item).Error; cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
if err := db.Create(&defaultChannels).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Info("Seeded channels successfully", zap.Int("count", len(defaultChannels)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// seedMaterialSubjects 初始化学习资料学科种子数据(仅在表为空时)
|
||||
func seedMaterialSubjects(db *gorm.DB) error {
|
||||
// 检查是否已有数据
|
||||
var count int64
|
||||
if err := db.Model(&MaterialSubject{}).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
zap.L().Info("Material subjects already exist, skipping seed", zap.Int64("count", count))
|
||||
return nil
|
||||
}
|
||||
|
||||
defaultSubjects := []MaterialSubject{
|
||||
{Name: "数学", Icon: "calculator-variant", Color: "#FF6B6B", Description: "高等数学、线性代数、概率论等", SortOrder: 10, IsActive: true},
|
||||
{Name: "英语", Icon: "translate", Color: "#4ECDC4", Description: "四六级、考研英语、托福雅思等", SortOrder: 20, IsActive: true},
|
||||
{Name: "物理", Icon: "atom", Color: "#45B7D1", Description: "大学物理、电磁学、力学等", SortOrder: 30, IsActive: true},
|
||||
{Name: "化学", Icon: "flask", Color: "#96CEB4", Description: "有机化学、无机化学、分析化学等", SortOrder: 40, IsActive: true},
|
||||
{Name: "计算机", Icon: "laptop", Color: "#FFEAA7", Description: "数据结构、算法、操作系统等", SortOrder: 50, IsActive: true},
|
||||
{Name: "经济管理", Icon: "chart-line", Color: "#DDA0DD", Description: "微观经济学、宏观经济学、管理学等", SortOrder: 60, IsActive: true},
|
||||
{Name: "法学", Icon: "scale-balance", Color: "#F39C12", Description: "民法、刑法、宪法等", SortOrder: 70, IsActive: true},
|
||||
{Name: "语文文学", Icon: "book-open-page-variant", Color: "#3498DB", Description: "古代文学、现代文学、写作等", SortOrder: 80, IsActive: true},
|
||||
}
|
||||
|
||||
if err := db.Create(&defaultSubjects).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zap.L().Info("Seeded material subjects successfully", zap.Int("count", len(defaultSubjects)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseDB 关闭数据库连接
|
||||
func CloseDB(db *gorm.DB) {
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
|
||||
113
internal/model/material.go
Normal file
113
internal/model/material.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// MaterialFileType 文件类型
|
||||
type MaterialFileType string
|
||||
|
||||
const (
|
||||
MaterialFileTypePDF MaterialFileType = "pdf"
|
||||
MaterialFileTypeWord MaterialFileType = "word"
|
||||
MaterialFileTypePPT MaterialFileType = "ppt"
|
||||
)
|
||||
|
||||
// MaterialStatus 资料状态
|
||||
type MaterialStatus string
|
||||
|
||||
const (
|
||||
MaterialStatusActive MaterialStatus = "active"
|
||||
MaterialStatusInactive MaterialStatus = "inactive"
|
||||
)
|
||||
|
||||
// MaterialSubject 学科分类
|
||||
type MaterialSubject struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
Name string `json:"name" gorm:"type:varchar(100);not null"`
|
||||
Icon string `json:"icon" gorm:"type:varchar(50)"` // MaterialCommunityIcons name
|
||||
Color string `json:"color" gorm:"type:varchar(20)"` // 主题色
|
||||
Description string `json:"description" gorm:"type:varchar(255)"` // 描述
|
||||
SortOrder int `json:"sort_order" gorm:"default:0;index"` // 排序权重
|
||||
IsActive bool `json:"is_active" gorm:"default:true;index"` // 是否启用
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
|
||||
// 关联
|
||||
Materials []MaterialFile `json:"materials,omitempty" gorm:"foreignKey:SubjectID"`
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前生成UUID
|
||||
func (s *MaterialSubject) BeforeCreate(tx *gorm.DB) error {
|
||||
if s.ID == "" {
|
||||
s.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (MaterialSubject) TableName() string {
|
||||
return "material_subjects"
|
||||
}
|
||||
|
||||
// StringArray 字符串数组类型(用于tags)
|
||||
type StringArray []string
|
||||
|
||||
// Value 实现 driver.Valuer 接口
|
||||
func (a StringArray) Value() (driver.Value, error) {
|
||||
if a == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return json.Marshal(a)
|
||||
}
|
||||
|
||||
// Scan 实现 sql.Scanner 接口
|
||||
func (a *StringArray) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
*a = nil
|
||||
return nil
|
||||
}
|
||||
bytes, ok := value.([]byte)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(bytes, a)
|
||||
}
|
||||
|
||||
// MaterialFile 文件资料
|
||||
type MaterialFile struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
SubjectID string `json:"subject_id" gorm:"type:varchar(36);index;not null"`
|
||||
Title string `json:"title" gorm:"type:varchar(200);not null"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
FileType MaterialFileType `json:"file_type" gorm:"type:varchar(20);not null;index"`
|
||||
FileSize int64 `json:"file_size" gorm:"default:0"` // 文件大小(字节)
|
||||
FileURL string `json:"file_url" gorm:"type:text;not null"` // 文件URL
|
||||
FileName string `json:"file_name" gorm:"type:varchar(255)"` // 原始文件名
|
||||
DownloadCount int `json:"download_count" gorm:"default:0"` // 下载次数
|
||||
AuthorID string `json:"author_id" gorm:"type:varchar(36);index"` // 上传者ID
|
||||
Tags StringArray `json:"tags" gorm:"type:json"` // 标签
|
||||
Status MaterialStatus `json:"status" gorm:"type:varchar(20);default:active;index"` // 状态
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
|
||||
// 关联
|
||||
Subject *MaterialSubject `json:"subject,omitempty" gorm:"foreignKey:SubjectID"`
|
||||
Author *User `json:"author,omitempty" gorm:"foreignKey:AuthorID"`
|
||||
}
|
||||
|
||||
// BeforeCreate 创建前生成UUID
|
||||
func (f *MaterialFile) BeforeCreate(tx *gorm.DB) error {
|
||||
if f.ID == "" {
|
||||
f.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (MaterialFile) TableName() string {
|
||||
return "material_files"
|
||||
}
|
||||
Reference in New Issue
Block a user