--- tags: [后端, Go, Gin, 中间件, 作用域] create time: 2026-04-27 12:55 --- # 跨分组共享中间件的作用域选择 ## 概述 讨论当多个 RouterGroup(如 `/api/v1` 和 `/api/v2`)需要同一中间件时,是注册到全局还是各自分组的取舍。结论:**同策略 → 全局;异策略 → 分组**。 ## 正文 ### 场景还原 ```go v1 := r.Group("/api/v1") // 需要 CORS v2 := r.Group("/api/v2") // 也需要 CORS ``` 两种方案: **方案 A — 全局注册**(推荐 ✅) ```go r.Use(cors()) // 一次注册,所有路由自动继承 v1 := r.Group("/api/v1") v2 := r.Group("/api/v2") ``` **方案 B — 分组注册** ```go v1 := r.Group("/api/v1", cors()) // 每个分组都写一遍 v2 := r.Group("/api/v2", cors()) // ← 重复代码 ``` ### 为什么全局更好? **1. DRY — 避免重复** 分组注册需要在每个 Group 构造函数中手动传入中间件,新增版本时容易漏写。 **2. 不会遗漏 — 更安全** ```go // 假设后来加了 v3 v3 := r.Group("/api/v3") // ← 分组注册下很容易忘记加 cors() // 跨域请求静默失败,排查成本高 ``` 全局注册一劳永逸,永远不会遗漏。 **3. OPTIONS 预检拦截天然正确** CORS 的核心逻辑是在最外层处理 `OPTIONS` 预检请求——预检触发条件、两轮对话机制、Header 语义详见 [[BACKEND/GIN/3-middleware/cors-registration-scope/cors-preflight|cors-preflight]]。 放在全局中间件中,预检请求在最早阶段被拦截,不会消耗后续中间件的算力。 **4. 性能差异可忽略** Gin 的 `c.Next()` 只是切片遍历,一个 CORS 中间件多走一趟链的成本微乎其微。为了省这点开销拆分到分组里,得不偿失。 ### 什么时候按分组注册? 只有一种情况例外:**不同分组需要不同的中间件配置**。 ```go // v1 宽松 — 允许所有来源 v1 := r.Group("/api/v1", corsAllowAll()) // v2 严格 — Origin 白名单校验 v2 := r.Group("/api/v2", corsStrict([]string{"https://app.example.com"})) ``` 此时策略不同,自然不能复用同一个全局中间件。 ### 决策对照表 | 条件 | 推荐方案 | |------|---------| | 各分组使用相同中间件配置 | **全局 `r.Use()`** | | 各分组需要不同配置 | 各自分组注册 | | 某些路由明确不需要该中间件 | 跳过全局,按需注册到子分组 | | 中间件本身有副作用(如限流) | 评估后决定,可能需要分组隔离 | ## 关联笔记 - [[GIN/3-middleware/cors-preflight]] — OPTIONS 预检请求完整机制(触发条件、两轮对话、Max-Age 缓存) - [[GIN/3-middleware]] — 中间件三级作用域机制 - [[middleware-abort]] — `c.Abort()` 终止机制 - [[GIN/gin-architecture]] — 中间件链底层执行机制