1. 程式人生 > 遊戲攻略 >《真女神轉生5》仲魔愛麗絲培養思路

《真女神轉生5》仲魔愛麗絲培養思路

1. Express 簡介 2. Express 的基本使用 3. Express 路由 4. Express 中介軟體 5. 使用 Express 寫介面 6. express-art-template模板引擎

@

目錄

一、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 目錄下面的檔案就可以訪問了, 比如:

注意: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 函式進行處理。

路由匹配的注意點:

  1. 按照定義的 先後順序 進行匹配
  2. 請求型別請求的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 上,而是 推薦將路由抽離為單獨的模組
將路由抽離為單獨模組的步驟如下:

  1. 建立路由模組對應的 .js 檔案
  2. 呼叫 express.Router() 函式建立路由物件
  3. 向路由物件上掛載具體的路由
  4. 使用 module.exports 向外共享路由物件
  5. 使用 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 引數。而普通路由處理函式中只包含 reqres

一個路由可以繫結多箇中間件函式:

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 專案的開發效率和體驗:

  1. express.static 快速託管靜態資源的內建中介軟體,例如: HTML 檔案、圖片、CSS 樣式等(無相容性)
  2. express.json 解析 JSON 格式的請求體資料(有相容性,僅在 4.16.0+ 版本中可用)
  3. 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 這個第三方中介軟體,來解析請求體資料。使用步驟如下:

  1. 執行 npm install body-parser 安裝中介軟體
  2. 使用 require 匯入中介軟體
  3. 呼叫 app.use() 註冊並使用中介軟體

注意:Express 內建的 express.urlencoded 中介軟體,就是基於 body-parser 這個第三方中介軟體進一步封裝出來的。



3. 自定義中介軟體

① 需求描述與實現步驟

自己手動模擬一個類似於 express.urlencoded 這樣的中介軟體,來解析 POST 提交到伺服器的表單資料。
實現步驟:

  1. 定義中介軟體
  2. 監聽 req 的 data 事件
  3. 監聽 req 的 end 事件
  4. 使用 querystring 模組解析請求體資料
  5. 將解析出來的資料物件掛載為 req.body
  6. 將自定義中介軟體封裝為模組
// 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>