技术栈: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
603 lines
15 KiB
Go
603 lines
15 KiB
Go
// ===========================================
|
|
// 多班级版班级管理系统 - Go 后端
|
|
//
|
|
// 开发者: Canglan
|
|
// 联系方式: admin@sea-studio.top
|
|
// 版权归属: Sea Network Technology Studio
|
|
// 许可证: Apache License 2.0
|
|
//
|
|
// 版权所有 © Sea Network Technology Studio
|
|
// ===========================================
|
|
|
|
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/middleware"
|
|
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/schema"
|
|
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/service"
|
|
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/response"
|
|
)
|
|
|
|
// AdminHandler 管理端处理器
|
|
type AdminHandler struct {
|
|
adminService *service.AdminService
|
|
conductService *service.ConductService
|
|
attendanceSvc *service.AttendanceService
|
|
rankingService *service.RankingService
|
|
logService *service.LogService
|
|
}
|
|
|
|
// NewAdminHandler 创建管理端处理器
|
|
func NewAdminHandler(
|
|
adminService *service.AdminService,
|
|
conductService *service.ConductService,
|
|
attendanceSvc *service.AttendanceService,
|
|
rankingService *service.RankingService,
|
|
logService *service.LogService,
|
|
) *AdminHandler {
|
|
return &AdminHandler{
|
|
adminService: adminService,
|
|
conductService: conductService,
|
|
attendanceSvc: attendanceSvc,
|
|
rankingService: rankingService,
|
|
logService: logService,
|
|
}
|
|
}
|
|
|
|
// ========== 学生管理 ==========
|
|
|
|
// GetDormitories 获取宿舍号列表
|
|
func (h *AdminHandler) GetDormitories(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
dormitories, err := h.adminService.GetDormitories(classID)
|
|
if err != nil {
|
|
response.InternalError(c, "获取宿舍号列表失败")
|
|
return
|
|
}
|
|
response.Success(c, gin.H{"dormitories": dormitories}, "操作成功")
|
|
}
|
|
|
|
// StudentList 获取学生列表
|
|
func (h *AdminHandler) StudentList(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
|
|
var query schema.StudentListQuery
|
|
if err := c.ShouldBindQuery(&query); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
result, err := h.adminService.GetStudents(classID, query.Page, query.PageSize, query.Search, query.DormitoryNumber)
|
|
if err != nil {
|
|
response.InternalError(c, "获取学生列表失败")
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// StudentImport 批量导入学生
|
|
func (h *AdminHandler) StudentImport(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
|
|
file, _, err := c.Request.FormFile("file")
|
|
if err != nil {
|
|
response.BadRequest(c, "请上传文件")
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
limitedReader := io.LimitReader(file, 5*1024*1024)
|
|
content, err := io.ReadAll(limitedReader)
|
|
if err != nil {
|
|
response.BadRequest(c, "读取文件失败")
|
|
return
|
|
}
|
|
|
|
var data struct {
|
|
Students []map[string]interface{} `json:"students"`
|
|
}
|
|
if err := json.Unmarshal(content, &data); err != nil {
|
|
response.BadRequest(c, "JSON格式错误")
|
|
return
|
|
}
|
|
if len(data.Students) == 0 {
|
|
response.BadRequest(c, "文件中没有学生数据")
|
|
return
|
|
}
|
|
|
|
result, err := h.adminService.ImportStudents(data.Students, classID)
|
|
if err != nil {
|
|
response.InternalError(c, "导入失败")
|
|
return
|
|
}
|
|
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// StudentCreate 新增学生
|
|
func (h *AdminHandler) StudentCreate(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
|
|
var req schema.StudentCreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
result, err := h.adminService.AddStudent(req.StudentNo, req.Name, req.ParentAccount, classID, req.DormitoryNumber)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
|
|
response.Success(c, result, "学生添加成功")
|
|
}
|
|
|
|
// StudentUpdate 编辑学生
|
|
func (h *AdminHandler) StudentUpdate(c *gin.Context) {
|
|
studentID, ok := parseID(c, "student_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
classID := middleware.GetClassID(c)
|
|
|
|
var req schema.StudentUpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.adminService.UpdateStudent(studentID, req.Name, req.ParentAccount, req.DormitoryNumber, classID); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "更新成功")
|
|
}
|
|
|
|
// StudentDelete 删除学生
|
|
func (h *AdminHandler) StudentDelete(c *gin.Context) {
|
|
studentID, ok := parseID(c, "student_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
classID := middleware.GetClassID(c)
|
|
|
|
if err := h.adminService.DeleteStudent(studentID, classID); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "删除成功")
|
|
}
|
|
|
|
// ResetStudentPassword 重置学生密码
|
|
func (h *AdminHandler) ResetStudentPassword(c *gin.Context) {
|
|
studentID, ok := parseID(c, "student_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req schema.ResetPasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.adminService.ResetStudentPassword(studentID, req.NewPassword); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "密码重置成功")
|
|
}
|
|
|
|
// ========== 操行分管理 ==========
|
|
|
|
// AddConductPoints 批量加减分
|
|
func (h *AdminHandler) AddConductPoints(c *gin.Context) {
|
|
var req schema.ConductAddRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
classID := middleware.GetClassID(c)
|
|
userID := middleware.GetUserID(c)
|
|
realName := middleware.GetRealName(c)
|
|
|
|
result, err := h.conductService.AddPoints(
|
|
req.StudentIDs, req.PointsChange, req.Reason,
|
|
userID, realName, classID, req.RelatedType,
|
|
)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// RevokeConductRecord 撤销记录
|
|
func (h *AdminHandler) RevokeConductRecord(c *gin.Context) {
|
|
var req schema.RevokeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
userID := middleware.GetUserID(c)
|
|
classID := middleware.GetClassID(c)
|
|
result, err := h.conductService.RevokeRecord(req.RecordID, userID, classID)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "撤销成功")
|
|
}
|
|
|
|
// RestoreConductRecord 反撤销记录
|
|
func (h *AdminHandler) RestoreConductRecord(c *gin.Context) {
|
|
var req schema.RevokeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
userID := middleware.GetUserID(c)
|
|
classID := middleware.GetClassID(c)
|
|
result, err := h.conductService.RestoreRecord(req.RecordID, userID, classID)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "反撤销成功")
|
|
}
|
|
|
|
// GetConductHistory 操行分历史
|
|
func (h *AdminHandler) GetConductHistory(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
|
|
var query schema.ConductHistoryQuery
|
|
if err := c.ShouldBindQuery(&query); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
result, err := h.conductService.GetHistory(
|
|
classID, query.StudentID, query.Page, query.PageSize,
|
|
query.StartDate, query.EndDate, query.RelatedType,
|
|
query.ReasonPrefix, query.IsRevoked, query.ReasonSearch,
|
|
)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// BatchRevokeConductRecords 批量撤销
|
|
func (h *AdminHandler) BatchRevokeConductRecords(c *gin.Context) {
|
|
var req schema.BatchRevokeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
userID := middleware.GetUserID(c)
|
|
classID := middleware.GetClassID(c)
|
|
successCount := 0
|
|
failCount := 0
|
|
var errors []map[string]interface{}
|
|
|
|
for _, recordID := range req.RecordIDs {
|
|
result, _ := h.conductService.RevokeRecord(recordID, userID, classID)
|
|
if result != nil {
|
|
if success, _ := result["success"].(bool); success {
|
|
successCount++
|
|
} else {
|
|
failCount++
|
|
msg, _ := result["message"].(string)
|
|
errors = append(errors, map[string]interface{}{"record_id": recordID, "error": msg})
|
|
}
|
|
} else {
|
|
failCount++
|
|
errors = append(errors, map[string]interface{}{"record_id": recordID, "error": "撤销失败"})
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"success_count": successCount,
|
|
"fail_count": failCount,
|
|
"errors": errors,
|
|
}, "批量撤销完成")
|
|
}
|
|
|
|
// BatchRestoreConductRecords 批量反撤销
|
|
func (h *AdminHandler) BatchRestoreConductRecords(c *gin.Context) {
|
|
var req schema.BatchRevokeRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
userID := middleware.GetUserID(c)
|
|
classID := middleware.GetClassID(c)
|
|
successCount := 0
|
|
failCount := 0
|
|
var errors []map[string]interface{}
|
|
|
|
for _, recordID := range req.RecordIDs {
|
|
result, _ := h.conductService.RestoreRecord(recordID, userID, classID)
|
|
if result != nil {
|
|
if success, _ := result["success"].(bool); success {
|
|
successCount++
|
|
} else {
|
|
failCount++
|
|
msg, _ := result["message"].(string)
|
|
errors = append(errors, map[string]interface{}{"record_id": recordID, "error": msg})
|
|
}
|
|
} else {
|
|
failCount++
|
|
errors = append(errors, map[string]interface{}{"record_id": recordID, "error": "反撤销失败"})
|
|
}
|
|
}
|
|
|
|
response.Success(c, gin.H{
|
|
"success_count": successCount,
|
|
"fail_count": failCount,
|
|
"errors": errors,
|
|
}, "批量反撤销完成")
|
|
}
|
|
|
|
// ========== 考勤管理 ==========
|
|
|
|
// CreateAttendanceRecord 添加考勤
|
|
func (h *AdminHandler) CreateAttendanceRecord(c *gin.Context) {
|
|
var req schema.AttendanceCreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
userID := middleware.GetUserID(c)
|
|
classID := middleware.GetClassID(c)
|
|
result, err := h.attendanceSvc.CreateRecord(
|
|
req.StudentID, req.Date, req.Slot, req.Status,
|
|
&req.Reason, req.ApplyDeduction, req.CustomDeduction, userID, classID,
|
|
)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作成功"
|
|
}
|
|
response.SuccessWithMessage(c, msg)
|
|
}
|
|
|
|
// GetAttendanceRecords 获取考勤记录
|
|
func (h *AdminHandler) GetAttendanceRecords(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
|
|
var query schema.AttendanceQuery
|
|
if err := c.ShouldBindQuery(&query); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
result, err := h.attendanceSvc.GetRecords(classID, query.Date, query.StudentID, query.Slot)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// ========== 管理员管理 ==========
|
|
|
|
// AdminList 管理员列表
|
|
func (h *AdminHandler) AdminList(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
result, err := h.adminService.GetAdmins(classID)
|
|
if err != nil {
|
|
response.InternalError(c, "获取管理员列表失败")
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|
|
|
|
// AdminCreate 添加管理员
|
|
func (h *AdminHandler) AdminCreate(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
|
|
var req schema.AdminCreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
result, err := h.adminService.AddAdmin(req.Username, req.RealName, req.Password, req.RoleType, classID, req.SubjectID)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
|
|
if success, _ := result["success"].(bool); !success {
|
|
msg, _ := result["message"].(string)
|
|
if msg == "" {
|
|
msg = "操作失败"
|
|
}
|
|
response.BadRequest(c, msg)
|
|
return
|
|
}
|
|
response.Success(c, result, "管理员添加成功")
|
|
}
|
|
|
|
// AdminUpdate 更新管理员
|
|
func (h *AdminHandler) AdminUpdate(c *gin.Context) {
|
|
userID, ok := parseID(c, "user_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
classID := middleware.GetClassID(c)
|
|
var req schema.AdminUpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.adminService.UpdateAdmin(userID, req.RealName, req.RoleType, classID, req.SubjectID); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "更新成功")
|
|
}
|
|
|
|
// AdminDelete 删除管理员
|
|
func (h *AdminHandler) AdminDelete(c *gin.Context) {
|
|
userID, ok := parseID(c, "user_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
classID := middleware.GetClassID(c)
|
|
if err := h.adminService.DeleteAdmin(userID, classID); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "删除成功")
|
|
}
|
|
|
|
// AdminResetPassword 重置管理员密码
|
|
func (h *AdminHandler) AdminResetPassword(c *gin.Context) {
|
|
userID, ok := parseID(c, "user_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req schema.ResetPasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.adminService.ResetAdminPassword(userID, req.NewPassword); err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "密码重置成功")
|
|
}
|
|
|
|
// UnlockAccount 解除登录锁定
|
|
func (h *AdminHandler) UnlockAccount(c *gin.Context) {
|
|
var req schema.UnlockUserRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "参数错误")
|
|
return
|
|
}
|
|
|
|
if err := h.adminService.UnlockAccount(req.Username, c.ClientIP()); err != nil {
|
|
response.InternalError(c, "解锁失败")
|
|
return
|
|
}
|
|
response.SuccessWithMessage(c, "解锁成功")
|
|
}
|
|
|
|
|
|
// GetRankings 分项排行榜
|
|
func (h *AdminHandler) GetRankings(c *gin.Context) {
|
|
classID := middleware.GetClassID(c)
|
|
if classID == 0 {
|
|
response.BadRequest(c, "请先选择班级")
|
|
return
|
|
}
|
|
|
|
rankType := c.DefaultQuery("type", "all")
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
|
if limit <= 0 {
|
|
limit = 50
|
|
}
|
|
if limit > 500 {
|
|
limit = 500
|
|
}
|
|
|
|
result, err := h.rankingService.GetRankings(classID, rankType, limit)
|
|
if err != nil {
|
|
response.InternalError(c, err.Error())
|
|
return
|
|
}
|
|
response.Success(c, result, "操作成功")
|
|
}
|