Nginx 從入門到實踐,萬字詳解!

最近愈來愈頻繁地遇到須要配置反向代理的場景,在本身搭建博客的時候,也不可避免要用到 Nginx,因此這段時間集中學習了一下 Nginx,同時作了一些筆記,但願也能夠幫助到你們~ 😜javascript

這篇文章會在 CentOS 環境下安裝和使用 Nginx,若是對 CentOS 基本操做還不太清楚的,能夠先看看 <半小時搞會 CentOS 入門必備基礎知識> 一文先作了解。css

相信做爲開發者,你們都知道 Nginx 的重要,廢話很少說,一塊兒來學習吧。html

CentOS 版本: 7.6前端

Nginx 版本: 1.16.1vue

1. Nginx 介紹

傳統的 Web 服務器,每一個客戶端鏈接做爲一個單獨的進程或線程處理,需在切換任務時將 CPU 切換到新的任務並建立一個新的運行時上下文,消耗額外的內存和 CPU 時間,當併發請求增長時,服務器響應變慢,從而對性能產生負面影響。java

Nginx

Nginx 是開源、高性能、高可靠的 Web 和反向代理服務器,並且支持熱部署,幾乎能夠作到 7 * 24 小時不間斷運行,即便運行幾個月也不須要從新啓動,還能在不間斷服務的狀況下對軟件版本進行熱更新。性能是 Nginx 最重要的考量,其佔用內存少、併發能力強、能支持高達 5w 個併發鏈接數,最重要的是,Nginx 是免費的並能夠商業化,配置使用也比較簡單。node

Nginx 的最重要的幾個使用場景:webpack

  1. 靜態資源服務,經過本地文件系統提供服務;
  2. 反向代理服務,延伸出包括緩存、負載均衡等;
  3. API 服務,OpenResty ;

對於前端來講 Node.js 不陌生了,Nginx 和 Node.js 的不少理念相似,HTTP 服務器、事件驅動、異步非阻塞等,且 Nginx 的大部分功能使用 Node.js 也能夠實現,但 Nginx 和 Node.js 並不衝突,都有本身擅長的領域。Nginx 擅長於底層服務器端資源的處理(靜態資源處理轉發、反向代理,負載均衡等),Node.js 更擅長上層具體業務邏輯的處理,二者能夠完美組合,共同助力前端開發。nginx

下面咱們着重學習一下 Nginx 的使用。git

2. 相關概念

2.1 簡單請求和非簡單請求

首先咱們來了解一下簡單請求和非簡單請求,若是同時知足下面兩個條件,就屬於簡單請求:

  1. 請求方法是 HEADGETPOST 三種之一;
  2. HTTP 頭信息不超過右邊着幾個字段:AcceptAccept-LanguageContent-LanguageLast-Event-ID Content-Type 只限於三個值 application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不一樣時知足這兩個條件的,都屬於非簡單請求。

瀏覽器處理簡單請求和非簡單請求的方式不同:

簡單請求

對於簡單請求,瀏覽器會在頭信息中增長 Origin 字段後直接發出,Origin 字段用來講明,本次請求來自的哪一個源(協議+域名+端口)。

若是服務器發現 Origin 指定的源不在許可範圍內,服務器會返回一個正常的 HTTP 迴應,瀏覽器取到迴應以後發現迴應的頭信息中沒有包含 Access-Control-Allow-Origin 字段,就拋出一個錯誤給 XHR 的 error 事件;

若是服務器發現 Origin 指定的域名在許可範圍內,服務器返回的響應會多出幾個 Access-Control- 開頭的頭信息字段。

非簡單請求

非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是 PUTDELETE,或 Content-Type 值爲 application/json。瀏覽器會在正式通訊以前,發送一次 HTTP 預檢 OPTIONS 請求,先詢問服務器,當前網頁所在的域名是否在服務器的許可名單之中,以及可使用哪些 HTTP 請求方法和頭信息字段。只有獲得確定答覆,瀏覽器纔會發出正式的 XHR 請求,不然報錯。

2.2 跨域

在瀏覽器上當前訪問的網站向另外一個網站發送請求獲取數據的過程就是跨域請求

跨域是瀏覽器的同源策略決定的,是一個重要的瀏覽器安全策略,用於限制一個 origin 的文檔或者它加載的腳本與另外一個源的資源進行交互,它可以幫助阻隔惡意文檔,減小可能被攻擊的媒介,可使用 CORS 配置解除這個限制。

關於跨域網上已經有不少解釋,這裏就不囉嗦,也能夠直接看 MDN 的 <瀏覽器的同源策略> 文檔進一步瞭解,這裏就列舉幾個同源和不一樣元的例子,相信程序員都能看得懂。

# 同源的例子
http://example.com/app1/index.html  # 只是路徑不一樣
http://example.com/app2/index.html

http://Example.com:80  # 只是大小寫差別
http://example.com

# 不一樣源的例子
http://example.com/app1   # 協議不一樣
https://example.com/app2

http://example.com        # host 不一樣
http://www.example.com
http://myapp.example.com

http://example.com        # 端口不一樣
http://example.com:8080
複製代碼

2.3 正向代理和反向代理

反向代理(Reverse Proxy)對應的是正向代理(Forward Proxy),他們的區別:

正向代理: 通常的訪問流程是客戶端直接向目標服務器發送請求並獲取內容,使用正向代理後,客戶端改成向代理服務器發送請求,並指定目標服務器(原始服務器),而後由代理服務器和原始服務器通訊,轉交請求並得到的內容,再返回給客戶端。正向代理隱藏了真實的客戶端,爲客戶端收發請求,使真實客戶端對服務器不可見;

舉個具體的例子 🌰,你的瀏覽器沒法直接訪問谷哥,這時候能夠經過一個代理服務器來幫助你訪問谷哥,那麼這個服務器就叫正向代理。

反向代理: 與通常訪問流程相比,使用反向代理後,直接收到請求的服務器是代理服務器,而後將請求轉發給內部網絡上真正進行處理的服務器,獲得的結果返回給客戶端。反向代理隱藏了真實的服務器,爲服務器收發請求,使真實服務器對客戶端不可見。通常在處理跨域請求的時候比較經常使用。如今基本上全部的大型網站都設置了反向代理。

