2.8 KiB
2.8 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-04-27 12:55 |
跨分组共享中间件的作用域选择
概述
讨论当多个 RouterGroup(如 /api/v1 和 /api/v2)需要同一中间件时,是注册到全局还是各自分组的取舍。结论:同策略 → 全局;异策略 → 分组。
正文
场景还原
v1 := r.Group("/api/v1") // 需要 CORS
v2 := r.Group("/api/v2") // 也需要 CORS
两种方案:
方案 A — 全局注册(推荐 ✅)
r.Use(cors()) // 一次注册,所有路由自动继承
v1 := r.Group("/api/v1")
v2 := r.Group("/api/v2")
方案 B — 分组注册
v1 := r.Group("/api/v1", cors()) // 每个分组都写一遍
v2 := r.Group("/api/v2", cors()) // ← 重复代码
为什么全局更好?
1. DRY — 避免重复
分组注册需要在每个 Group 构造函数中手动传入中间件,新增版本时容易漏写。
2. 不会遗漏 — 更安全
// 假设后来加了 v3
v3 := r.Group("/api/v3") // ← 分组注册下很容易忘记加 cors()
// 跨域请求静默失败,排查成本高
全局注册一劳永逸,永远不会遗漏。
3. OPTIONS 预检拦截天然正确
CORS 的核心逻辑是在最外层处理 OPTIONS 预检请求——预检触发条件、两轮对话机制、Header 语义详见 BACKEND/GIN/3-middleware/cors-registration-scope/cors-preflight。
放在全局中间件中,预检请求在最早阶段被拦截,不会消耗后续中间件的算力。
4. 性能差异可忽略
Gin 的 c.Next() 只是切片遍历,一个 CORS 中间件多走一趟链的成本微乎其微。为了省这点开销拆分到分组里,得不偿失。
什么时候按分组注册?
只有一种情况例外:不同分组需要不同的中间件配置。
// 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 — 中间件链底层执行机制