Skip to content

请求完整链路 ​

本章以一个具体的 API 请求为例,详细描述从浏览器发起 HTTP 请求到返回响应的完整链路,帮助开发者理解每一层的职责和数据流转方式。

请求链路总览 ​

链路概览

一个典型的 API 请求经过以下阶段:浏览器 → Nginx → CORS → Redis 注入 → DB 会话 → 操作日志 → 路由匹配 → JWT 认证 → RBAC 鉴权 → WTForms 校验 → Router → Service → Repository → DB → 响应序列化 → 中间件反向链 → 浏览器

以下以 POST /api/v1/position/add 添加岗位为例,逐步拆解完整链路。

第一阶段:网络层 ​

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 <JWT Token> 请求头
  • Content-Type: application/json 请求头
  • JSON 请求体

2. Nginx 反向代理(生产环境) ​

nginx
location /api/ {
    proxy_pass http://127.0.0.1:8021;
    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 将请求代理到 Flask 服务,同时附加客户端真实 IP 到 X-Forwarded-For 头。

第二阶段:中间件链(请求/响应钩子) ​

Flask 的中间件通过 before_request / after_request 钩子实现。请求从最外层进入,逐层穿透到路由处理函数,响应沿反向链返回。

3. CORS 跨域配置(最外层) ​

python
# src/app.py - create_app()
from flask_cors import CORS
from config import CORS_ALLOWED_ORIGINS

if CORS_ALLOWED_ORIGINS:
    allowed_origins = [origin.strip() for origin in CORS_ALLOWED_ORIGINS.split(',') if origin.strip()]
    CORS(app, resources={r"/*": {"origins": allowed_origins}}, supports_credentials=True)
else:
    CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=False)
  • 检查 Origin 头是否在允许列表中
  • 预检请求(OPTIONS)直接返回 CORS 响应头
  • 普通请求附加 Access-Control-Allow-* 响应头

4. Redis 上下文注入中间件 ​

python
# src/core/redis_client.py
@app.before_request
def redis_context():
    # 将 Redis 客户端注入到 g 对象
    g.redis = redis_client
  • 将 Redis 客户端注入 g 对象
  • 后续业务代码通过 get_redis() 获取当前请求的 Redis 客户端

6. 数据库会话中间件 ​

python
# src/middleware/transaction.py
@app.before_request
def _transaction_begin():
    """请求开始前:标记事务活跃"""
    g.transaction_active = True

@app.after_request
def _transaction_commit(response):
    """请求结束后:根据响应状态决定提交或回滚"""
    if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
        if response.status_code == 200:
            body = response.get_json()
            if body and body.get('code') == 0:
                db.session.commit()
            else:
                db.session.rollback()
    return response

@app.teardown_request
def _transaction_rollback(exception):
    """请求异常时:回滚事务"""
    if exception:
        db.session.rollback()
    db.session.remove()
  • 为每个请求管理数据库会话
  • 写操作根据响应体 code 字段自动 commit/rollback
  • 异常时自动 rollback,finally 中关闭会话

7. 操作日志中间件 ​

python
# src/middleware/operation_log.py
@app.after_request
def operation_log(response):
    if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
        # 记录操作日志到 flask_operation_log 表
        # 包括:操作人、操作IP、请求路径、请求参数、响应状态、耗时等
        ...
    return response
  • 在 DB 会话外层,记录写操作日志
  • 包含请求方法、URL、参数、响应码、耗时、操作人等信息

8. 请求计时中间件 ​

python
# src/app.py
@app.before_request
def start_timer():
    g.start_time = time.time()

@app.after_request
def add_process_time_header(response):
    if hasattr(g, 'start_time'):
        process_time = time.time() - g.start_time
        response.headers["X-Process-Time"] = str(round(process_time, 5))
    return response
  • 记录请求处理耗时
  • 通过 X-Process-Time 响应头返回

第三阶段:路由与认证 ​

9. 路由匹配 ​

