技术栈: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
231 lines
7.0 KiB
Go
231 lines
7.0 KiB
Go
// ===========================================
|
||
// 多班级版班级管理系统 - Go 后端
|
||
//
|
||
// 开发者: Canglan
|
||
// 联系方式: admin@sea-studio.top
|
||
// 版权归属: Sea Network Technology Studio
|
||
// 许可证: Apache License 2.0
|
||
//
|
||
// 版权所有 © Sea Network Technology Studio
|
||
// ===========================================
|
||
|
||
package repository
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"gorm.io/gorm"
|
||
|
||
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/model"
|
||
)
|
||
|
||
// StudentRepo 学生数据访问层
|
||
type StudentRepo struct {
|
||
db *gorm.DB
|
||
}
|
||
|
||
// NewStudentRepo 创建学生 Repository
|
||
func NewStudentRepo(db *gorm.DB) *StudentRepo {
|
||
return &StudentRepo{db: db}
|
||
}
|
||
|
||
// GetByID 根据ID获取学生信息(含班级名称)
|
||
func (r *StudentRepo) GetByID(studentID int) (*model.Student, error) {
|
||
var student model.Student
|
||
if err := r.db.Table("students s").
|
||
Select("s.*, c.class_name").
|
||
Joins("LEFT JOIN classes c ON s.class_id = c.class_id").
|
||
Where("s.student_id = ?", studentID).
|
||
First(&student).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &student, nil
|
||
}
|
||
|
||
// GetByStudentNo 根据学号获取学生(可指定班级)
|
||
func (r *StudentRepo) GetByStudentNo(studentNo string, classID int) (*model.Student, error) {
|
||
var student model.Student
|
||
query := r.db.Where("student_no = ?", studentNo)
|
||
if classID > 0 {
|
||
query = query.Where("class_id = ?", classID)
|
||
}
|
||
if err := query.First(&student).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &student, nil
|
||
}
|
||
|
||
// GetAll 获取指定班级的学生列表
|
||
func (r *StudentRepo) GetAll(classID int, includeDisabled bool) ([]model.Student, error) {
|
||
var students []model.Student
|
||
query := r.db.Where("class_id = ?", classID)
|
||
if !includeDisabled {
|
||
query = query.Where("status = 1")
|
||
}
|
||
if err := query.Order("student_no").Find(&students).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return students, nil
|
||
}
|
||
|
||
// GetDormitoryList 获取班级内所有不重复的宿舍号列表
|
||
func (r *StudentRepo) GetDormitoryList(classID int) ([]string, error) {
|
||
var dormitories []string
|
||
err := r.db.Model(&model.Student{}).
|
||
Where("class_id = ? AND status = 1 AND dormitory_number IS NOT NULL AND dormitory_number != ''", classID).
|
||
Distinct("dormitory_number").
|
||
Order("dormitory_number").
|
||
Pluck("dormitory_number", &dormitories).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return dormitories, nil
|
||
}
|
||
|
||
// Create 创建学生记录
|
||
func (r *StudentRepo) Create(student *model.Student) (int, error) {
|
||
if err := r.db.Create(student).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return student.StudentID, nil
|
||
}
|
||
|
||
// Update 更新学生信息(仅更新非零值字段)
|
||
func (r *StudentRepo) Update(studentID int, updates map[string]interface{}) error {
|
||
if len(updates) == 0 {
|
||
return nil
|
||
}
|
||
return r.db.Model(&model.Student{}).
|
||
Where("student_id = ?", studentID).
|
||
Updates(updates).Error
|
||
}
|
||
|
||
// SoftDelete 软删除学生
|
||
func (r *StudentRepo) SoftDelete(studentID int) error {
|
||
return r.db.Model(&model.Student{}).
|
||
Where("student_id = ?", studentID).
|
||
Update("status", 0).Error
|
||
}
|
||
|
||
// UpdateTotalPoints 更新学生总分(增量更新,下限保护为 0)
|
||
func (r *StudentRepo) UpdateTotalPoints(studentID int, pointsChange int) error {
|
||
return r.db.Model(&model.Student{}).
|
||
Where("student_id = ?", studentID).
|
||
Update("total_points", gorm.Expr("GREATEST(total_points + ?, 0)", pointsChange)).Error
|
||
}
|
||
|
||
// GetRanking 获取班级内学生排行
|
||
func (r *StudentRepo) GetRanking(classID int, limit int) ([]model.Student, error) {
|
||
var students []model.Student
|
||
if err := r.db.Where("status = 1 AND class_id = ?", classID).
|
||
Order("total_points DESC, student_id ASC").
|
||
Limit(limit).
|
||
Find(&students).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return students, nil
|
||
}
|
||
|
||
// GetTotalCount 获取班级内活跃学生总数
|
||
func (r *StudentRepo) GetTotalCount(classID int) (int64, error) {
|
||
var count int64
|
||
if err := r.db.Model(&model.Student{}).
|
||
Where("status = 1 AND class_id = ?", classID).
|
||
Count(&count).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return count, nil
|
||
}
|
||
|
||
// ListByClass 分页获取班级学生列表(支持搜索和宿舍号过滤)
|
||
func (r *StudentRepo) ListByClass(classID int, page, pageSize int, search, dormitoryNumber string) ([]model.Student, int64, error) {
|
||
var students []model.Student
|
||
var total int64
|
||
|
||
query := r.db.Model(&model.Student{}).Where("status = 1 AND class_id = ?", classID)
|
||
|
||
if search != "" {
|
||
escaped := strings.NewReplacer("\\", "\\\\", "%", "\\%", "_", "\\_").Replace(search)
|
||
searchPattern := fmt.Sprintf("%%%s%%", escaped)
|
||
query = query.Where("student_no LIKE ? OR name LIKE ?", searchPattern, searchPattern)
|
||
}
|
||
|
||
if dormitoryNumber != "" {
|
||
query = query.Where("dormitory_number = ?", dormitoryNumber)
|
||
}
|
||
|
||
// 获取总数
|
||
if err := query.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
// 分页查询
|
||
offset := (page - 1) * pageSize
|
||
if err := query.Order("student_no").
|
||
Limit(pageSize).
|
||
Offset(offset).
|
||
Find(&students).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
return students, total, nil
|
||
}
|
||
|
||
// BatchCreate 批量创建学生
|
||
func (r *StudentRepo) BatchCreate(students []model.Student) error {
|
||
return r.db.Create(&students).Error
|
||
}
|
||
|
||
// GetStudentNosByClass 获取指定班级所有学生学号(用于批量导入去重)
|
||
func (r *StudentRepo) GetStudentNosByClass(classID int) ([]string, error) {
|
||
var studentNos []string
|
||
if err := r.db.Model(&model.Student{}).
|
||
Where("class_id = ?", classID).
|
||
Pluck("student_no", &studentNos).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return studentNos, nil
|
||
}
|
||
|
||
// ResetPoints 重置班级内所有学生的操行分为初始值
|
||
func (r *StudentRepo) ResetPoints(classID int, initialPoints int) error {
|
||
return r.db.Model(&model.Student{}).
|
||
Where("class_id = ? AND status = 1", classID).
|
||
Update("total_points", initialPoints).Error
|
||
}
|
||
|
||
// GetByParentAccount 根据家长账号查找学生
|
||
func (r *StudentRepo) GetByParentAccount(parentAccount string) (*model.Student, error) {
|
||
var student model.Student
|
||
if err := r.db.Where("parent_account = ? AND status = 1", parentAccount).First(&student).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return &student, nil
|
||
}
|
||
|
||
// GetRankByStudentID 使用密集排名(dense rank)计算学生排名:相同分数同名次,后续名次不跳过
|
||
func (r *StudentRepo) GetRankByStudentID(classID, studentID int) (int, error) {
|
||
var student model.Student
|
||
if err := r.db.Select("total_points").Where("student_id = ?", studentID).First(&student).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
var distinctHigherCount int64
|
||
if err := r.db.Raw("SELECT COUNT(DISTINCT total_points) FROM students WHERE status = 1 AND class_id = ? AND total_points > ?",
|
||
classID, student.TotalPoints).Scan(&distinctHigherCount).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
return int(distinctHigherCount) + 1, nil
|
||
}
|
||
|
||
// GetStudentsByClassID 获取班级内所有活跃学生(用于归档等批量操作)
|
||
func (r *StudentRepo) GetStudentsByClassID(classID int) ([]model.Student, error) {
|
||
var students []model.Student
|
||
if err := r.db.Where("class_id = ? AND status = 1", classID).
|
||
Order("total_points DESC, student_id ASC").
|
||
Find(&students).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
return students, nil
|
||
}
|