05. Fiber 与项目实战串联
前面几节学的是语言基础,这一节把它们全部放回真实项目里。目标不是把 Fiber 全讲完,而是让你能顺着当前仓库的后端主线把代码读下去。
本节目标
- 看懂后端服务是如何启动的
- 看懂 Fiber 的中间件、路由组和处理器
- 理解 Handler、Repository、Response 之间的关系
- 能顺利衔接到下一章“项目结构解析”
先看程序入口
后端入口在 server/main.go,整体流程很清晰:
func main() {
if err := godotenv.Load(); err != nil {
log.Printf("未找到 .env 文件: %v", err)
}
dbConfig := config.GetDatabaseConfig()
dbService, err := database.NewDatabaseService(dbConfig.GetDSN())
if err != nil {
log.Fatalf("数据库初始化失败: %v", err)
}
defer dbService.Close()
agentInstance := agent.SetupAgent(dbService.GetDB())
syncAgentInstance := agent.SetupSyncAgent(dbService.GetDB())
app := router.SetupRouter(dbService, agentInstance, syncAgentInstance)
go func() {
if err := app.Listen(":8080"); err != nil {
log.Fatal(err)
}
}()
<-quit
}
你可以把这段代码拆成五步理解:
- 读取环境变量
- 初始化数据库
- 初始化 Agent
- 组装 Fiber 应用
- 启动服务并等待退出信号
这就是典型的 Go 后端启动流程。
Fiber 应用初始化
Fiber 路由组装在 server/router/router.go:
app := fiber.New(fiber.Config{
AppName: "Commit Dashboard Server",
ErrorHandler: customErrorHandler,
})
app.Use(recover.New())
app.Use(logger.New())
app.Use(cors.New(cors.Config{
AllowOrigins: "http://localhost:5173",
}))
api := app.Group("/api")
这里能看到几个常见概念:
fiber.New(...):创建应用实例app.Use(...):注册全局中间件app.Group("/api"):创建路由组ErrorHandler:统一处理未捕获错误
路由是怎么和业务代码接起来的
继续往下看:
db := dbService.GetDB()
gitea_router.SetupGiteaRoutes(api, db)
if agentInstance != nil {
ai_router.SetupAgentRoutes(api, db, agentInstance)
}
这说明路由层本身不做复杂业务,它更像“装配层”:
- 把依赖准备好
- 把不同模块挂到统一路由树上
- 让业务逻辑下沉到具体 handler / service / repository
处理器:面向 HTTP 的一层
以仓库列表处理器为例:
type RepoHandler struct {
repoRepo *repository.RepoRepository
}
func (h *RepoHandler) List(c *fiber.Ctx) error {
repos, err := h.repoRepo.List(c.Context())
if err != nil {
return response.InternalServerCtx(c, "查询仓库列表失败")
}
return response.SuccessCtx(c, repos)
}
这层代码主要负责:
- 从
fiber.Ctx读取请求 - 调用业务或数据访问层
- 把结果转成 HTTP 响应
换句话说,它是“Web 协议适配层”。
请求参数绑定
分页接口的处理器还会用到参数绑定:
var params gitea_req.RepoQueryParams
if err := c.QueryParser(¶ms); err != nil {
return response.BadRequestCtx(c, "参数解析失败")
}
params.Validate()
这里串联了前面学过的几个知识点:
- 结构体承接输入数据
- 标签决定字段如何绑定
- 方法负责校验和修正默认值
- 错误处理使用
if err != nil
仓储层:面向数据库的一层
再看仓储层:
type RepoRepository struct {
db *bun.DB
}
func (r *RepoRepository) List(ctx context.Context) ([]gitea_db.Repository, error) {
var repos []gitea_db.Repository
err := r.db.NewSelect().
Model(&repos).
Order("full_name ASC").
Scan(ctx)
if err != nil {
return nil, fmt.Errorf("获取仓库列表失败: %w", err)
}
return repos, nil
}
这一层的职责是:
- 操作数据库
- 屏蔽 SQL / ORM 细节
- 返回更接近业务的数据结构
这也就是为什么项目会拆出 Handler -> Repository 这样的分层。
统一响应:泛型在项目里的实际应用
项目里有统一响应工具:
type Response[T any] struct {
Code int `json:"code"`
Data T `json:"data"`
Message string `json:"message"`
}
func SuccessCtx[T any](c *fiber.Ctx, data T) error {
return c.JSON(Success(data))
}
这样处理器层就可以非常稳定地输出同一种 JSON 结构,而不用每个接口都重复写一套格式化逻辑。
你现在应该怎样读项目代码
建议按下面的路径阅读后端:
- 从
server/main.go看程序怎么启动 - 到
server/router/router.go看路由和中间件怎么组装 - 进入
server/app/gitea/router/看某个模块暴露了哪些接口 - 再看
handlers/、repository/、services/如何分工 - 最后结合
common/response、common/pagination看通用抽象
小结
如果你读到这里已经能理解下面这些问题,说明 Go 基础已经够用了:
- 服务是从哪里启动的
- 路由和中间件是怎么注册的
- 为什么处理器里要拿
*fiber.Ctx - 为什么仓储层函数普遍接收
context.Context - 为什么统一响应会用泛型