舉個具體的例子 🌰,去飯店吃飯,能夠點川菜、粵菜、江浙菜,飯店也分別有三個菜系的廚師 👨‍🍳,可是你做爲顧客不用管哪一個廚師給你作的菜,只用點菜便可,小二將你菜單中的菜分配給不一樣的廚師來具體處理,那麼這個小二就是反向代理服務器。

簡單的說,通常給客戶端作代理的都是正向代理,給服務器作代理的就是反向代理。

正向代理和反向代理主要的原理區別能夠參見下圖:

正向代理與反向代理

2.4 負載均衡

通常狀況下,客戶端發送多個請求到服務器,服務器處理請求,其中一部分可能要操做一些資源好比數據庫、靜態資源等,服務器處理完畢後,再將結果返回給客戶端。

這種模式對於早期的系統來講,功能要求不復雜,且併發請求相對較少的狀況下還能勝任,成本也低。隨着信息數量不斷增加,訪問量和數據量飛速增加,以及系統業務複雜度持續增長,這種作法已沒法知足要求,併發量特別大時,服務器容易崩。

很明顯這是因爲服務器性能的瓶頸形成的問題,除了堆機器以外,最重要的作法就是負載均衡。

請求爆發式增加的狀況下,單個機器性能再強勁也沒法知足要求了,這個時候集羣的概念產生了,單個服務器解決不了的問題,可使用多個服務器,而後將請求分發到各個服務器上,將負載分發到不一樣的服務器,這就是負載均衡,核心是「分攤壓力」。Nginx 實現負載均衡,通常來講指的是將請求轉發給服務器集羣。

舉個具體的例子 🌰,晚高峯乘坐地鐵的時候,入站口常常會有地鐵工做人員大喇叭「請走 B 口,B 口人少車空....」,這個工做人員的做用就是負載均衡。

負載均衡

2.5 動靜分離

爲了加快網站的解析速度,能夠把動態頁面和靜態頁面由不一樣的服務器來解析,加快解析速度,下降原來單個服務器的壓力。

動靜分離

通常來講,都須要將動態資源和靜態資源分開,因爲 Nginx 的高併發和靜態資源緩存等特性,常常將靜態資源部署在 Nginx 上。若是請求的是靜態資源,直接到靜態資源目錄獲取資源,若是是動態資源的請求,則利用反向代理的原理,把請求轉發給對應後臺應用去處理,從而實現動靜分離。

使用先後端分離後,能夠很大程度提高靜態資源的訪問速度,即便動態服務不可用,靜態資源的訪問也不會受到影響。

3. Nginx 快速安裝

3.1 安裝

咱們能夠先看看

yum list | grep nginx
複製代碼

來看看

image-20200307180412726

而後

yum install nginx
複製代碼

來安裝 Nginx,而後咱們在命令行中 nginx -v 就能夠看到具體的 Nginx 版本信息,也就安裝完畢了。

image-20200307180545816

3.2 相關文件夾

而後咱們可使用 rpm -ql nginx 來查看 Nginx 被安裝到了什麼地方,有哪些相關目錄,其中位於 /etc 目錄下的主要是配置文件,還有一些文件見下圖:

Xnip2020-03-07_21-46-11

主要關注的文件夾有兩個:

  1. /etc/nginx/conf.d/ 文件夾,是咱們進行子配置的配置項存放處,/etc/nginx/nginx.conf 主配置文件會默認把這個文件夾中全部子配置項都引入;
  2. /usr/share/nginx/html/ 文件夾,一般靜態文件都放在這個文件夾,也能夠根據你本身的習慣放其餘地方;

3.3 跑起來康康

安裝以後開啓 Nginx,若是系統開啓了防火牆,那麼須要設置一下在防火牆中加入須要開放的端口,下面列舉幾個經常使用的防火牆操做(沒開啓的話不用管這個):

systemctl start firewalld  # 開啓防火牆
systemctl stop firewalld   # 關閉防火牆
systemctl status firewalld # 查看防火牆開啓狀態,顯示running則是正在運行
firewall-cmd --reload      # 重啓防火牆,永久打開端口須要reload一下

# 添加開啓端口,--permanent表示永久打開,不加是臨時打開重啓以後失效
firewall-cmd --permanent --zone=public --add-port=8888/tcp

# 查看防火牆,添加的端口也能夠看到
firewall-cmd --list-all
複製代碼

而後設置 Nginx 的開機啓動:

systemctl enable nginx
複製代碼

啓動 Nginx (其餘命令後面有詳細講解):

systemctl start nginx
複製代碼

而後訪問你的 IP,這時候就能夠看到 Nginx 的歡迎頁面了~ Welcome to nginx! 👏

3.4 安裝 nvm & node & git

# 下載 nvm,或者看官網的步驟 https://github.com/nvm-sh/nvm#install--update-script
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

source   ~/.bashrc    # 安裝完畢後,更新配置文件便可使用 nvm 命令
nvm ls-remote         # 查看遠程 node 版本
nvm install v12.16.3  # 選一個你要安裝的版本安裝,我這裏選擇 12.16.3
nvm list              # 安裝完畢查看安裝的 node 版本
node -v               # 查看是否安裝好了

yum install git   # git 安裝
複製代碼

4. Nginx 操做經常使用命令

Nginx 的命令在控制檯中輸入 nginx -h 就能夠看到完整的命令,這裏列舉幾個經常使用的命令:

nginx -s reload  # 向主進程發送信號,從新加載配置文件,熱重啓
nginx -s reopen	 # 重啓 Nginx
nginx -s stop    # 快速關閉
nginx -s quit    # 等待工做進程處理完成後關閉
nginx -T         # 查看當前 Nginx 最終的配置
nginx -t -c <配置路徑>    # 檢查配置是否有問題,若是已經在配置目錄,則不須要-c
複製代碼

systemctl 是 Linux 系統應用管理工具 systemd 的主命令,用於管理系統,咱們也能夠用它來對 Nginx 進行管理,相關命令以下:

systemctl start nginx    # 啓動 Nginx
systemctl stop nginx     # 中止 Nginx
systemctl restart nginx  # 重啓 Nginx
systemctl reload nginx   # 從新加載 Nginx,用於修改配置後
systemctl enable nginx   # 設置開機啓動 Nginx
systemctl disable nginx  # 關閉開機啓動 Nginx
systemctl status nginx   # 查看 Nginx 運行狀態
複製代碼

