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

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

@@ -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)
})

View File

@@ -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配置

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日志优化性能瓶颈

View File

@@ -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,
)