Vue+Koa2 先後端分離項目線上部署

昨天嘗試部署一個 Vue+Koa2 的先後端分離項目,沒想到由於前端項目部署的問題,卡了一成天,今天才終於找到了問題所在,成功解決。這篇文章主要談談:css

  • 線上部署項目的相關事宜
  • 如何用 Nginx 實現同端口多項目部署

1. 項目結構說明

服務器上的項目結構大概是這樣的:html

在 /home 路徑下有兩個以下的項目文件夾:

Vue-mall
MiniProgram-Admin
    |--client
        |--css
  |--js
  |--images
        |--index.html
    |--server

其中,Vue-mall 是以前部署在根目錄下的項目,也就是輸入域名後默認訪問的項目,這個不用動它;而 MiniProgram-Admin 就是本次須要部署的項目,包括一個 client 前端項目文件夾和一個 server 後端項目文件夾,但願達到的效果是,輸入域名 + /admin/ 後,能夠訪問這個項目。前端

2. 修改配置文件

以前的項目是直接部署在根目錄下的,也即 Nginx 配置文件的  location / 下,因此不須要改動前端項目的配置文件,直接打包上傳便可;但此次不是部署在根目錄下,因此要修改兩個地方:打包路徑和路由配置vue

2.1 修改打包路徑

默認狀況下,Vue CLI 會假設你的應用是被部署在一個域名的根路徑上,例如 https://www.my-app.com/。若是應用被部署在一個子路徑上,你就須要用這個選項指定這個子路徑。例如,若是你的應用被部署在 https://www.my-app.com/my-app/,則設置 publicPath/my-app/node

這個值也能夠被設置爲空字符串 ('') 或是相對路徑 ('./'),這樣全部的資源都會被連接爲相對路徑,這樣打出來的包能夠被部署在任意路徑.nginx

vue.config.js 文件下的 publicPath 項,在 1. 開發環境 或者 2.生產環境但部署在根目錄的狀況下,直接使用默認的 / 便可,不須要特地去配置;但在生產環境且不是部署在根目錄的狀況下,則須要額外進行配置。web

具體的路徑配置是任意的,根據你本身的須要選擇 —— 由於咱們但願輸入域名 + /admin/ 後,就能夠訪問這個項目,因此配置了 /admin/ 路徑。這裏要注意,雖然路徑能夠任選,可是必須和後面 Nginx 的路徑配置一致,不然會報錯chrome

publicPath: process.env.NODE_ENV === 'production' ? '/admin/' : '/',

2.2 修改路由配置

一樣,須要配置一下路由:npm

const createRouter = () => new Router({
  routes
  mode: 'history'
  base: process.env.NODE_ENV === 'production' ? '/admin' : '/',
})

這裏必需要使用 history 模式,同時和上面同樣區分好項目環境。後端

2.3  修改請求地址

以前都是本地開發,沒有區分開發環境和生產環境下的請求地址,因此這裏還得修改一下。

在項目的 src 文件夾下新建 config.js 文件,內容以下:

const host = process.env.NODE_ENV === 'production' ?
            'http://mydomain.com:3000':'http://localhost:3000'

const config = {
  host
}

export default config

以後在請求相關的 api.js 裏再把 config 導入使用:

import config from '@/config'
const BASEURL = config.host

export function fetchData(){
  return request({
    method:'get',
    url:`${BASEURL}/..../....`
  })
}

這樣就能夠根據開發環境和生產環境向不一樣的地址發送請求了。

3. 前端項目部署

3.1 文件結構一覽

以後就能夠 npm run build 打包了,打包後生成的靜態資源結構是這樣的:

dist
  |--css
  |--js
  |--images
  |--index.html

