Files
SharedClassManager/backend-go/internal/repository/conduct_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

295 lines
8.9 KiB
Go
Raw 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"
"strings"
"time"
"gorm.io/gorm"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
)
// ConductRepo 操行分记录数据访问层
type ConductRepo struct {
db *gorm.DB
}
// NewConductRepo 创建操行分 Repository
func NewConductRepo(db *gorm.DB) *ConductRepo {
return &ConductRepo{db: db}
}
// CreateRecord 创建操行分记录
func (r *ConductRepo) CreateRecord(record *model.ConductRecord) (int64, error) {
if err := r.db.Create(record).Error; err != nil {
return 0, err
}
return record.RecordID, nil
}
// GetRecordByID 根据ID获取记录含学生信息
func (r *ConductRepo) GetRecordByID(recordID int64) (*model.ConductRecord, error) {
var record model.ConductRecord
if err := r.db.Table("conduct_records cr").
Select("cr.*, s.name as student_name, s.total_points").
Joins("JOIN students s ON cr.student_id = s.student_id").
Where("cr.record_id = ?", recordID).
First(&record).Error; err != nil {
return nil, err
}
return &record, nil
}
// CountStudentRecords 统计学生操行分记录总数
func (r *ConductRepo) CountStudentRecords(studentID int, includeRevoked bool, startDate, endDate string, recorderID int) (int64, error) {
var count int64
query := r.db.Model(&model.ConductRecord{}).Where("student_id = ?", studentID)
if !includeRevoked {
query = query.Where("is_revoked = 0")
}
if startDate != "" {
query = query.Where("DATE(created_at) >= ?", startDate)
}
if endDate != "" {
query = query.Where("DATE(created_at) <= ?", endDate)
}
if recorderID > 0 {
query = query.Where("recorder_id = ?", recorderID)
}
if err := query.Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// GetStudentRecords 获取学生操行分记录
func (r *ConductRepo) GetStudentRecords(studentID int, limit, offset int, includeRevoked bool, startDate, endDate string, recorderID int) ([]model.ConductRecord, error) {
var records []model.ConductRecord
query := r.db.Table("conduct_records cr").
Select("cr.*, u.real_name as recorder_real").
Joins("LEFT JOIN users u ON cr.recorder_id = u.user_id").
Where("cr.student_id = ?", studentID)
if !includeRevoked {
query = query.Where("cr.is_revoked = 0")
}
if startDate != "" {
query = query.Where("DATE(cr.created_at) >= ?", startDate)
}
if endDate != "" {
query = query.Where("DATE(cr.created_at) <= ?", endDate)
}
if recorderID > 0 {
query = query.Where("cr.recorder_id = ?", recorderID)
}
if err := query.Order("cr.created_at DESC").
Limit(limit).
Offset(offset).
Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}
// GetAllRecords 获取所有记录(管理员用,支持多种过滤条件)
func (r *ConductRepo) GetAllRecords(classID int, limit, offset int, startDate, endDate string,
studentID int, includeRevoked bool, relatedType, reasonPrefix string,
isRevoked *int, reasonSearch string) ([]model.ConductRecord, error) {
var records []model.ConductRecord
query := r.db.Table("conduct_records cr").
Select("cr.*, s.name as student_name, s.student_no, s.class_id, u.real_name as recorder_real, ru.real_name as revoker_name").
Joins("JOIN students s ON cr.student_id = s.student_id").
Joins("JOIN users u ON cr.recorder_id = u.user_id").
Joins("LEFT JOIN users ru ON cr.revoked_by = ru.user_id").
Where("1 = 1")
if !includeRevoked {
query = query.Where("cr.is_revoked = 0")
}
if classID > 0 {
query = query.Where("s.class_id = ?", classID)
}
if studentID > 0 {
query = query.Where("cr.student_id = ?", studentID)
}
if startDate != "" {
query = query.Where("DATE(cr.created_at) >= ?", startDate)
}
if endDate != "" {
query = query.Where("DATE(cr.created_at) <= ?", endDate)
}
if relatedType != "" {
query = query.Where("cr.related_type = ?", relatedType)
}
if reasonPrefix != "" {
query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%s%%", reasonPrefix))
}
if reasonSearch != "" {
escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(reasonSearch)
query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%%%s%%", escaped))
}
if isRevoked != nil {
query = query.Where("cr.is_revoked = ?", *isRevoked)
}
if err := query.Order("cr.created_at DESC").
Limit(limit).
Offset(offset).
Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}
// CountAllRecords 统计记录总数(与 GetAllRecords 使用相同过滤条件)
func (r *ConductRepo) CountAllRecords(classID int, startDate, endDate string,
studentID int, includeRevoked bool, relatedType, reasonPrefix string,
isRevoked *int, reasonSearch string) (int64, error) {
var count int64
query := r.db.Table("conduct_records cr").
Joins("JOIN students s ON cr.student_id = s.student_id").
Where("1 = 1")
if !includeRevoked {
query = query.Where("cr.is_revoked = 0")
}
if classID > 0 {
query = query.Where("s.class_id = ?", classID)
}
if studentID > 0 {
query = query.Where("cr.student_id = ?", studentID)
}
if startDate != "" {
query = query.Where("DATE(cr.created_at) >= ?", startDate)
}
if endDate != "" {
query = query.Where("DATE(cr.created_at) <= ?", endDate)
}
if relatedType != "" {
query = query.Where("cr.related_type = ?", relatedType)
}
if reasonPrefix != "" {
query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%s%%", reasonPrefix))
}
if reasonSearch != "" {
escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(reasonSearch)
query = query.Where("cr.reason LIKE ?", fmt.Sprintf("%%%s%%", escaped))
}
if isRevoked != nil {
query = query.Where("cr.is_revoked = ?", *isRevoked)
}
if err := query.Count(&count).Error; err != nil {
return 0, err
}
return count, nil
}
// RevokeRecord 撤销单条操行分记录
func (r *ConductRepo) RevokeRecord(recordID int64, revokerID int) error {
return r.db.Model(&model.ConductRecord{}).
Where("record_id = ? AND is_revoked = 0", recordID).
Updates(map[string]interface{}{
"is_revoked": 1,
"revoked_by": revokerID,
}).Error
}
// BatchRevokeRecords 批量撤销记录
func (r *ConductRepo) BatchRevokeRecords(recordIDs []int64, revokerID int) (int64, error) {
result := r.db.Model(&model.ConductRecord{}).
Where("record_id IN ? AND is_revoked = 0", recordIDs).
Updates(map[string]interface{}{
"is_revoked": 1,
"revoked_by": revokerID,
"revoked_at": time.Now(),
})
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// BatchRestoreRecords 批量反撤销记录
func (r *ConductRepo) BatchRestoreRecords(recordIDs []int64) (int64, error) {
result := r.db.Model(&model.ConductRecord{}).
Where("record_id IN ? AND is_revoked = 1", recordIDs).
Updates(map[string]interface{}{
"is_revoked": 0,
"revoked_by": nil,
"revoked_at": nil,
})
if result.Error != nil {
return 0, result.Error
}
return result.RowsAffected, nil
}
// AssociateSemester 将记录关联到学期
func (r *ConductRepo) AssociateSemester(recordID int64, semesterID int) error {
return r.db.Model(&model.ConductRecord{}).
Where("record_id = ? AND semester_id IS NULL", recordID).
Update("semester_id", semesterID).Error
}
// GetHomeworkRecords 获取学生作业相关的操行分记录
func (r *ConductRepo) GetHomeworkRecords(studentID int) ([]model.ConductRecord, error) {
var records []model.ConductRecord
if err := r.db.Where("student_id = ? AND related_type = 'homework' AND is_revoked = 0", studentID).
Order("created_at DESC").
Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}
// GetStudentPointsByType 按 related_type 在 SQL 层聚合学生分数(避免全量加载),支持 limit 限制返回数量
func (r *ConductRepo) GetStudentPointsByType(classID int, relatedType string, limit int) ([]struct {
StudentID int
StudentNo string
Name string
TotalPoints int
}, error) {
var results []struct {
StudentID int
StudentNo string
Name string
TotalPoints int
}
err := r.db.Table("conduct_records cr").
Select("cr.student_id, s.student_no, s.name, SUM(cr.points_change) as total_points").
Joins("JOIN students s ON cr.student_id = s.student_id").
Where("s.class_id = ? AND s.status = 1 AND cr.related_type = ? AND cr.is_revoked = 0", classID, relatedType).
Group("cr.student_id, s.student_no, s.name").
Order("total_points DESC").
Limit(limit).
Find(&results).Error
return results, err
}
// GetStudentTotalPoints 获取学生当前总分
func (r *ConductRepo) GetStudentTotalPoints(studentID int) (int, error) {
var student model.Student
if err := r.db.Where("student_id = ?", studentID).First(&student).Error; err != nil {
return 0, err
}
return student.TotalPoints, nil
}