package server import ( "fmt" "log" "os" "path/filepath" "strings" "github.com/goccy/go-json" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/template/html/v2" "gitee.ltd/lxh/wechat-robot/internal/config" "gitee.ltd/lxh/wechat-robot/internal/handler" "gitee.ltd/lxh/wechat-robot/internal/middleware" ) // Server 表示HTTP服务器 type Server struct { app *fiber.App config *config.Config } // New 创建新的服务器实例 func New(cfg *config.Config) *Server { // 确保视图目录存在 viewsDir := "./internal/view" if _, err := os.Stat(viewsDir); os.IsNotExist(err) { log.Fatalf("视图目录不存在: %s", viewsDir) } // 调试模式下输出所有模板文件 if cfg.Server.Env == "development" { log.Println("正在加载模板文件...") err := filepath.Walk(viewsDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && strings.HasSuffix(path, ".html") { relPath, _ := filepath.Rel(viewsDir, path) log.Printf("找到模板: %s", relPath) } return nil }) if err != nil { log.Printf("遍历模板文件失败: %v", err) } } // 初始化模板引擎 engine := html.New(viewsDir, ".html") engine.Reload(cfg.Server.Env == "development") // 开发环境下启用热重载 engine.Debug(cfg.Server.Env == "development") // 增加Debug输出 // 添加自定义模板函数 engine.AddFunc("sub", func(a, b int) int { return a - b }) // 添加时间格式化函数 engine.AddFunc("timeSince", handler.TimeSince) // 添加map函数,用于在模板中创建映射 engine.AddFunc("map", func(values ...interface{}) map[string]interface{} { if len(values)%2 != 0 { return nil } m := make(map[string]interface{}, len(values)/2) for i := 0; i < len(values); i += 2 { if key, ok := values[i].(string); ok { m[key] = values[i+1] } } return m }) // 创建Fiber应用 app := fiber.New(fiber.Config{ Views: engine, ViewsLayout: "layouts/main", // 默认布局 EnablePrintRoutes: cfg.Server.Env == "development", JSONEncoder: json.Marshal, JSONDecoder: json.Unmarshal, ErrorHandler: func(c *fiber.Ctx, err error) error { // 详细错误日志 log.Printf("错误: %+v\n请求路径: %s\n", err, c.Path()) code := fiber.StatusInternalServerError if e, ok := err.(*fiber.Error); ok { code = e.Code } // 对于API请求返回JSON响应 if strings.HasPrefix(c.Path(), "/api/") { return c.Status(code).JSON(fiber.Map{ "success": false, "message": err.Error(), }) } // 对于页面请求渲染错误页面,但使用简化的布局以避免布局中的错误 return c.Status(code).Render("error", fiber.Map{ "Title": fmt.Sprintf("Error %d", code), "StatusCode": code, "ErrorMessage": err.Error(), "NoLayout": true, // 不使用复杂布局,避免更多错误 }) }, }) // 注册中间件 app.Use(recover.New()) app.Use(logger.New(logger.Config{ Format: "[${time}] ${status} - ${latency} ${method} ${path}\n", TimeFormat: "2006-01-02 15:04:05", })) app.Use(cors.New()) // 静态文件服务 app.Static("/public", "./public") return &Server{ app: app, config: cfg, } } // SetupRoutes 设置路由 func (s *Server) SetupRoutes() { // 公共路由 s.app.Get("/", handler.Home) s.app.Get("/login", handler.LoginPage) s.app.Post("/login", handler.LoginSubmit) s.app.Get("/logout", handler.Logout) // 添加健康检查接口(保留此API,用于Docker健康检查) s.app.Get("/health", handler.HealthCheck) // 添加容器API接口 s.app.Get("/api/v1/containers/:id/logs", handler.GetContainerLogs) s.app.Get("/api/v1/containers/:id/stats", handler.GetContainerStats) // 受保护的路由 - 简化为仅保留必要功能 auth := s.app.Group("/admin", middleware.Authenticate()) // 机器人管理 - 核心功能 auth.Get("/robots", handler.ListRobots) auth.Get("/robots/new", handler.NewRobotForm) auth.Post("/robots", handler.CreateRobot) auth.Get("/robots/:id", handler.ShowRobot) auth.Delete("/robots/:id", handler.DeleteRobot) auth.Get("/robots/:id/login", handler.RobotLogin) auth.Post("/robots/:id/logout", handler.RobotLogout) auth.Get("/robots/:id/check-qrcode", handler.CheckQRCodeStatus) // 联系人和消息 - 必要功能 auth.Get("/robots/:id/contacts", handler.ListContacts) auth.Get("/robots/:id/contacts/:contactId", handler.ShowContact) auth.Get("/robots/:id/contacts/:contactId/messages", handler.ListMessages) } // Start 启动HTTP服务器 func (s *Server) Start() error { addr := s.config.Server.Address() fmt.Printf("Server starting on %s\n", addr) return s.app.Listen(addr) } // Shutdown 优雅地关闭服务器 func (s *Server) Shutdown() error { if s.app != nil { return s.app.Shutdown() } return nil }