Vue+Koa2 项目线上部署

前言

这篇文章可以说算是对最近使用 Vue + Koa2 来开发一个博客并且部署到服务器(Centos 7)一个总结。我走过的路,你终将会来,所以这篇文章你真的不容错过。即使你也可以像我这样遇到问题从网上找,但有现成的为什么还要去找?我真的不想自己浪费了时间去解决的一些问题,然后别人又用同样多甚至更多的时间去重复这些无聊的工作。为此我想写下这篇文章,把坑都填好,把路都铺平,让你直接秒变老司机。

这篇文章不是让你从入门到精通,它只是把你从入门到放弃中救回来,让你找回遗失已久的自信。这篇文章不讲 Vue2 如何,如何、也不说 Koa2 咋地,咋地。这篇文章关注的点是:项目开发好了,要部署到服务器上的这个过程。说长不长,说短不短,因为其中你会遇到不少的坑。可能也为此失眠过,但都不要紧,因为你即将迎来人生的曙光。

项目线上部署

这里我们从 Vue2 打包开始。

前后端代码打包上传

我用的是 vue-cli 来生成 Vue2 项目。所以在打包时,我只需要运行如下命令行就可以了。

npm run build

打包完成后,默认是放在 dist 目录下。完了之后你可以会觉文件过大,特别是一些 map文件。此时你可以修改 config/index.js 文件里的 productionSourceMap 默认值,把它设置成 false。网上拷来的一小段话希望对你理解 productionSourceMap 的作用有点帮助:

map文件的作用在于:项目打包后,代码都是经过压缩加密的,如果运行时报错,输出的错误信息无法准确得知是哪里的代码报错。有了map就可以像未加密的代码一样,准确的输出是哪一行哪一列有错。

重新打包后你就会发现打出来的包小了不是一般地多。

前端项目打包完了,后端也没什么好说的。后端服务的目录名为 server 有关后端的所有东西,都放在了这个目录下。对于后端 Koa2 我并没有使用什么脚手架,我觉得没那个必要,自己建几个目录就完事了。

把 dist 目录和 server 目录压缩成一个压缩包,通过 FTP 上传到服务器。至于 FTP 怎么配置就得靠你自己了。

这里有一点需要注意的,我们不需要把开发时通过 npm 安装的模块目录(node_modules,这个前端目录有一个,后端目录也有一个,这两个都不需要上传,前端的我们就不用管了,因为我们直接把前端代码打包到 dist 目录了,这里说的是后端的 node_modules 目录,在压缩前先把这个目录删掉)也上传到服务器上。因为这很大,可上传很久。我们把代码上传到服务器后,解压。我这里只有两个目录一个是 dist 目录 ,另一个是 server 目录。把这两个目录放到事先配置好的一个站点目录下。

安装服务端依赖

这些依赖就是我们上传的时候删掉的 server/node_modules 目录,由于我们在本地开发时已经在 server 目录下安装过所有项目需要的依赖,在 server 目录下你会看到一个 package.json 文件,这个文件就是记录了我们之前已经安装过的所有依赖。

此时我们只需要切换到 server 目录,然后执行如下命令行:

npm install

这样 npm 就会根据 package.json 文件中的依赖一个一个的下载安装了。这会比通过 FTP 上传快很多,反正我是这么觉得的。

在安装依赖时遇到的一个问题就是无法安装  bcrypt。

提示:node-pre-gyp install --fallback-to-build 等字样。

这个也耗了我不少时间,最终还是解决了。

把 npm 下载地址改成淘宝的,并重新安装就好

npm set registry https://registry.npm.taobao.org
npm install

然后再进行安装就可以了。估计由于网络问题有些文件无法下载,使用了淘宝的镜像就可以下载了。

当所有的依赖都安装好后,切换到 server 目录下执行启动服务命令:

node index.js

这时很有可能会报错。因为(假设你已经安装好了 mogodb ),我们本地建的数据库服务器上没有,所以会报错。

所以我们得先去创建一个数据库(比如:yunkus,这个要跟后端代码中连接的数据一致)。

切换到 mongodb 的安装目录,进入 bin 目录。

