ByteNoteByteNote
中间件要写在注册路由之前
字

字节笔记本

2026年10月7日 · 约 10 分钟读完

中间件要写在注册路由之前

API中转
¥120

go-zero 的网关样本从一把 goctl 开始,而不是先写路由表。命令是 goctl api new gateway,再 go mod tidy。路由写在 api 文件里,goctl api go -api gateway.api -dir . 生成处理函数。全局中间件要加在 RegisterHandlers 之前。

中间件要在注册路由之前挂上

路径参数的可选取值写在标签里

安装样本是 GO111MODULE=on go get -u github.com/zeromicro/go-zero。模块模式打开后再拉这一个路径。网关目录生成之后,api 文件里的类型是 Request 和 Response。Name 的标签是 `path:"name,options=you|me"`。路径是 get /from/:name。options 把取值收成 you 或 me。别的名字在进入处理函数之前就该被拒绝,而不是在逻辑里再比较一遍字符串。

响应只有一个 JSON 字段 message。处理函数名由 @handler GatewayHandler 指定,生成后落到 logic 文件。业务写在生成出来的逻辑里,不要改生成文件的签名后又重新生成,把签名盖掉。

配置里留了 etcd 的 Hosts 和 Key,类型嵌在 rest.RestConf 旁边。这是服务发现的位置,不是路由本身。没有 etcd 时不要填一套假地址让进程去连。样本没有给出可用的主机列表,所以发现这一段以你自己的集群为准,路由和中间件可以先在单进程里跑通。

启动是 rest.MustNewServer,defer server.Stop(),conf.MustLoad 读 yaml。运行命令是 go run gateway.go -f etc/gateway-api.yaml。配置文件路径错了,MustLoad 会停,轮不到监听端口。日志样本会打印 c.Host 和 c.Port。

全局中间件和某一条路由的中间件

全局的挂法是在 RegisterHandlers 之前 server.Use。包装函数收到 next,打印一行 global middleware,再调用 next(w, r)。不调用 next,后面的处理函数不会跑。打印放在 next 前面,是请求进来时。要看响应之后的状态,把代码放在 next 返回之后。

某一条路由的中间件写在 api 文件的 @server 里:middleware: AuthMiddleware。生成之后,这个字段要出现在 ServiceContext 上。样本在 NewServiceContext 里把 AuthMiddleware 设成 middleware.NewAuthMiddleware().Handle。上下文里没有这个字段,生成的注册代码对不上,编译会停在缺字段,而不是在请求时才 401。

鉴权失败时应在中间件里写完状态并返回,不要调用 next。样本只演示了把处理函数放进上下文,没有演示拒绝时的状态码。接上检查之后,拒绝和放行要分成两支。放行才 next。

后半段把负载模式写成三个字符串:round_robin、random、least_active。yaml 示例把 Mode 设成 round_robin,Endpoints 是三台占位地址。服务上下文里用 zrpc.MustNewClient 读这些字段。样本里的 Token 写成 your-token,这是占位符,不能当成可用凭证。这段客户端和前面的 get /from/:name 不是已经接好的同一条转发。先让 rest 路由和中间件跑通,再决定要不要接这组端点。把 Use 写到 RegisterHandlers 后面,已注册的路由不会走这个包装。

options 把路径参数收成两个值

生成、再填逻辑、再挂中间件

顺序是安装、goctl api new、写 api、goctl api go、在 logic 里写处理、在 RegisterHandlers 前 Use、在 api 里用 @server 声明路由级中间件并在服务上下文里提供同名函数。路径参数的可选取值放进 options。etcd 没有真实地址就不要填。重新生成之后核对中间件字段还在。启动用 -f 指向 yaml。

一条请求走过的顺序

配置从 etc/gateway-api.yaml 载入。MustLoad 失败时进程退出,不会监听。服务建好之后,全局中间件已经用 Use 挂上,然后才 RegisterHandlers。请求进来,先打到全局包装,打印 global middleware,再 next。路由若带了 @server 里的 AuthMiddleware,这一层在业务逻辑之前。两层都调用 next,才进入 GatewayHandler 生成的逻辑。任何一层不调用 next,后面的打印和业务都不会发生。

