1040 lines
27 KiB
Markdown
1040 lines
27 KiB
Markdown
# 中间件工具文档
|
||
|
||
## 概述
|
||
|
||
中间件工具提供了常用的HTTP中间件功能,包括CORS处理、时区管理、请求日志、Panic恢复和限流等。
|
||
|
||
## 功能特性
|
||
|
||
- **CORS中间件**:支持跨域资源共享配置
|
||
- **时区中间件**:从请求头读取时区信息,支持默认时区设置
|
||
- **日志中间件**:自动记录每个HTTP请求的详细信息
|
||
- **Recovery中间件**:捕获panic并恢复,防止服务崩溃
|
||
- **限流中间件**:基于令牌桶算法的请求限流
|
||
- **中间件链**:提供便捷的中间件链式调用
|
||
|
||
## CORS中间件
|
||
|
||
### 功能说明
|
||
|
||
CORS中间件用于处理跨域资源共享,支持:
|
||
- 配置允许的源(支持通配符)
|
||
- 配置允许的HTTP方法
|
||
- 配置允许的请求头
|
||
- 配置暴露的响应头
|
||
- 支持凭证传递
|
||
- 预检请求缓存时间设置
|
||
|
||
### 使用方法
|
||
|
||
#### 基本使用(默认配置)
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// 处理请求
|
||
})
|
||
|
||
// 使用默认CORS配置
|
||
corsHandler := middleware.CORS()(handler)
|
||
|
||
http.Handle("/api", corsHandler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 自定义配置
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
// 自定义CORS配置
|
||
corsConfig := &middleware.CORSConfig{
|
||
AllowedOrigins: []string{
|
||
"https://example.com",
|
||
"https://app.example.com",
|
||
"*.example.com", // 支持通配符
|
||
},
|
||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||
AllowedHeaders: []string{
|
||
"Content-Type",
|
||
"Authorization",
|
||
"X-Requested-With",
|
||
"X-Timezone",
|
||
},
|
||
ExposedHeaders: []string{"X-Total-Count"},
|
||
AllowCredentials: true,
|
||
MaxAge: 3600, // 1小时
|
||
}
|
||
|
||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
// 处理请求
|
||
})
|
||
|
||
corsHandler := middleware.CORS(corsConfig)(handler)
|
||
|
||
http.Handle("/api", corsHandler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 允许所有源(开发环境)
|
||
|
||
```go
|
||
corsConfig := &middleware.CORSConfig{
|
||
AllowedOrigins: []string{"*"},
|
||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||
AllowedHeaders: []string{"*"},
|
||
}
|
||
|
||
corsHandler := middleware.CORS(corsConfig)(handler)
|
||
```
|
||
|
||
### CORSConfig 配置说明
|
||
|
||
| 字段 | 类型 | 说明 | 默认值 |
|
||
|------|------|------|--------|
|
||
| AllowedOrigins | []string | 允许的源,支持 "*" 和 "*.example.com" | ["*"] |
|
||
| AllowedMethods | []string | 允许的HTTP方法 | ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] |
|
||
| AllowedHeaders | []string | 允许的请求头 | ["Content-Type", "Authorization", "X-Requested-With", "X-Timezone"] |
|
||
| ExposedHeaders | []string | 暴露给客户端的响应头 | [] |
|
||
| AllowCredentials | bool | 是否允许发送凭证 | false |
|
||
| MaxAge | int | 预检请求缓存时间(秒) | 86400 |
|
||
|
||
### 注意事项
|
||
|
||
1. 如果 `AllowCredentials` 为 `true`,`AllowedOrigins` 不能使用 "*",必须指定具体的源
|
||
2. 通配符支持:`"*.example.com"` 会匹配 `"https://app.example.com"` 等子域名
|
||
3. 预检请求(OPTIONS)会自动处理,无需在业务代码中处理
|
||
|
||
## 时区中间件
|
||
|
||
### 功能说明
|
||
|
||
时区中间件用于从请求头读取时区信息,并存储到context中,方便后续使用。
|
||
|
||
- 从请求头 `X-Timezone` 读取时区
|
||
- 如果未传递时区信息,使用默认时区 `AsiaShanghai`
|
||
- 时区信息存储到context中,可通过Handler的`GetTimezone()`方法获取
|
||
- 自动验证时区有效性,无效时区会回退到默认时区
|
||
|
||
### 使用方法
|
||
|
||
#### 基本使用(默认时区 AsiaShanghai)
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
"git.toowon.com/jimmy/go-common/tools"
|
||
)
|
||
|
||
func handler(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
|
||
// 从Handler获取时区
|
||
timezone := h.GetTimezone()
|
||
|
||
// 使用时区
|
||
now := tools.Now(timezone)
|
||
tools.FormatDateTime(now, timezone)
|
||
|
||
h.Success(map[string]interface{}{
|
||
"timezone": timezone,
|
||
"time": tools.FormatDateTime(now),
|
||
})
|
||
}
|
||
|
||
func main() {
|
||
handler := middleware.Timezone(http.HandlerFunc(handler))
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 自定义默认时区
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
"git.toowon.com/jimmy/go-common/tools"
|
||
)
|
||
|
||
func handler(w http.ResponseWriter, r *http.Request) {
|
||
// 处理请求
|
||
}
|
||
|
||
func main() {
|
||
// 使用自定义默认时区
|
||
handler := middleware.TimezoneWithDefault(tools.UTC)(http.HandlerFunc(handler))
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 在业务代码中使用时区
|
||
|
||
**推荐方式:通过 factory 使用(黑盒模式)**
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/factory"
|
||
)
|
||
|
||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||
fac, _ := factory.NewFactoryFromFile("config.json")
|
||
|
||
// 从请求中获取时区
|
||
timezone := fac.GetTimezone(r)
|
||
|
||
// 使用时区进行时间处理
|
||
now := fac.Now(timezone)
|
||
|
||
// 查询数据时使用时区
|
||
startTime := fac.StartOfDay(now, timezone)
|
||
endTime := fac.EndOfDay(now, timezone)
|
||
|
||
// 返回数据
|
||
fac.Success(w, map[string]interface{}{
|
||
"timezone": timezone,
|
||
"startTime": fac.FormatDateTime(startTime),
|
||
"endTime": fac.FormatDateTime(endTime),
|
||
})
|
||
}
|
||
```
|
||
|
||
**或者直接使用 tools 包:**
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
"git.toowon.com/jimmy/go-common/tools"
|
||
)
|
||
|
||
func GetUserList(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
|
||
// 从Handler获取时区
|
||
timezone := h.GetTimezone()
|
||
|
||
// 使用时区进行时间处理
|
||
now := tools.Now(timezone)
|
||
|
||
// 查询数据时使用时区
|
||
startTime := tools.StartOfDay(now, timezone)
|
||
endTime := tools.EndOfDay(now, timezone)
|
||
|
||
// 返回数据
|
||
h.Success(map[string]interface{}{
|
||
"timezone": timezone,
|
||
"startTime": tools.FormatDateTime(startTime),
|
||
"endTime": tools.FormatDateTime(endTime),
|
||
})
|
||
}
|
||
```
|
||
|
||
### 请求头格式
|
||
|
||
客户端需要在请求头中传递时区信息:
|
||
|
||
```
|
||
X-Timezone: Asia/Shanghai
|
||
```
|
||
|
||
支持的时区格式(IANA时区数据库):
|
||
- `Asia/Shanghai`
|
||
- `America/New_York`
|
||
- `Europe/London`
|
||
- `UTC`
|
||
- 等等
|
||
|
||
### 注意事项
|
||
|
||
1. 如果请求头中未传递 `X-Timezone`,默认使用 `AsiaShanghai`
|
||
2. 如果传递的时区无效,会自动回退到默认时区
|
||
3. 时区信息存储在context中,可以在整个请求生命周期中使用
|
||
4. 建议在CORS配置中包含 `X-Timezone` 请求头
|
||
|
||
## 日志中间件
|
||
|
||
### 功能说明
|
||
|
||
日志中间件用于自动记录每个HTTP请求的详细信息,帮助监控和调试应用。
|
||
|
||
记录内容包括:
|
||
- 请求方法、路径、查询参数
|
||
- 响应状态码、响应大小
|
||
- 请求处理时间(毫秒)
|
||
- 客户端IP地址(支持X-Forwarded-For)
|
||
- User-Agent、Referer等信息
|
||
|
||
### 使用方法
|
||
|
||
#### 基本使用(使用默认logger)
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
chain := middleware.NewChain(
|
||
middleware.Logging(nil), // 使用默认配置
|
||
middleware.CORS(),
|
||
middleware.Timezone,
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 使用自定义logger
|
||
|
||
```go
|
||
import (
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
"git.toowon.com/jimmy/go-common/logger"
|
||
)
|
||
|
||
func main() {
|
||
// 创建自定义logger(异步模式,输出到文件)
|
||
loggerConfig := &logger.LoggerConfig{
|
||
Level: "info",
|
||
Output: "file",
|
||
FilePath: "./logs/app.log",
|
||
Async: true, // 异步模式,不阻塞请求
|
||
BufferSize: 1000,
|
||
}
|
||
myLogger, _ := logger.NewLogger(loggerConfig)
|
||
|
||
// 配置日志中间件
|
||
loggingConfig := &middleware.LoggingConfig{
|
||
Logger: myLogger,
|
||
SkipPaths: []string{"/health", "/metrics"}, // 跳过健康检查接口
|
||
}
|
||
|
||
chain := middleware.NewChain(
|
||
middleware.Logging(loggingConfig),
|
||
middleware.CORS(),
|
||
middleware.Timezone,
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
### LoggingConfig 配置说明
|
||
|
||
| 字段 | 类型 | 说明 | 默认值 |
|
||
|------|------|------|--------|
|
||
| Logger | *logger.Logger | 日志记录器 | 创建默认logger(stdout) |
|
||
| SkipPaths | []string | 跳过记录的路径列表 | [] |
|
||
| LogRequestBody | bool | 是否记录请求体(慎用) | false |
|
||
| LogResponseBody | bool | 是否记录响应体(慎用) | false |
|
||
|
||
### 日志输出示例
|
||
|
||
```
|
||
[INFO] HTTP Request method=GET path=/api/users query= status=200 size=1024 duration=45 ip=192.168.1.100 user_agent=Mozilla/5.0 referer=
|
||
[WARN] HTTP Request method=POST path=/api/users query= status=400 size=128 duration=12 ip=192.168.1.100 user_agent=PostmanRuntime/7.29
|
||
[ERROR] HTTP Request method=GET path=/api/error query= status=500 size=256 duration=89 ip=192.168.1.100 user_agent=curl/7.64.1 referer=
|
||
```
|
||
|
||
### 注意事项
|
||
|
||
1. **异步模式推荐**:生产环境建议使用异步logger,避免日志写入阻塞请求
|
||
2. **跳过路径**:健康检查、监控接口等高频接口建议跳过日志记录
|
||
3. **日志级别**:根据状态码自动选择日志级别(5xx=ERROR, 4xx=WARN, 2xx-3xx=INFO)
|
||
4. **客户端IP**:自动从X-Forwarded-For、X-Real-IP或RemoteAddr获取真实IP
|
||
|
||
## Recovery中间件
|
||
|
||
### 功能说明
|
||
|
||
Recovery中间件用于捕获HTTP处理过程中的panic,防止panic导致整个服务崩溃。
|
||
|
||
功能包括:
|
||
- 捕获panic并恢复服务
|
||
- 记录panic信息和堆栈跟踪
|
||
- 返回500错误响应
|
||
- 支持自定义错误处理
|
||
|
||
### 使用方法
|
||
|
||
#### 基本使用(使用默认配置)
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(nil), // 使用默认配置
|
||
middleware.Logging(nil),
|
||
middleware.CORS(),
|
||
middleware.Timezone,
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 使用自定义logger
|
||
|
||
```go
|
||
import (
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
"git.toowon.com/jimmy/go-common/logger"
|
||
)
|
||
|
||
func main() {
|
||
myLogger, _ := logger.NewLogger(nil)
|
||
|
||
recoveryConfig := &middleware.RecoveryConfig{
|
||
Logger: myLogger,
|
||
EnableStackTrace: true, // 启用堆栈跟踪
|
||
}
|
||
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(recoveryConfig),
|
||
middleware.Logging(nil),
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 自定义错误响应
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
recoveryConfig := &middleware.RecoveryConfig{
|
||
EnableStackTrace: true,
|
||
CustomHandler: func(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||
// 使用统一的JSON响应格式
|
||
h := commonhttp.NewHandler(w, r)
|
||
h.SystemError("服务器内部错误")
|
||
},
|
||
}
|
||
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(recoveryConfig),
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
### RecoveryConfig 配置说明
|
||
|
||
| 字段 | 类型 | 说明 | 默认值 |
|
||
|------|------|------|--------|
|
||
| Logger | *logger.Logger | 日志记录器 | 创建默认logger |
|
||
| EnableStackTrace | bool | 是否记录堆栈跟踪 | true |
|
||
| CustomHandler | func(...) | 自定义错误处理函数 | nil(返回500文本) |
|
||
|
||
### 注意事项
|
||
|
||
1. **放在最外层**:Recovery中间件应该放在中间件链的最前面,以捕获所有panic
|
||
2. **日志记录**:建议配置logger,确保panic信息被记录下来
|
||
3. **堆栈跟踪**:生产环境建议启用,方便排查问题
|
||
4. **自定义响应**:可以自定义错误响应格式,统一错误处理
|
||
|
||
## 限流中间件
|
||
|
||
### 功能说明
|
||
|
||
限流中间件用于限制请求频率,防止API被滥用或遭受攻击。
|
||
|
||
特性:
|
||
- 基于令牌桶算法
|
||
- 支持按IP、用户ID等维度限流
|
||
- 自动设置限流响应头
|
||
- 内存存储,自动清理过期数据
|
||
|
||
### 使用方法
|
||
|
||
#### 基本使用(默认配置:100请求/分钟)
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(nil),
|
||
middleware.Logging(nil),
|
||
middleware.RateLimit(nil), // 默认100请求/分钟,按IP限流
|
||
middleware.CORS(),
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 自定义限流规则
|
||
|
||
```go
|
||
import (
|
||
"time"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
// 创建限流器:10请求/分钟
|
||
limiter := middleware.NewTokenBucketLimiter(10, time.Minute)
|
||
|
||
rateLimitConfig := &middleware.RateLimitConfig{
|
||
Limiter: limiter,
|
||
}
|
||
|
||
chain := middleware.NewChain(
|
||
middleware.RateLimit(rateLimitConfig),
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 按用户ID限流
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"time"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func main() {
|
||
limiter := middleware.NewTokenBucketLimiter(100, time.Minute)
|
||
|
||
rateLimitConfig := &middleware.RateLimitConfig{
|
||
Limiter: limiter,
|
||
KeyFunc: func(r *http.Request) string {
|
||
// 从请求头或JWT token中获取用户ID
|
||
userID := r.Header.Get("X-User-ID")
|
||
if userID != "" {
|
||
return "user:" + userID
|
||
}
|
||
// 如果没有用户ID,使用IP
|
||
return "ip:" + r.RemoteAddr
|
||
},
|
||
OnRateLimitExceeded: func(w http.ResponseWriter, r *http.Request, key string) {
|
||
// 记录限流事件
|
||
println("Rate limit exceeded for:", key)
|
||
},
|
||
}
|
||
|
||
chain := middleware.NewChain(
|
||
middleware.RateLimit(rateLimitConfig),
|
||
)
|
||
|
||
handler := chain.ThenFunc(apiHandler)
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 便捷函数
|
||
|
||
```go
|
||
// 按IP限流:10请求/分钟
|
||
chain := middleware.NewChain(
|
||
middleware.RateLimitByIP(10, time.Minute),
|
||
)
|
||
|
||
// 或使用自定义速率
|
||
chain := middleware.NewChain(
|
||
middleware.RateLimitWithRate(50, time.Minute),
|
||
)
|
||
```
|
||
|
||
### RateLimitConfig 配置说明
|
||
|
||
| 字段 | 类型 | 说明 | 默认值 |
|
||
|------|------|------|--------|
|
||
| Limiter | RateLimiter | 限流器实例 | 100请求/分钟 |
|
||
| KeyFunc | func(*http.Request) string | 生成限流键的函数 | 使用客户端IP |
|
||
| OnRateLimitExceeded | func(...) | 限流触发回调 | nil |
|
||
|
||
### 响应头说明
|
||
|
||
限流中间件会自动设置以下响应头:
|
||
|
||
| 响应头 | 说明 |
|
||
|--------|------|
|
||
| X-RateLimit-Limit | 窗口期内允许的请求数 |
|
||
| X-RateLimit-Remaining | 当前窗口剩余配额 |
|
||
| X-RateLimit-Reset | 配额重置时间(Unix时间戳) |
|
||
| Retry-After | 限流时,建议重试的等待时间(秒) |
|
||
|
||
### 响应示例
|
||
|
||
正常请求:
|
||
```
|
||
HTTP/1.1 200 OK
|
||
X-RateLimit-Limit: 100
|
||
X-RateLimit-Remaining: 95
|
||
X-RateLimit-Reset: 1640000000
|
||
```
|
||
|
||
触发限流:
|
||
```
|
||
HTTP/1.1 429 Too Many Requests
|
||
X-RateLimit-Limit: 100
|
||
X-RateLimit-Remaining: 0
|
||
X-RateLimit-Reset: 1640000000
|
||
Retry-After: 45
|
||
Too Many Requests
|
||
```
|
||
|
||
### 注意事项
|
||
|
||
1. **内存存储**:当前实现使用内存存储,适用于单机部署。如需分布式限流,建议使用Redis
|
||
2. **键设计**:合理设计限流键,可以按IP、用户、API等维度限流
|
||
3. **清理机制**:自动清理过期的限流数据,避免内存泄漏
|
||
4. **算法选择**:使用令牌桶算法,支持突发流量
|
||
|
||
## 中间件链
|
||
|
||
### 功能说明
|
||
|
||
中间件链提供便捷的中间件组合方式,支持链式调用。
|
||
|
||
### 使用方法
|
||
|
||
```go
|
||
import (
|
||
"net/http"
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
)
|
||
|
||
func handler(w http.ResponseWriter, r *http.Request) {
|
||
// 处理请求
|
||
}
|
||
|
||
func main() {
|
||
// 创建中间件链
|
||
chain := middleware.NewChain(
|
||
middleware.CORS(),
|
||
middleware.Timezone,
|
||
)
|
||
|
||
// 应用到处理器
|
||
handler := chain.ThenFunc(handler)
|
||
|
||
http.Handle("/api", handler)
|
||
http.ListenAndServe(":8080", nil)
|
||
}
|
||
```
|
||
|
||
#### 链式追加中间件
|
||
|
||
```go
|
||
chain := middleware.NewChain(middleware.CORS())
|
||
chain.Append(middleware.Timezone)
|
||
|
||
handler := chain.ThenFunc(handler)
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
### 示例1:完整的生产级中间件配置
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"log"
|
||
"net/http"
|
||
"time"
|
||
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
"git.toowon.com/jimmy/go-common/logger"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
"git.toowon.com/jimmy/go-common/tools"
|
||
)
|
||
|
||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
|
||
// 从Handler获取时区
|
||
timezone := h.GetTimezone()
|
||
now := tools.Now(timezone)
|
||
|
||
h.Success(map[string]interface{}{
|
||
"message": "Hello",
|
||
"timezone": timezone,
|
||
"time": tools.FormatDateTime(now),
|
||
})
|
||
}
|
||
|
||
func main() {
|
||
// 1. 配置logger(异步模式,输出到文件)
|
||
loggerConfig := &logger.LoggerConfig{
|
||
Level: "info",
|
||
Output: "both", // 同时输出到stdout和文件
|
||
FilePath: "./logs/app.log",
|
||
Async: true,
|
||
BufferSize: 1000,
|
||
}
|
||
myLogger, err := logger.NewLogger(loggerConfig)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
defer myLogger.Close() // 确保程序退出时关闭logger
|
||
|
||
// 2. 配置CORS
|
||
corsConfig := &middleware.CORSConfig{
|
||
AllowedOrigins: []string{"*"},
|
||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Timezone"},
|
||
}
|
||
|
||
// 3. 配置日志中间件
|
||
loggingConfig := &middleware.LoggingConfig{
|
||
Logger: myLogger,
|
||
SkipPaths: []string{"/health", "/metrics"},
|
||
}
|
||
|
||
// 4. 配置Recovery中间件
|
||
recoveryConfig := &middleware.RecoveryConfig{
|
||
Logger: myLogger,
|
||
EnableStackTrace: true,
|
||
CustomHandler: func(w http.ResponseWriter, r *http.Request, err interface{}) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
h.SystemError("服务器内部错误")
|
||
},
|
||
}
|
||
|
||
// 5. 配置限流中间件(100请求/分钟)
|
||
rateLimiter := middleware.NewTokenBucketLimiter(100, time.Minute)
|
||
rateLimitConfig := &middleware.RateLimitConfig{
|
||
Limiter: rateLimiter,
|
||
OnRateLimitExceeded: func(w http.ResponseWriter, r *http.Request, key string) {
|
||
myLogger.Warnf(map[string]interface{}{
|
||
"key": key,
|
||
"path": r.URL.Path,
|
||
}, "Rate limit exceeded")
|
||
},
|
||
}
|
||
|
||
// 6. 创建中间件链(顺序很重要!)
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(recoveryConfig), // 最外层:捕获panic
|
||
middleware.Logging(loggingConfig), // 日志记录
|
||
middleware.RateLimit(rateLimitConfig), // 限流
|
||
middleware.CORS(corsConfig), // CORS处理
|
||
middleware.Timezone, // 时区处理
|
||
)
|
||
|
||
// 7. 应用中间件
|
||
http.Handle("/api", chain.ThenFunc(apiHandler))
|
||
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||
w.Write([]byte("OK"))
|
||
})
|
||
|
||
log.Println("Server started on :8080")
|
||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||
}
|
||
```
|
||
|
||
### 示例2:基础的CORS + 时区中间件
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"log"
|
||
"net/http"
|
||
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
"git.toowon.com/jimmy/go-common/tools"
|
||
)
|
||
|
||
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
|
||
// 从Handler获取时区
|
||
timezone := h.GetTimezone()
|
||
now := tools.Now(timezone)
|
||
|
||
h.Success(map[string]interface{}{
|
||
"message": "Hello",
|
||
"timezone": timezone,
|
||
"time": tools.FormatDateTime(now),
|
||
})
|
||
}
|
||
|
||
func main() {
|
||
// 创建简单的中间件链
|
||
chain := middleware.NewChain(
|
||
middleware.CORS(nil), // 使用默认CORS配置
|
||
middleware.Timezone, // 使用默认时区
|
||
)
|
||
|
||
http.Handle("/api", chain.ThenFunc(apiHandler))
|
||
|
||
log.Println("Server started on :8080")
|
||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||
}
|
||
```
|
||
|
||
### 示例2:与路由框架集成
|
||
|
||
```go
|
||
package main
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"git.toowon.com/jimmy/go-common/middleware"
|
||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||
)
|
||
|
||
func main() {
|
||
mux := http.NewServeMux()
|
||
|
||
// 创建中间件链
|
||
chain := middleware.NewChain(
|
||
middleware.CORS(),
|
||
middleware.Timezone,
|
||
)
|
||
|
||
// 应用中间件到所有路由
|
||
mux.Handle("/api/users", chain.ThenFunc(getUsers))
|
||
mux.Handle("/api/posts", chain.ThenFunc(getPosts))
|
||
|
||
http.ListenAndServe(":8080", mux)
|
||
}
|
||
|
||
func getUsers(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
timezone := h.GetTimezone()
|
||
// 处理逻辑
|
||
h.Success(nil)
|
||
}
|
||
|
||
func getPosts(w http.ResponseWriter, r *http.Request) {
|
||
h := commonhttp.NewHandler(w, r)
|
||
timezone := h.GetTimezone()
|
||
// 处理逻辑
|
||
h.Success(nil)
|
||
}
|
||
```
|
||
|
||
## API 参考
|
||
|
||
### CORS中间件
|
||
|
||
#### CORS(config ...*CORSConfig) func(http.Handler) http.Handler
|
||
|
||
创建CORS中间件。
|
||
|
||
**参数:**
|
||
- `config`: 可选的CORS配置,不指定则使用默认配置
|
||
|
||
**返回:** 中间件函数
|
||
|
||
#### DefaultCORSConfig() *CORSConfig
|
||
|
||
返回默认的CORS配置。
|
||
|
||
### 时区中间件
|
||
|
||
#### Timezone(next http.Handler) http.Handler
|
||
|
||
时区处理中间件(默认时区为 AsiaShanghai)。
|
||
|
||
#### TimezoneWithDefault(defaultTimezone string) func(http.Handler) http.Handler
|
||
|
||
时区处理中间件(可自定义默认时区)。
|
||
|
||
**参数:**
|
||
- `defaultTimezone`: 默认时区字符串
|
||
|
||
**返回:** 中间件函数
|
||
|
||
#### GetTimezoneFromContext(ctx context.Context) string
|
||
|
||
从context中获取时区。
|
||
|
||
### 日志中间件
|
||
|
||
#### Logging(config *LoggingConfig) func(http.Handler) http.Handler
|
||
|
||
创建日志中间件。
|
||
|
||
**参数:**
|
||
- `config`: 日志配置,nil则使用默认配置
|
||
|
||
**返回:** 中间件函数
|
||
|
||
### Recovery中间件
|
||
|
||
#### Recovery(config *RecoveryConfig) func(http.Handler) http.Handler
|
||
|
||
创建Recovery中间件。
|
||
|
||
**参数:**
|
||
- `config`: Recovery配置,nil则使用默认配置
|
||
|
||
**返回:** 中间件函数
|
||
|
||
#### RecoveryWithLogger(log *logger.Logger) func(http.Handler) http.Handler
|
||
|
||
使用指定logger的Recovery中间件(便捷函数)。
|
||
|
||
#### RecoveryWithCustomHandler(customHandler func(...)) func(http.Handler) http.Handler
|
||
|
||
使用自定义错误处理的Recovery中间件(便捷函数)。
|
||
|
||
### 限流中间件
|
||
|
||
#### RateLimit(config *RateLimitConfig) func(http.Handler) http.Handler
|
||
|
||
创建限流中间件。
|
||
|
||
**参数:**
|
||
- `config`: 限流配置,nil则使用默认配置(100请求/分钟)
|
||
|
||
**返回:** 中间件函数
|
||
|
||
#### NewTokenBucketLimiter(rate int, windowSize time.Duration) RateLimiter
|
||
|
||
创建令牌桶限流器。
|
||
|
||
**参数:**
|
||
- `rate`: 每个窗口期允许的请求数
|
||
- `windowSize`: 窗口大小
|
||
|
||
**返回:** 限流器实例
|
||
|
||
#### RateLimitWithRate(rate int, windowSize time.Duration) func(http.Handler) http.Handler
|
||
|
||
使用指定速率创建限流中间件(便捷函数)。
|
||
|
||
#### RateLimitByIP(rate int, windowSize time.Duration) func(http.Handler) http.Handler
|
||
|
||
按IP限流(便捷函数)。
|
||
|
||
### 中间件链
|
||
|
||
#### NewChain(middlewares ...func(http.Handler) http.Handler) *Chain
|
||
|
||
创建新的中间件链。
|
||
|
||
#### (c *Chain) Then(handler http.Handler) http.Handler
|
||
|
||
将中间件链应用到处理器。
|
||
|
||
#### (c *Chain) ThenFunc(handler http.HandlerFunc) http.Handler
|
||
|
||
将中间件链应用到处理器函数。
|
||
|
||
#### (c *Chain) Append(middlewares ...func(http.Handler) http.Handler) *Chain
|
||
|
||
追加中间件到链中。
|
||
|
||
## 注意事项
|
||
|
||
### 1. CORS配置
|
||
- 生产环境建议明确指定允许的源,避免使用 "*"
|
||
- 如果使用凭证(cookies),必须明确指定源,不能使用 "*"
|
||
- CORS中间件应该在Recovery和Logging之后,以便正确处理预检请求
|
||
|
||
### 2. 时区处理
|
||
- 时区信息存储在context中,确保中间件在处理器之前执行
|
||
- 时区验证失败时会自动回退到默认时区,不会返回错误
|
||
- 建议在CORS配置中包含 `X-Timezone` 请求头
|
||
|
||
### 3. 日志记录
|
||
- **生产环境推荐异步模式**:避免日志写入阻塞请求,提升性能
|
||
- **跳过高频接口**:健康检查、监控接口等高频接口建议跳过日志
|
||
- **日志轮转**:使用文件输出时,建议配合日志轮转工具(如logrotate)
|
||
- **敏感信息**:不要记录请求体和响应体,避免泄露敏感信息
|
||
|
||
### 4. Panic恢复
|
||
- **放在最外层**:Recovery中间件应该放在中间件链的最前面
|
||
- **记录日志**:务必配置logger,确保panic信息被记录
|
||
- **监控告警**:建议将panic事件接入监控系统,及时发现问题
|
||
- **堆栈跟踪**:生产环境建议启用,方便排查问题
|
||
|
||
### 5. 限流配置
|
||
- **合理设置阈值**:根据实际业务需求设置限流阈值
|
||
- **分布式部署**:当前实现使用内存存储,适用于单机。分布式部署建议使用Redis
|
||
- **键设计**:合理设计限流键,可以按IP、用户、API等维度限流
|
||
- **响应头**:客户端可以根据X-RateLimit-*响应头实现智能重试
|
||
|
||
### 6. 中间件顺序(推荐)
|
||
建议的中间件顺序(从外到内):
|
||
1. **Recovery** - 最外层,捕获所有panic
|
||
2. **Logging** - 记录所有请求(包括限流的请求)
|
||
3. **RateLimit** - 限流保护
|
||
4. **CORS** - 处理跨域
|
||
5. **Timezone** - 时区处理
|
||
6. **业务中间件** - 认证、授权等
|
||
|
||
```go
|
||
chain := middleware.NewChain(
|
||
middleware.Recovery(recoveryConfig), // 1. Panic恢复
|
||
middleware.Logging(loggingConfig), // 2. 日志记录
|
||
middleware.RateLimit(rateLimitConfig), // 3. 限流
|
||
middleware.CORS(corsConfig), // 4. CORS
|
||
middleware.Timezone, // 5. 时区
|
||
)
|
||
```
|
||
|
||
### 7. 性能考虑
|
||
- **异步日志**:使用异步logger,避免IO阻塞
|
||
- **限流算法**:令牌桶算法支持突发流量
|
||
- **自动清理**:限流数据会自动清理,避免内存泄漏
|
||
- **跳过路径**:合理使用SkipPaths,减少不必要的处理
|
||
|
||
### 8. 生产环境建议
|
||
- 使用异步logger,配置日志文件和轮转
|
||
- 启用Recovery中间件,配置告警
|
||
- 根据业务设置合理的限流阈值
|
||
- 配置监控指标(请求量、错误率、限流触发次数等)
|
||
- 定期review日志,优化性能瓶颈
|
||
|