应直接用 fiber.New() 初始化应用,避免套用 MVC 目录结构;Fiber 是函数式路由驱动的轻量框架,推荐按业务域(如 internal/user/)组织代码,仅暴露 handler 和 model,通过接口封装依赖,禁用默认中间件并显式注册路由。

直接用 fiber.New() 初始化应用,别套 MVC 目录结构——Fiber 本身不依赖控制器/服务分层,硬拆出 controllers/、services/ 反而让依赖变隐晦、测试变重、启动变慢。
为什么不要照搬 Rails 或 Gin 的 MVC 目录?
Fiber 是函数式路由驱动的轻量框架,app.Get("/users", handler) 中的 handler 就是一个普通函数,它天然适合按业务域组织,而不是按抽象角色(controller/service/repo)切包。强行模仿会导致:
-
handlers/user_handler.go里调services.UserService.Create(),但UserService又依赖repo.UserRepo和pkg/middleware.Auth,最终所有包都得 importinternal/pkg,internal失去隔离意义 - 写单元测试时,要 mock 整条链:handler → service → repo → db client,而实际只需测 handler 输入输出和关键错误分支
- 新增一个
/admin/users管理端口,你得同步改handlers/、services/、repo/三个目录,但逻辑可能就只是加个if adminMode { ... }
推荐结构:按 HTTP 边界 + 领域内聚组织
以用户管理模块为例,把所有相关代码收进一个包,靠 Go 的包级可见性和接口抽象解耦,不是靠目录名“假装”分层:
myapi/
├── cmd/
│ └── myapi/
│ └── main.go # 只做三件事:加载 config、初始化 fiber.App、注册路由
├── internal/
│ └── user/ # 单一职责包:处理 /users 下所有事
│ ├── handler.go # func ListUsers(c *fiber.Ctx) error { ... }
│ ├── model.go # type UserReq struct { Name string `json:"name"` }; type UserResp struct { ID int }
│ ├── store.go # type Store interface { Create(ctx context.Context, u UserReq) (int, error) }
│ └── pgstore.go # 实现 Store,只在 user/ 包内 new,不暴露给其他包
├── pkg/
│ └── middleware/
│ └── auth.go # func Auth() fiber.Handler { ... },返回标准 fiber.Handler
├── config.yaml
└── go.mod关键点:
立即学习“go语言免费学习笔记(深入)”;
-
internal/user/包对外只暴露handler函数和model结构体,store接口和实现完全封装在内部 -
cmd/myapi/main.go里用依赖注入方式传入具体 store:user.NewHandler(&user.PGStore{db: pgConn}),不 new 全局单例 - 需要 mock 测试?直接传
&user.MockStore{},不用动任何中间层
fiber.New() 启动时必须关掉默认中间件
fiber.Default() 会悄悄启用 Logger 和 Recover,生产环境必须禁用:
-
Logger默认打印所有请求头,可能泄露Authorization、Cookie值 -
Recover在 panic 时返回完整堆栈,暴露路径、变量名、Go 版本等敏感信息 - 两者都会增加 TTFB(首字节时间)0.3–0.8ms,在高并发下显著拖慢
正确写法:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
app := fiber.New(fiber.Config{
ErrorHandler: func(c *fiber.Ctx, err error) error {
// 统一错误格式,不暴露 err.Error()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "internal error"})
},
})日志和 recover 按需自己加,例如:
app.Use(middleware.RequestID()) app.Use(middleware.Logger()) // 自定义 logger,过滤敏感 header app.Use(middleware.Recover()) // 自定义 recover,只记录到日志,不返回堆栈
路由注册必须显式、集中、无歧义
Fiber 默认开启 StrictRouting: true,意味着 /users 和 /users/ 是两个不同路由。前端发来带尾斜杠的请求,后端没注册就直接 404,排查困难。
别用 fiber.New(&fiber.Config{StrictRouting: false})——这会让 /users 错误匹配 /users/123,破坏 RESTful 路径语义。
正确做法:加一个前置中间件统一重定向
app.Use(func(c *fiber.Ctx) error {
path := c.Path()
if strings.HasSuffix(path, "/") && path != "/" {
return c.Redirect(strings.TrimSuffix(path, "/"), fiber.StatusMovedPermanently)
}
return c.Next()
})同时确保:
- 所有
app.Get("/users", ...)注册都不带尾斜杠 - OpenAPI 文档、前端 SDK、curl 示例全部使用无尾斜杠路径
- CI 流水线跑一遍
curl -I https://api.example.com/users/,验证是否 301 到/users
真正容易被忽略的是:Fiber 的 ctx.Next() 不是“继续执行下一个中间件”,而是“把控制权交还给 Fiber 调度器”。一旦你在某个中间件里调了 c.JSON() 或 c.Send(),后续中间件和 handler 就彻底跳过——权限校验失败后写了 c.Status(401).Send(),后面再跟 c.Next() 完全无效。

















