《真女神轉生5》仲魔愛麗絲培養思路
@
目錄一、Express 簡介
Express 的中文官網: http://www.expressjs.com.cn/
Express 是基於 Node.js 平臺,快速、開放、極簡的 Web 開發框架
Express 的作用和 Node.js 內建的 http 模組類似,是專門用來建立 Web 伺服器的,但http 內建模組用起來很複雜,開發效率低;Express 是基於內建的 http 模組進一步封裝出來的,能夠極大的提高開發效率。
Express 作用:
使用 Express,我們可以方便、快速的建立 Web 網站的伺服器或 API 介面的伺服器
- Web 網站伺服器:專門對外提供 Web 網頁資源的伺服器。
- API 介面伺服器:專門對外提供 API 介面的伺服器。
二、Express 的基本使用
1. 安裝
在專案所處的目錄中,執行如下的終端命令,即可將 express 安裝到專案中使用:npm i [email protected]
2. 建立基本的 Web 伺服器
// 1.匯入 express
const express = require('express');
// 2.建立 web 伺服器
const app = express();
// 3.呼叫 app.listen(埠號, 啟動成功後的回撥函式), 啟動伺服器
app.listen(3000, () => {
console.log('express server runing at http://127.0.0.1')
})
3. 監聽 GET 請求
通過app.get()
,可以監聽客戶端的 GET 請求,具體的語法格式如下:
/* 引數1: 在客戶端請求的URL地址
引數2:請求對應的處理函式
req:請求物件(包含了與請求相關的屬性與方法)
res:響應物件(包含了與響應相關的屬性與方法)
*/
app.get('請求URL', function(req, res){/*處理函式*/})
4. 監聽 POST 請求
通過 app.post()
,可以監聽客戶端的 POST 請求,具體的語法格式如下:
/* 引數1: 在客戶端請求的URL地址
引數2:請求對應的處理函式
req:請求物件(包含了與請求相關的屬性與方法)
res:響應物件(包含了與響應相關的屬性與方法)
*/
app.post('請求URL', function(req, res){/*處理函式*/})
5. 把內容響應給客戶端
res.send()
在express服務端無需再使用res.end()
, 只需通過res.send()
,就可以把處理好的內容,傳送給客戶端:
send()會自動檢測響應內容的型別,並把型別自動設定到響應頭當中 → 它自動實現的靜態資源的響應。
它也自動設定了響應內容的編碼,避免出現亂碼
它也自動設定了響應的http狀態碼
所以,可以不在使用res.writeHead()來設定響應內容型別以及內容編碼
app.get('/', (req, res) => {
// 向客戶端傳送 JSON 物件
res.send({ name: 'zs', age: 20, gender: '男' })
})
app.post('/', (req, res) => {
// 在Express中請求物件可以直接訪問伺服器物件
console.log(req.app);
// 向客戶端傳送文字內容
res.send('請求成功')
})
res.json()
向客戶端返回 json物件 字串
app.get('/json', (req, res) => {
res.json({
uname: 'lisi',
age: 13,
gender: 'male'
});
})
6. 獲取請求引數
① req.query
通過 req.query
物件,可以訪問到客戶端通過 查詢字串 的形式,傳送到伺服器的引數:
app.get('/', (req, res) => {
// req.query 預設是一個空物件
// 客戶端使用 ?name=zs&age=20 這種查詢字串的形式,傳送到服務端的引數
// 可以通過 req.query 物件訪問到,例如 req.qurey.name
console.log(req.query) //{'name': 'zs', 'age': '20'}
})
② req.params
通過 req.params
物件,可以訪問到 URL 中,通過:
匹配到的 動態引數,或又稱之為 路由引數,比如:
在瀏覽器的請求url是如下這樣的
localhost:3000/find/123/張三/20
如果url沒有引數,那麼就訪問不到 /find 路由
這是一種較為特殊的獲取請求引數的方式
//在路由中中指定請求引數, 寫法如下
app.get('/find/:id/:name/:age', (req, res) => {
// 獲取請求引數
console.log(req.params); // {id: '123', name: '張三', age: 20}
});
③獲取請求體中的引數
express.urlencoded()
如果要獲取 URL-encoded
格式的請求體資料,必須配置中介軟體
app.use(express.urlencoded({ extended: false }))
比如
//獲取 urlencoded格式 引數
app.use(express.urlencoded({ extended: false }))
app.post('/post', (req, res) => {
res.seng(req.body);// 請求引數物件
)
})
express.json()
body-parser 第三方模組
Express中接收請求體中的引數需要藉助第三方包 body-parser
// 引入body-parser模組
const bodyParser = require('body-parser');
/* 配置body-parser模組
bodyParser.urlencoded():對請求中 請求引數為urlencoded格式 進行處理,並返回一個函式
函式內部執行過程
方法內部會檢測當前請求是否包含請求引數,並接收請求引數
將請求引數轉換為物件型別
為req請求物件新增屬性,屬性名: "body",並把請求引數物件賦給body屬性
呼叫next()將請求控制權交給下一個中介軟體
extended引數
false: urlencoded()內部會使用querystring系統模組對請求引數的格式進行處理
true: urlencoded()內部會使用第三方模組 qs 對請求引數進行處理
bodyParser.json():對請求中 請求引數為 json格式 進行處理, 內部實現原理參考上述
*/
app.use(bodyParser.urlencoded({ extended: false }));
//app.use(boduParser.json());
// 接收請求
app.post('/add', (req, res) => {
// 接收請求引數
console.log(req.body);
})
7. 託管靜態資源
① express.static()
express 第三方包提供了一個 express.static()
,用來建立建立一個靜態資源伺服器,可以方便地託管靜態檔案
/*static()實現邏輯:
判斷當前請求是否訪問靜態資源,
如果是, 直接把靜態資源響應給客戶端並終止當前請求。
如果不是,那麼方法內部會呼叫next()將請求控制權傳遞給下一個中介軟體
*/
app.use(express.static('靜態資源存放目錄絕對路徑'));
例如,通過如下程式碼就可以將 public 目錄下的圖片、CSS 檔案、JavaScript 檔案對外開放訪問了:
app.use(express.static('public'))
現在,public 目錄下面的檔案就可以訪問了, 比如:
- http://localhost:3000/images/kitten.jpg
- http://localhost:3000/css/style.css
- http://localhost:3000/js/app.js
- http://localhost:3000/images/bg.png
- http://localhost:3000/hello.html
注意:Express 在指定的靜態目錄中查詢檔案,並對外提供資源的訪問路徑。因此,存放靜態檔案的目錄名不會出現在 URL 中。
② 託管多個靜態資源目錄
如果要託管多個靜態資源目錄,請多次呼叫express.static()
函式:
app.use(express.static('public'))
app.use(express.static('files'))
訪問靜態資原始檔時,express.static() 函式會根據目錄的新增順序查詢所需的檔案。
③ 掛載路徑字首
如果希望在託管的 靜態資源訪問路徑 之前,掛載路徑字首,則可以使用如下的方式:
//app.use('虛擬路徑',express.static('靜態資源存放目錄絕對路徑')
app.use('/static', express.static('public'))
訪問時要注意虛擬路勁
http://localhost:3000/static/images/kitten.jpg
注意: 虛擬路徑實際上不存在
三、Express 路由
1. 路由的概念
什麼是路由:廣義上來講,路由就是 對映關係。
Express 中的路由:
在 Express 中,路由指的是客戶端的請求與伺服器處理函式之間的 對映關係。
Express 中的路由分 3 部分組成,分別是
- 請求的型別
- 請求的 URL 地址
- 處理函式,
格式如下:
app.METHOD(PATH, HANDLER)
比如:
// 匹配 GET 請求, 且請求URL 為 /
app.get('/', function(req, res){
res.send('hello')
})
// 匹配 POST 請求, 且請求URL 為 /
app.post('/', function(req, res){
res.send('hello')
})
2. 路由的匹配過程
每當一個請求到達伺服器之後,需要先經過路由的匹配,只有匹配成功之後,才會呼叫對應的處理函式。
在匹配時,會按照路由的順序進行匹配,如果 請求型別 和 請求的 URL 同時匹配成功,則 Express 會將這次請求,轉交給對應的 function 函式進行處理。
路由匹配的注意點:
- 按照定義的 先後順序 進行匹配
- 請求型別 和 請求的URL 同時匹配成功,
才會呼叫對應的處理函式
當訪問了一個不存在路由,express會自動跳到一個頁面給出一個友好提示,比如Cannot GET /list
:
3. 路由的使用
① 普通路由
在 Express 中使用路由最簡單的方式,就是把路由掛載到 app 上,示例程式碼如下:
const express = require('express')
const app = express();
// 掛載路由
app.get('/', (req, res) => {res.send('hello')})
app.get('/', (req, res) => {res.send('hello')})
app.listen(3000)
② 模組化路由
為了 方便對路由進行模組化的管理 ,Express 不建議將路由直接掛載到 app 上,而是 推薦將路由抽離為單獨的模組。
將路由抽離為單獨模組的步驟如下:
- 建立路由模組對應的 .js 檔案
- 呼叫
express.Router()
函式建立路由物件 - 向路由物件上掛載具體的路由
- 使用 module.exports 向外共享路由物件
- 使用 app.use() 函式註冊路由模組
建立路由模組
const express = require('express') // 匯入express
const router = express.Router() // 建立路由物件
router.get('/user/list', function(req, res){ //掛載路由
res.send('Get user list');
})
router.post('/user/add', function(req, res){
res.send('Add new user')
})
module.export = router
註冊路由模組, 並新增字首
// 匯入路由模組
const userRouter = require('./router/user.js')
// 使用 app.use() 註冊路由模組,並新增統一的訪問字首 /api
app.use('/api',userRouter)
四、Express 中介軟體
1. 簡介
① 什麼是中介軟體
中介軟體(Middleware ),特指 業務流程 的 中間處理環節
Express 中介軟體的呼叫流程:
當一個請求到達 Express 的伺服器之後,可以連續呼叫多箇中間件,從而對這次請求進行 預處理。
中介軟體的作用:
多箇中間件之間,共享同一份 req 和 res。基於這樣的特性,我們可以在上游的中介軟體中,統一為 req 或 res 物件新增自定義的屬性或方法,供下游的中介軟體或路由進行使用。
② 中介軟體的組成格式
Express 的中介軟體,本質上就是一個 function 處理函式,Express 中介軟體的格式如下:
注意:中介軟體函式的形參列表中,必須包含 next
引數。而普通路由處理函式中只包含 req
和 res
。
一個路由可以繫結多箇中間件函式:
const mw1 = function(req, res, next){
next();
}
const mw2 = function(req, res, next){
next();
}
app.get('/', mw1, mw2, (req, res) => { res.send('Home Page')})
/* 等價於
app.get('/', [mw1, mw2], (req, res) => { res.send('Home Page')})
*/
③ next()
next 函式 是實現多箇中間件連續呼叫的關鍵,它表示把流轉關係轉交給下一個 中介軟體 或 路由。
注意:一個路由可以繫結多箇中間件
可以針對同一個請求設定多箇中間件,對同一個請求進行多次處理。
但是注意❕:預設情況下,請求從上到下依次匹配中介軟體,一旦匹配成功,終止匹配。
可以呼叫next()
將請求的控制權交給下一個中介軟體,直到遇到結束請求的中介軟體。
//呼叫next()之前需要在處理函式中傳遞 第三個引數next:許可權控制函式
app.get('/request', (req, res, next) => {
req.name = "張三";
next();
});
app.get('/request', (req, res) => {
res.send(req.name);
});
next()函式只能傳遞一個引數給錯誤處理中介軟體
2. 中介軟體的分類
① 應用級別的中介軟體
通過 app.use()
或 app.get()
或 app.post()
,繫結到 app 例項上的中介軟體,叫做應用級別的中介軟體
app.use()
客戶端發起的 任何請求,到達伺服器之後,都會觸發的中介軟體,叫做全域性生效的中介軟體。
通過呼叫 app.use(中介軟體函式)
,即可定義一個 全域性生效 的中介軟體,示例程式碼如下:
// 應用級別的中介軟體(全域性中介軟體)
app.use((req, res, next) => {
console.log(req.url);
next();
});
app.use 第一個引數也可以傳入請求地址,代表不論什麼請求方式,只要是這個請求地址就接收這個請求。
app.use('/admin', (req, res, next) => {
console.log(req.url);
next();
});
可以使用 app.use() 連續定義多個全域性中介軟體。客戶端請求到達伺服器之後,會按照中介軟體定義的先後順序依次進行呼叫,示例程式碼如下:
app.use(function(req, res, next){
console.log('呼叫了第1個全域性中介軟體')
next()
})
app.use(function(req, res, next){
console.log('呼叫了第2個全域性中介軟體')
next()
})
app.get('/user', function(req, res){
res.send('Home Page')
})
app.get() / app.post()
不使用 app.use()
定義的中介軟體,叫做 區域性生效的中介軟體,示例程式碼如下:
// 應用級別的中介軟體(區域性中介軟體)
app.get('/', mw1, (req, res) => {
res.send('Home Page')
})
依然可以定義多個區域性中介軟體,並按宣告順序依次呼叫
② 路由級別的中介軟體
繫結到 express.Router()
例項上的中介軟體,叫做路由級別的中介軟體。它的用法和應用級別中介軟體沒有任何區別。只不過,應用級別中介軟體是繫結到 app 例項上,路由級別中介軟體繫結到 router 例項上,程式碼示例如下
var app = express()
var router = express.Router()
//路由級別中介軟體
router.use(function(req, res, next){
console.log('Time: ', Data.now())
next()
})
app.use('/', router)
③ 錯誤級別的中介軟體
錯誤級別中介軟體的作用:專門用來捕獲整個專案中發生的異常錯誤,從而防止專案異常崩潰的問題。
格式:錯誤級別中介軟體的 function 處理函式中,必須有 4 個形參,形參順序從前到後,分別是 (err, req, res, next)
。
app.get('/', function(req, res){
throw new Error('伺服器內部發生了錯誤!')
res.send('Home Page')
})
app.use(function(err, req, res, next){
console.log('發生了錯誤: '+ err.message)
res.send('Error! '+ err.message )
})
注意:錯誤級別的中介軟體,必須註冊在所有路由之後!
Error()
new Error('錯誤資訊')
建構函式用來建立錯誤物件,通過throw
關鍵字丟擲錯誤物件
err
app.get('/index', (req, res) => {
//建立錯誤物件並丟擲,程式報錯
throw new Error('程式發生未知錯誤')
})
//錯誤處理中介軟體, 通過傳入引數err來接收程式丟擲的錯誤物件
app.use((err, req, res, next) => {
//err.message: 錯誤資訊
//通過status()手動設定http狀態碼, 可鏈式呼叫
res.status(500).send(err.message);
})
注意,錯誤處理中介軟體 只能捕獲同步程式碼執行出錯時throw的錯誤,無法捕獲非同步程式碼執行出錯時throw的錯誤
在node.js中,非同步API的錯誤資訊都是通過回撥函式獲取的,支援Promise物件的非同步API發生錯誤可以通過catch方法捕獲。
具體需要手動呼叫next()
方法,並且將錯誤資訊通過引數的形式傳遞給next()
方法,即可觸發錯誤處理中介軟體。
app.get("/", (req, res, next) => {
fs.readFile("/file-does-not-exist", (err, data) => {
if (err) {
//觸發錯誤中介軟體
next(err);
}
});
});
捕獲錯誤
非同步函式執行如果發生錯誤要如何捕獲錯誤呢?
try{}catch(ex){}
可以捕獲非同步函式以及其他同步程式碼在執行過程中發生的錯誤,但是不能其他型別的API發生的錯誤。
錯誤捕獲後通過next()錯誤資訊
即可觸發錯誤處理中介軟體
app.get("/", async (req, res, next) => {
try {
await User.find({name: '張三'})
}catch(ex) {
next(ex);
}
});
④ Express內建的中介軟體
自 Express 4.16.0 版本開始,Express 內建了 3 個常用的中介軟體,極大的提高了 Express 專案的開發效率和體驗:
express.static
快速託管靜態資源的內建中介軟體,例如: HTML 檔案、圖片、CSS 樣式等(無相容性)express.json
解析 JSON 格式的請求體資料(有相容性,僅在 4.16.0+ 版本中可用)express.urlencoded
解析 URL-encoded 格式的請求體資料(有相容性,僅在 4.16.0+ 版本中可用)
// 配置解析 application/json 格式資料的內建中介軟體
app.use(express.json())
// 配置解析 application/x-www-urlencoded 格式資料的內建中介軟體
app.use(express.urlencoded({extended: false}))
⑤ 第三方的中介軟體
非 Express 官方內建的,而是由第三方開發出來的中介軟體,叫做第三方中介軟體。在專案中,可以按需下載並配置第三方中介軟體,從而提高專案的開發效率。
例如:在 [email protected] 之前的版本中,經常使用 body-parser 這個第三方中介軟體,來解析請求體資料。使用步驟如下:
- 執行 npm install body-parser 安裝中介軟體
- 使用 require 匯入中介軟體
- 呼叫 app.use() 註冊並使用中介軟體
注意:Express 內建的 express.urlencoded 中介軟體,就是基於 body-parser 這個第三方中介軟體進一步封裝出來的。
3. 自定義中介軟體
① 需求描述與實現步驟
自己手動模擬一個類似於 express.urlencoded 這樣的中介軟體,來解析 POST 提交到伺服器的表單資料。
實現步驟:
- 定義中介軟體
- 監聽 req 的 data 事件
- 監聽 req 的 end 事件
- 使用 querystring 模組解析請求體資料
- 將解析出來的資料物件掛載為 req.body
- 將自定義中介軟體封裝為模組
// custom-body-parser.js
const qs = require('querystring');
function getBodyparam(req, res, next){
var str ='';
req.on('data', (chunk)=>{
str += chunk;
})
req.on('end', () => {
let body = qs.parse(str);
req.body = body;
next();
})
}
module.exports.myGetBodyparam = getBodyparam;
const express = require('express')
const bodyparam = require('./custom-body-parser')
const server = express();
// 自定義中介軟體
server.use(bodyparam.myGetBodyparam);
server.use((req, res) => {
console.log(req.body);
})
server.listen(3000, () => {
console.log('伺服器啟動成功')
})
五、使用 Express 寫介面
建立基本伺服器
// 匯入 express 模組
const express = require('express');
// 建立 express 的伺服器例項
const server = express();
// 匯入並註冊 路由模組
const apiRouter = require('./router/apiRouter')
server.use('/api',apiRouter);
// 指定埠並開啟伺服器
server.listen(3000, () => {
console.log('express server is running at http://localhost:3000')
})
建立 API 路由模組
// apiRouter.js [路由模組]
const express = require('express');
const apiRouter = express.Router();
const cors = require('cors');
/*
如果專案中已經配置了 CORS 跨域資源共享,為了防止衝突,必須在配置 CORS 中介軟體之前宣告 JSONP 的介面。
否則JSONP 介面會被處理成開啟了 CORS 的介面。
*/
//優先建立 JSONP 介面,並且這個介面不會被處理成 CORS 介面
apiRouter.get('/jsonp', (req, res) => {
// 獲取客戶端傳送過來的回撥函式名字
const funcName = req.query.callback;
// 得到通過 JSONP 形式傳送給客戶端的資料
const data = {name: 'lisi', age: 20};
// 拼接出一個函式呼叫字串
const scriptStr = `${funcName}(${JSON.stringify(data)})`;
// 把字串響應給客戶端的 <script> 標籤進行解析
res.send(scriptStr);
})
// 再配置 CORS 中介軟體,後面的所有介面,都會被處理成 CORS 介面
apiRouter.use(cors());
apiRouter.use(express.urlencoded({ extended: false }))
// 編寫 GET 介面
apiRouter.get('/get', (req, res) => {
// res.setHeader('Access-Control-Allow-Origin', '*');
// 1. 獲取客戶端通過查詢字串,傳送到伺服器的資料
const query = req.query;
// 2. 呼叫 res.send() 方法,把資料響應給客戶端
res.send({
status: 0, // 狀態,0 表示成功,1 表示失敗
msg: 'GET請求成功', // 狀態描述訊息
data: query // 需要響應給客戶端的具體資料
})
})
// 編寫 POST 介面
apiRouter.post('/post', (req, res) => {
// 獲取客戶端通過請求體,傳送到伺服器的 URL-encoded 資料
const body = req.body;
console.log(req.body);
// 呼叫 res.send() 方法, 把資料響應給客戶端
res.send({
status: 0, // 狀態,0 表示成功,1 表示失敗
msg: 'POST請求成功', // 狀態描述訊息
data: body // 需要響應給客戶端的具體資料
})
})
module.exports = apiRouter;
六、express-art-template模板引擎
app.engine()
app.set()
res.render()
為了使 art-template 模板引擎能夠更好的和 Express 框架配合,模板引擎官方在原 art-template 模板引擎的基礎上封裝了 express-art-template
,兩者語法上基本一致
使用npm install art-template express-art-template
命令進行安裝。
//當渲染字尾為art的模板時 使用express-art-template
app.engine('art', require('express-art-template')
/*app.set() : 對express框架進行配置, 通過第一個引數指定當前需要配置什麼
'views': 設定模板存放目錄
'view engine': 設定模板預設字尾
*/
app.set('views', path.join(__dirname, 'views'));
// 渲染模板時不寫字尾 預設拼接art字尾
app.set('view engine', 'art');
app.get('/', (req, res) => {
/* res.render(): 渲染模板
方式內部邏輯:
1. 拼接模板路徑
2. 拼接模板字尾
3. 哪一個模板和哪一個資料進行拼接
4. 將拼接結果響應給了客戶端
引數
引數1:模板檔名
引數2:渲染資料
*/
res.render('index',{});
});
app.locals 物件
將變數設定到app.locals物件下面,這個資料在所有的模板中都可以獲取到。
app.locals.users = [{
name: '張三',
age: 20
},{
name: '李四',
age: 20
}]
<ul>
{{each users}}
<li>$value.name</li>
<li>$value.age</li>
{{/each}}
</ul>