调整工具类的方法,优化方法调用及增加迁移工具及其用法
This commit is contained in:
@@ -3,9 +3,10 @@
|
||||
## 目录
|
||||
|
||||
- [数据库迁移工具](./migration.md) - 数据库版本管理和迁移
|
||||
- [完整使用指南](../MIGRATION.md) ⭐ - 独立工具,零耦合,Docker友好
|
||||
- [日期转换工具](./datetime.md) - 日期时间处理和时区转换
|
||||
- [HTTP Restful工具](./http.md) - HTTP请求响应处理和分页
|
||||
- [中间件工具](./middleware.md) - CORS和时区处理中间件
|
||||
- [中间件工具](./middleware.md) - 生产级HTTP中间件(CORS、时区、日志、Recovery、限流)
|
||||
- [配置工具](./config.md) - 外部配置文件加载和管理
|
||||
- [存储工具](./storage.md) - 文件上传和查看(OSS、MinIO)
|
||||
- [邮件工具](./email.md) - SMTP邮件发送
|
||||
@@ -23,22 +24,37 @@ go get git.toowon.com/jimmy/go-common
|
||||
|
||||
### 使用示例
|
||||
|
||||
#### 数据库迁移
|
||||
#### 数据库迁移(独立工具,零耦合)
|
||||
|
||||
```go
|
||||
import "git.toowon.com/jimmy/go-common/migration"
|
||||
```bash
|
||||
# 1. 复制模板:templates/migrate/main.go -> cmd/migrate/main.go
|
||||
# 2. 创建迁移文件:migrations/20240101000001_create_users.sql
|
||||
|
||||
migrator := migration.NewMigrator(db)
|
||||
migrator.AddMigration(migration.Migration{
|
||||
Version: "20240101000001",
|
||||
Description: "create_users_table",
|
||||
Up: func(db *gorm.DB) error {
|
||||
return db.Exec("CREATE TABLE users ...").Error
|
||||
},
|
||||
})
|
||||
migrator.Up()
|
||||
# 3. 开发环境
|
||||
go run cmd/migrate/main.go up
|
||||
|
||||
# 4. 生产环境(编译后使用,推荐)
|
||||
go build -o bin/migrate cmd/migrate/main.go
|
||||
./bin/migrate up # 使用默认配置
|
||||
./bin/migrate up -config /path/to/config.json # 指定配置
|
||||
./bin/migrate status # 查看状态
|
||||
|
||||
# 5. Docker(挂载配置,修改无需重启)
|
||||
# docker-compose.yml:
|
||||
# volumes:
|
||||
# - ./config.json:/app/config.json:ro
|
||||
# command: sh -c "./migrate up && ./server"
|
||||
```
|
||||
|
||||
**迁移文件示例**(`migrations/20240101000001_create_users.sql`):
|
||||
```sql
|
||||
CREATE TABLE users (id BIGINT PRIMARY KEY AUTO_INCREMENT, ...);
|
||||
```
|
||||
|
||||
**优势**:独立工具,零耦合,支持命令行参数,Docker友好
|
||||
|
||||
详细说明:[MIGRATION.md](../MIGRATION.md)
|
||||
|
||||
#### 日期转换
|
||||
|
||||
```go
|
||||
@@ -65,24 +81,26 @@ func GetUser(h *commonhttp.Handler) {
|
||||
http.HandleFunc("/user", commonhttp.HandleFunc(GetUser))
|
||||
```
|
||||
|
||||
#### 中间件
|
||||
#### 中间件(生产级配置)
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
"git.toowon.com/jimmy/go-common/middleware"
|
||||
commonhttp "git.toowon.com/jimmy/go-common/http"
|
||||
)
|
||||
|
||||
// CORS + 时区中间件
|
||||
// 完整的中间件链
|
||||
chain := middleware.NewChain(
|
||||
middleware.CORS(),
|
||||
middleware.Timezone,
|
||||
middleware.Recovery(nil), // Panic恢复
|
||||
middleware.Logging(nil), // 请求日志
|
||||
middleware.RateLimitByIP(100, time.Minute), // 限流
|
||||
middleware.CORS(nil), // CORS
|
||||
middleware.Timezone, // 时区
|
||||
)
|
||||
|
||||
handler := chain.ThenFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h := commonhttp.NewHandler(w, r)
|
||||
// 在Handler中获取时区
|
||||
timezone := h.GetTimezone()
|
||||
h.Success(data)
|
||||
})
|
||||
|
||||
@@ -163,13 +163,26 @@ addr := config.GetRedisAddr()
|
||||
### 5. 获取CORS配置
|
||||
|
||||
```go
|
||||
// 获取CORS配置(返回middleware.CORSConfig类型,可直接用于中间件)
|
||||
corsConfig := config.GetCORS()
|
||||
// 获取CORS配置(返回config.CORSConfig类型)
|
||||
configCORS := config.GetCORS()
|
||||
|
||||
// 使用CORS中间件
|
||||
// 转换为middleware.CORSConfig并使用CORS中间件
|
||||
import "git.toowon.com/jimmy/go-common/middleware"
|
||||
|
||||
var middlewareCORS *middleware.CORSConfig
|
||||
if configCORS != nil {
|
||||
middlewareCORS = middleware.NewCORSConfig(
|
||||
configCORS.AllowedOrigins,
|
||||
configCORS.AllowedMethods,
|
||||
configCORS.AllowedHeaders,
|
||||
configCORS.ExposedHeaders,
|
||||
configCORS.AllowCredentials,
|
||||
configCORS.MaxAge,
|
||||
)
|
||||
}
|
||||
|
||||
chain := middleware.NewChain(
|
||||
middleware.CORS(corsConfig),
|
||||
middleware.CORS(middlewareCORS),
|
||||
)
|
||||
```
|
||||
|
||||
@@ -328,9 +341,20 @@ func main() {
|
||||
fmt.Printf("Redis Address: %s\n", redisAddr)
|
||||
|
||||
// 使用CORS配置
|
||||
corsConfig := cfg.GetCORS()
|
||||
configCORS := cfg.GetCORS()
|
||||
var middlewareCORS *middleware.CORSConfig
|
||||
if configCORS != nil {
|
||||
middlewareCORS = middleware.NewCORSConfig(
|
||||
configCORS.AllowedOrigins,
|
||||
configCORS.AllowedMethods,
|
||||
configCORS.AllowedHeaders,
|
||||
configCORS.ExposedHeaders,
|
||||
configCORS.AllowCredentials,
|
||||
configCORS.MaxAge,
|
||||
)
|
||||
}
|
||||
chain := middleware.NewChain(
|
||||
middleware.CORS(corsConfig),
|
||||
middleware.CORS(middlewareCORS),
|
||||
)
|
||||
|
||||
// 使用OSS配置
|
||||
|
||||
@@ -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 | 日志记录器 | 创建默认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. **算法选择**:使用令牌桶算法,支持突发流量
|
||||
|
||||
## 中间件链
|
||||
|
||||
### 功能说明
|
||||
@@ -277,7 +645,107 @@ handler := chain.ThenFunc(handler)
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例1:CORS + 时区中间件
|
||||
### 示例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日志,优化性能瓶颈
|
||||
|
||||
|
||||
@@ -325,8 +325,20 @@ func main() {
|
||||
proxyHandler := storage.NewProxyHandler(ossStorage)
|
||||
|
||||
// 创建中间件链
|
||||
var corsConfig *middleware.CORSConfig
|
||||
if cfg.GetCORS() != nil {
|
||||
c := cfg.GetCORS()
|
||||
corsConfig = middleware.NewCORSConfig(
|
||||
c.AllowedOrigins,
|
||||
c.AllowedMethods,
|
||||
c.AllowedHeaders,
|
||||
c.ExposedHeaders,
|
||||
c.AllowCredentials,
|
||||
c.MaxAge,
|
||||
)
|
||||
}
|
||||
chain := middleware.NewChain(
|
||||
middleware.CORS(cfg.GetCORS()),
|
||||
middleware.CORS(corsConfig),
|
||||
middleware.Timezone,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user