python
# src/routes/v1.py
def register_v1(app):
    v1 = Blueprint('v1', __name__, url_prefix='/api/v1')
    for blue in BLUEPRINTS:
        v1.register_blueprint(blue)
    app.register_blueprint(v1)
  • Flask 根据 HTTP 方法 + URL 路径匹配到 POST /api/v1/position/add 端点
  • 该端点挂载在 v1 蓝图上
  • v1 蓝图在 src/routes/v1.py 中集中注册所有模块路由

10. JWT 认证(login_required) ​

python
# src/middleware/auth.py
@app.before_request
def login_required():
    # 白名单路径跳过认证
    if request.path in LOGIN_WHITELIST:
        return

    # 1. 从 Authorization 头提取 Bearer Token
    # 2. 验证 Token 签名、有效期
    # 3. 检查 Token 是否在 Redis 黑名单中
    # 4. 解析出 user_id、username、realname
    # 5. 注入到 g 对象
    g.user_id = user_id
    g.username = username
    g.realname = realname
  • 认证失败返回 {"code": 401, "msg": "..."} 响应
  • 认证通过后,用户信息注入 g 对象供后续使用

11. RBAC 鉴权(check_permission) ​

python
# src/modules/system/position/router.py
@position_blue.route('/add', methods=['POST'])
@check_permission('sys:position:add')
def add():
    from src.modules.system.position.service import add_position
    from src.modules.system.position.schema import PositionForm
    data = parse_request_body(request, PositionForm)
    return add_position(data)
python
# src/core/permission.py
def check_permission(permission: str):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user_id = get_user_id()
            if user_id == 1:
                return func(*args, **kwargs)
            from src.modules.system.menu.service import get_permissions_list
            permission_list = get_permissions_list(user_id)
            if permission not in permission_list:
                return R.failed("权限不足")
            return func(*args, **kwargs)
        return wrapper
    return decorator
  • 权限字符串格式:sys:{module}:{action}(如 sys:position:add)
  • 用户 ID=1(超级管理员)跳过所有权限检查
  • 使用懒加载 from src.modules.system.menu.service import get_permissions_list 避免 core → 业务模块顶层循环依赖

第四阶段:参数校验 ​

12. WTForms 请求校验 ​

python
# src/modules/system/position/schema.py
class PositionForm(BaseForm):
    name = StringField('岗位名称', validators=[
        DataRequired(message='岗位名称不能为空'),
        Length(min=1, max=150, message='岗位名称长度必须在1-150个字符之间')
    ])
    status = IntegerField('岗位状态', validators=[
        DataRequired(message='岗位状态不能为空'),
        NumberRange(min=1, max=2, message='岗位状态必须是1或2')
    ])
    sort = IntegerField('岗位排序', validators=[
        DataRequired(message='岗位排序不能为空'),
        NumberRange(min=0, max=99999, message='岗位排序必须在0-99999之间')
    ])
  • parse_request_body 函数解析 JSON 请求体
  • WTForms 按 validators 约束校验每个字段
  • 校验失败返回:
json
{"code": 1, "data": null, "msg": "岗位名称不能为空"}
  • 校验通过后,data 参数为已验证的字典

第五阶段:业务处理 ​

13. Router(HTTP 层) ​

python
# src/modules/system/position/router.py
def add():
    from src.modules.system.position.service import add_position
    from src.modules.system.position.schema import PositionForm
    data = parse_request_body(request, PositionForm)
    return add_position(data)
  • HTTP 层仅做请求转发,不含业务逻辑
  • 直接调用 Service 层函数

14. Service(业务逻辑层) ​

python
# src/modules/system/position/service.py
class PositionService(BaseService):
    repository = PositionRepository
    form = PositionForm
    status_form = PositionStatusForm
    model_name = "岗位"

    @classmethod
    def before_delete(cls, ids):
        """删除前校验"""
        for pid in ids:
            if UserRepository.count_by({'position_id': pid}) > 0:
                raise BizError(f"岗位ID {pid} 已被用户引用,无法删除")

# 模块级代理函数
add_position = PositionService.add

15. Repository(数据访问层) ​

python
# src/common/base_repository.py
class BaseRepository:
    def create(self, **fields):
        # 清洗数据:camelCase → snake_case,过滤非模型字段
        obj = cls.model(**cls._clean_form_data(fields))
        obj.save()  # add + flush(不 commit)
        return obj