路径 /from/:name 的 name 只接受 you 或 me。请求 /from/other 应在参数校验失败,而不是进入逻辑再比较。标签是 `path:"name,options=you|me"`。去掉 options,任意字符串都会进来,逻辑里若仍假设只有两个值,分支会漏。响应 JSON 只有 message。逻辑若返回别的字段名,生成的类型对不上,编译失败。改响应要同时改 api 文件并重新 goctl api go。

重新生成会覆盖处理函数的签名和注册。手写在生成文件里的中间件字段可能被盖掉。业务放在 logic,中间件的构造放在 NewServiceContext。生成之后看 ServiceContext 是否仍有 AuthMiddleware。没有就补上,再编译。不要为了编译通过把 @server 那行删掉,路由级鉴权会一起消失。

etcd 的 Hosts 和 Key 嵌在配置里。没有集群时留空或不要启用发现,不要填一个不可达地址让启动去拨号。样本的运行命令不包含 etcd。单进程先用 go run gateway.go -f etc/gateway-api.yaml 看 c.Port 是否打印。端口打出来,说明配置和 MustNewServer 已经走过。再测 /from/you 与 /from/me,最后测一个不在 options 里的名字。

options=you|me 写在 path 标签里。请求 /from/you 与 /from/me 才能进入处理函数。其它名字应在校验阶段停下。另两条样本路由是 post /api/v1/users 和 get /api/v1/files/:filename,方法不同,中间件若只写在其中一条的 @server 上,另外两条不会走它。全局 Use 才会罩住全部已注册路由,并且必须写在注册之前。

重新执行 goctl api go 之后,核对 ServiceContext 里仍有 AuthMiddleware,值仍是 middleware.NewAuthMiddleware().Handle。字段被生成覆盖掉,编译会缺字段。不要为了通过编译删掉 api 文件里的 middleware 行,那一行删了,路由级包装就没了。

etcd 的 Hosts 和 Key 没有给出可用地址。没有集群就不要填不可达主机。启动命令只带 -f etc/gateway-api.yaml。日志里打出 c.Host 和 c.Port,说明配置已经载入并建好了服务器。接着用两个允许的名字各请求一次,再用一个不在 options 里的名字确认被拒绝。

占位令牌 your-token 不要写进仓库当正式凭证。端点列表里的 service1 到 service3 也是示例主机名。接真实上游时换成自己的地址,并确认 Mode 仍是那三个字符串之一。样本没有说明这三个模式的内部计数器怎么更新,使用前要在自己的版本里核对,不要凭名字猜测分配规则。 全局中间件的样本在调用 next 之前打印 global middleware。请求能打到业务逻辑,说明这行已经执行并且 next 被调用了。业务没执行、日志却有这行,问题在后面的路由级中间件没有放行。两行日志都没有,说明 Use 没有罩住这条路由,常见原因是写在 RegisterHandlers 之后。路径参数不在 you 和 me 之中就不应进入逻辑。若任意字符串都能进来,检查标签里的 options 是否还在,重新生成时有没有把 api 文件盖掉。启动仍用 go run gateway.go -f etc/gateway-api.yaml。配置路径错误会停在 MustLoad,端口日志不会出现。

请求先过全局包装,再过路由上声明的包装,最后才进业务。任何一层不把控制权交出去,后面都不会执行。路径上的名字只允许那两个取值,别的名字应在进业务之前被拒绝。负载那段给出了三种模式的名字和一份占位端点,令牌也是占位,不能写进仓库当正式密钥。它和前面那条按名字取值的路由不是已经接好的同一条转发。重新生成之后要核对服务上下文里的中间件字段还在。启动只带配置文件参数。配置读入失败时,端口不会打印出来。

相关文章

分享: