目錄
一、express是什么?
二、安裝?express
三、安裝Mysql
四、安裝 nodemon 實現(xiàn)項目熱更新
五、這里先了解下express的post get delete接口
post接口說明:
get接口說明?:
?DELETE 接口
六、注冊功能
1、流程分析
校驗表單數(shù)據(jù)是否合法
檢測用戶名是否占用
密碼加密處理
插入新用戶
2、完整注冊接口
regUser(req, res) 注冊函數(shù)
七、封裝錯誤處理函數(shù)(即:注冊功能使用的res.cc)
八、登錄功能
1、流程分析
1.判斷前端提交的后端的數(shù)據(jù)是否合法。
2.查詢登錄的用戶是否存在。
3.判斷當(dāng)前用戶的密碼是否正確。
2、生成token字符
?1.安裝jsonwebtoken用于生成token
2.導(dǎo)入jsonwebtoken
?3.全局配置文件(里面有token的密鑰)
3.使用jwt.sign對用戶的信息進(jìn)行加密,生成 token 字符串?
4.登錄成功后將生成的token返回給客戶端
3、登錄接口的完整代碼
1.login()登錄函數(shù)代碼
九、最后附上users路由模塊和登錄注冊函數(shù).js的完整代碼
users.js
login.js登錄注冊函數(shù)
接口文檔:
十、解析token中間件
?1.安裝解析中間件
2.在App.js 中引入
3.注冊全局中間件并解析token
4.注冊全局錯誤中間件 當(dāng)token失效時 返回信息
十一、CORS跨域中間件
?1. 使用 cors 中間件解決跨域問題
完整代碼:(jsonp就不做過多解釋了)
1、CORS 響應(yīng)頭部 - Access-Control-Allow-Origin
2、CORS 響應(yīng)頭部 - Access-Control-Allow-Headers
3、CORS 響應(yīng)頭部 - Access-Control-Allow-Methods
4、?CORS請求的分類
5、 簡單請求
6、預(yù)檢請求
一、express是什么?
- Express 是一個簡潔而靈活的 node.js?Web應(yīng)用框架, 提供了一系列強大特性幫助你創(chuàng)建各種 Web 應(yīng)用,和豐富的 HTTP 工具。
- 使用 Express 可以快速地搭建一個完整功能的網(wǎng)站。
- Express 框架核心特性:
-
可以設(shè)置中間件來響應(yīng) HTTP 請求。
-
定義了路由表用于執(zhí)行不同的 HTTP 請求動作。
-
可以通過向模板傳遞參數(shù)來動態(tài)渲染 HTML 頁面。
二、安裝?express
搭建Express項目有兩種方式:
- 方式一:從零搭建自己的express應(yīng)用結(jié)構(gòu)
- 方式二:安裝express-generator腳手架 一鍵生成express項目
在這里我們使用方式二快速構(gòu)建一個express項目:
????????express-generator 是 Express 應(yīng)用程序生成器工具,我們可以使用它來快速創(chuàng)建應(yīng)用程序框架。
- 在項目文件夾下打開node終端
-
安裝express-generator 腳手架
npm install -g express-generator
- 創(chuàng)建項目
express expressFrame (expressFrame 是項目名)
? ? ? ? ?執(zhí)行完后項目目錄下的結(jié)構(gòu)
?
?注:bin/www 是啟動入口文件,在里面可以設(shè)置端口號等
3.?安裝依賴
npm install
4. 啟動項目
?
npm start
此時在瀏覽器打開?http://localhost:3000/
?出現(xiàn)以上頁面,那么恭喜你express服務(wù)器已創(chuàng)建完成
三、安裝Mysql
npm i mysql
1、新建db 數(shù)據(jù)庫文件夾,文件夾下新建index.js 用來配置數(shù)據(jù)庫信息,index.js內(nèi)容如下
// 導(dǎo)入 mysql 模塊
const mysql = require('mysql')
// 建立與 MySQL 數(shù)據(jù)庫的連接
const db = mysql.createPool({
host: '127.0.0.1', // 數(shù)據(jù)庫的IP地址
port: 3306, //數(shù)據(jù)庫端口號
user: 'web2245321733', // 登錄數(shù)據(jù)庫的賬號
password: 'web2245321733', // 登錄數(shù)據(jù)庫的密碼
database: 'web2245321733' // 指定要操作哪個數(shù)據(jù)庫
})
// 檢測數(shù)據(jù)庫是否連接成功
db.query("select 1", (err, results) => {
if (err) return console.log(err);
console.log(results, '數(shù)據(jù)庫鏈接成功');
});
module.exports = db
此時重啟項目,終端看到打印出數(shù)據(jù)庫鏈接成功,
四、安裝 nodemon 實現(xiàn)項目熱更新
在剛才的添加數(shù)據(jù)庫當(dāng)中,我們發(fā)現(xiàn)每次修改代碼都需要重啟項目,非常麻煩
1、安裝 nodemon 來監(jiān)控 node.js 源代碼的任何變化和自動重啟你的服務(wù)器
npm install -g nodemon
2、在package.json中修改啟動命令
?然后重啟項目,就可以了。
五、這里先了解下express的post get delete接口
post接口說明:
// 定義 POST 接口
router.post('/post', (req, res) => {
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
const body = req.body
// 調(diào)用 res.send() 方法,向客戶端響應(yīng)結(jié)果
res.send({
status: 0, // 0 表示處理成功,1 表示處理失敗
msg: 'POST 請求成功!', // 狀態(tài)的描述
data: body, // 需要響應(yīng)給客戶端的數(shù)據(jù)
})
})
get接口說明?:
// 在這里掛載對應(yīng)的路由
router.get('/get', (req, res) => {
// 通過 req.query 獲取客戶端通過查詢字符串,發(fā)送到服務(wù)器的數(shù)據(jù)
const query = req.query
// 調(diào)用 res.send() 方法,向客戶端響應(yīng)處理的結(jié)果
res.send({
status: 0, // 0 表示處理成功,1 表示處理失敗
msg: 'GET 請求成功!', // 狀態(tài)的描述
data: query, // 需要響應(yīng)給客戶端的數(shù)據(jù)
})
})
?DELETE 接口
// 定義 DELETE 接口
router.delete('/delete', (req, res) => {
res.send({
status: 0,
msg: 'DELETE請求成功',
})
})
參數(shù)說明:
- router.post 用于創(chuàng)建post接口? 它有兩個參數(shù) 參數(shù)1:路由匹配規(guī)則? ? 參數(shù)2:請求的回調(diào)函數(shù)
- 回調(diào)函數(shù)的req參數(shù):客戶端請求信息,包括 請求頭 請求參數(shù)等,req.bodey或req.query獲取請求參數(shù)
- 回調(diào)函數(shù)的res參數(shù):用于提交服務(wù)端的響應(yīng)給客戶端
- 調(diào)用 res.send() 方法,向客戶端響應(yīng)處理的結(jié)果
六、注冊功能
1、流程分析
注冊的一般流程 1.校驗表單數(shù)據(jù)是否合法 2.檢測用戶名是否占用 3.密碼加密處理 4.插入新用戶
校驗表單數(shù)據(jù)是否合法
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
const userInfo = req.body
//【步驟一】對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
檢測用戶名是否占用
// 定義sql語句,查詢用戶名是否被占用
let sql = 'select * from ev_users where username=?'
db.query(sql, [userInfo.username], (error, result) => {
if (error) {
return res.cc(error)
}
if (result.length > 0) {
return res.cc('用戶名已被占用!')
}
})
密碼加密處理
安裝bcryptjs 加密包 用于密碼加密
npm i bcryptjs
?引入加密包
// 導(dǎo)入 bcryptjs 加密包
const bcrypt = require('bcryptjs')
調(diào)用 bcrypt.hashSync() 對密碼加密
// 調(diào)用 bcrypt.hashSync() 對密碼加密
userInfo.password = bcrypt.hashSync(userInfo.password, 10)
?說明:bcrypt.hashSync() 的參數(shù)1:要加密的密碼? 參數(shù)2:?加密等級 填10即可
插入新用戶
前面我們已經(jīng)連接過數(shù)據(jù)庫了,這里我們直接引入數(shù)據(jù)庫操作模塊
// 導(dǎo)入數(shù)據(jù)庫操作模塊
const db = require('../../db/index')
?定義插入新用戶的 SQL 語句
// 定義插入新用戶的 SQL 語句
let sql1 = 'insert into ev_users set ?'
?調(diào)用 db.query() 執(zhí)行 sql 語句 插入新用戶 并給客戶端返回結(jié)果
// 調(diào)用 db.query() 執(zhí)行 sql 語句
db.query(sql1, {
username: userInfo.username,
password: userInfo.password
}, (error, result) => {
if (error) return res.cc(error)
// 判斷影響行數(shù)是否為 1
if (result.affectedRows !== 1) return res.cc('注冊用戶失?。?)
return res.cc('注冊用戶成功', 0, {
username: userInfo.username
})
})
2、完整注冊接口
- 打開routes文件夾下的users.js 路由模塊,添加以下內(nèi)容
/**
* POST 用戶注冊
* @param username 用戶名
* @param password 用戶密碼
*/
router.post('/add', (req, res, next) => {
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
console.log(req.body)
const userInfo = req.body
//【步驟一】對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
// 【步驟二】執(zhí)行定義好的注冊函數(shù)
regUser(req, res)
});
regUser(req, res) 注冊函數(shù)
在這里我單獨新建了個login.js文件用于寫注冊和登錄的處理的函數(shù)放
?
?引入
// 導(dǎo)入寫好的注冊/登錄函數(shù)
const {
regUser,
login
} = require('../public/javascripts/login')
regUser(req, res) 注冊函數(shù)內(nèi)容如下:
// 導(dǎo)入數(shù)據(jù)庫操作模塊
const db = require('../../db/index')
// 導(dǎo)入 bcryptjs 加密包
const bcrypt = require('bcryptjs')
/**
* POST 用戶注冊
* @param username 用戶名
* @param password 用戶密碼
*/
exports.regUser = (req, res) => {
// 獲取客戶端提交到服務(wù)器的用戶信息
const userInfo = req.body
// 定義sql語句,查詢用戶名是否被占用
let sql = 'select * from ev_users where username=?'
db.query(sql, [userInfo.username], (error, result) => {
if (error) {
return res.cc(error)
}
if (result.length > 0) {
return res.cc('用戶名已被占用!')
}
// 調(diào)用 bcrypt.hashSync() 對密碼加密
userInfo.password = bcrypt.hashSync(userInfo.password, 10)
// 定義插入新用戶的 SQL 語句
let sql1 = 'insert into ev_users set ?'
// 調(diào)用 db.query() 執(zhí)行 sql 語句
db.query(sql1, {
username: userInfo.username,
password: userInfo.password
}, (error, result) => {
if (error) return res.cc(error)
// 判斷影響行數(shù)是否為 1
if (result.affectedRows !== 1) return res.cc('注冊用戶失敗!')
return res.cc('注冊用戶成功', 0, {
username: userInfo.username
})
})
})
}
/**
* POST 登錄的回調(diào)函數(shù)
* @param username 用戶名
* @param password 用戶密碼
*/
exports.login = (req, res) => {
}
七、封裝錯誤處理函數(shù)(即:注冊功能使用的res.cc)
在注冊功能里我對res.send向客戶端響應(yīng)函數(shù)做了處理,
在app.js中,放在路由前面
//封裝錯誤處理函數(shù)
app.use((req, res, next) => {
res.cc = function(err, status = 1, data = {}) {
res.send({
status,
data,
message: err instanceof Error ? err.message : err
})
}
next()
})
八、登錄功能
1、流程分析
登錄的一般流程 1.判斷前端提交的后端的數(shù)據(jù)是否合法。 2.查詢登錄的用戶是否存在。 3.判斷當(dāng)前用戶的密碼是否正確。
1.判斷前端提交的后端的數(shù)據(jù)是否合法。
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
console.log(req.body)
const userInfo = req.body
//【步驟一】對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
2.查詢登錄的用戶是否存在。
// 定義 SQL 語句
const sql = 'select * from ev_users where username=?'
// 執(zhí)行 SQL 語句,根據(jù)用戶名查詢用戶的信息
db.query(sql, userInfo.username, (err, result) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// 執(zhí)行 SQL 語句成功,但是獲取的數(shù)據(jù)條數(shù)不為1 也是失敗的
if (result.length !== 1) return res.cc('登錄失??!')
// 經(jīng)過上方倆條判斷條件,則證明執(zhí)行 SQL 成功
})
3.判斷當(dāng)前用戶的密碼是否正確。
使用 加密包的bcrypt.compareSync方法對比用戶提交的密碼和數(shù)據(jù)庫中的密碼是否一致,如果一直即:登錄成功
// TODO :判斷密碼是否正確
const comRes = bcrypt.compareSync(userInfo.password, result[0].password)
if (!comRes) return res.cc('登陸失敗')
2、生成token字符
密碼正確的話,登錄成功,根據(jù)用戶信息生成唯一的token返回給客戶端
?1.安裝jsonwebtoken用于生成token
npm i jsonwebtoken
2.導(dǎo)入jsonwebtoken
// 導(dǎo)入生成Token的包
const jwt = require('jsonwebtoken')
?3.全局配置文件(里面有token的密鑰)
config.js放在根目錄
// 全局配置文件 config.js
module.exports = {
// 加密和解密 token 的密鑰
jwtSecretKey: 'itheima No1. ^_^',
// token 有效期
expiresIn: '10h'
}
導(dǎo)入
// 導(dǎo)入全局配置文件(里面有token的密鑰)
const config = require('../../config')
3.使用jwt.sign對用戶的信息進(jìn)行加密,生成 token 字符串?
jwt.sign 有三個參數(shù)依次是 生成token的數(shù)據(jù),加密的形式,token有效期
?加密前先處理用戶信息,將用戶的敏感信息置空(如:密碼等)
// 在服務(wù)器端生成 Token 字符串
const user = {
...result[0], // 解構(gòu)用戶信息
password: '', //密碼等敏感信息置空
}
// 對用戶的信息進(jìn)行加密,生成 token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn //tonken 有效期
})
4.登錄成功后將生成的token返回給客戶端
// 調(diào)用 res.send 將Token響應(yīng)給客戶端
res.send({
status: 0,
data: {
user: user,
token: 'Bearer ' + tokenStr,
},
message: '登錄成功?。?!',
})
3、登錄接口的完整代碼
/**
* POST 用戶登錄
* @param username 用戶名
* @param password 用戶密碼
*/
router.post('/login', (req, res, next) => {
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
// console.log(req.body)
const userInfo = req.body
//對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
// 執(zhí)行定義好的登錄函數(shù)
login(req, res)
});
1.login()登錄函數(shù)代碼
/**
* POST 登錄的回調(diào)函數(shù)
* @param username 用戶名
* @param password 用戶密碼
*/
exports.login = (req, res) => {
console.log('user', req.user);
// 接收表單的數(shù)據(jù)
const userInfo = req.body
// 定義 SQL 語句
const sql = 'select * from ev_users where username=?'
// 執(zhí)行 SQL 語句,根據(jù)用戶名查詢用戶的信息
db.query(sql, userInfo.username, (err, result) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// 執(zhí)行 SQL 語句成功,但是獲取的數(shù)據(jù)條數(shù)不為1 也是失敗的
if (result.length !== 1) return res.cc('登錄失?。?)
// 經(jīng)過上方倆條判斷條件,則證明執(zhí)行 SQL 成功
// TODO :判斷密碼是否正確
const comRes = bcrypt.compareSync(userInfo.password, result[0].password)
if (!comRes) return res.cc('登陸失敗')
// 在服務(wù)器端生成 Token 字符串
const user = {
...result[0], // 解構(gòu)用戶信息
password: '', //密碼等敏感信息置空
}
// 對用戶的信息進(jìn)行加密,生成 token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn //tonken 有效期
})
// 調(diào)用 res.send 將Token響應(yīng)給客戶端
res.send({
status: 0,
data: {
user: user,
token: 'Bearer ' + tokenStr,
},
message: '登錄成功?。。?,
})
})
}
九、最后附上users路由模塊和登錄注冊函數(shù).js的完整代碼
users.js
var express = require('express');
var router = express.Router();
// 導(dǎo)入寫好的注冊/登錄函數(shù)
const {
regUser,
login
} = require('../public/javascripts/login')
/* GET users listing. */
router.get('/', function(req, res, next) {
// 獲取客戶端提交到服務(wù)器的用戶信息
const userInfo = req.body
if(req.user){
return res.cc('獲取成功', 0, req.user)
}
// 獲取到中間件的時間
res.send('GET 請求成功');
});
/**
* POST 用戶注冊
* @param username 用戶名
* @param password 用戶密碼
*/
router.post('/add', (req, res, next) => {
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
console.log(req.body)
const userInfo = req.body
//【步驟一】對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
// 【步驟二】執(zhí)行定義好的注冊函數(shù)
regUser(req, res)
});
/**
* POST 用戶登錄
* @param username 用戶名
* @param password 用戶密碼
*/
router.post('/login', (req, res, next) => {
// 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
// console.log(req.body)
const userInfo = req.body
//對客戶端的數(shù)據(jù)進(jìn)行校驗
if (userInfo.username == '' || userInfo.password == '') {
return res.send({
status: 1,
msg: '用戶名和密碼不能為空'
})
}
// 執(zhí)行定義好的登錄函數(shù)
login(req, res)
});
/* 模板 */
// // 在這里掛載對應(yīng)的路由
// router.get('/get', (req, res) => {
// // 通過 req.query 獲取客戶端通過查詢字符串,發(fā)送到服務(wù)器的數(shù)據(jù)
// const query = req.query
// // 調(diào)用 res.send() 方法,向客戶端響應(yīng)處理的結(jié)果
// res.send({
// status: 0, // 0 表示處理成功,1 表示處理失敗
// msg: 'GET 請求成功!', // 狀態(tài)的描述
// data: query, // 需要響應(yīng)給客戶端的數(shù)據(jù)
// })
// })
// // 定義 POST 接口
// router.post('/post', (req, res) => {
// // 通過 req.body 獲取請求體中包含的 url-encoded 格式的數(shù)據(jù)
// const body = req.body
// // 調(diào)用 res.send() 方法,向客戶端響應(yīng)結(jié)果
// res.send({
// status: 0,
// msg: 'POST 請求成功!',
// data: body,
// })
// })
// // 定義 DELETE 接口
// router.delete('/delete', (req, res) => {
// res.send({
// status: 0,
// msg: 'DELETE請求成功',
// })
// })
module.exports = router;
login.js登錄注冊函數(shù)
// 導(dǎo)入數(shù)據(jù)庫操作模塊
const db = require('../../db/index')
// 導(dǎo)入 bcryptjs 加密包
const bcrypt = require('bcryptjs')
// 導(dǎo)入生成Token的包
const jwt = require('jsonwebtoken')
// 導(dǎo)入全局配置文件(里面有token的密鑰)
const config = require('../../config')
/**
* POST 用戶注冊
* @param username 用戶名
* @param password 用戶密碼
*/
exports.regUser = (req, res) => {
// 獲取客戶端提交到服務(wù)器的用戶信息
const userInfo = req.body
// 定義sql語句,查詢用戶名是否被占用
let sql = 'select * from ev_users where username=?'
db.query(sql, [userInfo.username], (error, result) => {
if (error) {
return res.cc(error)
}
if (result.length > 0) {
return res.cc('用戶名已被占用!')
}
// 調(diào)用 bcrypt.hashSync() 對密碼加密
userInfo.password = bcrypt.hashSync(userInfo.password, 10)
// 定義插入新用戶的 SQL 語句
let sql1 = 'insert into ev_users set ?'
// 調(diào)用 db.query() 執(zhí)行 sql 語句
db.query(sql1, {
username: userInfo.username,
password: userInfo.password
}, (error, result) => {
if (error) return res.cc(error)
// 判斷影響行數(shù)是否為 1
if (result.affectedRows !== 1) return res.cc('注冊用戶失??!')
return res.cc('注冊用戶成功', 0, {
username: userInfo.username
})
})
})
}
/**
* POST 登錄的回調(diào)函數(shù)
* @param username 用戶名
* @param password 用戶密碼
*/
exports.login = (req, res) => {
console.log('user', req.user);
// 接收表單的數(shù)據(jù)
const userInfo = req.body
// 定義 SQL 語句
const sql = 'select * from ev_users where username=?'
// 執(zhí)行 SQL 語句,根據(jù)用戶名查詢用戶的信息
db.query(sql, userInfo.username, (err, result) => {
// 執(zhí)行 SQL 語句失敗
if (err) return res.cc(err)
// 執(zhí)行 SQL 語句成功,但是獲取的數(shù)據(jù)條數(shù)不為1 也是失敗的
if (result.length !== 1) return res.cc('登錄失敗!')
// 經(jīng)過上方倆條判斷條件,則證明執(zhí)行 SQL 成功
// TODO :判斷密碼是否正確
const comRes = bcrypt.compareSync(userInfo.password, result[0].password)
if (!comRes) return res.cc('登陸失敗')
// 在服務(wù)器端生成 Token 字符串
const user = {
...result[0], // 解構(gòu)用戶信息
password: '', //密碼等敏感信息置空
}
// 對用戶的信息進(jìn)行加密,生成 token 字符串
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn //tonken 有效期
})
// 調(diào)用 res.send 將Token響應(yīng)給客戶端
res.send({
status: 0,
data: {
user: user,
token: 'Bearer ' + tokenStr,
},
message: '登錄成功?。?!',
})
})
}
到此我們的登錄和注冊接口已經(jīng)實現(xiàn),
接口文檔:
注冊:post? ? ? ?http://127.0.0.1/users/add
? ? ? ? ? ?請求參數(shù):username:用戶名? ?
?????????????????????????????password:密碼
登錄:post? ? ? ?http://127.0.0.1/users/login
? ? ? ? ? ?請求參數(shù):username:用戶名? ?
?????????????????????????????password:密碼
十、解析token中間件
在剛剛我沒完成了token的生成,現(xiàn)在我們做一個中間件用來解析token,來對用戶進(jìn)行身份認(rèn)證
?1.安裝解析中間件
npm i express-jwt
2.在App.js 中引入
//token解析中間件 一定要在路由之前配置解析 Token 的中間件
const expressJWT = require('express-jwt')
//映入解密
const config = require('./config')
3.注冊全局中間件并解析token
// 注冊全局中間件 鏈?zhǔn)秸{(diào)用 unless 方法,接收一個配置對象,path 字段設(shè)置一個正則表達(dá)式,表示不需要 token 身份認(rèn)證的路由前綴。
app.use(expressJWT({
// 加密時設(shè)置的密鑰
secret: config.jwtSecretKey,
// 設(shè)置算法
algorithms: ['HS256'],
// 無token請求不進(jìn)行解析,并且拋出異常
// credentialsRequired: false
}).unless({
path: [
'/users/add',
'/users/login',
{
url: /^\/public\/.*/,
methods: ['GET', 'POST']
}
]
// path: ['/users/login','/users']
}))
4.注冊全局錯誤中間件 當(dāng)token失效時 返回信息
// 錯誤中間件 當(dāng)token失效時 返回信息
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).send({
status: 1,
data: {},
message: '身份認(rèn)證失敗!'
});
}
});
十一、CORS跨域中間件
注冊登錄接口寫好了,但是使用的時候會有一個很嚴(yán)重的問題:不支持跨域請求。
解決接口跨域問題的方案主要有兩種:
① CORS(主流的解決方案,推薦使用)
② JSONP(有缺陷的解決方案:只支持 GET 請求)
?1. 使用 cors 中間件解決跨域問題
cors 是 Express 的一個第三方中間件。通過安裝和配置 cors 中間件,可以很方便地解決跨域問題。
使用步驟分為如下 3 步:
① 運行 npm install cors 安裝中間件
npm install cors
② 使用 const cors = require(‘cors’) 導(dǎo)入中間件
// 一定要在路由之前,配置 cors 這個中間件,從而解決接口跨域的問題
const cors = require('cors')
③ 在路由之前調(diào)用 app.use(cors()) 配置中間件
app.use(cors())
完整代碼:(jsonp就不做過多解釋了)
// 【必須在配置 cors 中間件之前,配置 JSONP 的接口】
app.get('/api/jsonp', (req, res) => {
// TODO: 定義 JSONP 接口具體的實現(xiàn)過程
// 1. 得到函數(shù)的名稱
const funcName = req.query.callback
// 2. 定義要發(fā)送到客戶端的數(shù)據(jù)對象
const data = {
name: 'zs',
age: 22
}
// 3. 拼接出一個函數(shù)的調(diào)用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,響應(yīng)給客戶端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 這個中間件,從而解決接口跨域的問題
const cors = require('cors')
app.use(cors())
注意事項:
①CORS 主要在服務(wù)器端進(jìn)行配置??蛻舳藶g覽器無須做任何額外的配置,即可請求開啟了CORS的接口。
②CORS在瀏覽器中有兼容。只有支持XMLHttpRequest Level2的瀏覽器,才能正常訪問開啟了CORS的服務(wù)端接口(例如:IE10+、Chrome4+、FireFox3.5+)。
1、CORS 響應(yīng)頭部 - Access-Control-Allow-Origin
如果指定了 Access-Control-Allow-Origin 字段的值為通配符 *,表示允許來自任何域的請求,示例代碼如下:
res.setHeader('Access-Control-Allow-Origin','*')
2、CORS 響應(yīng)頭部 - Access-Control-Allow-Headers
默認(rèn)情況下,CORS 僅支持客戶端向服務(wù)器發(fā)送如下的 9 個請求頭:
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值僅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客戶端向服務(wù)器發(fā)送了額外的請求頭信息,則需要在服務(wù)器端,通過 Access-Control-Allow-Headers 對額外的請求頭進(jìn)行聲明,否則這次請求會失敗!
?
// 允許客戶端額外向服務(wù)器發(fā)送 Content-Type 請求頭和 X-Custom-Header 請求頭
// 注意: 多個請求頭之間使用英文的逗號進(jìn)行分割
res.setHeader('Access-Control-Allow-Headers ''Content-Type X-Custom-Header')
3、CORS 響應(yīng)頭部 - Access-Control-Allow-Methods
默認(rèn)情況下,CORS 僅支持客戶端發(fā)起 GET、POST、HEAD 請求。
如果客戶端希望通過?PUT、DELETE?等方式請求服務(wù)器的資源,則需要在服務(wù)器端,通過 Access-Control-Alow-Methods來指明實際請求所允許使用的 HTTP 方法。示例代碼如下:
// 只允許 POST、GET、DELETE、HEAD 請求方法
res,setHeader('Access-Control-A1low-Methods','POST,GET,DELETE,HEAD')
// 允許所有的 HTTP 請求方法
res,setHeader('Access-Control-A1low-Methods','*')
4、?CORS請求的分類
客戶端在請求 CORS 接口時,根據(jù) 請求方式和請求頭的不同,可以將 CORS 的請求分為兩大類,分別是:
① 簡單請求
② 預(yù)檢請求
5、 簡單請求
同時滿足以下兩大條件的請求,就屬于簡單請求:
① 請求方式:GET、POST、HEAD 三者之一
② HTTP 頭部信息不超過以下幾種字段:無自定義頭部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三個值application/x-www-form-urlencoded、multipart/form-data、text/plain)
6、預(yù)檢請求
只要符合以下任何一個條件的請求,都需要進(jìn)行預(yù)檢請求:
① 請求方式為 GET、POST、HEAD 之外的請求 Method 類型
② 請求頭中包含自定義頭部字段
③ 向服務(wù)器發(fā)送了 application/json 格式的數(shù)據(jù)
在瀏覽器與服務(wù)器正式通信之前,瀏覽器會先發(fā)送 OPTION 請求進(jìn)行預(yù)檢,以獲知服務(wù)器是否允許該實際請求,所以這一次的 OPTION 請求稱為“預(yù)檢請求”。服務(wù)器成功響應(yīng)預(yù)檢請求后,才會發(fā)送真正的請求,并且攜帶真實數(shù)據(jù)。
7.、簡單請求和預(yù)檢請求的區(qū)別
簡單請求的特點:客戶端與服務(wù)器之間只會發(fā)生一次請求。
預(yù)檢請求的特點:客戶端與服務(wù)器之間會發(fā)生兩次請求,OPTION 預(yù)檢請求成功之后,才會發(fā)起真正的請求。
2、最后附上app.js完整代碼?
// 導(dǎo)入 express
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
// 引入路由模塊
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// 創(chuàng)建服務(wù)器實例
var app = express();
app.use(logger('dev'));
// 處理 application/json
app.use(express.json());
// 配置解析表單數(shù)據(jù)的中間件 處理 x-www-form-urlencoded
app.use(express.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
//token解析中間件 一定要在路由之前配置解析 Token 的中間件
const expressJWT = require('express-jwt')
//映入解密
const config = require('./config')
// 注冊全局中間件 鏈?zhǔn)秸{(diào)用 unless 方法,接收一個配置對象,path 字段設(shè)置一個正則表達(dá)式,表示不需要 token 身份認(rèn)證的路由前綴。
app.use(expressJWT({
// 加密時設(shè)置的密鑰
secret: config.jwtSecretKey,
// 設(shè)置算法
algorithms: ['HS256'],
// 無token請求不進(jìn)行解析,并且拋出異常
// credentialsRequired: false
}).unless({
path: [
'/users/add',
'/users/login',
{
url: /^\/public\/.*/,
methods: ['GET', 'POST']
}
]
// path: ['/users/login','/users']
}))
// 【必須在配置 cors 中間件之前,配置 JSONP 的接口】
app.get('/api/jsonp', (req, res) => {
// TODO: 定義 JSONP 接口具體的實現(xiàn)過程
// 1. 得到函數(shù)的名稱
const funcName = req.query.callback
// 2. 定義要發(fā)送到客戶端的數(shù)據(jù)對象
const data = {
name: 'zs',
age: 22
}
// 3. 拼接出一個函數(shù)的調(diào)用
const scriptStr = `${funcName}(${JSON.stringify(data)})`
// 4. 把拼接的字符串,響應(yīng)給客戶端
res.send(scriptStr)
})
// 一定要在路由之前,配置 cors 這個中間件,從而解決接口跨域的問題
const cors = require('cors')
app.use(cors())
//定義第一個全局中間件
app.use((req, res, next) => { //res用于返回客戶端 req客戶端的請求參數(shù) next() 提交給下一個中間件
// 只允許 請求方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE,HEAD')
// 響應(yīng)頭 允許所有的 HTTP 請求方法
res.setHeader('Access-Control-Allow-Methods', '*')
// 如果指定了 Access-Control-Allow-Origin 字段的值為通配符 *,表示允許來自任何域的請求
res.setHeader('Access-Control-Allow-Origin', '*')
// res.setHeader("Access-Control-Allow-Headers", "content-type,Authorization");
next();
})
//封裝錯誤處理函數(shù)
app.use((req, res, next) => {
res.cc = function(err, status = 1, data = {}) {
res.send({
status,
data,
message: err instanceof Error ? err.message : err
})
}
next()
})
// 錯誤中間件 當(dāng)token失效時 返回信息
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).send({
status: 1,
data: {},
message: '身份認(rèn)證失??!'
});
}
});
// 掛載路由
app.use('/', indexRouter);
/* 用戶路由 */
app.use('/users', usersRouter);
module.exports = app;
?跨域這部分和jsonp參考了以下文章?文章來源:http://www.zghlxwxcb.cn/news/detail-672639.html
【nodejs-03】黑馬nodejs學(xué)習(xí)筆記03-express中間件與跨域_簡單長庚的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-672639.html
到了這里,關(guān)于基于NodeJs+Express+MySQL 實現(xiàn)實現(xiàn)登錄注冊接口+token生成與解析驗證+跨域-CORS的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!