# 4.2 请求处理链路 :::tip 概述 一个受保护的写请求依次穿过七层中间件与 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;看服务端日志 |