ThinkPHP6和Vue怎么做前后端分离开发?

文章导读
ThinkPHP6 和 Vue 配合做前后端分离,并不是简单把页面和接口拆开就够了。开发时两个服务分别启动,联调时要处理跨域;上线后要解决静态文件和 API 的转发;中间还要统一接口返回格式和鉴权方式。下面按实际布置时容易卡壳的几个点整理思路。
📋 目录
  1. 接口返回结构先定下来
  2. 跨域和开发代理
  3. JWT 鉴权
  4. 路由定义、参数校验和错误页
  5. 生产环境部署
A A

ThinkPHP6 和 Vue 配合做前后端分离,并不是简单把页面和接口拆开就够了。开发时两个服务分别启动,联调时要处理跨域;上线后要解决静态文件和 API 的转发;中间还要统一接口返回格式和鉴权方式。下面按实际布置时容易卡壳的几个点整理思路。

接口返回结构先定下来

前后端分离时,接口的返回结构必须固定下来。建议在 ThinkPHP6 中定义一个统一的 JSON 返回格式,至少包含 code、msg 和 data 三个字段。可以在控制器基类里写一个 success 和 error 方法,或者封装成 trait 方便复用。需要特别注意的是,异常处理也要走同一个结构,不要直接输出服务器错误页。否则前端拦截器拿到非标准数据,往往只能弹一个没头没尾的错误。判断标准就是:随便调用一个接口,响应体里能不能稳定拿到这三个键。如果缺少或字段名带下划线,前端就得为每个接口单独适配,这等于白做了分离。

这个约定要覆盖到所有出口。ThinkPHP6 的异常处理默认会输出 HTML 页面,前端拦截器拿到 3xx 或者 500 的 HTML 会直接懵。建议重写 app/ExceptionHandle.php,在 render 方法里判断请求期望的是 JSON 还是 HTML,把业务异常和参数校验异常都转成 code、msg、data。另外分页数据也要放进 data,不要单独返回一个 total 在顶层,否则前端没办法用统一类型接收。

跨域和开发代理

开发时前端跑在 8080,后端在 8000,必然碰到跨域。不要只在前端配代理就算完,后端还是得做跨域支持,因为生产环境可能直接没代理。在 ThinkPHP6 里,可以写一个全局中间件处理 CORS:检查请求的 Origin,设置 Access-Control-Allow-Origin,针对 OPTIONS 请求直接返回 204。注意预检请求不会带业务参数,所以一定要拦截,否则浏览器会报 CORS 错误。最简单的验证方法是用 curl 模拟带 Origin 的请求,看响应头有没有对应的 Allow 字段。风险点是 Allow-Origin 不能设成 *,否则带凭证的请求会失败,或者让任意站点都拿到你的接口。

ThinkPHP6和Vue怎么做前后端分离开发?

后端 CORS 中间件要注册到全局,这样控制器还没执行时,预检请求就能被拦截。写的时候注意 Allow-Methods 要包含 OPTIONS,Access-Control-Allow-Headers 要带上 Authorization,因为 JWT 放在请求头里。如果你自己不确定,可以在 postman 里模拟 OPTIONS 请求看响应头。

Vue 开发服务器的 proxy 可以少写一堆完整地址。这里需要结合环境确认:如果你用 vite,配置文件是 vite.config.js;如果是 vue-cli,则在 vue.config.js 里配 devServer.proxy。常见做法是给前端请求加 /api 前缀,代理到 http://localhost:8000。注意如果后端路由本身没有 /api,要用 pathRewrite 把前缀去掉,否则后端收到带 /api 的路径会 404。改完配置一定要重启 dev server,配置文件变更不会自动生效。

ThinkPHP6和Vue怎么做前后端分离开发?

JWT 鉴权

ThinkPHP6 做 API 鉴权,最常用的方案是 JWT。登录成功后,用 firebase/php-jwt 生成一个签名 token 返回给前端。前端每次请求都带上 Bearer token,后端写一个中间件在控制器执行前解析验证。关键是要判断 token 的过期时间,建议把过期时间设短一点,比如两小时,同时提供刷新接口。常见坑是只验证签名就信了,没检查过期;另一个是把密钥写死在代码里,导致重新部署后所有 token 失效。检查方法很简单:故意改一下 token 里的某个字符,看接口会不会返回 401,而不是 500。

用中间件做鉴权时,要排除不需要登录的接口,比如 login 和 refresh。建议把中间件绑定到路由分组上,不要全局挂载,否则静态路由都受影响。密钥不要放代码里,放到 .env,通过 env 函数读取。还有一种情况:token 过期后,前端如果只刷新页面不重新登录,通常需要配一个刷新接口,刷新时用旧 token 换新 token,这里要防止旧 token 在过期前被重复使用,可以记录一个 jti 并维护黑名单,但具体要不要做,得看你的用户并发和踢人需求。

路由定义、参数校验和错误页

接口路由要按资源语义来定义,ThinkPHP6 支持资源路由,比如 Route::resource('user', 'UserController'),会自动映射 index、save、read、update、delete。前端只对 /api/user 发 GET、POST,对 /api/user/1 发 PUT、DELETE,不要在 URL 里带 method 参数。参数校验用验证器,think\Validate 的规则定义在类里,控制器只做注入和返回错误信息。要注意的是,路由未匹配时 ThinkPHP 默认会抛出异常,如果你不处理,直接暴露 HTML 页面。可以在异常处理里判断是否 instanceof HttpException,统一输出 JSON。联调时最常见的报错就是加载首页正常,但 API 地址输错一个层级,返回的是 404 页面而不是 JSON,前端拦截器根本解析不了。

ThinkPHP6和Vue怎么做前后端分离开发?

生产环境部署

生产环境不要用 ThinkPHP 去托管 Vite 打包出来的 dist。PHP 处理静态文件的性能不如 Nginx,而且 URL 重写容易和接口路由冲突。更干净的方式是:前端执行 npm run build,将 dist 部署到 Nginx 的某个目录,Nginx 把 / 指向 dist 下的 index.html,把 /api 的请求 reverse proxy 到 ThinkPHP 服务,例如 http://127.0.0.1:8000。如果 Vue Router 用的是 history 模式,Nginx 必须配置 try_files $uri $uri/ /index.html;,否则用户直接访问 /user/list 刷新时,Nginx 找不到这个文件,会返回 404。代理后端时还要设置合适的超时时间,PHP 的 max_execution_time 和 Nginx 的 proxy_read_timeout 要配合,避免导出大文件时前端收到 504。

前后端分离的难点不在框架本身,而在接口约定和异常路径的处理。上面这些点确认以后,再开始写业务会顺手很多。