package main import ( "fmt" "log" "net/http" "os" "time" "github.com/gin-gonic/gin" "gorm.io/driver/mysql" "gorm.io/gorm" "ai-myopia-prevention/api/handlers" "ai-myopia-prevention/db/models" "ai-myopia-prevention/internal/middleware" ) func main() { fmt.Println("启明计划 - AI近视防控系统 后端服务启动中...") // 初始化Gin路由器 r := gin.Default() // 初始化数据库连接 db, err := initDatabase() if err != nil { log.Fatal("数据库连接失败:", err) } // 禁用外键约束自动创建 db = db.Set("gorm:table_options", "ENGINE=InnoDB") // 自动迁移数据库表结构 (禁用外键) err = migrateDatabase(db) if err != nil { log.Fatal("数据库迁移失败:", err) } // 初始化服务 authService := handlers.NewAuthService(db) detectionService := handlers.NewDetectionService(db) // 设置路由 setupRoutes(r, authService, detectionService) // 启动服务器 port := "8080" log.Printf("服务器将在 :%s 端口启动", port) if err := r.Run(":" + port); err != nil { log.Fatal("服务器启动失败:", err) } } // initDatabase 初始化数据库连接 func initDatabase() (*gorm.DB, error) { // 从环境变量读取数据库配置 dbHost := os.Getenv("DB_HOST") if dbHost == "" { dbHost = "mysql" // Docker 网络中的默认主机名 } dbUser := os.Getenv("DB_USER") if dbUser == "" { dbUser = "myopia_user" } dbPassword := os.Getenv("DB_PASSWORD") if dbPassword == "" { dbPassword = "MyopiaTest2026!" } dbName := os.Getenv("DB_NAME") if dbName == "" { dbName = "ai_myopia" } dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbUser, dbPassword, dbHost, dbName) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ // 配置日志记录 Logger: nil, // 在生产环境中可以使用gorm/logger.Default }) if err != nil { return nil, fmt.Errorf("连接数据库失败: %v", err) } // 设置连接池 sqlDB, err := db.DB() if err != nil { return nil, fmt.Errorf("获取底层sql.DB失败: %v", err) } // 设置连接池参数 sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) return db, nil } // migrateDatabase 数据库表结构迁移 func migrateDatabase(db *gorm.DB) error { // 自动迁移数据库表结构 (禁用外键) // 注意:在生产环境中,通常使用迁移文件而不是自动迁移 err := db.Migrator().DropTable( ) err = db.AutoMigrate( &models.User{}, &models.Student{}, &models.Parent{}, &models.Teacher{}, &models.School{}, &models.Class{}, &models.UserAccount{}, &models.DetectionTask{}, &models.Detection{}, &models.DetectionReport{}, &models.Alert{}, &models.AlertConfig{}, &models.AlertSummary{}, &models.Device{}, &models.DeviceConfig{}, &models.DeviceLog{}, &models.DeviceStatusInfo{}, &models.DeviceCommand{}, &models.DeviceMessage{}, &models.DeviceHeartbeat{}, &models.DeviceGroup{}, &models.DeviceGroupRelation{}, &models.DeviceMaintenance{}, ) if err != nil { return fmt.Errorf("数据库迁移失败: %v", err) } fmt.Println("数据库表结构迁移完成") return nil } // setupRoutes 设置路由 func setupRoutes(r *gin.Engine, auth *handlers.AuthService, detection *handlers.DetectionService) { // 健康检查端点 r.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "service": "ai-myopia-prevention-backend", "time": time.Now().Unix(), }) }) // API版本路由组 v1 := r.Group("/api/v1") { // 认证相关路由 authGroup := v1.Group("/auth") { authGroup.POST("/login", auth.Login) authGroup.POST("/register", auth.Register) authGroup.GET("/profile", middleware.JWTAuthMiddleware(), auth.GetProfile) authGroup.PUT("/profile", middleware.JWTAuthMiddleware(), auth.UpdateProfile) authGroup.PUT("/password", middleware.JWTAuthMiddleware(), auth.ChangePassword) } // 检测相关路由 detectionGroup := v1.Group("/detections") { detectionGroup.POST("/start", middleware.JWTAuthMiddleware(), middleware.RBACMiddleware("teacher", "admin"), detection.StartDetection) detectionGroup.POST("/submit", middleware.JWTAuthMiddleware(), middleware.RBACMiddleware("student", "teacher", "admin"), detection.SubmitDetection) detectionGroup.GET("/report/:detection_id/student/:student_id", middleware.JWTAuthMiddleware(), detection.GetDetectionReport) detectionGroup.GET("/history", middleware.JWTAuthMiddleware(), detection.GetDetectionHistory) detectionGroup.GET("/class/:class_id/stats", middleware.JWTAuthMiddleware(), middleware.RBACMiddleware("teacher", "admin"), detection.GetClassStats) } // 学生相关路由 studentGroup := v1.Group("/students") { studentGroup.Use(middleware.JWTAuthMiddleware()) // 学生只能访问自己的信息,家长可以访问孩子的信息,老师可以访问班级学生信息,管理员可以访问所有 // TODO: 实现学生相关API } // 班级相关路由 classGroup := v1.Group("/classes") { classGroup.Use(middleware.JWTAuthMiddleware()) // 只有老师和管理员可以访问班级信息 // TODO: 实现班级相关API } // 预警相关路由 alertGroup := v1.Group("/alerts") { alertGroup.Use(middleware.JWTAuthMiddleware()) // 学生可以查看自己的预警,家长可以查看孩子的预警,老师可以查看班级预警,管理员可以查看所有 // TODO: 实现预警相关API } // 训练相关路由 trainingGroup := v1.Group("/training") { trainingGroup.Use(middleware.JWTAuthMiddleware()) // 学生可以查看自己的训练,家长可以查看孩子的训练,老师可以查看班级训练,管理员可以查看所有 // TODO: 实现训练相关API } // 设备相关路由 deviceGroup := v1.Group("/devices") { deviceGroup.Use(middleware.JWTAuthMiddleware(), middleware.RBACMiddleware("admin")) // 只有管理员可以管理设备 // TODO: 实现设备相关API } } // 为静态文件提供服务 r.Static("/static", "./static") // 为上传文件提供服务 r.Static("/uploads", "./uploads") fmt.Println("路由设置完成") }