推荐使用全局中间件MockMiddleware,仅在APP_DEBUG=true且mock_enabled=true时生效,通过$request->url()匹配路径并返回JSON,手动设置Content-Type为application/json。

ThinkPHP 6 中间件拦截请求返回 Mock 数据
开发阶段接口未就绪时,直接在中间件里截断请求、返回预设 JSON,是最轻量可控的方式。关键不是“能不能”,而是“在哪拦、怎么返、怎么不污染生产环境”。
推荐用全局中间件 + 环境开关,避免侵入业务逻辑。TP6 的中间件执行时机早于控制器,适合做路由级 Mock。
-
App\Middleware\MockMiddleware是最合适的拦截点,不要塞进控制器或模型里 - 只在
APP_DEBUG = true且配置了mock_enabled = true时生效,否则中间件直接return $next($request) - 匹配路径建议用
$request->url()或$request->path(),别依赖Route::rule(),后者可能还没解析完 - 返回前必须手动设置
Content-Type: application/json,否则前端fetch().json()会失败
<?php
// app/middleware/MockMiddleware.php
namespace app\middleware;
use think\Request;
use think\Response;
class MockMiddleware
{
public function handle(Request $request, \Closure $next)
{
if (!config('app.debug') || !config('mock.enabled')) {
return $next($request);
}
$mockMap = config('mock.routes', []);
$path = $request->path();
if (isset($mockMap[$path])) {
$data = is_callable($mockMap[$path])
? $mockMap[$path]($request)
: $mockMap[$path];
return Response::create($data, 'json')->header(['Content-Type' => 'application/json']);
}
return $next($request);
}
}
配置 mock.routes 实现多态接口(不同参数返回不同数据)
一个 URL 对应多个响应体,比如 /api/user/123 和 /api/user/456 返回不同用户,不能靠写死数组,得支持动态解析路径和查询参数。
核心是把 mock.routes 设计成键为正则、值为闭包的结构,让匹配更灵活,也避免大量 if-else。
立即学习“PHP免费学习笔记(深入)”;
- 键必须是 PCRE 正则字符串,如
'#^api/user/(\d+)$#',开头结尾加#避免转义混乱 - 闭包参数固定为
Request $request,从中取$request->param()、$request->get()或$request->path() - 不要在闭包里调用
db()或任何真实服务,Mock 就该零依赖 - 配置文件里用 PHP 数组而非 YAML/JSON,TP6 原生只认 PHP 格式配置
// config/mock.php
return [
'enabled' => true,
'routes' => [
'#^api/user/(\d+)$#' => function ($request) {
$id = $request->param(0); // 匹配第一个捕获组
return ['code' => 0, 'data' => ['id' => (int)$id, 'name' => 'Mock User ' . $id]];
},
'#^api/order/list$#' => function ($request) {
$page = $request->get('page', 1);
return ['code' => 0, 'data' => array_fill(0, 10, ['order_no' => 'MOCK' . uniqid()])];
},
],
];
为什么不用路由绑定或事件监听做 Mock
有人试过在 route/app.php 里用 Route::get('api/xxx', function(){}) 写一堆闭包,或者监听 RouteChecked 事件——这两种方式都容易出问题。
- 路由闭包会覆盖真实路由定义,上线前漏删一条就炸;且无法按环境自动关闭
- 事件监听太晚,部分中间件(如日志、权限)已经执行过了,Mock 返回后日志里仍记了一条“失败请求”
- TP6 的
think\Route在中间件之后才完成最终分发,所以中间件拦截是唯一能“彻底取消控制器执行”的位置 - 网关类中间件(如鉴权、限流)如果已注册,Mock 中间件必须排在其前面,否则会被拦截掉
上线前必须检查的三个地方
Mock 数据跑得再顺,只要漏掉其中一项,上线就会 500 或返回假数据。
- 确认
config/mock.php没被 git 提交到生产分支,最好加到.gitignore里 - 检查中间件注册顺序:
app/middleware.php中MockMiddleware::class必须在AuthMiddleware::class之前 - 确保所有 Mock 闭包返回的是纯数组或标量,不要返回
Collection、Model或Query实例,Response::create()不会自动 toArray()
Mock 的边界就一条:它只该存在于本地调试和 CI 构建阶段。一旦涉及跨环境共享、数据联动或状态保持,就得切回真实接口——那不是 Mock 能解决的事。


















