package bootstrap import ( "net/http" "path/filepath" "strconv" "strings" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" routers "dbview/service/internal/modules" "go.uber.org/zap" ) // Server HTTP服务器 type Server struct { app *App engine *gin.Engine } // NewServer 创建新的服务器实例 func NewServer(app *App) *Server { return &Server{ app: app, } } // Setup 设置服务器 func (s *Server) Setup() { // 创建Gin引擎 s.engine = gin.Default() // 设置中间件(已优化重复CORS) s.setupMiddleware() // 1. 先注册所有API路由(确保API路由优先) s.registerRoutes() // 2. 挂载本地静态文件(Vue打包的dist目录) //s.mountStaticFiles() s.app.Logger.Info("HTTP服务器设置完成", zap.String("addr", s.app.Config.Server.Ip), zap.Bool("cors_enabled", true), zap.Bool("audit_enabled", s.app.Config.Audit.Enabled)) } // 新增:挂载本地静态文件(不使用embed) func (s *Server) mountStaticFiles() { // 获取本地dist目录的绝对路径(确保程序能找到Vue打包后的文件) // 注意:dist目录需放在项目根目录(与main.go同级) distPath, err := filepath.Abs("dist") if err != nil { s.app.Logger.Error("获取静态文件目录失败", zap.Error(err)) return } // 挂载静态文件到 /static 前缀(与API路由完全隔离,避免冲突) // 访问路径:/static/index.html → 对应本地 dist/index.html s.engine.StaticFS("/static", http.Dir(distPath)) s.app.Logger.Info("静态文件挂载成功", zap.String("dist_path", distPath), zap.String("mount_path", "/static")) // 3. 处理Vue SPA前端路由(如 /dashboard、/settings 等前端路由) s.engine.NoRoute(func(c *gin.Context) { // 过滤API路由(避免API请求被误处理为前端路由) // 现有API前缀:/connection_storage、/db_connection、/db_view、/sql_execute、/api 等 apiPrefixes := []string{ "/connection_storage", "/db_connection", "/db_view", "/sql_execute", "/api", } for _, prefix := range apiPrefixes { if strings.HasPrefix(c.Request.URL.Path, prefix) { c.Status(http.StatusNotFound) // API路由不存在时返回404 return } } // 非API路由:返回dist目录下的index.html(由前端路由处理) indexPath := filepath.Join(distPath, "index.html") c.File(indexPath) }) } // setupMiddleware 设置中间件(清理重复CORS) func (s *Server) setupMiddleware() { // 依赖注入中间件 - 暂时注释,等待 middleware 包实现 // s.engine.Use(middleware.InjectDependencies(s.app.StorageManager, s.app.ConnectionPool)) // 自定义CORS配置(保留自定义配置,移除重复的cors.Default()) s.engine.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, // 允许所有来源 AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的方法 AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"}, // 允许的头部 ExposeHeaders: []string{"Content-Length"}, // 可暴露的头部 AllowCredentials: true, // 允许携带凭据 MaxAge: 12 * time.Hour, // 缓存时间 })) // 客户端地址中间件 - 暂时注释,等待 middleware 包实现 // s.engine.Use(middleware.ClientAddrMiddleware()) // Gin恢复中间件(异常恢复) s.engine.Use(gin.Recovery()) // 使用修复后的日志中间件 - 暂时注释,等待 middleware 包实现 // s.engine.Use(middleware.RequestLoggerMiddleware()) } // registerRoutes 注册路由(现有逻辑不变) func (s *Server) registerRoutes() { // 注册模块路由 routers.RegisterRoutes(s.engine, s.app.StorageManager, s.app.TaskManager, s.app.ConnectionPool) // 基础健康检查路由 s.engine.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{ "status": "ok", "time": time.Now().Format(time.RFC3339), }) }) s.app.Logger.Info("路由注册完成") } // Start 启动服务器(现有逻辑不变) func (s *Server) Start() error { s.app.Logger.Info("HTTP服务器启动完成", zap.String("IP:Port", s.app.Config.Server.Ip+":"+strconv.Itoa(s.app.Config.Server.Port))) addr := s.app.Config.Server.Ip + ":" + strconv.Itoa(s.app.Config.Server.Port) return s.engine.Run(addr) }