go-zero 的 middleware 放哪?有没有 http 目录

「middleware 目录一般建议放哪里?没有专门类似 http 的目录吗?」—— 这篇把 go-zero 的目录约定、为什么没有 http 目录、middleware 该放哪、以及 HTTP 中间件怎么写怎么注册一次讲清。

放哪 / 有没有 http 目录 推荐目录结构 中间件长啥样 怎么注册 在 .api 声明 RPC 拦截器 结论

middleware 放哪?有没有专门的 http 目录

两步结论:① 没有名为 http 的目录② 中间件建议放 internal/middleware/,而「HTTP 层」其实由 internal/handler 承担。

❓ 为什么没有 http 目录
go-zero 用的是「分层」而不是「按协议命名目录」。HTTP 相关的代码被拆成了 handler(路由+薄壳)和 logic(业务),所以不需要一个笼统的 http 文件夹。goctl 生成的目录里也没有它。
✅ middleware 建议放哪
社区和 goctl 的约定是独立成一个包:internal/middleware/。当 .api 里声明了中间件、或你手动建,这个目录就放所有 HTTP 中间件。RPC 的拦截器同理放这里或 internal/rpc/middleware

一句话:go-zero 把 HTTP 层拆进了 handler,把横切逻辑抽到了 middleware。所以「专门类似 http 的目录」—— 它的角色由 handler 扮演,而不是叫 http。

📌 goctl 生成时的实际产物:config / handler / logic / svc / types 一定会有;middleware 目录在你在 .api 里声明了 middleware: 时由 goctl 生成,否则你手动建一个即可。无论哪种,约定都指向 internal/middleware/

一套清晰的 middleware 布局

HTTP 中间件统一收口在 internal/middleware/,路由注册仍然在 internal/handler/routes.go。下面是一份标准骨架。

user-api/ ├── etc/ │ └── user-api.yaml ├── internal/ │ ├── config/ # 配置类型 │ ├── handler/ # ★ HTTP 层:routes.go 在这里 Use / WithMiddlewares │ │ ├── routes.go │ │ └── loginhandler.go │ ├── logic/ # 业务逻辑 │ ├── middleware/ # ★ 所有 HTTP 中间件放这里 │ │ ├── jwt.go │ │ ├── cors.go │ │ └── log.go │ ├── svc/ # 依赖装配 │ └── types/ # DTO ├── user.go # main:server.Use(全局中间件) └── go.mod
middleware 包的职责
每个文件导出一个中间件(JWT 校验、CORS、访问日志、限流、Trace)。需要依赖就从构造器注入 svcCtx 或具体 client。
handler 包的职责
routes.go 负责「把哪些路由套上哪些中间件」。全局的用 server.Use(),路由级的用 rest.WithMiddlewares()
⚠️ 别把中间件塞进 handlerlogic。handler 应该是薄壳、logic 应该只关心业务;横切关注点(鉴权、日志、跨域)属于 middleware,分开放才好复用、好测试。

go-zero 的 HTTP 中间件类型

go-zero 的 HTTP 中间件就是 rest.Middleware 类型:func(next http.HandlerFunc) http.HandlerFunc。约定写法是用一个结构体包一层,方便注入依赖。

// internal/middleware/jwt.go
package middleware

import (
    "net/http"
    "github.com/zeromicro/go-zero/rest"
)

// 需要依赖时,用结构体包一层,构造器注入
type JwtMiddleware struct {
    Secret string
}

func NewJwtMiddleware(secret string) *JwtMiddleware {
    return &JwtMiddleware{Secret: secret}
}

// Handle 的签名正好就是 rest.Middleware
func (m *JwtMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if !m.valid(token) {                  // 校验
            httpx.Error(w, errors.New("unauthorized"))
            return                            // 不放行
        }
        // 把 userId 透传给下游(走 context,不是 svc)
        ctx := context.WithValue(r.Context(), "uid", uid)
        next(w, r.WithContext(ctx))         // 放行
    }
}

// 不需要依赖的轻量中间件,可以直接是函数
func LogMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        logx.Infof("req %s %s", r.Method, r.URL.Path)
        next(w, r)
    }
}

要点:中间件签名 = func(next) next。放行就调 next(w, r),拦截就直接写响应并 return。需要数据库/Redis/配置,就用结构体 + 构造器注入(和 svc 的依赖注入思路一致)。

全局中间件 vs 路由级中间件

两种注册方式:server.Use() 对所有路由生效;rest.WithMiddlewares() 只对某组路由生效。推荐在 routes.go 里集中管理。

① 全局(main.go 里 Use)

// user.go (main)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()

// 所有请求都过一遍:访问日志 + 跨域
server.Use(middleware.LogMiddleware)
server.Use(middleware.NewCorsMiddleware().Handle)

