4.2 请求处理链路
概述
一个受保护的写请求依次穿过七层中间件与 Bind 包装层。理解这条链路是排查 401/403/429/演示拦截/日志缺失的钥匙。注册顺序即执行顺序(router.go s.Use)。
中间件链总览
text
CORS → Response → RateLimit → Demo → JWT → Tenant → OperationLog → 处理器
(Bind 包装层做权限校验)| # | 中间件 | 短路行为 |
|---|---|---|
| 1 | CORS | 预检 204;头设置 |
| 2 | Response | SVG 加固;元信息入 ctx;panic → 500 信封 |
| 3 | RateLimit | 超限 code=429 |
| 4 | Demo | app.demo=true 时写操作 code=1015 |
| 5 | JWT | 无效/黑名单 code=401 |
| 6 | Tenant | 租户禁用 1006 / 过期 1005 |
| 7 | OperationLog | 不短路,异步采集 |
| 8 | Bind 包装层 | 权限不足 code=403;参数校验失败 400 |
请求生命周期(12 步)
text
1 CORS 预检/头设置
2 Response 元信息入 ctx;defer panic 兜底
3 RateLimit 固定窗口计数,超限 429
4 Demo 演示模式拦截写操作
5 JWT 解析校验 token → 注入身份;黑名单 401
6 Tenant 租户状态/过期校验(60s 缓存)
7 OperationLog 记 start,Next()
8 Bind 包装层 元数据入 ctx → HasPerm 权限校验(403 短路)→ Parse 参数 → 调 controller
9 controller 转发 service 接口
10 logic ModuleSpec 流水线 / 手写业务;dao.Ctx 查询(钩子已注入条件)
11 response.Write 信封写出(code/body 记入 ctx)
12 OperationLog 组装记录 → 异步通道 → 批量落库时序图(受保护写请求 + 日志异步落库)
text
Client 中间件链 Bind包装/业务 缓存 DB/日志
│ POST /api/user/update │ │ │
│───▶│ CORS 头/预检 │ │ │
│ │ Response: 元信息入ctx, defer recover │ │
│ │ RateLimit ──────────────────────────────────────▶ SET/GET 计数│
│ │ Demo(app.demo=false 放行) │ │ │
│ │ JWT: Parse+黑名单 ───────────────────────────────▶ Exists jti │
│ │ 注入身份; Roles/Perms/DataScope/DeptId ──────▶ 命中缓存 │
│ │ 未命中 → IdentityLoader 回源 ───────────────────────▶ user/role/menu
│ │ Tenant: status/expire ───────────────────────────▶ tenant:info │
│ │ OperationLog: start=now │ │ │
│ │ │ │ │ │
│ │ ▼ ▼ HasPerm(403?短路) │ │
│ │ Parse(req) → logic.Update │ │
│ │ spec 流水线: BeforeUpdate → │ │
│ │ saveTransform → 唯一预检 → ────────────────▶ UPDATE
│ │ (hooks 盖章 update_time/user) │ │
│ │ ▼ │ │ │
│◀───│ WriteJson{code:0,...}(code/body 记 ctx) │ │
│ │ OperationLog: 组装 entry(脱敏 param/result) │ │
│ │ select{ logChan<-entry } 非阻塞 │ │
│ │ worker: 攒批 100条/1s ──────────────────▶ INSERT operation_log异常分支:
text
* 任一中间件短路(429 / 演示拦截 / 401 / 403)→ 直接 WriteJson,后续不执行
* panic → Response recover → 500 信封(已写出响应则仅记录日志)
* logChan 满 → 丢弃并计数告警(每丢 100 条输出一次 Warning),绝不阻塞业务各层关键实现速查
| 层 | 实现位置 | 要点 |
|---|---|---|
| CORS | middleware/cors.go | cors.allowOrigin 空或 * 走框架默认;具体来源显式回显凭证 |
| Response | middleware/response.go | .svg 强制 attachment+nosniff(防存储型 XSS);realClientIP/UserAgent 入 ctx |
| RateLimit | middleware/ratelimit.go | 固定窗口;键 ratelimit:{ip}:{path}:{窗口起点};计数经 utility/cache 多实例共享;存储异常放行 |
| Demo | middleware/demo.go | GET/HEAD/OPTIONS 与公开白名单放行;其余写操作拒绝 |
| JWT | middleware/jwt.go | Parse(HS256/过期/签发者)→ 黑名单 Exists → 注入身份(缓存 300s,回源 LoadIdentity) |
| Tenant | middleware/tenant.go | 缓存 60s;租户不存在按禁用处理;数据库基础错误放行(防存储抖动全站不可用) |
| OperationLog | middleware/operation_log.go | 有界通道 2048 + 单 worker;批量 100 条或 1s flush;参数脱敏 password/salt/token → *** |
| Bind | router/bind.go | 权限校验 + r.Parse(gvalid)+ 信封写出;BodyUnmarshaler 逃生舱 |
真实 IP 与受信代理
text
util.go realClientIP:
直连来源不在 api.proxyIps(逗号分隔,精确 IP 与 CIDR)内 → 直接用 GetRemoteIp()
在列表内 → 才信任 X-Forwarded-For 首段 / X-Real-IP防止伪造 XFF 绕过限流或污染日志。Nginx 反代部署务必把反代地址加入 api.proxyIps。
排障速查
| 症状 | 检查点 |
|---|---|
| 401 | token 过期/黑名单(登出、轮换、强退都会拉黑 jti) |
| 403 | gmeta permission ↔ 菜单节点 ↔ 角色勾选 ↔ 身份缓存(300s) |
| 429 | 限流阈值;认证入口独立 authLimit=10 |
| 1015 | app.demo=true 拦截写操作 |
| 1005/1006 | 租户过期/禁用(Tenant 中间件,缓存 60s) |
| 日志缺失 | operationLog.enable / log 标签空 / logQuery 关 / 通道满告警 |
| panic 无堆栈 | server.errorStack=false;看服务端日志 |