2025-04-02 14:29:44 +08:00

183 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}