Files
SharedClassManager/backend-go/internal/repository/attendance_repo.go
canglan 4a82eff3c6 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 04:41:49 +08:00

185 lines
5.2 KiB
Go

// ===========================================
// 多班级版班级管理系统 - Go 后端
//
// 开发者: Canglan
// 联系方式: admin@sea-studio.top
// 版权归属: Sea Network Technology Studio
// 许可证: Apache License 2.0
//
// 版权所有 © Sea Network Technology Studio
// ===========================================
package repository
import (
"time"
"gorm.io/gorm"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
)
// AttendanceRepo 考勤数据访问层
type AttendanceRepo struct {
db *gorm.DB
}
// NewAttendanceRepo 创建考勤 Repository
func NewAttendanceRepo(db *gorm.DB) *AttendanceRepo {
return &AttendanceRepo{db: db}
}
// GetStudentRecords 获取学生考勤记录
func (r *AttendanceRepo) GetStudentRecords(studentID int, month string) ([]model.AttendanceRecord, error) {
var records []model.AttendanceRecord
query := r.db.Where("student_id = ?", studentID)
if month != "" {
query = query.Where("DATE_FORMAT(date, '%Y-%m') = ?", month)
}
if err := query.Order("date DESC").Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}
// GetClassRecords 获取班级考勤记录(支持多种过滤条件)
func (r *AttendanceRepo) GetClassRecords(classID int, date string, studentID int, slot string) ([]model.AttendanceRecord, error) {
var records []model.AttendanceRecord
query := r.db.Table("attendance_records ar").
Select("ar.*, s.name as student_name, s.student_no").
Joins("JOIN students s ON ar.student_id = s.student_id").
Where("1 = 1")
if classID > 0 {
query = query.Where("s.class_id = ?", classID)
}
if date != "" {
query = query.Where("ar.date = ?", date)
}
if studentID > 0 {
query = query.Where("ar.student_id = ?", studentID)
}
if slot != "" {
query = query.Where("ar.slot = ?", slot)
}
if err := query.Order("ar.date DESC, s.student_no").Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}
// CreateRecordResult 创建或更新考勤记录的结果
type CreateRecordResult struct {
AttendanceID int
IsUpdate bool
OldDeductionApplied int8
OldDeductionRecordID *int64
}
// CreateRecord 创建或更新考勤记录(存在则更新),使用事务+行锁防止并发竞态
func (r *AttendanceRepo) CreateRecord(record *model.AttendanceRecord) (*CreateRecordResult, error) {
var result CreateRecordResult
err := r.db.Transaction(func(tx *gorm.DB) error {
// 使用 SELECT ... FOR UPDATE 锁定记录,防止并发请求同时判定为"不存在"
var existing model.AttendanceRecord
findErr := tx.Set("gorm:query_option", "FOR UPDATE").
Where("student_id = ? AND date = ? AND slot = ?",
record.StudentID, record.Date, record.Slot).
First(&existing).Error
if findErr == nil {
// 更新已有记录
if updateErr := tx.Model(&existing).Updates(map[string]interface{}{
"status": record.Status,
"reason": record.Reason,
"recorder_id": record.RecorderID,
}).Error; updateErr != nil {
return updateErr
}
result = CreateRecordResult{
AttendanceID: existing.AttendanceID,
IsUpdate: true,
OldDeductionApplied: existing.DeductionApplied,
OldDeductionRecordID: existing.DeductionRecordID,
}
return nil
}
if findErr != gorm.ErrRecordNotFound {
return findErr
}
// 插入新记录
if createErr := tx.Create(record).Error; createErr != nil {
return createErr
}
result = CreateRecordResult{
AttendanceID: record.AttendanceID,
IsUpdate: false,
}
return nil
})
if err != nil {
return nil, err
}
return &result, nil
}
// GetAttendanceStatsBySemester 批量查询学期内所有学生的考勤统计
func (r *AttendanceRepo) GetAttendanceStatsBySemester(semesterID int, startDate, endDate string) ([]struct {
StudentID int
Status string
Count int64
}, error) {
var stats []struct {
StudentID int
Status string
Count int64
}
err := r.db.Model(&model.AttendanceRecord{}).
Select("student_id, status, COUNT(*) as count").
Where("semester_id = ? OR (semester_id IS NULL AND `date` BETWEEN ? AND ?)", semesterID, startDate, endDate).
Group("student_id, status").
Find(&stats).Error
if err != nil {
return nil, err
}
return stats, nil
}
// GetAttendanceStatsByDateRange 通过日期范围查询学生考勤统计
func (r *AttendanceRepo) GetAttendanceStatsByDateRange(startDate, endDate time.Time, classID int) ([]struct {
StudentID int
Status string
Count int64
}, error) {
var stats []struct {
StudentID int
Status string
Count int64
}
query := r.db.Model(&model.AttendanceRecord{}).
Select("student_id, status, COUNT(*) as count").
Where("date BETWEEN ? AND ?", startDate, endDate)
if classID > 0 {
query = query.Where("student_id IN (SELECT student_id FROM students WHERE class_id = ?)", classID)
}
err := query.Group("student_id, status").Find(&stats).Error
if err != nil {
return nil, err
}
return stats, nil
}
// AssociateSemester 将考勤记录关联到学期
func (r *AttendanceRepo) AssociateSemester(attendanceID int, semesterID int) error {
return r.db.Model(&model.AttendanceRecord{}).
Where("attendance_id = ? AND semester_id IS NULL", attendanceID).
Update("semester_id", semesterID).Error
}