183 lines
4.9 KiB
Go
183 lines
4.9 KiB
Go
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
|
||
}
|