执行如下命令行:

./mongo

但很有可能你又会遇到另一个问题,提示你:

MongoDB shell version v4.0.0
connecting to: mongodb://127.0.0.1:27017
2018-07-21T19:49:26.118+0800 E QUERY    [js] Error: couldn't connect to server 127.0.0 .1:27017, connection attempt failed: SocketException: Error connecting to 127.0.0.1:27 017 :: caused by :: Connection refused :
connect@src/mongo/shell/mongo.js:251:13
@(connect):1:6
exception: connect failed

连接失败。天呀这又是什么回事呢?

原来我修改了mongodb 的默认端口号。所以我们就不能这这么简单粗暴地启动 mongo 服务,你们还需要告诉它端口号是多少,比如你把端口号改成了20201,你就需要像下面这样启动 mongo 服务:

./mongo localhost:20201

这样你就查连接数据库了,接下来就是新建一个数据库,命令如下:

use yunkus

就这么简单,一个数据库就新建好了。

现在你就可以再回到 server 目录下尝试再次启动网站后台服务:

node index.js

幸运的话,你就可以启动成功了。

简单的代理配置

为什么说简单,因为我用我认为的最简单的配置来完成我们所需要的功能(访问某域名就可以访问到我们的网站)。那怎么访问页面呢?

如果你是用 nginx 并且已经配置了几个网站,那么现在就可以再新增一个 xxx.conf 文件,内容如下:

server{
     listen 80;
     server_name yunkus.com;
     root /opt/web/vip.yunkus.com;
     index index.php index.html;

     location / {
         proxy_pass http://localhost:3001;
     }

     location = /favicon.ico {
         log_not_found off;
         access_log off;
     }

     location = /robots.txt {
         allow all;
         log_not_found off;
         access_log off;
     }
}

其实配置中的最后两块也可以不要。但是为了让你看起来有点印象,我还是加上了。

这里我们使用反向代理,把用户访问 vip.yunkus.com 这个域名时代理到服务器本地的 http://localhost:3001 端口,我们的服务端启动时使用的就是 3001 端口。这就好像我们在自己本地启动服务后,通过 http://localhost:3001 访问的效果了。

访问接口报错

这个问题,一看控制台打印出来的错误就可以略知一二。在上线前,我们 Vue Router 中的 hash 模式改也了 history 模式,这就会带来一些,比如:打开网站首页是正常的,但当访问其它有请求的页面时,访问接口会报错:

Failed to load resource: the server responded with a status of 404 (Not Found)

看了下请求的 url :

http://vip.yunkus.com/postDetail/api/getPostDetail?id=5b3b8d1a01cbe50fbcd2837e

接口请求时竟然前端还带上了前端的路由,本应该是这样子的

http://vip.yunkus.com/api/getPostDetail?id=5b3b8d1a01cbe50fbcd2837e

这个要怎么解决呢?很简单,我们在创建 axios (vue 前端路由我使用了它)设置一下 baseURL,注意这个需要全局设置,即对所有请求起作用。

import axios from "axios";
const BaseService = axios.create({
    baseURL: "/",
});
export default BaseService;

其它页面要请求时,直接使用暴露出去的 BaseService 来发起就可以了。改成了 history 模式还不仅仅只是上面一个问题,其它问题,比如下面将要说到的。

意外 404,为那般

当我们刷新页面后,就会出现 404 无法访问,网上查了很多,99% 都是一样的处理方式,跳转到一个指定的页面,这样处理有什么意思?我看着看着页面,不小心刷新了下页面,你就给我跳到其它页面,它真的很没意思(特别是跳转首页的)。

要想解决这个问题,我们需要安装一个模块:

npm install 'koa2-history-api-fallback'

安装完之后,我们就需要在页面中引入并使用它(启动文件 index.js 部分代码):

const Koa = require('koa');
const static = require('koa-static');
const path = require('path');
const bodyParser = require('koa-bodyparser');
const Routers = require('./routers');
const historyApiFallback = require('koa2-history-api-fallback'); // 引入依赖
const app = new Koa();
app.use(historyApiFallback());
app.use(static(path.resolve('../dist')));
app.use(bodyParser());
app.use(Routers.routes());
app.use(Routers.allowedMethods());
app.listen(20201);

