Files
xlgo-core/middleware/cors.go
T
杭州明婳科技 dcfd24b624 release: v1.0.3 bug fix release
v1.0.3 定位为 bug fix release,收口 v1.0.2 引入的破坏性清理并修复
4 个轻量 bug + 依赖复查 + 版本号治理 + 文档对齐。同时包含此前未提交
的 v1.0.2 全部工作。

v1.0.3 变更:

Fixed:
- generateJTI 忽略 rand.Read 错误(jwt)— 改为 (string, error) 并传播
- QueryBuilder.Page Count 受残留 Limit 截断(repository)— countDB 加 Limit(-1).Offset(-1)
- OSS/本地存储文件名冲突(storage)— 新增 uniqueFilename 加 8 字节随机后缀
- 数据库重试策略对不可恢复错误无效(database)— 新增 isTransientDBError

Dependencies:
- go mod tidy 补全 postgres 方言传递依赖;安全补丁升级
  x/crypto v0.49→v0.53、golang-jwt/jwt/v5 v5.2.1→v5.3.1、gorilla/websocket v1.5.1→v1.5.3

Removed:
- Breaking: 清理 v1.0.2 兼容别名(InitMySQL* / driverName)
- 死代码 database.DBResolver
- 代码中 292 行"评分/理由"自夸注释(#26,23 个文件)

Changed:
- Breaking: 错误码体系重构 CodeSuccess 1→0、CodeFail 0→1,删除 CodeInvalidParams,加编译期防撞码
- database/mysql.go → manager.go;Logger 拆分三独立 core 修复 Tee 重复写入
- 版本号常量化:app.go 新增 const Version 作为唯一来源,CLI/脚手架模板引用之,不再散落字面量

Security:
- CORS 中间件按 W3C 规范修复 Allow-Credentials / Vary: Origin / 非白名单不回显

Added:
- console 包显式 level 控制(SetLevel/WithLevel/LevelSilent,atomic.Int32 并发安全)
- 新增测试 jwt/repository/storage/database/router/middleware/app
- 文档:新增 CHANGELOG.md、Version_v1.0.2_report.md;更新 README/GUIDE 顶部更新日志
- 文档对齐:删除对不存在的 config.DefaultManager() getter 的描述(保持 API 与文档一致)

详见 CHANGELOG.md 与 README.md 更新日志。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 21:40:10 +08:00

167 lines
4.8 KiB
Go
Raw 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.
package middleware
import (
"net/http"
"strconv"
"strings"
"github.com/EthanCodeCraft/xlgo-core/config"
"github.com/gin-gonic/gin"
)
// CORS 跨域中间件
// 支持从配置文件读取 CORS 配置;遵循 W3C CORS 规范:
// 当 AllowCredentials=true 时,Access-Control-Allow-Origin 必须回显为具体 Origin
// 不能使用 "*"(浏览器会拒绝携带凭证的响应)。
func CORS() gin.HandlerFunc {
return CORSWithConfig(nil)
}
// CORSWithConfig 使用自定义配置的 CORS 中间件
func CORSWithConfig(corsCfg *config.CORSConfig) gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.GetHeader("Origin")
// 获取配置
cfg := config.Get()
var corsConfig *config.CORSConfig
if corsCfg != nil {
corsConfig = corsCfg
} else if cfg != nil {
corsConfig = &cfg.CORS
}
// 是否允许携带凭证(影响 Origin 回显策略)
allowCredentials := corsConfig != nil && corsConfig.AllowCredentials
// 获取允许的域名列表
allowedOrigins := getAllowedOrigins(cfg, corsConfig)
// 匹配 Origin
// 注意:当 allowCredentials=true 且匹配到通配符时,
// 必须回显具体 Origin 而非 "*",否则浏览器会拒绝响应。
allowedOrigin := ""
matchedWildcard := false
if origin != "" {
for _, ao := range allowedOrigins {
if ao == "*" {
matchedWildcard = true
break
}
// 支持通配符匹配(如 *.example.com
if strings.HasPrefix(ao, "*.") {
domain := ao[2:]
if strings.HasSuffix(origin, domain) {
allowedOrigin = origin
break
}
}
if ao == origin {
allowedOrigin = origin
break
}
}
}
// 处理通配符 + 兜底策略
if allowedOrigin == "" {
if matchedWildcard {
if allowCredentials && origin != "" {
// AllowCredentials=true 时 spec 禁止 "*",回显具体 Origin
allowedOrigin = origin
} else {
allowedOrigin = "*"
}
} else if cfg != nil && cfg.IsDevelopment() && origin != "" {
// 开发环境兜底:回显具体 Origin(兼容 credentials
allowedOrigin = origin
}
}
// 设置 CORS 响应头
if allowedOrigin != "" {
c.Header("Access-Control-Allow-Origin", allowedOrigin)
// Origin 不是 "*" 时,下游缓存(CDN / 网关)必须按 Origin 区分缓存
if allowedOrigin != "*" {
c.Header("Vary", "Origin")
}
}
// 从配置获取允许的方法、请求头等
methods := corsConfig.GetAllowedMethods()
headers := corsConfig.GetAllowedHeaders()
exposedHeaders := corsConfig.GetExposedHeaders()
maxAge := corsConfig.GetMaxAge()
c.Header("Access-Control-Allow-Methods", strings.Join(methods, ", "))
c.Header("Access-Control-Allow-Headers", strings.Join(headers, ", "))
c.Header("Access-Control-Expose-Headers", strings.Join(exposedHeaders, ", "))
c.Header("Access-Control-Max-Age", strconv.Itoa(maxAge))
// 仅在显式启用且 Origin 不是 "*" 时才发 Allow-Credentials
// CORS 规范:Allow-Origin: * 时禁止携带凭证)
if allowCredentials && allowedOrigin != "" && allowedOrigin != "*" {
c.Header("Access-Control-Allow-Credentials", "true")
}
// 处理预检请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// getAllowedOrigins 获取允许的域名列表
// 优先使用配置文件,生产环境必须显式配置,开发环境提供 localhost 兜底。
func getAllowedOrigins(cfg *config.Config, corsConfig *config.CORSConfig) []string {
// 优先使用 CORS 配置
if corsConfig != nil && len(corsConfig.AllowedOrigins) > 0 {
return corsConfig.AllowedOrigins
}
// 生产环境:必须配置具体的域名
if cfg != nil && cfg.IsProduction() {
// 返回空列表,生产环境必须配置
return []string{}
}
// 开发环境允许 localhost
return []string{
"http://localhost:3000",
"http://localhost:5173",
"http://localhost:8080",
"http://localhost:4200",
"http://127.0.0.1:3000",
"http://127.0.0.1:5173",
"http://127.0.0.1:8080",
"http://127.0.0.1:4200",
}
}
// CORSWithOrigins 使用指定域名列表的 CORS 中间件
func CORSWithOrigins(origins []string) gin.HandlerFunc {
return CORSWithConfig(&config.CORSConfig{
AllowedOrigins: origins,
})
}
// CORSWithWildcard 允许所有来源的 CORS 中间件(仅用于开发环境)
// 注意:生产环境不建议使用
func CORSWithWildcard() gin.HandlerFunc {
return CORSWithConfig(&config.CORSConfig{
AllowedOrigins: []string{"*"},
})
}
// CORSForAPI 适用于 API 的 CORS 中间件
func CORSForAPI() gin.HandlerFunc {
return CORSWithConfig(&config.CORSConfig{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
AllowCredentials: false, // API 模式不允许凭证
})
}