(固然,可能你的靜態資源會出如今 static 文件夾裏,這要看你是否配置了 assetsDir: 'static'

打開 index.html 文件看一下,大概是這樣的:

<!DOCTYPE html>
<html>
<head>
  <meta charset=utf-8>
  <meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1">
  <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
  <link rel=icon href=/admin/favicon.ico>
  <title>Vue Admin</title>
  <link href=/admin/css/app.6b18b5b0.css rel=preload as=style>
  <link href=/admin/css/chunk-elementUI.43fc3011.css rel=preload as=style>
  <link href=/admin/css/chunk-libs.5cf311f0.css rel=preload as=style>
  <link href=/admin/js/app.8d30b2c2.js rel=preload as=script>
  <link href=/admin/js/chunk-elementUI.cb459a4a.js rel=preload as=script>
  <link href=/admin/js/chunk-libs.b0da50a4.js rel=preload as=script>
  <link href=/admin/css/chunk-elementUI.43fc3011.css rel=stylesheet>
  <link href=/admin/css/chunk-libs.5cf311f0.css rel=stylesheet>
  <link href=/admin/css/app.6b18b5b0.css rel=stylesheet>
</head>
<body>
 <noscript>
  <strong>
   We're sorry but Vue Admin Template doesn't work properly without JavaScript enabled. Please enable it to continue.
  </strong>
 </noscript>
  <div id=app></div>
  <script>............</script>
  <script src=/admin/js/chunk-elementUI.cb459a4a.js></script>
  <script src=/admin/js/chunk-libs.b0da50a4.js></script>
  <script src=/admin/js/app.8d30b2c2.js></script>
</body>
</html>

注意看這裏的資源引用路徑,都是以 /admin/ 開頭的,後面跟上的是靜態資源文件夾的名字。

可能你會在本地開個服務器看看效果,可是呢,這時候的頁面必定會是空白的,畢竟資源引用路徑不對嘛,本地並無 admin 文件夾。因此不用管本地預覽效果了,直接上傳到服務器便可。

3.2 上傳文件並修改 Nginx 配置

我這裏使用 MobaXterm (順便安利一下,這軟件挺全能的,惟一缺點就是有點卡)將文件上傳到服務器,最後的結構就像文章開頭那樣:

在 /home 路徑下有兩個以下的項目文件夾:

Vue-mall
MiniProgram-Admin
    |--client
        |--css
   |--js
   |--images
        |--index.html

接着就是最關鍵的地方了,配置 Nginx 的文件。你能夠直接在 nginx.conf 文件配置,也能夠引入單獨的 .conf 文件。但不管如何,必定要弄清本身引入了哪些 .conf 文件,防止發生配置被覆蓋的問題。

爲了方便項目管理,我這裏引入了額外的 MyProject.conf 文件,配置好以後的內容以下:

# MyProject.conf
server {
  listen 80;
  server_name localhost;
  
  location / {
    root  /home/vue-mall/;
    index index.html index.htm;
    try_files $uri $uri/ @fallback;
  }
  location /admin/ {
    alias /home/MiniProgram-Admin/client/;
    try_files $uri $uri/ @fallback;
  }
  location @fallback {
    root  /home/vue-mall/;
    index index.html index.htm;
  }

下面詳細介紹各個配置:

1. server

全部配置都是寫在 server 模塊下的,而且經過配置 listen 80 監聽 80 端口,server_name 主要用於配置基於名稱的虛擬主機,就本項目而言,能夠不寫

2. location

server 模塊下有多個 location 塊,一個 location 對應一個項目。能夠看到,第一個塊的路徑是 /,這決定了咱們輸入域名後將訪問該 location 塊下的項目;同理,第二個塊的路徑是 admin,因此輸入域名 + /admin/ 以後,訪問的是如今部署的這個項目,注意,正如前面所說的,這裏的 location 路徑務必和以前前端項目配置的路徑保持一致;第三個塊是作重定向用的,稍後再解釋

3. location 下的各個配置:

  • rootalias:這兩個指令後面都跟着路徑,路徑指示了 對應項目的入口文件(一般是 index.html)的絕對路徑,它們的區別是:
    • 由於要在同端口部署多項目,因此給根目錄的項目使用的是 root  指令,而給非根目錄的項目使用的則是 alias  指令。
    • 直接輸入域名訪問的時候,會根據 root 路徑 + location 路徑來尋找入口文件;輸入域名 + /admin/ 訪問的時候,會用整個 alias 路徑去替換 location 路徑,從而尋找入口文件
  • index:說是尋找入口文件,不過怎麼指定入口文件是哪個呢?一般 Vue-Cli 打包後生成的入口文件都是 index.html,因此這裏對應的配置就是 index index.html index.htm;。記住,不須要攜帶路徑,由於它僅僅是指明入口文件的名稱,並根據該名稱去尋找
  • try_files:前面的 rootaliasindex 都只是指明瞭路徑,那麼具體是用哪一個指令來完成「尋找文件」這個動做的呢?就是 try_files,顧名思義就是「嘗試讀取文件」,它做用的過程是這樣的:
    • 就像咱們看到的,這個指令接受三個參數,其中 $uri 是用戶訪問的路徑。假如用戶輸入域名 + /admin/ 進行訪問,那麼 $uri 就會等於 /admin/,就會去 /admin/ 下尋找資源,而 /admin 又是被 alias 路徑替換的,所以就會去 alias 路徑下尋找資源,最後就找到了這個路徑下面的 index.html 入口文件。
    • 若是按照這樣找,找不到怎麼辦呢?那麼就會用第二個選項 $uri/ 嘗試再次尋找,而若是仍是找不到呢,就只能使用備選的 @fallback 啦,它表示重定向到這個 fallback 指向的頁面,而 fallback 具體指向哪一個頁面,咱們能夠在下面經過 location 塊進行配置。

確保配置正確且成功引入以後,就能夠重啓 Nginx 了:

nginx -t   // 測試配置是否經過
nginx -s reload

不出意外的話,經過域名 + /admin/ 就能訪問咱們的項目了。固然,有可能打開以後遇到頁面空白的狀況,這種狀況基本就是配置錯誤了,須要回過頭再仔細檢查一遍各類路徑的配置。

這樣,前端項目就部署好了,接下來部署後端項目

4. 後端項目部署

4.1 修改文件

後端項目的部署就比較簡單了,基本不須要額外的配置。這裏主要是解決跨域問題,其實咱們用 Nginx 的話直接經過反向代理就能夠解決跨域,但以前本地開發的時候,我是經過 koa2-cors 解決跨域的,所以仍是繼續用這個方案吧,安裝模塊後,在app.js  使用,大體配置以下:

const cors = require('koa2-cors')

app.use(cors({
  originfunction(ctx
    const whiteList = ['http://mydomain.com']   //可跨域白名單
    let currentUrl = ctx.request.origin     
    let changeUrl = currentUrl.substring(0,currentUrl.length - 5)   
    if(whiteList.includes(changeUrl)){
        return changeUrl    
    }
    return 'http://localhost:9528' // 開發環境用的,默認容許本地端口跨域
  },
  credentialstrue
}))

須要設置的是 origincredentialsorigin  能夠是函數或者字符串,指示可信任的域名。這裏的話我準備了一個白名單,前端發送請求的時候會判斷域名是否在白名單裏,不在的話就拒絕這次請求。最後,默認返回的是本地開發用的端口。

須要改動的就是這裏,以後直接把後端項目文件夾上傳到服務器便可(node_modules 就不要拖過去了,直接在服務器那邊安裝好),所以最後的結構是這樣的:

在 /home 路徑下有兩個以下的項目文件夾:

Vue-mall
MiniProgram-Admin
    |--client
        |--css
   |--js
   |--images
        |--index.html
    |--server
   |--app.js
   |-- .........

同時,還須要再次修改 Nginx 的配置文件,在開頭添加:

upstream koa.server {
  server localhost:3000;
}

和本地同樣,服務器監聽的是 3000 端口。

4.2 安裝依賴

接下來安裝後端項目的依賴。但個人服務器並無 node 環境,因此還要安裝一下 node:

apt install nodejs

同時安裝 npm:

apt install npm

最後安裝項目依賴:

cd /home/MiniProgram-Admin/server
npm install

安裝速度可能很慢,換個源就行了。最後 npm run server (也多是 npm run dev,看本身的配置)開啓後端服務,訪問域名 + 3000 端口,顯示以下的頁面就說明成功了:

你的頁面內容可能不是這個,具體看你給 ctx.body 返回什麼。

4.3 Node常駐後臺運行

最後還有一個問題,如今是經過 npm run server 開啓後端服務的,一旦關閉終端或者斷開 ssh 鏈接,後端服務就中止了。怎麼才能讓它常駐後臺運行呢?

能夠安裝 forever 或者 pm2 解決這個問題,這裏我用的是 pm2。

先安裝 pm2:

npm install pm2 -g

安裝完 pm2 -v 查看一下,確認安裝正確,接着啓動 node 服務:

cd /home/MiniProgram-Admin/server
pm2 start npm --name byNpm -- run server

這裏經過 --name 參數能夠本身指定一個項目名字,後面的 run server 對應此前給後端項目配置的 npm 啓動指令。

固然,也能夠經過直接指定文件的方式啓動 node 服務:

cd /home/MiniProgram-Admin/server
pm2 start --name byFile app.js

能夠看到這裏有兩個開啓同一個 node 服務的項目。下面是一些平常會用到的 pm2 指令:

監聽文件改動,重啓服務器:

pm2 start app.js --watch

終止某個項目:

pm2 stop 項目名

完全刪除某個項目:

pm2 delete 項目名

查看項目列表:

pm2 list

重啓項目:

pm2 restart 項目名

固然,這個過程還可能會遇到端口衝突的問題,解決方法參考下面。

5. 錯誤說明

5.1 端口占用

在開啓後端服務的時候,可能會遇到下面的報錯:

這是由於 3000 端口被佔用了,因此咱們先找出佔用端口的進程:

netstat -tunlp|grep 3000

能夠看到,這裏佔用端口的是以前沒有正確關閉的 node 進程,找到進程號 17821,直接 kill 便可:

kill -9 17821

5.2 沒法訪問端口

若是在訪問 3000 端口的時候,頁面顯示沒法鏈接,多是由於服務器的安全組沒有開放 3000 端口,去控制檯看看,配置一下入口規則便可。

5.3  Uuexpected token < / MIME 類型錯誤

這兩個報錯都是由於數據返回格式不對。前面咱們在 Nginx 的文件裏配置過 try_files —— 若是找不到入口文件,就會使用 fallback,返回一個默認的 index.html(或者是 404.html),可是由於向服務端請求的是 css 和 js 文件,而且對於返回的資源也是按照  css 或者 js 去解析的,因此在遇到 html 文件的 <  時就會出現解析出錯的問題。

5.4 排查方法

要學會多經過 network 和日誌去進行排錯。能夠配置 Nginx 的文件,開啓訪問日誌和錯誤日誌,看看能不能從日誌中找出什麼問題。

nginx.conf 中配置:

# nginx.conf
http {
    include  mime.types;
    default_type  application/octet-stream;
    log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # 配置訪問日誌
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    .......
    .....
}

MyProject.conf 中配置:

# MyProject.conf
upstream koa.server {
  server localhost:3000;
}
server {
  listen 80;
  server_name localhost;
  
  location / {
    .......
  }
  location /admin/ {
    .......
      
  }
  location @fallback {
    .....
  }
  access_log /ect/nginx/logs/access_.log main;   # 開啓訪問日誌  
  error_log  /etc/nginx/logs/error_.log  error;  # 開啓錯誤日誌

好比,在訪問 location /MiniProgram-Admin/ 的時候出現了問題,那麼能夠看一下錯誤日誌:

注意這句話:rewrite or internal redirection cycle while internally redirecting to "/MiniProgram-Admin/client/index.html"

它所說的 internally redirecting 就是以前用 try_files  指令配置的重定向,而 cycle 指的是陷入了循環。那麼爲何會陷入循環呢?以前不是已經配置好了,若是找不到入口文件,就將 /MiniProgram-Admin/client/index.html 做爲入口文件嗎?惟一的解釋就是這個路徑自己就是錯的,由於找不到這個路徑下的 html 文件,因此又再次發生了重定向,最後陷入了循環。通過檢查,確實是路徑的問題,這裏應該用絕對路徑,前面少了一個 /home

固然,能夠直接給個 404 的 fallback:

try_files $uri $uri/ =404;

6. 最後

以上就是本文的所有內容了。總的來講仍是踩了很多坑的,並且也很差排查。尤爲是靜態資源引用錯誤的問題卡了一成天,後面才發現是 Nginx 的路徑配置有問題。所幸最後算是都順利解決了,更重要的是在這個過程當中學到了不少新的東西,感受仍是挺不錯的。

參考:

https://cli.vuejs.org/zh/config/#publicpath https://juejin.im/post/5ee59b0af265da76c4245e39 https://www.nginx.cn/doc/standard/httpcore.html


留言板

本文分享自微信公衆號 - 漫遊前端世界(gh_6ac344b74a01)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索