Vue 前后端分离的正确方式
可能过时的信息
你正阅读的文章的发布日期距今已经有 1349 天了,其中的部分信息、个人观点或者措辞习惯等可能已经发生改变,因此仅供参考,请酌情阅读。
在之前我一直在想 Vue 的前后端分离怎么那么麻烦,让人头大。后来才发现原来我是小丑,实际上特别简单。本文只是用来记录方法,没有科普的意思,可能此刻的你已经知道了这一切。本文中的前后端分离指的是以 Vue 等框架作为前端技术栈,与后端形成分离,通过 XHR 进行交流的粗略概念,并没有深究的必要。
先前的方式
一般情况下,后端会位于一个单独的目录里面,里面就是各种各样的 .php
、.py
等等。要开始使用,就要想办法让前端能够 post 到他们。然而对于 Vue 来说,打开的 dev server 是仅限于 Vue 的,所有的请求都会给 Vue 自己;而对于后端来说,比如 PHP 的 php -S
、Python 的 python -m
或者 flask 的 python ./App.py
等,也都只是开的自己的 dev server,这就让开发过程很难访问到后端。每一个 dev server 会占用一个独立的端口,这就让它们彻底独立了。
然后,这种确实是从一开始就有解决方式的,那就是借助前端一个叫做代理(这里的代理不是前文的 Proxy)的东西。比如 Vue 里面就是在 vue.config.js
里
module.exports = {
// ...
proxy: "http://127.0.0.1:8081"
}
设置好了以后,按照官方文档的说法,会把一切无法识别的请求(也就是那些跟前端没有关系的请求)代理到这个地址,而这个地址就可以直接指向后端服务器的地址,比如 http://127.0.0.1:5000
等。不过这个地方曾经让我无语过很多次,因为实际上你写成 http://localhost:XXXX
、http://localhost:XXXX/
、http://127.0.0.1:XXXX
、http://127.0.0.1:XXXX/
的结果,有的时候是不一样的,还会导致一些类似于 ECONNREFUSED
或者无法代理等迷惑问题。PHP 也是,当执行 php -S
时,后面如果跟的是 .
(当前目录)也会引起一些莫名其妙的错误,对于 PHP 来说,localhost
和 127.0.0.1
的效果也并不完全相同。这实在是太坑了,可我也没有时间去了解原理,麻烦懂的大佬给我发个邮件讲讲!
OK,这是开发阶段,我们解决了,使用起来没有任何问题。然而遇到生产环境就怂了。那么接下来就是一种很 low 的解决方式——子域名。
很多时候我觉得没必要这么大费周章开个子域名出来挂给后端,然而我又这样做了,想想都后悔:因为这样首先会再费一张 SSL 证书,其次还会多一个配置文件,然后让整个主机配置都变得臃肿(虽然实际上没有这么严重但让我很不爽)。在解决了 CORS 以后就能正常使用了,而这跟直接去 post 另一个属于你自己的网站无异。
现在的方式
现在我终于知道 .htaccess
能够把我的请求从 Vue 手中解离出来。因为实际上,用子域名就是为了彻底逃脱当前网站里 Vue 对请求的劫持,因为只要开了一个新的站点,那么它就是个全新的根目录,里面的环境也都是全新的了。
那么该如何解离呢,请看
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !/api/
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
哦,原来可以直接在 .htaccess
里面指定一个 RewriteCond
来限制它的重写操作。而且实际上,Vue 对链接的劫持是由 Apache 或 Nginx 帮助完成的,我怎么这么久都没想到呢(啪)!那么在这个地方我们加上的一行 RewriteCond %{REQUEST_URI} !/api/
意思是不让对 /api
目录的请求进行重写。那么这个时候对我们来说唯一的要求就是要让所有的后端请求都发往这个地址。当然处理方法不止这一种,而这一种是最简洁的。其实也有想过把 *.php
取消重写,但是不知道怎么写,这里的正则都太迷惑了,动不动就是个 500。
接下来就只需要把所有的后端文件都装在这个文件夹里,然后在 build
的时候一起放到 dist
里面(因为 dist
是网站根目录,.htaccess
也在里面)就完了。而这也很简单,在 package.json
里面加上这样一句就可。
{
//...
"scripts": {
// ...
"build": "... && cp -r /path/to/api dist"
}
}