重启 Koa 服务就可以了。

服务掉线

只要关闭了终端(我用的是 putty ),Koa 服务就挂掉了,这肯定是不是行的。我们可以使用 pm2 来给我们做守护进程。即使我们关掉了终端,Koa 服务依然在线,这也是我们想要的。

切换到 server 目录,安装 pm2 模块

npm install pm2 -g

安装完后,pm2 开头的命令还不可以使用,很有可能会提示:

bash: pm2: command not found

此时你只需要执行如下命令行就可以了:

ln -s /home/software/node-v10.6.0-linux-x64/bin/pm2 /usr/local/bin/

/home/software/node-v10.6.0-linux-x64/ 为你安装 node 的路由,现在就可以使用 pm2 命令来启动 Koa 服务了,切换到 server 目录

pm2 start index.js

Koa2 服务启动成功,并“永不掉线”。

如果访问网站出现:502 Bad Gateway,很有可能是你的 Koa 服务挂了,此时你就需要重新启动 Koa 服务。

--------------------------------------------------------------------------------------------------------------------

上面的打包是针对 vue-cli 的,并且也没有做相应的打包优化,由于 vue-cli 3.0 已经出来,所以下面的代码配置是基本 vue-cli 3.0 的。 

--------------------------------------------------------------------------------------------------------------------

打包大小,及网站访问速度优化

虽然,按上面的步骤做完后,网站就可以正常访问了,但是访问速度实在有点让人难受,首次加载非常地慢,主要是要下载一个很大的文件,导致了页面空白时间过长。为此我们需要作一些优化。

1、打包文件过大

2、不生成对应的 map 文件

我们可以把一些不变的并且比较大的文件独立来了,不打包到项目文件中,而已以 CDN 的方式引入。这样就要可以解决文件过大的问题。并且通过外部引入的方式可以同时加载多个文件。map 文件对于我们上线的项目来说也没什么用,所以可能去掉,配置也很简单

在项目根目录下创建一个 vue.config.js(如果没有的话),添加如下内容:

module.exports = {
  devServer: { // 这项配置只是解决跨域的问题,可以不用管
    proxy: 'http://localhost:3000' 
  },
  productionSourceMap: false, // 不打包 sourceMap 文件
  configureWebpack: { // 不打包以下资源,从外部引入
    externals: {
      'vue': 'Vue',
      'vue-router': 'VueRouter',
      'vuex': 'Vuex',
      'highlight.js': 'hljs'
    }
  }
}

接下来,我们就在 public/index.html 中引入相关的外部资源:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <title>前端百科全书</title>
</head>
<body>
  <noscript>
    <strong>We're sorry but new-blog doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
  </noscript>
  <div id="app"></div>
  <!-- built files will be auto injected -->
  <script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script>
  <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
  <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
  <script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
  <script src="https://cdn.bootcss.com/highlight.js/9.13.1/highlight.min.js"></script>
</body>
</html>

这样打包后的文件大小就有会有很大的变化了,并且网站的访问速度飞快。

有些资源是要打包到文件中,还是外部引入,可以根据实际需要进行处理。比如 monent.js,highlight.js, 如果项目中使用了它,们打包出来的的文件会明显的增大了不少,特别是 highlight.js 而通过 CDN 引入你会发现这两个文件的大小并没有很大。但如果使用 CDN 引入 monent.js 的话,有时候这个资源下载得也很慢,所以也不好说。

配置 Gzip 压缩

打开服务器中的 nginx 配置文件 /etc/nginx/nginx.conf 添加如下内容:

#gzip模块设置
gzip on; #开启gzip压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 5; #压缩等级 1-9,数字越大压缩的越好,也越占用CPU时间
gzip_types text/plain application/javascript application/x-javascript text/css application/xml; #压缩类型,默认就已经包含text/html,所以就不用再写了
gzip_vary on; #是否在http header中添加Vary: Accept-Encoding,建议开启
#limit_zone crawler $binary_remote_addr 10m; #开启限制IP连接数的时候需要使用