feat: 多班级版 v2.0 - Go后端重写 + 43轮代码审查

- 后端从 Python FastAPI 重写为 Go Gin(端口 56789)
- 多班级完全隔离
- 超级管理员独立登录
- 课代表作业管理、排行榜分项排行
- 角色加减分上下限可配置
- 家长改密功能(可开关)
- 周度/月度重置功能
- MySQL 5.7 兼容
- 43轮代码审查+全部修复
- Apache 2.0 许可证
This commit is contained in:
2026-06-22 10:06:10 +08:00
parent 4084afc53c
commit d6dec878bd
214 changed files with 12622 additions and 9725 deletions

View File

@@ -0,0 +1,210 @@
// ===========================================
// 多班级版班级管理系统 - Go 后端
//
// 开发者: Canglan
// 联系方式: admin@sea-studio.top
// 版权归属: Sea Network Technology Studio
// 许可证: Apache License 2.0
//
// 版权所有 © Sea Network Technology Studio
// ===========================================
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/config"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/handler"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/repository"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/router"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/internal/service"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/database"
"hz-gitea.sea-studio.top/canglan/SharedClassManager/pkg/logger"
)
func main() {
// ========== 1. 加载配置 ==========
cfg, err := config.Load()
if err != nil {
fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err)
os.Exit(1)
}
// ========== 2. 初始化日志 ==========
logger.Init(cfg.LogLevel, cfg.IsProduction())
defer logger.Sync()
logger.Sugared.Infof("应用启动: %s (env=%s, port=%s)", cfg.AppName, cfg.AppEnv, cfg.AppPort)
// ========== 3. 初始化 MySQL ==========
mysqlDB, err := database.InitMySQL(cfg)
if err != nil {
logger.Sugared.Fatalf("初始化 MySQL 失败: %v", err)
}
logger.Sugared.Info("MySQL 连接成功")
sqlDB, err := mysqlDB.DB()
if err != nil {
logger.Sugared.Fatalf("获取 sql.DB 失败: %v", err)
}
defer sqlDB.Close()
// ========== 4. 初始化 Redis ==========
redisClient, err := database.InitRedis(cfg)
if err != nil {
logger.Sugared.Fatalf("初始化 Redis 失败: %v", err)
}
logger.Sugared.Info("Redis 连接成功")
defer redisClient.Close()
// ========== 5. 初始化 Repository 层 ==========
userRepo := repository.NewUserRepo(mysqlDB)
studentRepo := repository.NewStudentRepo(mysqlDB)
adminRoleRepo := repository.NewAdminRoleRepo(mysqlDB)
classRepo := repository.NewClassRepo(mysqlDB)
conductRepo := repository.NewConductRepo(mysqlDB)
attendanceRepo := repository.NewAttendanceRepo(mysqlDB)
semesterRepo := repository.NewSemesterRepo(mysqlDB)
subjectRepo := repository.NewSubjectRepo(mysqlDB)
assignmentRepo := repository.NewAssignmentRepo(mysqlDB)
logRepo := repository.NewLogRepo(mysqlDB)
superAdminRepo := repository.NewSuperAdminRepo(mysqlDB)
settingRepo := repository.NewSystemSettingRepo(mysqlDB)
// ========== 6. 初始化 Service 层 ==========
logService := service.NewLogService(logRepo)
authService := service.NewAuthService(
userRepo, studentRepo, adminRoleRepo, classRepo, logService,
)
adminService := service.NewAdminService(
userRepo, studentRepo, adminRoleRepo, classRepo,
)
conductService := service.NewConductService(
conductRepo, studentRepo, adminRoleRepo, semesterRepo, classRepo,
)
attendanceService := service.NewAttendanceService(
attendanceRepo, studentRepo, userRepo, conductRepo, semesterRepo, settingRepo, classRepo,
)
semesterService := service.NewSemesterService(
semesterRepo, studentRepo, classRepo, attendanceRepo, assignmentRepo, logService,
)
classService := service.NewClassService(
classRepo, userRepo, adminRoleRepo,
)
subjectService := service.NewSubjectService(subjectRepo)
studentService := service.NewStudentService(
studentRepo, conductRepo, attendanceRepo, semesterRepo,
)
parentService := service.NewParentService(
userRepo, studentRepo, conductRepo, attendanceRepo,
)
rankingService := service.NewRankingService(
studentRepo, conductRepo,
)
superAdminService := service.NewSuperAdminService(superAdminRepo, logService)
configService := service.NewConfigService(classRepo)
// 确保默认超级管理员存在
if err := superAdminService.EnsureDefaultAdmin(); err != nil {
logger.Sugared.Errorf("初始化默认超级管理员失败: %v", err)
}
// ========== 7. 初始化 Handler 层 ==========
handlers := &router.Handlers{
Auth: handler.NewAuthHandler(authService, superAdminService),
Admin: handler.NewAdminHandler(adminService, conductService, attendanceService, rankingService, logService),
Student: handler.NewStudentHandler(studentService, classRepo),
Parent: handler.NewParentHandler(parentService, authService, classService),
Subject: handler.NewSubjectHandler(subjectService),
Semester: handler.NewSemesterHandler(semesterService),
Class: handler.NewClassHandler(classService),
Config: handler.NewConfigHandler(configService),
SuperAdmin: handler.NewSuperAdminHandler(superAdminService),
Cadre: handler.NewCadreHandler(assignmentRepo, conductService, adminRoleRepo),
}
// ========== 8. 初始化路由 ==========
r := router.SetupRouter(cfg, handlers)
// ========== 9. 启动 HTTP 服务 ==========
addr := fmt.Sprintf(":%s", cfg.AppPort)
srv := &http.Server{
Addr: addr,
Handler: r,
ReadTimeout: 30 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 120 * time.Second,
}
// 优雅关闭
go func() {
logger.Sugared.Infof("HTTP 服务启动: http://0.0.0.0%s", addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Sugared.Fatalf("HTTP 服务异常: %v", err)
}
}()
// ========== 10. 等待中断信号 ==========
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// ========== 自动周期重置定时任务 ==========
// 每天凌晨 1:00 检查是否有班级需要执行周/月重置
// 使用独立 done 通道避免与 quit 通道的竞态条件
timerDone := make(chan struct{})
go func() {
runAutoPeriodReset := func() {
defer func() {
if r := recover(); r != nil {
logger.Sugared.Errorf("自动周期重置 panic: %v", r)
}
}()
semesterService.AutoPeriodReset()
}
// 计算距离下一个凌晨 1:00 的等待时间
waitUntilNext1AM := func() time.Duration {
now := time.Now()
next := time.Date(now.Year(), now.Month(), now.Day(), 1, 0, 0, 0, now.Location())
if now.After(next) {
next = next.Add(24 * time.Hour)
}
return next.Sub(now)
}
timer := time.NewTimer(waitUntilNext1AM())
defer timer.Stop()
for {
select {
case <-timerDone:
logger.Sugared.Info("定时任务收到退出信号,停止")
return
case <-timer.C:
runAutoPeriodReset()
timer.Reset(24 * time.Hour)
}
}
}()
sig := <-quit
close(timerDone)
logger.Sugared.Infof("收到信号 %v正在关闭服务...", sig)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
logger.Sugared.Errorf("服务关闭异常: %v", err)
}
logger.Sugared.Info("服务已安全停止")
}