This commit introduces several architectural improvements and optimizations across the codebase: - **Performance & Reliability**: - Implemented Redis pipelining in `ConversationCache.CacheMessage` to reduce network round-trips. - Added a circuit breaker to the JPush client to prevent cascading failures. - Introduced batch deletion and batch member addition capabilities in repositories. - Added message idempotency support using `client_msg_id` and a Redis-based cache. - Optimized WebSocket handling with connection limits (total and per-user) and improved error logging. - **Code Refactoring**: - Refactored `Router` to use a `RouterDeps` struct, simplifying the constructor and improving maintainability. - Unified model ID generation logic using new `id_helper.go` (supporting UUID and Snowflake). - Standardized JSON serialization/deserialization in models using `json_helper.go`. - Refactored DTO conversion logic, specifically for `UserResponse` (using functional options) and `Report` responses. - Removed redundant/deprecated DTOs like `PostDetailResponse` and `TradeItemDetailResponse`. - **Cache Improvements**: - Enhanced `LayeredCache` with `SetRaw` to avoid double-encoding when promoting values from Redis to local cache. - Added `DeleteBatch` support to the cache interface. - **Other Changes**: - Cleaned up `config.go` by removing redundant default values and explicit environment variable overrides. - Improved WebSocket registration flow to handle connection limits gracefully.
104 lines
3.6 KiB
Go
104 lines
3.6 KiB
Go
package model
|
||
|
||
import (
|
||
"database/sql/driver"
|
||
"time"
|
||
|
||
"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 {
|
||
SetUUIDIfEmpty(&s.ID)
|
||
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 ValueJSON(a)
|
||
}
|
||
|
||
// Scan 实现 sql.Scanner 接口
|
||
func (a *StringArray) Scan(value any) error {
|
||
if value == nil {
|
||
*a = nil
|
||
return nil
|
||
}
|
||
return ScanJSON(a, value)
|
||
}
|
||
|
||
// 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 {
|
||
SetUUIDIfEmpty(&f.ID)
|
||
return nil
|
||
}
|
||
|
||
func (MaterialFile) TableName() string {
|
||
return "material_files"
|
||
}
|