Skip to content

4.2 请求处理链路 ​

链路概览

一个典型的 API 请求经过以下阶段:浏览器 → Nginx → CORS 中间件 → Auth 中间件(JWT 认证 + RBAC 鉴权)→ Tenant 中间件 → Demo 中间件 → Log 中间件 → 控制器 → 逻辑层 → 模型层 → 数据库 → 响应序列化 → 浏览器

本节以 POST /api/example/add 添加案例为例,逐步拆解完整链路。

第一阶段:网络层 ​

1. 浏览器发起请求 ​

typescript
// 前端 Axios 调用(ui/src/api/tool/example.ts)
export function exampleAdd(data: any) {
  return http.request({
    url: '/example/add',
    method: 'POST',
    data,  // { name: '测试案例', status: 1, sort: 0 }
  });
}

前端通过 Axios 发起 HTTP POST 请求,携带:

  • Authorization: Bearer <JWT Token> 请求头(由请求拦截器自动添加)
  • Content-Type: application/json 请求头
  • JSON 请求体

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

nginx
location /api/ {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

开发环境下,Vite 开发服务器将 /api 请求代理到后端:

typescript
// ui/vite.config.ts
server: {
  proxy: {
    '/api': {
      target: 'http://localhost:8000',
      changeOrigin: true,
    },
  },
}

第二阶段:路由解析 ​

3. ThinkPHP 入口 ​

text
public/index.php → 引导框架 → 路由解析 route/app.php

4. 路由匹配 ​

php
// route/app.php

// 公开接口(无需认证)
Route::group('api', function () {
    Route::post('login', 'LoginController/login');
    Route::get('captcha', 'LoginController/captcha');
});

// 需认证接口
Route::group('api', function () {
    Route::group('example', function () {
        Route::post('add', 'ExampleController/add');  // ← 匹配到此处
    });
})->middleware([
    \app\middleware\AuthMiddleware::class,      // JWT 认证 + 权限校验
    \app\middleware\TenantMiddleware::class,     // 租户上下文
    \app\middleware\DemoMiddleware::class,       // 演示环境拦截
    \app\middleware\LogMiddleware::class,        // 操作日志
]);

路由匹配到 POST api/example/add → ExampleController::add(),同时绑定四个中间件。

第三阶段:中间件链(按顺序执行) ​

5. CorsMiddleware(全局中间件) ​

php
// app/middleware/CorsMiddleware.php

public function handle(Request $request, \Closure $next): Response
{
    // 处理 OPTIONS 预检请求
    if ($request->isOptions()) {
        $response = Response::create('', 'html', 204);
    } else {
        $response = $next($request);
    }

    // 设置跨域响应头
    $response->header([
        'Access-Control-Allow-Origin'      => $origin,
        'Access-Control-Allow-Methods'     => 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
        'Access-Control-Allow-Headers'     => 'Content-Type, Authorization, ...',
        'Access-Control-Allow-Credentials' => 'true',
    ]);

    return $response;
}
  • 每个请求都经过(全局中间件)
  • OPTIONS 预检请求直接返回 204
  • 普通请求附加 CORS 响应头

6. AuthMiddleware(JWT 认证 + 权限校验) ​

php
// app/middleware/AuthMiddleware.php

public function handle(Request $request, \Closure $next): Response
{
    // 1. 检查排除路由(login/captcha 等直接放行)
    $path = $request->pathinfo();
    foreach ($exceptList as $except) {
        if (strpos($path, $except) !== false) {
            return $next($request);
        }
    }

    // 2. 从请求头获取 Token
    $token = Jwt::getTokenFromHeader();
    if (empty($token)) {
        return json(['code' => 401, 'msg' => '未提供认证令牌']);
    }

    // 3. 解析 Token,校验签名和有效期
    try {
        $decoded = Jwt::decode($token);
    } catch (\RuntimeException $e) {
        return json(['code' => 401, 'msg' => $e->getMessage()]);
    }

    // 4. 校验 token 类型必须为 access
    if (($decoded->type ?? '') !== 'access') {
        return json(['code' => 401, 'msg' => '无效的认证令牌']);
    }

    // 5. 注入用户信息到 Request
    $request->userInfo = $decoded;

    // 6. 读取 #[Permission] 注解,校验权限
    $permission = AttributeService::getPermission($controllerClass, $action);
    if ($permission !== null) {
        $hasPermission = $this->checkPermission($decoded->uid, $permission->code);
        if (!$hasPermission) {
            return json(['code' => 403, 'msg' => '无访问权限']);
        }
    }

    return $next($request);
}

处理流程:

text
请求进入
    │
    ├─ 路径在排除列表? → 直接放行
    │
    ├─ 提取 Authorization: Bearer <token>
    │   └─ 为空 → 返回 401
    │
    ├─ Jwt::decode($token) 解析验证
    │   └─ 失败(过期/签名错误)→ 返回 401
    │
    ├─ 校验 type === 'access'
    │   └─ 不是 → 返回 401
    │
    ├─ 注入 $request->userInfo = {uid, username, type, exp}
    │
    ├─ 读取 #[Permission] 注解
    │   ├─ 无注解 → 跳过权限校验
    │   └─ 有注解 → checkPermission()
    │       ├─ uid === 1 → 直接放行(超级管理员)
    │       └─ 查询 UserRole → RoleMenu → Menu.permission
    │           ├─ 包含 → 放行
    │           └─ 不包含 → 返回 403
    │
    └─ 放行 → 进入下一个中间件

7. TenantMiddleware(租户上下文) ​

php
// app/middleware/TenantMiddleware.php

public function handle(Request $request, \Closure $next): Response
{
    $userInfo = $request->userInfo ?? null;
    $userId = $userInfo->uid ?? 0;

    $user = User::find($userId);
    $tenantId = $user->tenant_id ?? 0;

    // 注入租户上下文
    $request->tenantId = $tenantId;
    $request->isSuperAdmin = ($tenantId == 0);

    // 非超级管理员校验租户状态
    if ($tenantId > 0) {
        $tenant = Tenant::find($tenantId);
        if (!$tenant || $tenant->status != 1) {
            return json(['code' => 403, 'msg' => '租户已被禁用']);
        }
        if ($tenant->expire_time && strtotime($tenant->expire_time) < time()) {
            return json(['code' => 403, 'msg' => '租户已过期']);
        }
        $request->tenantInfo = $tenant;
    }

    return $next($request);
}

注入内容:

  • $request->tenantId — 租户 ID(0=超级管理员)
  • $request->isSuperAdmin — 是否超级管理员
  • $request->tenantInfo — 租户信息对象

8. DemoMiddleware(演示环境拦截) ​

php
// app/middleware/DemoMiddleware.php

public function handle(Request $request, \Closure $next): Response
{
    // 非演示环境直接放行
    if (!env('app_demo', false)) {
        return $next($request);
    }

    // GET 请求放行(读操作)
    if (strtoupper($request->method()) === 'GET') {
        return $next($request);
    }

    // 检查 #[DemoAllow] 注解
    if (AttributeService::hasDemoAllow($controllerClass, $action)) {
        return $next($request);
    }

    // 拦截写操作
    return json(['code' => 403, 'msg' => '演示环境,禁止操作']);
}

9. LogMiddleware(操作日志) ​

php
// app/middleware/LogMiddleware.php

public function handle(Request $request, \Closure $next): Response
{
    $startTime = microtime(true);

    // 执行后续中间件和控制器
    $response = $next($request);

    // 写操作始终记录
    if (in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH'])) {
        // 读取 #[Log] 注解
        $logAttr = AttributeService::getLog($controllerClass, $action);

        // 组装日志数据
        $logData = [
            'title'       => $logAttr?->title ?: $path,
            'type'        => $logAttr?->type ?: $this->guessType($method),
            'method'      => $method,
            'url'         => $request->url(),
            'param'       => json_encode($request->param()),
            'ip'          => $requestInfo->getIp(),
            'location'    => $requestInfo->getIpLocation(),
            'os'          => $requestInfo->getOs(),
            'browser'     => $requestInfo->getBrowser(),
            'consume_time'=> round((microtime(true) - $startTime) * 1000),
            'status'      => $this->detectStatus($response->getContent()),
        ];

        // 写入数据库(失败不影响主业务)
        OperationLog::create($logData);
    }

    return $response;
}

第四阶段:控制器层 ​

10. ExampleController::add() ​

php
// app/controller/ExampleController.php

#[Log('案例演示-新增记录', Log::TYPE_ADD, '新增案例演示:{name}')]
#[Permission('sys:example:add', '添加案例演示')]
public function add(): Json
{
    // getJsonBody() 获取前端 POST 提交的 JSON 数据
    // _add() 内部调用 $this->logic->add($data)
    return parent::_add($this->logic, $this->getJsonBody());
}

控制器层职责:

  • 通过 #[Permission] 声明所需权限
  • 通过 #[Log] 声明日志信息
  • 获取参数,调用 Logic 层,返回响应

第五阶段:逻辑层 ​

11. ExampleLogic::add() ​

php
// app/logic/ExampleLogic.php → 继承 BaseLogic

// BaseLogic::add() 的完整流程:
public function add(array $data): int
{
    // 1. 钩子:新增前(可修改数据,抛异常可拦截)
    $data = $this->beforeAdd($data);

    // 2. 保留原始数据供 afterAdd 使用
    $originalData = $data;

    // 3. 入库前转换:camelCase → snake_case
    $data = array_camel_to_snake($data);

    // 4. 租户ID自动填充
    $data = $this->processTenantId($data);

    // 5. 文件字段:迁移临时文件,去掉域名
    $data = $this->processFileFieldsOnSave($data);

    // 6. 富文本字段:迁移临时文件
    $data = $this->processContentFieldsOnSave($data);

    // 7. 过滤非数据库字段
    $saveData = $this->filterTableFields($data);

    // 8. 唯一性校验
    $this->checkUnique($saveData);

    // 9. 写入数据库
    $model = $this->getModel()->create($saveData);
    $id = $model->id;

    // 10. 钩子:新增后
    $this->afterAdd($id, $originalData);

    return $id;
}

第六阶段:模型层 ​

12. BaseModel 自动处理 ​

php
// app/model/Example.php
class Example extends BaseModel
{
    protected $name = 'example';
}

// BaseModel 提供的能力:
// 1. 全局查询范围 soft_delete → WHERE is_delete = 0
// 2. onBeforeInsert → 自动填充 create_user, create_time
// 3. onBeforeUpdate → 自动填充 update_user, update_time

13. 数据库执行 ​

ThinkPHP ORM 将操作转换为 SQL:

sql
INSERT INTO think_example (name, status, sort, avatar, create_user, create_time)
VALUES ('测试案例', 1, 0, '', 'admin', '2026-09-20 12:00:00');

第七阶段:响应返回 ​

14. 响应序列化 ​

php
// BaseController::_add()
protected function _add(BaseLogic $logic, array $data): Json
{
    $id = $logic->add($data);
    return $this->success(['id' => $id], '添加成功');
}

// Result::success()
public static function success($data, string $msg = '操作成功'): Json
{
    return json([
        'code' => 0,
        'ok'   => true,
        'msg'  => $msg,
        'data' => self::convertData($data),  // snake_case → camelCase
    ]);
}

15. 浏览器接收响应 ​

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

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

完整链路流程图 ​

text
[浏览器]
   │ POST /api/example/add
   │ Authorization: Bearer <token>
   ▼
[Nginx / Vite Proxy] ── 反向代理转发
   │
   ▼
[ThinkPHP 入口] ── public/index.php
   │
   ▼
[路由解析] ── route/app.php 匹配 api/example/add
   │
   ▼
[CorsMiddleware] ── 跨域校验通过(全局)
   │
   ▼
[AuthMiddleware]
   ├─ 提取 Token → Jwt::decode()
   ├─ 校验 type === 'access'
   ├─ 注入 $request->userInfo
   ├─ 读取 #[Permission] 注解
   ├─ checkPermission() 权限校验
   │   ├─ uid=1 → 跳过(超级管理员)
   │   └─ UserRole → RoleMenu → Menu.permission
   └─ 放行
   │
   ▼
[TenantMiddleware]
   ├─ 查询用户 tenant_id
   ├─ 注入 $request->tenantId
   └─ 校验租户状态(禁用/过期 → 403)
   │
   ▼
[DemoMiddleware]
   ├─ 非演示环境 → 放行
   ├─ GET 请求 → 放行
   └─ 有 #[DemoAllow] → 放行
   │
   ▼
[LogMiddleware] ── 记录开始时间
   │
   ▼
[ExampleController::add()]
   ├─ #[Log] 注解元数据
   ├─ #[Permission] 注解元数据
   └─ parent::_add($this->logic, $data)
   │
   ▼
[ExampleLogic::add()]
   ├─ beforeAdd() 钩子
   ├─ array_camel_to_snake() 键名转换
   ├─ processTenantId() 租户填充
   ├─ processFileFieldsOnSave() 文件处理
   ├─ filterTableFields() 字段过滤
   ├─ checkUnique() 唯一性校验
   ├─ Model::create() 写入数据库
   └─ afterAdd() 钩子
   │
   ▼
[BaseModel]
   ├─ onBeforeInsert → create_user, create_time
   └─ 执行 SQL INSERT
   │
   ▼
[数据库] ── 返回自增 ID
   │
   ▼ 逐层返回响应
[Result::success()] ── {code:0, ok:true, msg:"添加成功", data:{id:42}}
   │
   ▼
[LogMiddleware] ── 记录操作日志(写操作)
   │
   ▼
[浏览器] ← JSON 响应

异常处理链路 ​

当链路中发生异常时,ExceptionHandle 统一捕获并返回标准响应:

异常类型触发场景响应
ValidateException参数验证失败{code:1, msg:"参数验证失败", data:{错误明细}}
ModelNotFoundException数据不存在{code:1, msg:"数据不存在"}
RuntimeException (401)JWT 过期/无效{code:1, msg:"token已过期"}
HttpExceptionHTTP 错误{code:1, msg:"请求错误"}
Exception未捕获异常{code:1, msg:"操作失败"}(生产环境隐藏详情)

异常处理原则

所有异常均返回 HTTP 200 状态码,通过响应体 code 字段区分业务成功(0)与失败(1/401/403/404/422)。前端统一按 code 判断,无需处理 HTTP 状态码差异。

总结 ​

一个 API 请求从浏览器到数据库经过 7 个阶段、15+ 个处理节点。中间件链负责横切关注点(认证、权限、租户、日志),控制器层负责参数获取和响应返回,逻辑层负责业务处理和数据加工,模型层负责数据库操作。这种分层设计使得每一层职责单一、可独立测试。

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