前后端分离的核心思路其实很朴素:后端不再负责渲染页面,只对外提供数据接口;前端用Vue这样的框架独立开发、独立部署,通过HTTP请求获取数据后自行渲染界面。Yii框架虽然是从传统MVC模式起家的,但它的RESTful支持做得相当到位,官方自带的yii\rest\ActiveController让增删改查接口几乎不需要写太多代码。下面就把Yii做后端API、Vue做前端的整套方案拆开讲清楚,从接口搭建、跨域、鉴权到最后的联调部署,一步步落实。

一、Yii后端API层的搭建
首先建议在项目中单独划分一个API模块,比如路径为modules/api,把所有接口相关的控制器集中管理,避免和传统的后台管理代码混在一起。在配置文件中注册模块之后,所有接口请求统一走/api/xxx这样的URL前缀,结构清晰也方便后续做版本控制(比如再加一个v1目录)。
Yii的RESTful能力核心在ActiveController,它默认提供了index、view、create、update、delete等动作,配合UrlManager的RESTful路由规则,能自动把HTTP动词映射到对应操作。写一个商品列表接口大概是这样:
namespace app\modules\api\controllers;
use yii\rest\ActiveController;
class GoodsController extends ActiveController
{
public $modelClass = 'app\models\Goods';
// 只暴露需要的动作,其余禁用
public function actions()
{
$actions = parent::actions();
unset($actions['delete'], $actions['create']);
return $actions;
}
// 自定义一个额外接口
public function actionSearch($keyword)
{
return \app\models\Goods::find()
->where(['like', 'title', $keyword])
->all();
}
}
对应的URL规则配置也要调整,关闭enableStrictParsing或使用官方推荐的RESTful规则模板。需要注意的一点是,ActiveController返回的数据会经过yii\rest\Serializer序列化,列表数据自动包裹在items字段里并附带分页信息,这个行为要提前了解,否则前端拿到数据时会疑惑为什么结构多了一层。
二、跨域处理与统一响应格式
开发阶段Vue跑在localhost:8080,Yii跑在localhost:80,端口不同就是跨域。解决方式有两种:一是Vue开发服务器配置代理,把/api开头的请求转发到Yii;二是在Yii端配置CORS。生产环境用Nginx反向代理后同源,跨域问题自然消失。这里推荐开发期用代理、生产期用Nginx统一入口的组合,Yii端只保留一份CORS配置兜底即可。
Yii自带Cors过滤器,在控制器里通过behaviors()挂载。同时建议自定义一个响应格式,所有接口统一返回code、message、data三个字段,前端封装请求拦截器时处理起来会轻松很多:
public function behaviors()
{
$behaviors = parent::behaviors();
// 解除认证的跨域预检请求
$behaviors['authenticator'] = [
'class' => \yii\filters\auth\CompositeAuth::class,
'authMethods' => [
['class' => \sizeg\jwt\JwtHttpBearerAuth::class],
],
'except' => ['options'],
];
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::class,
'cors' => [
'Origin' => ['http://localhost:8080'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
],
];
return $behaviors;
}
统一响应可以通过重写beforeAction或在控制器基类里封装一个success()和fail()方法实现,把业务码和提示语集中管理。异常处理方面,配置response组件的on beforeSend事件,把Yii抛出的异常统一转换成约定好的JSON结构,避免前端时不时收到HTML格式的错误页。
三、JWT鉴权的实现思路
前后端分离后不能再依赖Session维持登录态,常规做法是JWT。Yii生态里用得较多的是sizeg\jwt这个扩展,通过Composer安装后,登录接口校验账号密码,成功则签发Token返回给前端,前端存入localStorage并在后续请求的Header中带上Authorization: Bearer xxx。
public function actionLogin()
{
$params = Yii::$app->request->post();
$user = \app\models\User::findByUsername($params['username']);
if ($user && $user->validatePassword($params['password'])) {
$token = Yii::$app->jwt->getBuilder()
->issuedBy('https://api.ipipp.com')
->withClaim('uid', $user->id)
->expiresAt(time() + 3600 * 24)
->getToken(Yii::$app->jwt->getSigner(), Yii::$app->jwt->getKey());
return ['code' => 0, 'message' => 'ok', 'data' => ['token' => $token->toString()]];
}
return ['code' => 1001, 'message' => '用户名或密码错误'];
}
Token过期后可以考虑refresh token机制:签发一个短期访问令牌加一个长期刷新令牌,访问令牌过期时用刷新令牌换取新的,这样既保证安全性又不会让用户频繁重新登录。Yii端的其他接口只需要在behaviors()中启用Bearer认证,控制器内部通过Yii::$app->user->id就能拿到当前用户身份,写法和传统模式几乎没有差别,迁移成本很低。
四、Vue端封装与联调部署
Vue这边用axios做请求库,统一封装一个实例,请求拦截器里自动附带Token,响应拦截器里统一处理错误码:Token失效就清除本地存储并跳转登录页,业务错误就弹出提示。这种集中式封装能避免每个页面都写重复的判断逻辑。
import axios from 'axios'
import router from '@/router'
const http = axios.create({ baseURL: '/api', timeout: 10000 })
http.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = 'Bearer ' + token
return config
})
http.interceptors.response.use(
res => res.data.code === 0 ? res.data.data : Promise.reject(res.data),
err => {
if (err.response && err.response.status === 401) {
localStorage.removeItem('token')
router.push('/login')
}
return Promise.reject(err)
}
)
export default http
开发阶段的代理配置写在vite.config.js或vue.config.js里,把/api代理到Yii所在端口。部署时Vue执行npm run build产出静态文件,Nginx配置中,前端资源由root直接指向静态目录,/api路径则通过proxy_pass转发给PHP-FPM处理的Yii应用,两边同域名,彻底告别跨域烦恼。
location / {
root /var/www/vue-dist;
try_files $uri $uri/ /index.html;
}
location /api {
try_files $uri $uri/ /index.php?$args;
}
整体来看,Yii做后端API的改造成本并不高:模块化拆分接口、RESTful控制器快速出活、JWT接管鉴权、Nginx统一入口解决跨域,每一环都有现成的组件可用。把响应格式和请求封装的约定定好之后,前后端各自迭代互不干扰,这正是分离架构带来的最大收益。
Yii前后端分离Yii RESTful APIYii Vue开发修改时间:2026-09-10 16:04:38