05. Fiber 与项目实战串联

7 阅读3分钟

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
}

你可以把这段代码拆成五步理解:

  1. 读取环境变量
  2. 初始化数据库
  3. 初始化 Agent
  4. 组装 Fiber 应用
  5. 启动服务并等待退出信号

这就是典型的 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(&params); 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 结构,而不用每个接口都重复写一套格式化逻辑。

你现在应该怎样读项目代码

建议按下面的路径阅读后端:

  1. server/main.go 看程序怎么启动
  2. server/router/router.go 看路由和中间件怎么组装
  3. 进入 server/app/gitea/router/ 看某个模块暴露了哪些接口
  4. 再看 handlers/repository/services/ 如何分工
  5. 最后结合 common/responsecommon/pagination 看通用抽象

小结

如果你读到这里已经能理解下面这些问题,说明 Go 基础已经够用了:

  • 服务是从哪里启动的
  • 路由和中间件是怎么注册的
  • 为什么处理器里要拿 *fiber.Ctx
  • 为什么仓储层函数普遍接收 context.Context
  • 为什么统一响应会用泛型