touka/docs/middleware.md
wjqserver 58fd877ae2 docs: 修复审查意见,统一术语并补充注册顺序说明
- 补充中间件注册顺序说明(必须在路由定义之前)
- 统一术语:'组中间件' → '路由组中间件'
- 统一流程图术语
2026-04-21 18:32:10 +08:00

164 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 中间件 (Middleware)
中间件是运行在 HTTP 请求处理链中的函数。它们可以拦截请求、修改数据、控制流向(通过 `c.Next()``c.Abort()`),并执行通用的前置/后置逻辑。
## 如何使用中间件
### 全局中间件
全局中间件应用于所有注册的路由。
```go
r := touka.New()
r.Use(touka.Recovery()) // 崩溃恢复
r.Use(MyCustomLogger()) // 自定义日志
```
### 路由组中间件
仅应用于特定组下的路由。
```go
api := r.Group("/api")
api.Use(AuthMiddleware())
{
api.GET("/user", handleUser)
}
```
也可以在创建组时直接传入中间件:
```go
api := r.Group("/api", AuthMiddleware(), RateLimitMiddleware())
{
api.GET("/user", handleUser)
api.POST("/data", handleData)
}
```
### 路由级中间件
为单个路由注册中间件,仅对该路由生效。
```go
// 单个路由中间件
r.GET("/protected", AuthMiddleware(), func(c *touka.Context) {
c.String(http.StatusOK, "Protected content")
})
// 多个路由中间件(按顺序执行)
r.POST("/upload",
RateLimitMiddleware(),
AuthMiddleware(),
PermissionCheckMiddleware(),
func(c *touka.Context) {
// 处理上传
},
)
// 路由组中的单个路由也可以使用路由级中间件
api := r.Group("/api")
api.GET("/admin", AdminAuthMiddleware(), adminHandler)
```
## 编写自定义中间件
中间件的函数签名是 `touka.HandlerFunc`
### 示例:请求计时器
```go
func TimerMiddleware() touka.HandlerFunc {
return func(c *touka.Context) {
// --- 前置逻辑 ---
start := time.Now()
// 执行处理链中的下一个函数
c.Next()
// --- 后置逻辑 ---
duration := time.Since(start)
log.Printf("Request %s %s took %v", c.Request.Method, c.Request.URL.Path, duration)
}
}
```
### 示例:简单的 API 密钥验证
```go
func APIKeyAuth() touka.HandlerFunc {
return func(c *touka.Context) {
apiKey := c.GetReqHeader("X-API-KEY")
if apiKey != "secret-token" {
// 验证失败,返回错误并中止后续逻辑
c.JSON(http.StatusUnauthorized, touka.H{"error": "Invalid API Key"})
c.Abort()
return
}
// 验证通过,继续执行
c.Next()
}
}
```
## 中间件执行顺序
理解中间件的执行顺序对于构建正确的处理流程至关重要。**注意:注册顺序决定了执行逻辑**,中间件必须在注册路由之前调用(全局中间件应在创建组或定义路由前注册)。中间件按照以下顺序执行:
```go
// 全局中间件
r.Use(GlobalMiddleware1())
r.Use(GlobalMiddleware2())
// 组中间件
api := r.Group("/api", GroupMiddleware1())
api.Use(GroupMiddleware2())
// 路由级中间件
api.GET("/users", RouteMiddleware1(), RouteMiddleware2(), userHandler)
```
对于 `/api/users` 请求,执行顺序为:
1. `GlobalMiddleware1()` - 全局中间件
2. `GlobalMiddleware2()` - 全局中间件
3. `GroupMiddleware1()` - 路由组中间件
4. `GroupMiddleware2()` - 路由组中间件
5. `RouteMiddleware1()` - 路由级中间件
6. `RouteMiddleware2()` - 路由级中间件
7. `userHandler` - 最终处理函数
```
请求进入 → 全局中间件 → 路由组中间件 → 路由级中间件 → 最终处理函数 → 路由级中间件后置逻辑 → 路由组中间件后置逻辑 → 全局中间件后置逻辑 → 响应
```
## 内置中间件
- **Recovery**: 捕获任何发生的 panic恢复运行并返回 500 错误。它还负责调用全局错误处理器。
Touka 的设计非常精简,许多扩展功能(如 Gzip, JWT, Sessions由外部或第三方库提供您可以轻松通过 `r.Use()` 集成它们。
## 条件中间件 (Conditional Middleware)
Touka 支持根据布尔条件动态启用或禁用中间件。这在根据环境配置启用插件时非常有用。
### `UseIf`
```go
// 仅在 Debug 模式下启用日志
r.Use(r.UseIf(config.IsDebug, MyDebugLogger))
```
### `UseChainIf` (条件中间件链)
如果您有一组相关的中间件需要根据同一条件启用,可以使用 `UseChainIf`
```go
r.Use(r.UseChainIf(config.EnableMetrics,
MetricsMiddleware,
PrometheusMiddleware,
MonitoringMiddleware,
))
```
这些方法利用了 `MiddlewareXFunc`(即返回 `HandlerFunc` 的工厂函数),确保中间件实例按需创建或高效复用。