// Package middleware 提供 HTTP 中间件 package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" ) // CORS 跨域中间件 // - 白名单 origin 精确匹配(生产用) // - 同时自动放行 http(s)://localhost:* 所有端口(开发期 Flutter Web/React Vite 随机端口) // - 不在范围内的请求不设置 Allow-Origin 头,浏览器会自行阻止 func CORS(allowedOrigins []string) gin.HandlerFunc { originSet := make(map[string]bool, len(allowedOrigins)) for _, o := range allowedOrigins { originSet[strings.TrimRight(o, "/")] = true } return func(c *gin.Context) { origin := strings.TrimRight(c.GetHeader("Origin"), "/") if origin == "" { c.Next() return } // 是否允许:白名单精确匹配 或 localhost 通配 allowed := originSet[origin] || isLocalhostOrigin(origin) if c.Request.Method == http.MethodOptions { if allowed { c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID") c.Writer.Header().Set("Access-Control-Expose-Headers", "X-Request-ID") c.Writer.Header().Set("Access-Control-Max-Age", "86400") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") } c.AbortWithStatus(http.StatusNoContent) return } if allowed { c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Vary", "Origin") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") } c.Next() } } // isLocalhostOrigin 匹配 http://localhost:* 和 http://127.0.0.1:* // 开发期 Flutter Web / Vite / CRA 等工具常用随机端口 func isLocalhostOrigin(origin string) bool { return strings.HasPrefix(origin, "http://localhost:") || strings.HasPrefix(origin, "http://127.0.0.1:") || strings.HasPrefix(origin, "http://[::1]:") }