5. Nginx 配置語法

就跟前面文件做用講解的圖所示,Nginx 的主配置文件是 /etc/nginx/nginx.conf,你可使用 cat -n nginx.conf 來查看配置。

nginx.conf 結構圖能夠這樣歸納:

main        # 全局配置,對全局生效
├── events  # 配置影響 Nginx 服務器或與用戶的網絡鏈接
├── http    # 配置代理,緩存,日誌定義等絕大多數功能和第三方模塊的配置
│   ├── upstream # 配置後端服務器具體地址,負載均衡配置不可或缺的部分
│   ├── server   # 配置虛擬主機的相關參數,一個 http 塊中能夠有多個 server 塊
│   ├── server
│   │   ├── location  # server 塊能夠包含多個 location 塊,location 指令用於匹配 uri
│   │   ├── location
│   │   └── ...
│   └── ...
└── ...
複製代碼

一個 Nginx 配置文件的結構就像 nginx.conf 顯示的那樣,配置文件的語法規則:

  1. 配置文件由指令與指令塊構成;
  2. 每條指令以 ; 分號結尾,指令與參數間以空格符號分隔;
  3. 指令塊以 {} 大括號將多條指令組織在一塊兒;
  4. include 語句容許組合多個配置文件以提高可維護性;
  5. 使用 # 符號添加註釋,提升可讀性;
  6. 使用 $ 符號使用變量;
  7. 部分指令的參數支持正則表達式;

5.1 典型配置

Nginx 的典型配置:

user  nginx;                        # 運行用戶,默認便是nginx,能夠不進行設置
worker_processes  1;                # Nginx 進程數,通常設置爲和 CPU 核數同樣
error_log  /var/log/nginx/error.log warn;   # Nginx 的錯誤日誌存放目錄
pid        /var/run/nginx.pid;      # Nginx 服務啓動時的 pid 存放位置

events {
    use epoll;     # 使用epoll的I/O模型(若是你不知道Nginx該使用哪一種輪詢方法,會自動選擇一個最適合你操做系統的)
    worker_connections 1024;   # 每一個進程容許最大併發數
}

