← 返回博客
go2026-09-12 22:01:032 分钟 · 465 0

Gin + GraphQL:用 gqlgen 落地 GraphQL 服务

用 gqlgen 以 schema-first 方式在 Gin 项目里接入 GraphQL,讲清代码生成、resolver 实现、N+1 与查询复杂度限制。

#gin#GraphQL#gqlgen

REST 一个资源一个端点,前端要拼好几个接口才能凑齐页面数据。GraphQL 一个 /query 端点,前端自己声明要哪些字段,后端按 schema 返回。这一篇在已有 Gin 项目旁挂一个 GraphQL 服务,用 gqlgen 走 schema-first。

1. 为什么接 GraphQL

前端按需取字段,减少过度拉取和多次往返。强类型 schema 是前后端契约,改字段有编译期保障。它不是 REST 的替代品,文件上传、内部服务间调用仍适合 REST。

2. 定义 schema

gqlgen 是 schema-first:先写 .graphqls 再生成代码。

type Article { id: ID! title: String! views: Int! }
type Query { article(id: ID!): Article }
type Mutation { incViews(id: ID!): Article }

3. 生成代码

go.modgithub.com/99designs/gqlgen,放 gqlgen.yml

schema:
  - schema.graphqls
exec:
  filename: graph/generated/generated.go
model:
  filename: graph/model/models.go
resolver:
  filename: graph/resolver.go
  type: Resolver

go run github.com/99designs/gqlgen generate,生成 resolver 骨架和绑定代码,你只填业务逻辑。

4. 实现 resolver

生成的 Resolver 是空结构体,业务依赖通过构造函数注入:

type Resolver struct{ svc ArticleService }

func (r *mutationResolver) IncViews(ctx context.Context, id string) (*model.Article, error) {
    return r.svc.IncViews(ctx, id)
}
func (r *queryResolver) Article(ctx context.Context, id string) (*model.Article, error) {
    return r.svc.Get(ctx, id)
}

ArticleService 复用第 5 篇的分层,不用为 GraphQL 重写数据层。

5. 挂到 Gin

gqlgen 自带 handler,包一层挂到路由:

import "github.com/99designs/gqlgen/graphql/handler"

r.POST("/query", func(c *gin.Context) {
    h := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &resolver}))
    h.ServeHTTP(c.Writer, c.Request)
})

也可以独立端口跑 GraphQL,Gin 只管 REST 和鉴权。

6. 把登录态传给 resolver

GraphQL 没有 per-field 路由,鉴权在中间件里做,uid 写进 context:

r.POST("/query", AuthRequired(), func(c *gin.Context) {
    ctx := context.WithValue(c.Request.Context(), "uid", c.MustGet("uid"))
    h.ServeHTTP(c.Writer, c.Request.WithContext(ctx))
})

resolver 里 ctx.Value("uid") 取当前用户。字段级权限在 resolver 内判断。

7. 解决 N+1

一个 query 拉文章列表再各拉作者,会打 N 次库。gqlgen 配 dataloader,按 id 批量查:

// loader 在 resolver 间共享,收集本请求内的 id 一次性查
authors, _ := dataloader.For(ctx).Article.LoadMany(ids)

一次查询代替 N 次,列表接口延迟从 O(N) 掉到常数级。

8. 防查询滥用

GraphQL 一个请求能嵌套很深,恶意查询拖垮服务。限制查询深度和复杂度:

h := handler.NewDefaultServer(exec)
h.Use(extension.FixedComplexityLimit(1000)) // 超复杂度直接拒

配合第 2 篇的限流,双保险。

下一篇讲限流,给这套服务加一道流量闸门。

相关推荐

本文为原创文章,采用CC BY-NC-SA 4.0协议授权,转载请保留署名与原文链接。原文链接:https://www.wxbuluo.com/article/189