技术栈: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
185 lines
5.2 KiB
Go
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
|
|
}
|