handler.RegisterHandlers(server, svcCtx)
server.Start()

② 路由级(routes.go 里 WithMiddlewares)

// internal/handler/routes.go
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
    server.AddRoutes(
        []rest.Route{
            {Method: http.MethodPost, Path: "/login", Handler: LoginHandler(serverCtx)},
            {Method: http.MethodGet,  Path: "/profile", Handler: ProfileHandler(serverCtx)},
        },
        // 只对这组路由套 JWT 鉴权
        rest.WithMiddlewares([]rest.Middleware{
            middleware.NewJwtMiddleware(serverCtx.Config.Auth.AccessSecret).Handle,
        }),
    )

    // 登录接口不套 JWT(否则永远进不来),单独加
    server.AddRoutes([]rest.Route{
        {Method: http.MethodPost, Path: "/login", Handler: LoginHandler(serverCtx)},
    })
}
请求 Log 中间件 全局 Use JWT 中间件 路由级 Handler 薄壳 Logic

用 DSL 声明中间件(goctl 帮你接好)

不想手动改 routes.go?可以在 .api@server 里声明 middleware:,goctl 生成时会在 routes.go 留好调用位,你只管实现。

// user.api
@server(
    jwt:    Auth
    middleware: AuthMiddleware   # 声明:这组接口要过 AuthMiddleware
)
service user-api {
    @handler profile
    get /profile
}

// goctl 生成后,routes.go 会写出引用;你实现 internal/middleware/authmiddleware.go
// 命名约定:XxxMiddleware → NewXxxMiddleware().Handle
⚠️ 用 DSL 声明时,实现文件必须按约定命名(如 AuthMiddlewareinternal/middleware/authmiddleware.go 里的 NewAuthMiddleware),否则编译报「未定义」。手动在 routes.go 里 WithMiddlewares 更直观可控,新手优先用后者。
📌 小提示:jwt: Auth 是 go-zero 内置的 JWT 中间件(配置里写 Auth 的 secret/expire 即可),不用自己写;只有「非标准」的横切逻辑(自定义 header 校验、租户解析等)才需要你手写 middleware。

zrpc 没有 middleware,只有拦截器

go-zero 的 RPC 层(zrpc)不叫 middleware,叫拦截器 interceptor。写法不同,但目录约定一样——放 internal/middleware/internal/rpc/middleware

// 服务端拦截器(在 main 注册)
s := zrpc.MustNewServer(c.RpcServerConf, func(g *grpc.Server) {
    user.RegisterUserServer(g, server.NewUserServer(ctx))
}, rpcserver.WithUnaryServerInterceptor(myInterceptor))
// 客户端拦截器:zrpc.WithUnaryClientInterceptor(...)

// 拦截器签名(和 HTTP 中间件不一样)
func myInterceptor(ctx context.Context, req interface{},
    info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // 前置:日志/鉴权/限流
    resp, err := handler(ctx, req)   // 放行
    // 后置:统计/报错
    return resp, err
}
维度HTTP 中间件RPC 拦截器 所在层internal/middleware,注册在 handler/routesinternal/middleware 或 rpc 包,注册在 zrpc server/client 签名func(next http.HandlerFunc) http.HandlerFuncUnaryServerInterceptor(含 info/handler) 注册server.Use / rest.WithMiddlewaresWithUnaryServerInterceptor / 客户端 WithUnaryClientInterceptor

你的两个问题,一句话版

疑问答案
middleware 建议放哪?独立成包 internal/middleware/。HTTP 中间件放这;需要依赖就结构体 + 构造器注入(和 svc 思路一致)。别塞进 handler/logic。
有没有专门类似 http 的目录?没有名为 http 的目录。go-zero 用分层代替按协议命名:HTTP 层由 internal/handler(路由 + 薄壳)承担,横切逻辑抽到 middleware。所以「http 目录」的角色被 handler 扮演了。
中间件长啥样?rest.Middleware 类型 = func(next http.HandlerFunc) http.HandlerFunc。放行调 next(w,r),拦截直接写响应 return。
怎么注册?全局 server.Use(mw);路由级 rest.WithMiddlewares([...], routes...)。鉴权类别套在登录接口上。
能在 .api 声明吗?能,@server(middleware: Xxx),goctl 留好调用位,你按约定命名实现。但 JWT 用内置的 jwt: Auth 即可,不用手写。
RPC 呢?RPC 不叫 middleware,叫拦截器UnaryServerInterceptor),目录约定类似,注册在 zrpc server/client。

终极一句话:go-zero 没有 http 目录,但 HTTP 层由 handler 承担、横切逻辑收到 middleware。中间件就放 internal/middleware/,用 rest.Middleware 签名写、用 Use / WithMiddlewares 注册;RPC 那边对应物叫拦截器。