// =========================================== // 多班级版班级管理系统 - 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("服务已安全停止") }