feat: 多班级版 v2.0 - Go后端重写 + 43轮代码审查
- 后端从 Python FastAPI 重写为 Go Gin(端口 56789) - 多班级完全隔离 - 超级管理员独立登录 - 课代表作业管理、排行榜分项排行 - 角色加减分上下限可配置 - 家长改密功能(可开关) - 周度/月度重置功能 - MySQL 5.7 兼容 - 43轮代码审查+全部修复 - Apache 2.0 许可证
This commit is contained in:
291
backend-go/internal/repository/semester_repo.go
Normal file
291
backend-go/internal/repository/semester_repo.go
Normal file
@@ -0,0 +1,291 @@
|
||||
// ===========================================
|
||||
// 多班级版班级管理系统 - Go 后端
|
||||
//
|
||||
// 开发者: Canglan
|
||||
// 联系方式: admin@sea-studio.top
|
||||
// 版权归属: Sea Network Technology Studio
|
||||
// 许可证: Apache License 2.0
|
||||
//
|
||||
// 版权所有 © Sea Network Technology Studio
|
||||
// ===========================================
|
||||
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
|
||||
)
|
||||
|
||||
// SemesterRepo 学期数据访问层
|
||||
type SemesterRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewSemesterRepo 创建学期 Repository
|
||||
func NewSemesterRepo(db *gorm.DB) *SemesterRepo {
|
||||
return &SemesterRepo{db: db}
|
||||
}
|
||||
|
||||
// GetDB 获取底层数据库连接(用于事务操作)
|
||||
func (r *SemesterRepo) GetDB() *gorm.DB {
|
||||
return r.db
|
||||
}
|
||||
|
||||
// Create 创建学期
|
||||
func (r *SemesterRepo) Create(semester *model.Semester) (int, error) {
|
||||
if err := r.db.Create(semester).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return semester.SemesterID, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取学期信息
|
||||
func (r *SemesterRepo) GetByID(semesterID int) (*model.Semester, error) {
|
||||
var semester model.Semester
|
||||
if err := r.db.Where("semester_id = ?", semesterID).First(&semester).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &semester, nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有学期列表
|
||||
func (r *SemesterRepo) GetAll() ([]model.Semester, error) {
|
||||
var semesters []model.Semester
|
||||
if err := r.db.Order("created_at DESC").Find(&semesters).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return semesters, nil
|
||||
}
|
||||
|
||||
// GetActive 获取当前活跃学期(优先 is_active 标记,降级为日期范围匹配)
|
||||
func (r *SemesterRepo) GetActive() (*model.Semester, error) {
|
||||
var semester model.Semester
|
||||
|
||||
// 第一优先级:is_active 标记
|
||||
if err := r.db.Where("is_active = 1 AND is_archived = 0").
|
||||
Limit(1).First(&semester).Error; err == nil {
|
||||
return &semester, nil
|
||||
}
|
||||
|
||||
// 第二优先级:日期范围匹配
|
||||
today := time.Now().Format("2006-01-02")
|
||||
if err := r.db.Where("is_archived = 0 AND start_date <= ? AND (end_date IS NULL OR end_date >= ?)", today, today).
|
||||
Limit(1).First(&semester).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &semester, nil
|
||||
}
|
||||
|
||||
// DeactivateAll 将所有学期设为非活跃
|
||||
func (r *SemesterRepo) DeactivateAll() error {
|
||||
return r.db.Model(&model.Semester{}).
|
||||
Where("is_active = 1").
|
||||
Update("is_active", 0).Error
|
||||
}
|
||||
|
||||
// Activate 设为当前活跃学期
|
||||
func (r *SemesterRepo) Activate(semesterID int) error {
|
||||
return r.db.Model(&model.Semester{}).
|
||||
Where("semester_id = ? AND is_archived = 0", semesterID).
|
||||
Update("is_active", 1).Error
|
||||
}
|
||||
|
||||
// Archive 归档学期
|
||||
func (r *SemesterRepo) Archive(semesterID int) error {
|
||||
return r.db.Model(&model.Semester{}).
|
||||
Where("semester_id = ? AND is_archived = 0", semesterID).
|
||||
Updates(map[string]interface{}{
|
||||
"is_archived": 1,
|
||||
"is_active": 0,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// Update 编辑学期信息(仅未归档)
|
||||
func (r *SemesterRepo) Update(semesterID int, updates map[string]interface{}) error {
|
||||
if len(updates) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.db.Model(&model.Semester{}).
|
||||
Where("semester_id = ? AND is_archived = 0", semesterID).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
// Delete 删除学期
|
||||
func (r *SemesterRepo) Delete(semesterID int) error {
|
||||
return r.db.Where("semester_id = ?", semesterID).Delete(&model.Semester{}).Error
|
||||
}
|
||||
|
||||
// CountArchives 统计学期归档数据数量
|
||||
func (r *SemesterRepo) CountArchives(semesterID int) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.Model(&model.SemesterArchive{}).
|
||||
Where("semester_id = ?", semesterID).
|
||||
Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountRecordsBySemester 统计学期关联的记录数
|
||||
func (r *SemesterRepo) CountRecordsBySemester(semesterID int) (conductCount, attendanceCount int64, err error) {
|
||||
if err = r.db.Model(&model.ConductRecord{}).
|
||||
Where("semester_id = ?", semesterID).
|
||||
Count(&conductCount).Error; err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if err = r.db.Model(&model.AttendanceRecord{}).
|
||||
Where("semester_id = ?", semesterID).
|
||||
Count(&attendanceCount).Error; err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return conductCount, attendanceCount, nil
|
||||
}
|
||||
|
||||
// AssociateRecordsByDateRange 按日期范围关联记录到学期
|
||||
func (r *SemesterRepo) AssociateRecordsByDateRange(semesterID int, startDate, endDate string) (conductCount, attendanceCount int64, err error) {
|
||||
if startDate == "" || endDate == "" {
|
||||
return 0, 0, fmt.Errorf("日期范围不能为空")
|
||||
}
|
||||
|
||||
// 关联操行分记录
|
||||
result := r.db.Model(&model.ConductRecord{}).
|
||||
Where("semester_id IS NULL AND created_at BETWEEN ? AND CONCAT(?, ' 23:59:59')", startDate, endDate).
|
||||
Update("semester_id", semesterID)
|
||||
if result.Error != nil {
|
||||
return 0, 0, result.Error
|
||||
}
|
||||
conductCount = result.RowsAffected
|
||||
|
||||
// 关联考勤记录
|
||||
result = r.db.Model(&model.AttendanceRecord{}).
|
||||
Where("semester_id IS NULL AND `date` BETWEEN ? AND ?", startDate, endDate).
|
||||
Update("semester_id", semesterID)
|
||||
if result.Error != nil {
|
||||
return conductCount, 0, result.Error
|
||||
}
|
||||
attendanceCount = result.RowsAffected
|
||||
|
||||
return conductCount, attendanceCount, nil
|
||||
}
|
||||
|
||||
// GetConductRecordSemesterID 获取操行分记录所属的学期ID
|
||||
func (r *SemesterRepo) GetConductRecordSemesterID(recordID int64) (*int, error) {
|
||||
var record model.ConductRecord
|
||||
if err := r.db.Where("record_id = ?", recordID).First(&record).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return record.SemesterID, nil
|
||||
}
|
||||
|
||||
// ========== 学期归档操作 ==========
|
||||
|
||||
// BatchCreateArchives 批量创建归档快照
|
||||
func (r *SemesterRepo) BatchCreateArchives(archives []model.SemesterArchive) error {
|
||||
if len(archives) == 0 {
|
||||
return nil
|
||||
}
|
||||
return r.db.Create(&archives).Error
|
||||
}
|
||||
|
||||
// DeleteArchivesBySemester 删除指定学期的所有归档数据
|
||||
func (r *SemesterRepo) DeleteArchivesBySemester(semesterID int) error {
|
||||
return r.db.Where("semester_id = ?", semesterID).Delete(&model.SemesterArchive{}).Error
|
||||
}
|
||||
|
||||
// GetArchivesBySemester 获取学期的归档数据
|
||||
func (r *SemesterRepo) GetArchivesBySemester(semesterID int, classID int, page, pageSize int) ([]model.SemesterArchive, int64, error) {
|
||||
var archives []model.SemesterArchive
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&model.SemesterArchive{}).Where("semester_id = ?", semesterID)
|
||||
if classID > 0 {
|
||||
query = query.Where("class_id = ?", classID)
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("rank_position ASC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&archives).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return archives, total, nil
|
||||
}
|
||||
|
||||
// GetArchivesByStudent 获取学生在所有已归档学期的数据
|
||||
func (r *SemesterRepo) GetArchivesByStudent(studentID int) ([]model.SemesterArchive, error) {
|
||||
var archives []model.SemesterArchive
|
||||
if err := r.db.Table("semester_archives sa").
|
||||
Select("sa.archive_id, sa.semester_id, sa.student_id, sa.student_no, "+
|
||||
"sa.student_name, sa.final_points, sa.rank_position, "+
|
||||
"sa.total_students, sa.attendance_present, sa.attendance_absent, "+
|
||||
"sa.attendance_late, sa.attendance_leave, "+
|
||||
"sa.homework_submitted, sa.homework_not_submitted, sa.homework_late, "+
|
||||
"sa.archived_at, s.semester_name, s.start_date, s.end_date").
|
||||
Joins("JOIN semesters s ON sa.semester_id = s.semester_id").
|
||||
Where("sa.student_id = ?", studentID).
|
||||
Order("sa.archived_at DESC").
|
||||
Find(&archives).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return archives, nil
|
||||
}
|
||||
|
||||
// ========== 周期归档操作 ==========
|
||||
|
||||
// GetPeriodArchives 获取周期归档列表
|
||||
func (r *SemesterRepo) GetPeriodArchives(classID int, periodType string, page, pageSize int) ([]model.PeriodArchive, int64, error) {
|
||||
var archives []model.PeriodArchive
|
||||
var total int64
|
||||
|
||||
query := r.db.Model(&model.PeriodArchive{}).
|
||||
Where("class_id = ? AND period_type = ?", classID, periodType)
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("archived_at DESC, period_label DESC, rank_position ASC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&archives).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return archives, total, nil
|
||||
}
|
||||
|
||||
// GetPeriodArchiveLabels 获取班级的所有周期归档标签(按时间倒序去重)
|
||||
func (r *SemesterRepo) GetPeriodArchiveLabels(classID int, periodType string) ([]string, error) {
|
||||
var labels []string
|
||||
if err := r.db.Model(&model.PeriodArchive{}).
|
||||
Where("class_id = ? AND period_type = ?", classID, periodType).
|
||||
Distinct("period_label").
|
||||
Order("period_label DESC").
|
||||
Pluck("period_label", &labels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// GetLatestPeriodArchiveLabel 获取指定班级最近一次周期归档的标签
|
||||
func (r *SemesterRepo) GetLatestPeriodArchiveLabel(classID int, periodType string) (string, error) {
|
||||
var archive model.PeriodArchive
|
||||
if err := r.db.Where("class_id = ? AND period_type = ?", classID, periodType).
|
||||
Order("archived_at DESC").
|
||||
Limit(1).
|
||||
First(&archive).Error; err != nil {
|
||||
return "", err
|
||||
}
|
||||
return archive.PeriodLabel, nil
|
||||
}
|
||||
Reference in New Issue
Block a user