Files
ai-myopia-prevention/cmd/main.go
虾司令 881144269c 🚀 AI 近视防控系统 - 生产环境上线版本 v1.0
 已完成功能:
- 后端 Go 服务 (认证/授权/检测)
- JWT 认证 + RBAC 权限控制
- 登录速率限制 (5 次失败锁定 15 分钟)
- 密码强度校验
- 敏感数据脱敏
- Vue3 管理后台
- 路由守卫
- 删除二次确认

📦 部署配置:
- Docker Compose 生产环境配置
- MySQL/Redis/MongoDB 数据库
- Nginx 前端服务
- 强密码安全配置

⚠️ P2 待办 (下次迭代):
- 学生/检测/预警等业务模块实现
- 错误处理统一化
- 缓存策略优化
- 日志分级

📍 生产环境:
- 服务器:192.168.15.222
- 管理后台:http://192.168.15.222:8081
- API 服务:http://192.168.15.222:8080

2026-03-29 上线部署完成
2026-03-29 18:16:41 +08:00

222 lines
6.1 KiB
Go

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("路由设置完成")
}