# 请求完整链路 本章以一个具体的 API 请求为例,详细描述从浏览器发起 HTTP 请求到返回响应的完整链路,帮助开发者理解每一层的职责和数据流转方式。 ## 请求链路总览 :::tip 链路概览 一个典型的 API 请求经过以下阶段:浏览器 → Nginx → Tornado → Handler 生命周期(set_default_headers → prepare → handler_method → on_finish)→ 响应返回浏览器 ::: 以下以 `POST /api/v1/position/add` 添加岗位为例,逐步拆解完整链路。 ## Tornado 请求处理模型 Tornado 通过 **Handler 生命周期方法** 和 **装饰器** 实现横切关注点: ``` ┌─────────────────────────────────────────────────────────────────┐ │ Tornado Handler 生命周期 │ │ │ │ ① set_default_headers() → 设置 CORS 头 + trace_id │ │ ② prepare() → @login_required(JWT 认证) │ │ ③ handler method → @permission_required + @check_demo │ │ + @operation_log + 业务逻辑 │ │ ④ on_finish() → db.close()(释放数据库会话) │ │ │ │ 异常时:write_error() → 统一异常响应 + 日志记录 │ └─────────────────────────────────────────────────────────────────┘ ``` ## 第一阶段:网络层 ### 1. 浏览器发起请求 ```javascript // 前端 Axios 调用 const res = await request({ url: '/api/v1/position/add', method: 'post', data: { name: '高级工程师', status: 1, sort: 10 } }); ``` 前端通过 Axios 发起 HTTP POST 请求,携带: - `Authorization: Bearer ` 请求头 - `Content-Type: application/json` 请求头 - JSON 请求体 ### 2. Nginx 反向代理(生产环境) ```nginx location /api/ { proxy_pass http://127.0.0.1:8041; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ``` Nginx 将请求代理到 Tornado 服务,同时附加客户端真实 IP 到 `X-Forwarded-For` 头。 ## 第二阶段:Handler 生命周期 ### 3. 路由匹配 Tornado 根据 HTTP 方法 + URL 路径匹配到对应的 Handler 类: ```python # src/api/v1/router.py def build_routes() -> list: routes = [] for module in _RESOURCE_MODULES: routes.extend([(V1_PREFIX + path, handler) for path, handler in module.routes]) routes.append((V1_PREFIX + "/health", HealthHandler)) return routes ``` 路由表中 `POST /api/v1/position/add` 匹配到 `PositionAddHandler`。 ### 4. set_default_headers() — 设置响应头 ```python # src/core/base_handler.py class BaseHandler(tornado.web.RequestHandler): def set_default_headers(self): super().set_default_headers() cros_required(self) # CORS 跨域头 apply_trace_id(self) # 链路追踪 ID ``` - **CORS**:检查 `Origin` 头是否在白名单中,设置 `Access-Control-Allow-*` 响应头 - **trace_id**:生成唯一追踪 ID,写入响应头和上下文变量 ### 5. prepare() — JWT 认证 ```python # src/core/base_handler.py class BaseHandler(tornado.web.RequestHandler): @login_required def prepare(self): pass ``` `login_required` 装饰器在 `prepare()` 阶段执行认证: ```python # src/common/middleware/authentication.py def login_required(func): def wrapper(self, *args, **kwargs): request_url = self.request.path # 白名单(/login、/captcha)和 OPTIONS 预检直接放行 if request_url not in IGNORE_URL and self.request.method != "OPTIONS": # 1. 从 Authorization 头提取 Bearer Token success, access_token, _ = get_access_token(self.request) if not success or not access_token: return R.failed(self, code=401, msg="登录过期") # 2. JWT 解密验证(签名 + 有效期) result = parse_payload(access_token) if result["code"] != 0: return R.failed(self, code=401, msg="登录过期") # 3. 黑名单校验(Redis,不可用时 fail-open 放行) if is_token_blacklisted(access_token): return R.failed(self, code=401, msg="登录过期") # 4. 将用户信息写入上下文变量 data = result["data"] current_user_id.set(int(data["userId"])) current_username.set(data.get("username", "")) current_realname.set(data.get("realname", "")) return func(self, *args, **kwargs) return wrapper ``` 认证失败时直接返回 `{"code": 401, "msg": "登录过期"}`,不进入后续 Handler 方法。 ### 6. Handler 方法 — 权限校验 + 业务处理 ```python # src/modules/position/handlers.py class PositionAddHandler(BaseHandler): @permission_required("sys:position:add") # ① RBAC 权限校验 @check_demo # ② 演示模式拦截 @operation_log("岗位管理", "添加") # ③ 操作日志记录 async def post(self): return await position_service.add(self) # ④ 调用 Service 层 ``` **装饰器执行顺序**(从外到内): ``` ① permission_required → 校验用户是否拥有 sys:position:add 权限 ├── user_id == 1(admin)→ 直接放行 └── 其他用户 → 查询权限列表(Redis 缓存)→ 匹配则放行,否则返回 403 ② check_demo → TORNADO_DEMO=True 时返回 "演示环境,暂无操作权限" ③ operation_log → 记录操作日志(请求参数、操作人、IP、耗时等) ④ handler method → 调用 position_service.add(self) ``` ### 7. Service 层 — 业务逻辑 ```python # src/core/base_service.py class BaseService: async def add(self, handler) -> R: try: # 1. 解析请求体 JSON + Pydantic 校验 form = self._validate(handler, self.create_schema) data = form.model_dump() # 2. 唯一性校验 err = self._check_unique(form) if err: return R.failed(handler, err) # 3. 前置扩展钩子 self._before_add(handler, data) # 4. 组装入库字段 fields = self._build_create_fields(data) # 5. 调用 Repository 创建记录 self.repo.create(self.model(**fields)) # 6. 返回成功响应 return R.ok(handler, msg="添加成功") finally: self.repo.close() ``` ### 8. Repository 层 — 数据访问 ```python # src/core/base_repository.py class BaseRepository: def create(self, obj: T) -> T: # 自动写入创建人 if hasattr(obj, "create_user"): obj.create_user = str(self._uid()) self.db.add(obj) self.db.commit() return obj ``` ### 9. 响应返回 ```python # src/core/response.py def ok(self, data=None, msg="操作成功", code=0, **kwargs): body = {"code": code, "data": data, "msg": msg, "ok": True} if kwargs: body.update(kwargs) self._api_result = body self.finish(body) # Tornado 写出 JSON 响应 ``` `R.ok()` / `R.failed()` 内部调用 `self.finish(body)`,Tornado 自动序列化为 JSON 并发送 HTTP 响应。 ### 10. on_finish() — 释放资源 ```python # src/core/base_handler.py class BaseHandler(tornado.web.RequestHandler): def on_finish(self): try: db.close() # 兜底关闭数据库会话 except Exception: pass ``` 请求结束后关闭数据库会话,避免长连接泄漏。Service 层的 `finally` 块已关闭时此处为幂等 no-op。 ### 11. 异常处理 ```python # src/core/base_handler.py class BaseHandler(tornado.web.RequestHandler): def write_error(self, status_code, **kwargs): exc = kwargs.get("exc_info", (None, None, None))[1] if isinstance(exc, BaseAppError): # 业务异常:按类型分级记录日志 self.set_status(exc.status_code) self.finish({"code": exc.code, "data": None, "msg": exc.msg, "ok": False}) else: # 未预期异常:记录完整堆栈 self.set_status(500) self.finish({"code": 500, "data": None, "msg": "服务器内部错误", "ok": False}) ``` | 异常类型 | 触发场景 | 响应 | |:---|:---|:---| | `AuthError` | JWT 过期 / 无权限 | `{"code": 401, "msg": "登录过期"}` | | `PermissionDeniedError` | 权限不足 | `{"code": 403, "msg": "权限不足"}` | | `ValidationError` | Pydantic 校验失败 | `{"code": 1, "msg": "field: message"}` | | `BusinessError` | 业务逻辑错误 | `{"code": 1, "msg": "..."}` | | `NotFoundError` | 记录不存在 | `{"code": 404, "msg": "记录不存在"}` | | `Exception` | 未捕获异常 | `{"code": 500, "msg": "服务器内部错误"}` | ## 完整链路流程图 ``` [浏览器] │ POST /api/v1/position/add │ Authorization: Bearer ▼ [Nginx] ── 反向代理转发 │ ▼ [Tornado 路由匹配] ── 匹配 PositionAddHandler │ ▼ [set_default_headers()] ── 设置 CORS 头 + trace_id │ ▼ [prepare() + @login_required] ── JWT 认证 │ ├─ 白名单(/login、/captcha)→ 跳过 │ ├─ Token 无效/过期 → 返回 {code:401, msg:"登录过期"} │ ├─ Token 在黑名单中 → 返回 {code:401, msg:"登录过期"} │ └─ 认证通过 → current_user_id / current_username / current_realname 写入上下文 │ ▼ [@permission_required("sys:position:add")] ── RBAC 权限校验 │ ├─ user_id == 1 → 直接放行 │ ├─ 权限不足 → 返回 {code:403, msg:"权限不足"} │ └─ 权限通过 → 继续 │ ▼ [@check_demo] ── 演示模式拦截 │ ├─ TORNADO_DEMO=True → 返回 {code:1, msg:"演示环境,暂无操作权限"} │ └─ 非演示模式 → 继续 │ ▼ [@operation_log("岗位管理", "添加")] ── 操作日志装饰器 │ ▼ [async def post(self)] ── Handler 方法 │ ▼ [position_service.add(self)] ── Service 层 ├─ parse_json(handler) ── 解析请求体 JSON ├─ validate(schema, data) ── Pydantic 校验 ├─ _check_unique(form) ── 唯一性校验(DB 查询) ├─ _before_add(handler, data) ── 前置扩展钩子 ├─ _build_create_fields(data) ── 组装入库字段 └─ repo.create(model(**fields)) ── Repository 层 │ ▼ [BaseRepository.create(obj)] ├─ obj.create_user = str(uid) ── 自动写入创建人 ├─ db.add(obj) + db.commit() ── 提交事务 └─ return obj │ ▼ [R.ok(handler, msg="添加成功")] ── 统一响应 ├─ self._api_result = body ── 保存结果供日志解析 └─ self.finish(body) ── 写出 JSON 响应 │ ▼ [on_finish()] ── 释放资源 └─ db.close() ── 关闭数据库会话 │ ▼ [浏览器] ← { "code": 0, "data": null, "msg": "添加成功", "ok": true } ``` ## 总结 Tornado 的请求处理通过 Handler 生命周期方法(`set_default_headers` → `prepare` → `handler_method` → `on_finish`)和装饰器(`@permission_required`、`@check_demo`、`@operation_log`)实现横切关注点。认证在 `prepare()` 阶段完成,权限/演示模式/操作日志通过装饰器在 Handler 方法执行前校验,数据库会话在 `on_finish()` 中兜底关闭。这种设计使得每一层职责单一、可独立测试。