调整工具类的方法,优化方法调用及增加迁移工具及其用法

This commit is contained in:
2025-12-04 22:30:48 +08:00
parent de8fc13f18
commit 0650feb0d2
28 changed files with 3753 additions and 162 deletions

View File

@@ -2,12 +2,15 @@
## 概述
中间件工具提供了常用的HTTP中间件功能包括CORS处理时区管理。
中间件工具提供了常用的HTTP中间件功能包括CORS处理时区管理、请求日志、Panic恢复和限流等
## 功能特性
- **CORS中间件**:支持跨域资源共享配置
- **时区中间件**:从请求头读取时区信息,支持默认时区设置
- **日志中间件**自动记录每个HTTP请求的详细信息
- **Recovery中间件**捕获panic并恢复防止服务崩溃
- **限流中间件**:基于令牌桶算法的请求限流
- **中间件链**:提供便捷的中间件链式调用
## CORS中间件
@@ -233,6 +236,371 @@ X-Timezone: Asia/Shanghai
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 | 日志记录器 | 创建默认loggerstdout |
| 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. **算法选择**:使用令牌桶算法,支持突发流量
## 中间件链
### 功能说明
@@ -277,7 +645,107 @@ handler := chain.ThenFunc(handler)
## 完整示例
### 示例1CORS + 时区中间件
### 示例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/datetime"
)
func apiHandler(w http.ResponseWriter, r *http.Request) {
h := commonhttp.NewHandler(w, r)
// 从Handler获取时区
timezone := h.GetTimezone()
now := datetime.Now(timezone)
h.Success(map[string]interface{}{
"message": "Hello",
"timezone": timezone,
"time": datetime.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
@@ -306,23 +774,13 @@ func apiHandler(w http.ResponseWriter, r *http.Request) {
}
func main() {
// 配置CORS
corsConfig := &middleware.CORSConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Timezone"},
}
// 创建中间件链
// 创建简单的中间件链
chain := middleware.NewChain(
middleware.CORS(corsConfig),
middleware.Timezone,
middleware.CORS(nil), // 使用默认CORS配置
middleware.Timezone, // 使用默认时区
)
// 应用中间件
handler := chain.ThenFunc(apiHandler)
http.Handle("/api", handler)
http.Handle("/api", chain.ThenFunc(apiHandler))
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
@@ -408,6 +866,65 @@ func getPosts(w http.ResponseWriter, r *http.Request) {
从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
@@ -428,19 +945,63 @@ func getPosts(w http.ResponseWriter, r *http.Request) {
## 注意事项
1. **CORS配置**
- 生产环境建议明确指定允许的源,避免使用 "*"
- 如果使用凭证cookies必须明确指定源不能使用 "*"
### 1. CORS配置
- 生产环境建议明确指定允许的源,避免使用 "*"
- 如果使用凭证cookies必须明确指定源不能使用 "*"
- CORS中间件应该在Recovery和Logging之后以便正确处理预检请求
2. **时区处理**
- 时区信息存储在context中确保中间件在处理器之前执行
- 时区验证失败时会自动回退到默认时区,不会返回错误
### 2. 时区处理
- 时区信息存储在context中确保中间件在处理器之前执行
- 时区验证失败时会自动回退到默认时区,不会返回错误
- 建议在CORS配置中包含 `X-Timezone` 请求头
3. **中间件顺序**
- CORS中间件应该放在最外层以便处理预检请求
- 时区中间件可以放在CORS之后
### 3. 日志记录
- **生产环境推荐异步模式**:避免日志写入阻塞请求,提升性能
- **跳过高频接口**:健康检查、监控接口等高频接口建议跳过日志
- **日志轮转**使用文件输出时建议配合日志轮转工具如logrotate
- **敏感信息**:不要记录请求体和响应体,避免泄露敏感信息
4. **性能考虑**
- CORS预检请求会被缓存减少重复请求
- 时区验证只在请求头存在时进行,性能影响很小
### 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日志优化性能瓶颈