http {   # 配置使用最頻繁的部分,代理、緩存、日誌定義等絕大多數功能和第三方模塊的配置都在這裏設置
    # 設置日誌模式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;   # Nginx訪問日誌存放位置

    sendfile            on;   # 開啓高效傳輸模式
    tcp_nopush          on;   # 減小網絡報文段的數量
    tcp_nodelay         on;
    keepalive_timeout   65;   # 保持鏈接的時間,也叫超時時間,單位秒
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;      # 文件擴展名與類型映射表
    default_type        application/octet-stream;   # 默認文件類型

    include /etc/nginx/conf.d/*.conf;   # 加載子配置項
    
    server {
    	listen       80;       # 配置監聽的端口
    	server_name  localhost;    # 配置的域名
    	
    	location / {
    		root   /usr/share/nginx/html;  # 網站根目錄
    		index  index.html index.htm;   # 默認首頁文件
    		deny 172.168.22.11;   # 禁止訪問的ip地址,能夠爲all
    		allow 172.168.33.44# 容許訪問的ip地址,能夠爲all
    	}
    	
    	error_page 500 502 503 504 /50x.html;  # 默認50x對應的訪問頁面
    	error_page 400 404 error.html;   # 同上
    }
}
複製代碼

server 塊能夠包含多個 location 塊,location 指令用於匹配 uri,語法:

location [ = | ~ | ~* | ^~] uri {
	...
}
複製代碼

指令後面:

  1. = 精確匹配路徑,用於不含正則表達式的 uri 前,若是匹配成功,再也不進行後續的查找;
  2. ^~ 用於不含正則表達式的 uri; 前,表示若是該符號後面的字符是最佳匹配,採用該規則,再也不進行後續的查找;
  3. ~ 表示用該符號後面的正則去匹配路徑,區分大小寫;
  4. ~* 表示用該符號後面的正則去匹配路徑,不區分大小寫。跟 ~ 優先級都比較低,若有多個location的正則能匹配的話,則使用正則表達式最長的那個;

若是 uri 包含正則表達式,則必需要有 ~~* 標誌。

5.2 全局變量

Nginx 有一些經常使用的全局變量,你能夠在配置的任何位置使用它們,以下表:

全局變量名 功能
$host 請求信息中的 Host,若是請求中沒有 Host 行,則等於設置的服務器名,不包含端口
$request_method 客戶端請求類型,如 GETPOST
$remote_addr 客戶端的 IP 地址
$args 請求中的參數
$arg_PARAMETER GET 請求中變量名 PARAMETER 參數的值,例如:$http_user_agent(Uaer-Agent 值), $http_referer...
$content_length 請求頭中的 Content-length 字段
$http_user_agent 客戶端agent信息
$http_cookie 客戶端cookie信息
$remote_addr 客戶端的IP地址
$remote_port 客戶端的端口
$http_user_agent 客戶端agent信息
$server_protocol 請求使用的協議,如 HTTP/1.0HTTP/1.1
$server_addr 服務器地址
$server_name 服務器名稱
$server_port 服務器的端口號
$scheme HTTP 方法(如http,https)

還有更多的內置預約義變量,能夠直接搜索關鍵字「nginx內置預約義變量」能夠看到一堆博客寫這個,這些變量均可以在配置文件中直接使用。

6. 設置二級域名虛擬主機

在某某雲 ☁️ 上購買了域名以後,就能夠配置虛擬主機了,通常配置的路徑在 域名管理 -> 解析 -> 添加記錄 中添加二級域名,配置後某某雲會把二級域名也解析到咱們配置的服務器 IP 上,而後咱們在 Nginx 上配置一下虛擬主機的訪問監聽,就能夠拿到從這個二級域名過來的請求了。

image-20200426150644768

如今我本身的服務器上配置了一個 fe 的二級域名,也就是說在外網訪問 fe.sherlocked93.club 的時候,也能夠訪問到咱們的服務器了。

因爲默認配置文件 /etc/nginx/nginx.conf 的 http 模塊中有一句 include /etc/nginx/conf.d/*.conf 也就是說 conf.d 文件夾下的全部 *.conf 文件都會做爲子配置項被引入配置文件中。爲了維護方便,我在 /etc/nginx/conf.d 文件夾中新建一個 fe.sherlocked93.club.conf

# /etc/nginx/conf.d/fe.sherlocked93.club.conf

server {
  listen 80;
	server_name fe.sherlocked93.club;

	location / {
		root  /usr/share/nginx/html/fe;
		index index.html;
	}
}
複製代碼

而後在 /usr/share/nginx/html 文件夾下新建 fe 文件夾,新建文件 index.html,內容隨便寫點,改完 nginx -s reload 從新加載,瀏覽器中輸入 fe.sherlocked93.club,發現從二級域名就能夠訪問到咱們剛剛新建的 fe 文件夾:

image-20200426153006505

7. 配置反向代理

反向代理是工做中最經常使用的服務器功能,常常被用來解決跨域問題,下面簡單介紹一下如何實現反向代理。

首先進入 Nginx 的主配置文件:

vim /etc/nginx/nginx.conf
複製代碼

爲了看起來方便,把行號顯示出來 :set nu (我的習慣),而後咱們去 http 模塊的 server 塊中的 location /,增長一行將默認網址重定向到最大學習網站 Bilibili 的 proxy_pass 配置 🤓 :

image-20200311153131642

改完保存退出,nginx -s reload 從新加載,進入默認網址,那麼如今就直接跳轉到 B 站了,實現了一個簡單的代理。

實際使用中,能夠將請求轉發到本機另外一個服務器上,也能夠根據訪問的路徑跳轉到不一樣端口的服務中。

好比咱們監聽 9001 端口,而後把訪問不一樣路徑的請求進行反向代理:

  1. 把訪問 http://127.0.0.1:9001/edu 的請求轉發到 http://127.0.0.1:8080
  2. 把訪問 http://127.0.0.1:9001/vod 的請求轉發到 http://127.0.0.1:8081

這種要怎麼配置呢,首先一樣打開主配置文件,而後在 http 模塊下增長一個 server 塊:

server {
  listen 9001;
  server_name *.sherlocked93.club;

  location ~ /edu/ {
    proxy_pass http://127.0.0.1:8080;
  }
  
  location ~ /vod/ {
    proxy_pass http://127.0.0.1:8081;
  }
}
複製代碼

反向代理還有一些其餘的指令,能夠了解一下:

  1. proxy_set_header:在將客戶端請求發送給後端服務器以前,更改來自客戶端的請求頭信息。
  2. proxy_connect_timeout:配置Nginx與後端代理服務器嘗試創建鏈接的超時時間。
  3. proxy_read_timeout:配置Nginx向後端服務器組發出read請求後,等待相應的超時時間。
  4. proxy_send_timeout:配置Nginx向後端服務器組發出write請求後,等待相應的超時時間。
  5. proxy_redirect:用於修改後端服務器返回的響應頭中的Location和Refresh。

8. 跨域 CORS 配置

關於簡單請求、非簡單請求、跨域的概念,前面已經介紹過了,還不瞭解的能夠看看前面的講解。如今先後端分離的項目一統天下,常常本地起了前端服務,須要訪問不一樣的後端地址,不可避免遇到跨域問題。

image-20200427220536208

要解決跨域問題,咱們來製造一個跨域問題。首先和前面設置二級域名的方式同樣,先設置好 fe.sherlocked93.clubbe.sherlocked93.club 二級域名,都指向本雲服務器地址,雖然對應 IP 是同樣的,可是在 fe.sherlocked93.club 域名發出的請求訪問 be.sherlocked93.club 域名的請求仍是跨域了,由於訪問的 host 不一致(若是不知道啥緣由參見前面跨域的內容)。

8.1 使用反向代理解決跨域

在前端服務地址爲 fe.sherlocked93.club 的頁面請求 be.sherlocked93.club 的後端服務致使的跨域,能夠這樣配置:

server {
  listen 9001;
  server_name fe.sherlocked93.club;

  location / {
    proxy_pass be.sherlocked93.club;
  }
}
複製代碼

這樣就將對前一個域名 fe.sherlocked93.club 的請求全都代理到了 be.sherlocked93.club,前端的請求都被咱們用服務器代理到了後端地址下,繞過了跨域。

這裏對靜態文件的請求和後端服務的請求都以 fe.sherlocked93.club 開始,不易區分,因此爲了實現對後端服務請求的統一轉發,一般咱們會約定對後端服務的請求加上 /apis/ 前綴或者其餘的 path 來和對靜態資源的請求加以區分,此時咱們能夠這樣配置:

# 請求跨域,約定代理後端服務請求path以/apis/開頭
location ^~/apis/ {
    # 這裏重寫了請求,將正則匹配中的第一個分組的path拼接到真正的請求後面,並用break中止後續匹配
    rewrite ^/apis/(.*)$ /$1 break;
    proxy_pass be.sherlocked93.club;
  
    # 兩個域名之間cookie的傳遞與回寫
    proxy_cookie_domain be.sherlocked93.club fe.sherlocked93.club;
}
複製代碼

這樣,靜態資源咱們使用 fe.sherlocked93.club/xx.html,動態資源咱們使用 fe.sherlocked93.club/apis/getAwo,瀏覽器頁面看起來仍然訪問的前端服務器,繞過了瀏覽器的同源策略,畢竟咱們看起來並無跨域。

也能夠統一一點,直接把先後端服務器地址直接都轉發到另外一個 server.sherlocked93.club,只經過在後面添加的 path 來區分請求的是靜態資源仍是後端服務,看需求了。

8.2 配置 header 解決跨域

當瀏覽器在訪問跨源的服務器時,也能夠在跨域的服務器上直接設置 Nginx,從而前端就能夠無感地開發,不用把實際上訪問後端的地址改爲前端服務的地址,這樣可適性更高。

好比前端站點是 fe.sherlocked93.club,這個地址下的前端頁面請求 be.sherlocked93.club 下的資源,好比前者的 fe.sherlocked93.club/index.html 內容是這樣的:

<html>
<body>
    <h1>welcome fe.sherlocked93.club!!<h1>
    <script type='text/javascript'> var xmlhttp = new XMLHttpRequest() xmlhttp.open("GET", "http://be.sherlocked93.club/index.html", true); xmlhttp.send(); </script>
</body>
</html>
複製代碼

打開瀏覽器訪問 fe.sherlocked93.club/index.html 的結果以下:

image-20200428191153736

很明顯這裏是跨域請求,在瀏覽器中直接訪問 http://be.sherlocked93.club/index.html 是能夠訪問到的,可是在 fe.sherlocked93.club 的 html 頁面訪問就會出現跨域。

/etc/nginx/conf.d/ 文件夾中新建一個配置文件,對應二級域名 be.sherlocked93.club

# /etc/nginx/conf.d/be.sherlocked93.club.conf

server {
  listen       80;
  server_name  be.sherlocked93.club;
  
	add_header 'Access-Control-Allow-Origin' $http_origin;   # 全局變量得到當前請求origin,帶cookie的請求不支持*
	add_header 'Access-Control-Allow-Credentials' 'true';    # 爲 true 可帶上 cookie
	add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';  # 容許請求方法
	add_header 'Access-Control-Allow-Headers' $http_access_control_request_headers;  # 容許請求的 header,能夠爲 *
	add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
	
  if ($request_method = 'OPTIONS') {
		add_header 'Access-Control-Max-Age' 1728000;   # OPTIONS 請求的有效期,在有效期內不用發出另外一條預檢請求
		add_header 'Content-Type' 'text/plain; charset=utf-8';
		add_header 'Content-Length' 0;
    
		return 204;                  # 200 也能夠
	}
  
	location / {
		root  /usr/share/nginx/html/be;
		index index.html;
	}
}
複製代碼

而後 nginx -s reload 從新加載配置。這時再訪問 fe.sherlocked93.club/index.html 結果以下,請求中出現了咱們剛剛配置的 Header:

image-20200428192028636

解決了跨域問題。

9. 開啓 gzip 壓縮

gzip 是一種經常使用的網頁壓縮技術,傳輸的網頁通過 gzip 壓縮以後大小一般能夠變爲原來的一半甚至更小(官網原話),更小的網頁體積也就意味着帶寬的節約和傳輸速度的提高,特別是對於訪問量巨大大型網站來講,每個靜態資源體積的減少,都會帶來可觀的流量與帶寬的節省。

百度能夠找到不少檢測站點來查看目標網頁有沒有開啓 gzip 壓縮,在下隨便找了一個 <網頁GZIP壓縮檢測> 輸入掘金 juejin.im 來偷窺下掘金有沒有開啓 gzip。

image-20200427110415809

這裏能夠看到掘金是開啓了 gzip 的,壓縮效果還挺不錯,達到了 52% 之多,原本 34kb 的網頁體積,壓縮完只須要 16kb,能夠想象網頁傳輸速度提高了很多。

9.1 Nginx 配置 gzip

使用 gzip 不只須要 Nginx 配置,瀏覽器端也須要配合,須要在請求消息頭中包含 Accept-Encoding: gzip(IE5 以後全部的瀏覽器都支持了,是現代瀏覽器的默認設置)。通常在請求 html 和 css 等靜態資源的時候,支持的瀏覽器在 request 請求靜態資源的時候,會加上 Accept-Encoding: gzip 這個 header,表示本身支持 gzip 的壓縮方式,Nginx 在拿到這個請求的時候,若是有相應配置,就會返回通過 gzip 壓縮過的文件給瀏覽器,並在 response 相應的時候加上 content-encoding: gzip 來告訴瀏覽器本身採用的壓縮方式(由於瀏覽器在傳給服務器的時候通常還告訴服務器本身支持好幾種壓縮方式),瀏覽器拿到壓縮的文件後,根據本身的解壓方式進行解析。

先來看看 Nginx 怎麼進行 gzip 配置,和以前的配置同樣,爲了方便管理,仍是在 /etc/nginx/conf.d/ 文件夾中新建配置文件 gzip.conf

# /etc/nginx/conf.d/gzip.conf

gzip on; # 默認off,是否開啓gzip
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

# 上面兩個開啓基本就能跑起了,下面的願意折騰就瞭解一下
gzip_static on;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 6;
gzip_buffers 16 8k;
# gzip_min_length 1k;
gzip_http_version 1.1;
複製代碼

稍微解釋一下:

  1. gzip_types:要採用 gzip 壓縮的 MIME 文件類型,其中 text/html 被系統強制啓用;
  2. gzip_static:默認 off,該模塊啓用後,Nginx 首先檢查是否存在請求靜態文件的 gz 結尾的文件,若是有則直接返回該 .gz 文件內容;
  3. gzip_proxied:默認 off,nginx作爲反向代理時啓用,用於設置啓用或禁用從代理服務器上收到相應內容 gzip 壓縮;
  4. gzip_vary:用於在響應消息頭中添加 Vary:Accept-Encoding,使代理服務器根據請求頭中的 Accept-Encoding 識別是否啓用 gzip 壓縮;
  5. gzip_comp_level:gzip 壓縮比,壓縮級別是 1-9,1 壓縮級別最低,9 最高,級別越高壓縮率越大,壓縮時間越長,建議 4-6;
  6. gzip_buffers:獲取多少內存用於緩存壓縮結果,16 8k 表示以 8k*16 爲單位得到;
  7. gzip_min_length:容許壓縮的頁面最小字節數,頁面字節數從header頭中的 Content-Length 中進行獲取。默認值是 0,無論頁面多大都壓縮。建議設置成大於 1k 的字節數,小於 1k 可能會越壓越大;
  8. gzip_http_version:默認 1.1,啓用 gzip 所需的 HTTP 最低版本;

這個配置能夠插入到 http 模塊整個服務器的配置裏,也能夠插入到須要使用的虛擬主機的 server 或者下面的 location 模塊中,固然像上面咱們這樣寫的話就是被 include 到 http 模塊中了。

其餘更全的配置信息能夠查看 <官網文檔ngx_http_gzip_module>,配置前是這樣的:

image-20200427161022215

配置以後 response 的 header 裏面多了一個 Content-Encoding: gzip,返回信息被壓縮了:

image-20200427164033577

注意了,通常 gzip 的配置建議加上 gzip_min_length 1k,不加的話:

image-20200427164408389

因爲文件過小,gzip 壓縮以後獲得了 -48% 的體積優化,壓縮以後體積還比壓縮以前體積大了,因此最好設置低於 1kb 的文件就不要 gzip 壓縮了 🤪

9.2 Webpack 的 gzip 配置

當前端項目使用 Webpack 進行打包的時候,也能夠開啓 gzip 壓縮:

// vue-cli3 的 vue.config.js 文件
const CompressionWebpackPlugin = require('compression-webpack-plugin')

module.exports = {
  // gzip 配置
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      // 生產環境
      return {
        plugins: [new CompressionWebpackPlugin({
          test: /\.js$|\.html$|\.css/,    // 匹配文件名
          threshold: 10240,               // 文件壓縮閾值,對超過10k的進行壓縮
          deleteOriginalAssets: false     // 是否刪除源文件
        })]
      }
    }
  },
  ...
}
複製代碼

由此打包出來的文件以下圖:

image-20200427144824829

這裏能夠看到某些打包以後的文件下面有一個對應的 .gz 通過 gzip 壓縮以後的文件,這是由於這個文件超過了 10kb,有的文件沒有超過 10kb 就沒有進行 gzip 打包,若是你指望壓縮文件的體積閾值小一點,能夠在 compression-webpack-plugin 這個插件的配置裏進行對應配置。

那麼爲啥這裏 Nginx 已經有了 gzip 壓縮,Webpack 這裏又整了個 gzip 呢,由於若是全都是使用 Nginx 來壓縮文件,會耗費服務器的計算資源,若是服務器的 gzip_comp_level 配置的比較高,就更增長服務器的開銷,相應增長客戶端的請求時間,得不償失。

若是壓縮的動做在前端打包的時候就作了,把打包以後的高壓縮等級文件做爲靜態資源放在服務器上,Nginx 會優先查找這些壓縮以後的文件返回給客戶端,至關於把壓縮文件的動做從 Nginx 提早給 Webpack 打包的時候完成,節約了服務器資源,因此通常推介在生產環境應用 Webpack 配置 gzip 壓縮。

10. 配置負載均衡

負載均衡在以前已經介紹了相關概念了,主要思想就是把負載均勻合理地分發到多個服務器上,實現壓力分流的目的。

主要配置以下:

http {
  upstream myserver {
  	# ip_hash; # ip_hash 方式
    # fair; # fair 方式
    server 127.0.0.1:8081;  # 負載均衡目的服務地址
    server 127.0.0.1:8080;
    server 127.0.0.1:8082 weight=10;  # weight 方式,不寫默認爲 1
  }
 
  server {
    location / {
    	proxy_pass http://myserver;
      proxy_connect_timeout 10;
    }
  }
}
複製代碼

Nginx 提供了好幾種分配方式,默認爲輪詢,就是輪流來。有如下幾種分配方式:

  1. 輪詢,默認方式,每一個請求按時間順序逐一分配到不一樣的後端服務器,若是後端服務掛了,能自動剔除;
  2. weight,權重分配,指定輪詢概率,權重越高,在被訪問的機率越大,用於後端服務器性能不均的狀況;
  3. ip_hash,每一個請求按訪問 IP 的 hash 結果分配,這樣每一個訪客固定訪問一個後端服務器,能夠解決動態網頁 session 共享問題。負載均衡每次請求都會從新定位到服務器集羣中的某一個,那麼已經登陸某一個服務器的用戶再從新定位到另外一個服務器,其登陸信息將會丟失,這樣顯然是不妥的;
  4. fair(第三方),按後端服務器的響應時間分配,響應時間短的優先分配,依賴第三方插件 nginx-upstream-fair,須要先安裝;

11. 配置動靜分離

動靜分離在以前也介紹過了,就是把動態和靜態的請求分開。方式主要有兩種,一種 是純粹把靜態文件獨立成單獨的域名,放在獨立的服務器上,也是目前主流推崇的方案。另一種方法就是動態跟靜態文件混合在一塊兒發佈, 經過 Nginx 配置來分開。

經過 location 指定不一樣的後綴名實現不一樣的請求轉發。經過 expires 參數設置,可使瀏覽器緩存過時時間,減小與服務器以前的請求和流量。具體 expires 定義:是給一個資源設定一個過時時間,也就是說無需去服務端驗證,直接經過瀏覽器自身確認是否過時便可,因此不會產生額外的流量。此種方法很是適合不常常變更的資源。(若是常常更新的文件,不建議使用 expires 來緩存),我這裏設置 3d,表示在這 3 天以內訪問這個URL,發送一個請求,比對服務器該文件最後更新時間沒有變化。則不會從服務器抓取,返回狀態碼 304,若是有修改,則直接從服務器從新下載,返回狀態碼 200。

server {
  location /www/ {
  	root /data/;
    index index.html index.htm;
  }
  
  location /image/ {
  	root /data/;
    autoindex on;
  }
}
複製代碼

12. 配置高可用集羣(雙機熱備)

當主 Nginx 服務器宕機以後,切換到備份 Nginx 服務器

2020-03-13-雙機熱備

首先安裝 keepalived,

yum install keepalived -y
複製代碼

而後編輯 /etc/keepalived/keepalived.conf 配置文件,並在配置文件中增長 vrrp_script 定義一個外圍檢測機制,並在 vrrp_instance 中經過定義 track_script 來追蹤腳本執行過程,實現節點轉移:

global_defs{
   notification_email {
        acassen@firewall.loc
   }
   notification_email_from Alexandre@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30 // 上面都是郵件配置,沒卵用
   router_id LVS_DEVEL     // 當前服務器名字,用hostname命令來查看
}
vrrp_script chk_maintainace { // 檢測機制的腳本名稱爲chk_maintainace
    script "[[ -e/etc/keepalived/down ]] && exit 1 || exit 0" // 能夠是腳本路徑或腳本命令
    // script "/etc/keepalived/nginx_check.sh" // 好比這樣的腳本路徑
    interval 2  // 每隔2秒檢測一次
    weight -20  // 當腳本執行成立,那麼把當前服務器優先級改成-20
}
vrrp_instanceVI_1 {   // 每個vrrp_instance就是定義一個虛擬路由器
    state MASTER      // 主機爲MASTER,備用機爲BACKUP
    interface eth0    // 網卡名字,能夠從ifconfig中查找
    virtual_router_id 51 // 虛擬路由的id號,通常小於255,主備機id須要同樣
    priority 100      // 優先級,master的優先級比backup的大
    advert_int 1      // 默認心跳間隔
    authentication {  // 認證機制
        auth_type PASS
        auth_pass 1111   // 密碼
    }
    virtual_ipaddress {  // 虛擬地址vip
       172.16.2.8
    }
}
複製代碼

其中檢測腳本 nginx_check.sh,這裏提供一個:

#!/bin/bash
A=`ps -C nginx --no-header | wc -l`
if [ $A -eq 0 ];then
    /usr/sbin/nginx # 嘗試從新啓動nginx
    sleep 2         # 睡眠2秒
    if [ `ps -C nginx --no-header | wc -l` -eq 0 ];then
        killall keepalived # 啓動失敗,將keepalived服務殺死。將vip漂移到其它備份節點
    fi
fi
複製代碼

複製一份到備份服務器,備份 Nginx 的配置要將 state 後改成 BACKUPpriority 改成比主機小。

設置完畢後各自 service keepalived start 啓動,通過訪問成功以後,能夠把 Master 機的 keepalived 停掉,此時 Master 機就再也不是主機了 service keepalived stop,看訪問虛擬 IP 時是否可以自動切換到備機 ip addr

再次啓動 Master 的 keepalived,此時 vip 又變到了主機上。

13. 適配 PC 或移動設備

根據用戶設備不一樣返回不一樣樣式的站點,之前常用的是純前端的自適應佈局,但不管是複雜性和易用性上面仍是不如分開編寫的好,好比咱們常見的淘寶、京東......這些大型網站就都沒有采用自適應,而是用分開製做的方式,根據用戶請求的 user-agent 來判斷是返回 PC 仍是 H5 站點。

首先在 /usr/share/nginx/html 文件夾下 mkdir 分別新建兩個文件夾 PCmobilevim 編輯兩個 index.html 隨便寫點內容。

cd /usr/share/nginx/html
mkdir pc mobile
cd pc
vim index.html   # 隨便寫點好比 hello pc!
cd ../mobile
vim index.html   # 隨便寫點好比 hello mobile!
複製代碼

而後和設置二級域名虛擬主機時候同樣,去 /etc/nginx/conf.d 文件夾下新建一個配置文件 fe.sherlocked93.club.conf

# /etc/nginx/conf.d/fe.sherlocked93.club.conf

server {
  listen 80;
	server_name fe.sherlocked93.club;

	location / {
		root  /usr/share/nginx/html/pc;
    if ($http_user_agent ~* '(Android|webOS|iPhone|iPod|BlackBerry)') {
      root /usr/share/nginx/html/mobile;
    }
		index index.html;
	}
}
複製代碼

配置基本沒什麼不同的,主要多了一個 if 語句,而後使用 $http_user_agent 全局變量來判斷用戶請求的 user-agent,指向不一樣的 root 路徑,返回對應站點。

在瀏覽器訪問這個站點,而後 F12 中模擬使用手機訪問:

62haogU3DtwMRiZ

能夠看到在模擬使用移動端訪問的時候,Nginx 返回的站點變成了移動端對應的 html 了。

14. 配置 HTTPS

具體配置過程網上挺多的了,也可使用你購買的某某雲,通常都會有免費申請的服務器證書,安裝直接看所在雲的操做指南便可。

我購買的騰訊雲提供的亞洲誠信機構頒發的免費證書只能一個域名使用,二級域名什麼的須要另外申請,可是申請審批比較快,通常幾分鐘就能成功,而後下載證書的壓縮文件,裏面有個 nginx 文件夾,把 xxx.crtxxx.key 文件拷貝到服務器目錄,再配置下:

server {
  listen 443 ssl http2 default_server;   # SSL 訪問端口號爲 443
  server_name sherlocked93.club;         # 填寫綁定證書的域名

  ssl_certificate /etc/nginx/https/1_sherlocked93.club_bundle.crt;   # 證書文件地址
  ssl_certificate_key /etc/nginx/https/2_sherlocked93.club.key;      # 私鑰文件地址
  ssl_session_timeout 10m;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;      #請按照如下協議配置
  ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
  ssl_prefer_server_ciphers on;
  
  location / {
    root         /usr/share/nginx/html;
    index        index.html index.htm;
  }
}
複製代碼

寫完 nginx -t -q 校驗一下,沒問題就 nginx -s reload,如今去訪問 https://sherlocked93.club/ 就能訪問 HTTPS 版的網站了。

通常還能夠加上幾個加強安全性的命令:

add_header X-Frame-Options DENY;           # 減小點擊劫持
add_header X-Content-Type-Options nosniff; # 禁止服務器自動解析資源類型
add_header X-Xss-Protection 1;             # 防XSS攻擊
複製代碼

15. 一些經常使用技巧

15.1 靜態服務

server {
  listen       80;
  server_name  static.sherlocked93.club;
  charset utf-8;    # 防止中文文件名亂碼

  location /download {
    alias	          /usr/share/nginx/html/static;  # 靜態資源目錄
    
    autoindex               on;    # 開啓靜態資源列目錄
    autoindex_exact_size    off;   # on(默認)顯示文件的確切大小,單位是byte;off顯示文件大概大小,單位KB、MB、GB
    autoindex_localtime     off;   # off(默認)時顯示的文件時間爲GMT時間;on顯示的文件時間爲服務器時間
  }
}
複製代碼

15.2 圖片防盜鏈

server {
  listen       80;        
  server_name  *.sherlocked93.club;
  
  # 圖片防盜鏈
  location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
    valid_referers none blocked server_names ~\.google\. ~\.baidu\. *.qq.com;  # 只容許本機 IP 外鏈引用,感謝 @木法傳 的提醒,將百度和谷歌也加入白名單
    if ($invalid_referer){
      return 403;
    }
  }
}
複製代碼

15.3 請求過濾

# 非指定請求全返回 403
if ( $request_method !~ ^(GET|POST|HEAD)$ ) {
  return 403;
}

location / {
  # IP訪問限制(只容許IP是 192.168.0.2 機器訪問)
  allow 192.168.0.2;
  deny all;
  
  root   html;
  index  index.html index.htm;
}
複製代碼

15.4 配置圖片、字體等靜態文件緩存

因爲圖片、字體、音頻、視頻等靜態文件在打包的時候一般會增長了 hash,因此緩存能夠設置的長一點,先設置強制緩存,再設置協商緩存;若是存在沒有 hash 值的靜態文件,建議不設置強制緩存,僅經過協商緩存判斷是否須要使用緩存。

# 圖片緩存時間設置
location ~ .*\.(css|js|jpg|png|gif|swf|woff|woff2|eot|svg|ttf|otf|mp3|m4a|aac|txt)$ {
	expires 10d;
}

# 若是不但願緩存
expires -1;
複製代碼

15.5 單頁面項目 history 路由配置

server {
  listen       80;
  server_name  fe.sherlocked93.club;
  
  location / {
    root       /usr/share/nginx/html/dist;  # vue 打包後的文件夾
    index      index.html index.htm;
    try_files  $uri $uri/ /index.html @rewrites;  
    
    expires -1;                          # 首頁通常沒有強制緩存
    add_header Cache-Control no-cache;
  }
  
  # 接口轉發,若是須要的話
  #location ~ ^/api {
  # proxy_pass http://be.sherlocked93.club;
  #}
  
  location @rewrites {
    rewrite ^(.+)$ /index.html break;
  }
}
複製代碼

15.6 HTTP 請求轉發到 HTTPS

配置完 HTTPS 後,瀏覽器仍是能夠訪問 HTTP 的地址 http://sherlocked93.club/ 的,能夠作一個 301 跳轉,把對應域名的 HTTP 請求重定向到 HTTPS 上

server {
    listen      80;
    server_name www.sherlocked93.club;

    # 單域名重定向
    if ($host = 'www.sherlocked93.club'){
        return 301 https://www.sherlocked93.club$request_uri;
    }
    # 全局非 https 協議時重定向
    if ($scheme != 'https') {
        return 301 https://$server_name$request_uri;
    }

    # 或者所有重定向
    return 301 https://$server_name$request_uri;

    # 以上配置選擇本身須要的便可,不用所有加
}
複製代碼

15.7 泛域名路徑分離

這是一個很是實用的技能,常常有時候咱們可能須要配置一些二級或者三級域名,但願經過 Nginx 自動指向對應目錄,好比:

  1. test1.doc.sherlocked93.club 自動指向 /usr/share/nginx/html/doc/test1 服務器地址;
  2. test2.doc.sherlocked93.club 自動指向 /usr/share/nginx/html/doc/test2 服務器地址;
server {
    listen       80;
    server_name  ~^([\w-]+)\.doc\.sherlocked93\.club$;

    root /usr/share/nginx/html/doc/$1;
}
複製代碼

15.8 泛域名轉發

和以前的功能相似,有時候咱們但願把二級或者三級域名連接重寫到咱們但願的路徑,讓後端就能夠根據路由解析不一樣的規則:

  1. test1.serv.sherlocked93.club/api?name=a 自動轉發到 127.0.0.1:8080/test1/api?name=a
  2. test2.serv.sherlocked93.club/api?name=a 自動轉發到 127.0.0.1:8080/test2/api?name=a
server {
    listen       80;
    server_name ~^([\w-]+)\.serv\.sherlocked93\.club$;

    location / {
        proxy_set_header        X-Real-IP $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        Host $http_host;
        proxy_set_header        X-NginX-Proxy true;
        proxy_pass              http://127.0.0.1:8080/$1$request_uri;
    }
}
複製代碼

16. 最佳實踐

  1. 爲了使 Nginx 配置更易於維護,建議爲每一個服務建立一個單獨的配置文件,存儲在 /etc/nginx/conf.d 目錄,根據需求能夠建立任意多個獨立的配置文件。
  2. 獨立的配置文件,建議遵循如下命名約定 <服務>.conf,好比域名是 sherlocked93.club,那麼你的配置文件的應該是這樣的 /etc/nginx/conf.d/sherlocked93.club.conf,若是部署多個服務,也能夠在文件名中加上 Nginx 轉發的端口號,好比 sherlocked93.club.8080.conf,若是是二級域名,建議也都加上 fe.sherlocked93.club.conf
  3. 經常使用的、複用頻率比較高的配置能夠放到 /etc/nginx/snippets 文件夾,在 Nginx 的配置文件中須要用到的位置 include 進去,以功能來命名,並在每一個 snippet 配置文件的開頭註釋標明主要功能和引入位置,方便管理。好比以前的 gzipcors 等經常使用配置,我都設置了 snippet。
  4. Nginx 日誌相關目錄,內以 域名.type.log 命名(好比 be.sherlocked93.club.access.logbe.sherlocked93.club.error.log )位於 /var/log/nginx/ 目錄中,爲每一個獨立的服務配置不一樣的訪問權限和錯誤日誌文件,這樣查找錯誤時,會更加方便快捷。

感謝 @木法傳 的提醒,Nginx 設置防盜鏈的時候,能夠將百度和 google 設置爲白名單,利於 SEO


網上的帖子大多深淺不一,甚至有些先後矛盾,在下的文章都是學習過程當中的總結,若是發現錯誤,歡迎留言指出~

參考文檔:

  1. Nginx中文文檔
  2. Nginx安裝,目錄結構與配置文件詳解
  3. Keepalived安裝與配置
  4. Keepalived+Nginx實現高可用
  5. Nginx與前端開發
  6. 跨域資源共享 CORS 詳解 - 阮一峯的網絡日誌
  7. 前端開發者必備的nginx知識
  8. 我也說說Nginx解決前端跨域問題,正確的Nginx跨域配置
  9. vue-router history模式nginx配置並配置靜態資源緩存 | HolidayPenguin
  10. nginx重定向,全局https,SSL配置,反代配置參考
  11. Nginx 入門教程

PS:本人博客地址 Github - SHERlocked93/blog,也歡迎你們關注個人公衆號【前端下午茶】,一塊兒加油吧~

另外能夠加入「前端下午茶交流羣」微信羣,長按識別下面二維碼便可加我好友,備註加羣,我拉你入羣~

相關文章
相關標籤/搜索