Files
canglan c6db68a9f4 feat: 多班级版班级管理系统 v2.0
技术栈:Go (Gin + GORM) + PHP + MySQL 5.7 + Redis

主要功能:
- 多班级完全隔离(class_id 贯穿全系统)
- 后端 Go Gin(端口 56789),Nginx 反代
- 超级管理员独立登录(env 配置,默认账密 admin/Admin123)
- bcrypt 密码加密(无 PASSWORD_SALT)
- 科任老师/课代表新角色
- 课代表作业管理页面
- 排行榜分项排行(操行分/考勤/作业)
- 角色加减分上下限由班主任配置
- 家长改密功能(可开关)
- 班级角色按需开关
- 宿舍号格式:南0-000
- 周度/月度重置功能
- MySQL 5.7 兼容
- 43 轮代码审查 + 全部修复

开发者: Canglan
版权归属: Sea Network Technology Studio
许可证: Apache License 2.0
2026-06-23 16:02:28 +08:00

292 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===========================================
// 多班级版班级管理系统 - 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
}