技术栈:Go (Gin + GORM) + PHP + MySQL 5.7 + Redis 主要功能: - 多班级完全隔离(class_id 贯穿全系统) - 后端从 Python FastAPI 重写为 Go Gin(端口 56789) - 超级管理员独立登录(env 配置路径,默认账密 admin/Admin123) - 科任老师/课代表新角色 - 课代表作业管理页面 - 排行榜分项排行(操行分/考勤/作业) - 角色加减分上下限由班主任配置 - 家长改密功能(可开关) - 班级角色按需开关 - 宿舍号格式:南0-000 - 周度/月度重置功能 - MySQL 5.7 兼容 - Nginx 反向代理部署 开发者: Canglan 版权归属: Sea Network Technology Studio 许可证: Apache License 2.0
292 lines
8.9 KiB
Go
292 lines
8.9 KiB
Go
// ===========================================
|
||
// 多班级版班级管理系统 - 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
|
||
}
|