16. Model(模型层) ​

python
# src/common/base_db.py
class BaseDB:
    def save(self):
        db.session.add(self)
        db.session.flush()  # 发送 SQL 到数据库,获取自增 ID
        return self

17. 数据库执行 ​

  • SQLAlchemy 将 ORM 操作转换为 SQL:INSERT INTO flask_position (name, status, sort, create_user, ...) VALUES (...)
  • flush() 将 SQL 发送到数据库,获取自增主键
  • 事务由中间件在响应返回后统一 commit

第六阶段:响应返回 ​

18. 响应序列化 ​

python
# Service 返回 R.ok(msg='添加成功')
# src/core/response.py
R.ok(msg='添加成功')
# → jsonify({"code": 0, "data": None, "msg": "添加成功", "ok": True})

19. 中间件反向链 ​

响应沿中间件链反向返回:

  1. 请求计时:附加 X-Process-Time 响应头
  2. 操作日志:记录写操作日志到数据库
  3. DB 会话:检测 code==0,执行 db.session.commit()
  4. Redis 上下文:无操作(g 对象自动清理)
  5. CORS:附加 Access-Control-Allow-* 响应头

20. 浏览器接收响应 ​

json
{
    "code": 0,
    "data": null,
    "msg": "添加成功",
    "ok": true
}

前端 Axios 拦截器统一处理响应,code===0 时展示成功提示,否则展示错误信息。

完整链路流程图 ​

请求链路(自上而下)

[浏览器]
   │ POST /api/v1/position/add
   ▼
[Nginx] ── 反向代理转发
   │
   ▼
[CORS 中间件] ── 跨域校验通过
   │
   ▼
[DB 会话中间件] ── 标记事务活跃
   │
   ▼
[操作日志中间件] ── 记录写操作(不影响主流程)
   │
   ▼
[路由层] ── 匹配 /api/v1/position/add
   │
   ▼
[认证中间件 login_required] ── JWT 验证 + 黑名单检查(Redis)
   │  ├─ 失败 → 返回 401
   │  └─ 通过 → 解析 user_id/username 注入 g 对象 ▼
   ▼
[Router 层]
   ├─ @check_permission ── 权限校验(Redis 缓存)
   ├─ parse_request_body ── WTForms 参数校验
   └─ 调用 Service ▼
   ▼
[Service 层]
   ├─ _validate_form ── WTForms 表单校验
   ├─ before_add ── 前置扩展钩子
   ├─ _clean_form_data ── 过滤无效字段
   └─ 调用 Repository.create ▼
   ▼
[Repository 层]
   └─ model(**kwargs).save() ── add + flush(获取自增 ID,不提交)▼
   ▼
[数据库] ── 执行 INSERT,返回自增 ID
   │
   ▼ 逐层返回响应
[Repository] → [Service] → [Router] → [OpLog] → [DBSession]
   │
   ├─ 业务成功(code=0)→ commit 提交事务
   └─ 业务失败(code≠0)→ rollback 回滚事务
   │
   ▼
[CORS] → [Nginx]
   │
   ▼
[浏览器] ← JSON 响应({ code, data, msg, ok })

异常处理链路 ​

当请求链路中发生异常时,全局异常处理器统一捕获并返回标准响应:

异常类型触发场景响应
BizError业务逻辑错误{"code": 1, "msg": "..."}
ValidationErrorWTForms 校验失败{"code": 1, "msg": "field: message"}
Exception未捕获异常{"code": 1, "msg": "服务器内部错误"}

异常处理原则

所有异常均返回 HTTP 200 状态码,通过 code 字段区分业务成功(0)与失败(1)。这是前后端约定,前端统一按 code 判断,无需处理 HTTP 状态码差异。

总结 ​

一个 API 请求从浏览器到数据库经过 7 个阶段、20+ 个处理节点。中间件链负责横切关注点(安全、日志、事务),路由层负责认证鉴权,WTForms 负责参数校验,三层架构负责业务逻辑与数据访问。这种分层设计使得每一层职责单一、可独立测试,系统在高并发场景下仍能保持稳定的性能和可靠的事务一致性。

小蚂蚁云团队 · 提供技术支持