ThinkPHP Vue前后端分离教程
ThinkPHP与Vue前后端分离开发中常见路由不返回JSON、POST参数接收失败、跨域报错及验证码失效问题。解决方案包括关闭模板渲染并显式调用json()输出、使用input()或getContent()读取JSON请求、安装think-cors扩展配置跨域、验证码改用Redis存储唯一标识替代Session。
前后端分离开发中,ThinkPHP 和 Vue 这对组合虽然常见,但坑也不少。路由不返回 JSON、POST 收不到参数、跨域报错、验证码失效……这些问题如果只靠“百度一下”去修,往往修一个漏三个。这里把几个最典型的场景和根治方法整理出来,希望能帮你少走弯路。

ThinkPHP 路由不返回 JSON?关掉模板渲染和默认控制器
很多人在前后端分离刚上手时,会发现 ThinkPHP 默认走了 index/index 控制器,然后试图渲染 HTML 模板——结果不是 500 就是乱码。问题的根源在于框架的模板渲染是默认开启的,而 API 模式下根本不需要视图层。
正确做法是直接在路由配置里放弃那种“兜底”写法(比如 Route::rule('/*', 'index/index')),而是按资源定义 RESTful 路由:
Route::group('api', function () {
Route::post('login', 'Auth/login');
Route::get('user', 'User/info')->middleware('auth');
})->cross(false);
每个控制器方法末尾必须显式调用 json(),别指望自动输出:
public function login(){
$data = $this->request->param(); // 注意:仅对表单有效
return json(['token' => 'xxx', 'code' => 200]);
}
- 控制器继承
think\Controller即可,不需要额外 base 类。 - 千万别在控制器里出现
$this->fetch()或任何视图相关方法。 - 如果用了中间件做统一响应封装,务必检查它会不会拦截或覆盖
json()的返回值。
Vue 发 POST 请求,ThinkPHP 收不到参数?分清 param() 和 input()
这个问题几乎每周都有人问。核心原因:$this->request->param() 只解析 application/x-www-form-urlencoded 和 multipart/form-data 格式的请求,而 Axios 默认发的是 application/json——这时候 param() 拿到的当然是空数组。
两种解法,选一个就行:
- 前端改表单格式:在 Axios 请求里加上
transformRequest+qs.stringify(),再手动设置Content-Type: application/x-www-form-urlencoded。 - 后端用原始读取:
input('username')或json_decode($this->request->getContent(), true)。
通常更推荐后者——更符合 API 的设计直觉,也省得前端每次都要处理序列化:
public function login(){
$raw = $this->request->getContent();
$data = json_decode($raw, true) ?: [];
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
// 后续逻辑...
}
顺带提醒一句:$this->request->input() 在 TP6/8 里已经废弃了,别再用它。
跨域请求被拦、OPTIONS 404?用 think-cors 扩展,别手写 header
手动在中间件里加一句 $response->header('Access-Control-Allow-Origin', '*'),看起来简单,但一旦上线,OPTIONS 预检、credentials 冲突、expose-headers 这些细节没处理好,分分钟崩给你看。
正确做法是用官方扩展,统一处理:
composer require topthink/think-cors
然后在 config/cors.php 里配死域名(生产环境绝对不能写 *):
return [
'origin' => ['https://your-vue-app.com'],
'allow_credentials'=> true,
'allow_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
'allow_headers' => ['Content-Type', 'Authorization', 'X-Requested-With'],
];
- 如果前端用 JWT,可以把
allow_credentials设为false,改用Authorization: Bearer xxx传 token。 - Vue 的 Axios 请求需要加
withCredentials: true(仅限于后端允许 credentials 时)。 - TP 默认没给 OPTIONS 路由注册,但 think-cors 会自动拦截并返回 204,不需要再补一条空路由。
验证码总校验失败?Session 在跨域下根本不可用
这是另一个高频踩坑点。前端请求 /captcha 拿到图片,后端往 Session 写了 captcha.key——然后提交验证码时再从 Session 读。但在跨域环境下,浏览器根本不会自动携带 Cookie,Session ID 就断了,$this->session->get() 永远返回 null。
必须放弃 Session 绑定,改用可显式传递的唯一 ID:
- 生成验证码的接口返回
{captcha_id: "abc123", image: "data:image/png;base64,..."} - 前端把
captcha_id存进表单的 hidden 字段或请求头里 - 校验接口接收
captcha_id和用户输入的code,然后在 Redis 里查 keycaptcha:abc123 - 查完之后立刻
DEL captcha:abc123,TTL 设 120 秒就够用
别图省事用文件或数据库存验证码——Redis 原子性好、过期自动清理、性能无压力。ThinkPHP 自带的 cache('redis') 可以直接拿来用。
