feat: 多班级版班级管理系统 v2.0
技术栈: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
This commit is contained in:
210
backend-go/cmd/server/main.go
Normal file
210
backend-go/cmd/server/main.go
Normal 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("服务已安全停止")
|
||||
}
|
||||
Reference in New Issue
Block a user