Node 第 6 天
1. 学习目标
◆ 前后端的身份认证
◆ 能够了解 Session 的实现原理
◆ 能够了解 JWT 的实现原理
◆ 大事件项目初始化
◆ 大事件注册新用户功能
2. web 开发模式
2.1 主流的两种开发模式
- 基于服务端渲染的传统 Web 开发模式
- 基于前后端分离的新型 Web 开发模式
2.2 了解服务端渲染的概念以及优缺点
- 服务端渲染的概念:服务器发送给客户端的
HTML
页面,是在服务器通过字符串的拼接动态生成的。因此,客户端不需要使用 Ajax 这样的技术额外请求页面的数据
- 服务端渲染的优点
服务端渲染的缺点
- 占用服务器端资源。即服务器端完成 HTML 页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力
- 不利于前后端分离,开发效率低。使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于项目高效开发
2.3 了解前后端分离的概念以及优缺点
前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式,就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式
前后端分离的优点
开发体验好。前端专注于 UI 页面的开发,后端专注于api 的开发,且前端有更多的选择性
用户体验好。Ajax 技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新
减轻了服务器端的渲染压力。因为页面最终是在每个用户的浏览器中生成的
- 前后端分离的缺点
- 不利于 SEO。因为完整的 HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息。(解决方案:利用 Vue、React 等前端框架的 SSR 技术能够很好的解决 SEO 问题!)
2.4 如何选择 Web 开发模式
- 不谈业务场景而盲目选择使用何种开发模式都是耍流氓
- 另外,具体使用何种开发模式并不是绝对的,为了同时兼顾了首页的渲染速度和前后端分离的开发效率,一些网站采用了首屏服务器端渲染 + 其他页面前后端分离的开发模式
3. 身份认证
3.1 什么是身份认证
- 身份认证,又称 ”身份验证“,”鉴权“,是指通过一定的手段,完成对用户身份的确认,例如:
- 各大网站的手机验证码登录
- 邮箱密码登录
- 二维码登录
3.2 为什么需要身份认证
- 身份认证的目的,是为了确认当前所声称为某种身份的用户,确实是所声称的用户
- 例如:你去找快递员取快递,你要怎么证明这份快递是你的
- 不同开发模式下的身份认证
- 对于服务端渲染和前后端分离这两种开发模式来说,分别有着不同的身份认证方案
- 服务端渲染 推荐使用 Session 认证机制
- 前后端分离推荐使用 JWT 认证机制
4. session
4.1 了解 HTTP 协议的无状态性
HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会主动保留每次 HTTP 请求的状态
4.2 如何突破 HTTP 无状态的限制
对于超市来说,为了方便收银员在进行结算时给 VIP
用户打折,超市可以为每个 VIP
用户发放会员卡
注意:现实生活中的会员卡身份认证方式,在 Web
开发中的专业术语叫做 Cookie
4.3 什么是 Cookie
-
Cookie
是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成
- 不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器
-
Cookie
的几大特性
4.4 Cookie 在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie
,客户端会自动将 Cookie
保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie
,通过请求头的形式发送给服务器,服务器即可验明客户端的身份
4.5 Cookie 不具有安全性
由于 Cookie
是存储在浏览器中的,而且浏览器也提供了读写 Cookie
的 API
,因此 Cookie
很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据通过 Cookie
的形式发送给浏览器
注意:千万不要使用 Cookie 存储重要且隐私的数据! 比如用户的身份信息、密码等
4.6 提高身份认证的安全性
- 为了防止客户伪造会员卡,收银员在拿到客户出示的会员卡之后,可以在收银机上进行刷卡认证。只有收银机确认存在的会员卡,才能被正常使用
- 这种“会员卡 + 刷卡认证”的设计理念,就是
Session
认证机制的精髓
4.7 Session 的工作原理
4.8 安装并 express-session 中间件
- 安装
express-session
- 配置
express-session
中间件
- 案例代码
const express = require('express')
const app = express()
const session = require('express-session') app.use( session({ secret: 'itheima', resave: false, saveUninitialized: true, }) )
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
app.post('/api/login', (req, res) => {})
app.get('/api/username', (req, res) => {})
app.post('/api/logout', (req, res) => {})
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
4.9 向 session 中存数据
当 express-session
中间件配置成功后,即可通过 req.session
来访问和使用 session
对象,从而存储用户的关键信息
示例
案例代码
const express = require('express')
const app = express()
const session = require('express-session') app.use( session({ secret: 'itheima', resave: false, saveUninitialized: true, }) )
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
app.post('/api/login', (req, res) => { if (req.body.username !== 'admin' || req.body.password !== '000000') { return res.send({ status: 1, msg: '登录失败' }) }
req.session.user = req.body req.session.islogin = true
res.send({ status: 0, msg: '登录成功' }) })
app.get('/api/username', (req, res) => { })
app.post('/api/logout', (req, res) => { })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
4.10 从 session 中取数据
可以直接从 req.session
对象上获取之前存储的数据
示例
- 案例代码
const express = require('express')
const app = express()
const session = require('express-session') app.use( session({ secret: 'itheima', resave: false, saveUninitialized: true, }) )
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
app.post('/api/login', (req, res) => { if (req.body.username !== 'admin' || req.body.password !== '000000') { return res.send({ status: 1, msg: '登录失败' }) }
req.session.user = req.body req.session.islogin = true
res.send({ status: 0, msg: '登录成功' }) })
app.get('/api/username', (req, res) => { if (!req.session.islogin) { return res.send({ status: 1, msg: 'fail' }) } res.send({ status: 0, msg: 'success', username: req.session.user.username, }) })
app.post('/api/logout', (req, res) => { })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
4.11 清空 session
调用 req.session.destroy()
函数,即可清空服务器保存的 session
信息
实例
- 案例代码
const express = require('express')
const app = express()
const session = require('express-session') app.use( session({ secret: 'itheima', resave: false, saveUninitialized: true, }) )
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
app.post('/api/login', (req, res) => { if (req.body.username !== 'admin' || req.body.password !== '000000') { return res.send({ status: 1, msg: '登录失败' }) }
req.session.user = req.body req.session.islogin = true
res.send({ status: 0, msg: '登录成功' }) })
app.get('/api/username', (req, res) => { if (!req.session.islogin) { return res.send({ status: 1, msg: 'fail' }) } res.send({ status: 0, msg: 'success', username: req.session.user.username, }) })
app.post('/api/logout', (req, res) => { req.session.destroy() res.send({ status: 0, msg: '退出登录成功', }) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
4.12 演示 session 案例效果
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>后台主页</title> <script src="./jquery.js"></script> </head>
<body> <h1>首页</h1>
<button id="btnLogin">退出登录</button>
<script> $(function () { function init() { $.ajax({ url: '/api/username', type: 'get', success: (data) => { if (!data.status) { alert('欢迎登录:' + data.username) } else { alert('您尚未登录,请登录后在执行此操作!') window.location.href = '/login.html' } } }) } init()
$('#btnLogin').click(() => { $.ajax({ url: '/api/logout', type: 'post', success: (data) => { if (!data.status) { alert(data.msg) window.location.href = '/login.html' } } }) })
}) </script> </body>
</html>
|
<!DOCTYPE html> <html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>登录页面</title> <script src="./jquery.js"></script> </head>
<body> <form id="form1"> <div>账号:<input type="text" name="username" autocomplete="off"> </div> <div>密码:<input type="password" name="password" autocomplete="off"></div> <button>登录</button> </form>
<script> $(function () { $('button').click((e) => { e.preventDefault() const params = $('#form1').serialize() $.ajax({ url: '/api/login', type: 'post', data: params, success: (data) => { window.location.href = '/index.html' } }) }) }) </script> </body>
</html>
|
5. jwt
5.1 了解 Session 认证的局限性
-
Session
认证机制需要配合 Cookie
才能实现。由于 Cookie
默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session
认证
- 注意:
- 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制
- 前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制
- 什么是 JWT
JWT
(英文全称:JSON Web Token
)是目前最流行的跨域认证解决方案
5.2 JWT 的工作原理
- 用户的信息通过
Token
字符串的形式,保存在客户端浏览器中
- 服务器通过还原
Token
字符串的形式来认证用户的身份
5.3 JWT 的组成部分
JWT
通常由三部分组成,分别是 Header
(头部)、Payload
(有效荷载)、Signature
(签名)
三者之间使用英文的“.”分隔,格式如下
- 下面是
JWT
字符串的示例
-
JWT
的三个组成部分,从前到后分别是 Header
、Payload
、Signature
5.4 JWT 的使用方式
客户端收到服务器返回的 JWT
之后,通常会将它储存在 localStorage
或 sessionStorage
中
此后,客户端每次与服务器通信,都要带上这个 JWT
的字符串,从而进行身份认证。推荐的做法是把 JWT
放在 HTTP
请求头的 Authorization
字段中
5.5 安装并导入 JWT 相关的包
5.5.1 安装 JWT 相关的包
- 运行如下命令,安装如下两个 JWT 相关的包
npm i jsonwebtoken express-jwt
|
- 其中
jsonwebtoken
用于生成 JWT
字符串
express-jwt
用于将 JWT
字符串解析还原成 JSON
对象
5.5.2 导入 JWT 相关的包
使用 require()
函数,分别导入 JWT
相关的两个包
实例
案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
5.6 定义 secret 密钥
- 为了保证
JWT
字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密的 secret
密钥
- 案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
const secretKey = 'itheima No1 ^_^'
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
5.7 在登录成功后生成 JWT 字符串
调用 jsonwebtoken
包提供的 sign()
方法,将用户的信息加密成 JWT
字符串,响应给客户端
示例
- 案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
const secretKey = 'itheima No1 ^_^'
app.post('/api/login', function (req, res) { const userinfo = req.body if (userinfo.username !== 'admin' || userinfo.password !== '000000') { return res.send({ status: 400, message: '登录失败!', }) } const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) res.send({ status: 200, message: '登录成功!', token: tokenStr, }) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
5.8 将 JWT 字符串还原为 JSON 对象
客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的 Authorization
字段,将 Token
字符串发送到服务器进行身份认证
此时,服务器可以通过 express-jwt
这个中间件,自动将客户端发送过来的 Token
解析还原成 JSON
对象
示例
- 案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
const secretKey = 'itheima No1 ^_^'
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
app.post('/api/login', function (req, res) { const userinfo = req.body if (userinfo.username !== 'admin' || userinfo.password !== '000000') { return res.send({ status: 400, message: '登录失败!', }) } const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) res.send({ status: 200, message: '登录成功!', token: tokenStr, }) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
5.9 使用 req.user 获取用户信息
当 express-jwt
这个中间件配置成功之后,即可在那些有权限的接口中,使用 req.user
对象,来访问从 JWT
字符串中解析出来的用户信息了
实例
- 案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
const secretKey = 'itheima No1 ^_^'
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
app.post('/api/login', function (req, res) { const userinfo = req.body if (userinfo.username !== 'admin' || userinfo.password !== '000000') { return res.send({ status: 400, message: '登录失败!', }) } const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) res.send({ status: 200, message: '登录成功!', token: tokenStr, }) })
app.get('/admin/getinfo', function (req, res) { console.log(req.user) res.send({ status: 200, message: '获取用户信息成功!', data: req.user, }) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
5.10 捕获解析 JWT 失败后产生的错误
当使用 express-jwt
解析 Token
字符串时,如果客户端发送过来的 Token
字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
可以通过 Express
的错误中间件,捕获这个错误并进行相关的处理
示例
- 案例代码
const express = require('express')
const app = express()
const jwt = require('jsonwebtoken') const expressJWT = require('express-jwt')
const cors = require('cors') app.use(cors())
app.use(express.static('./pages'))
app.use(express.urlencoded({ extended: false }))
const secretKey = 'itheima No1 ^_^'
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
app.post('/api/login', function (req, res) { const userinfo = req.body if (userinfo.username !== 'admin' || userinfo.password !== '000000') { return res.send({ status: 400, message: '登录失败!', }) } const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' }) res.send({ status: 200, message: '登录成功!', token: tokenStr, }) })
app.get('/admin/getinfo', function (req, res) { console.log(req.user) res.send({ status: 200, message: '获取用户信息成功!', data: req.user, }) })
app.use((err, req, res, next) => { if (err.name === 'UnauthorizedError') { return res.send({ status: 401, message: '无效的token', }) } res.send({ status: 500, message: '未知的错误', }) })
app.listen(80, function () { console.log('Express server running at http://127.0.0.1:80') })
|
6. 项目初始化
6.1 项目概述
- 在线笔记与文档
- 需要完成的大事件功能
- 本地笔记与文档,在
ppt
文件夹中,ev_api_server.pdf
6.2 创建项目
- 新建
api_server
文件夹作为项目根目录,并在项目根目录中运行如下的命令,初始化包管理配置文件
- 运行如下的命令,安装特定版本的
express
- 在项目根目录中新建
app.js
作为整个项目的入口文件,并初始化如下的代码
const express = require('express')
const app = express()
app.listen(8000, () => { console.log('api server running at http://127.0.0.1:8000') })
|
6.3 配置 cors 跨域和解析表单数据的中间件
6.3.1 配置 cors 跨域
- 运行如下的命令,安装
cors
中间件
- 在
app.js
中导入并配置 cors
中间件
const express = require('express')
const app = express()
const cors = require('cors')
app.use(cors)
app.listen(8000, () => { console.log('api server running at http://127.0.0.1:8000') })
|
6.3.2 配置解析表单数据的中间件
- 配置解析
application/x-www-form-urlencoded
格式的表单数据的中间件
app.use(express.urlencoded({ extended: false }))
|
- 完整代码
const express = require('express')
const app = express()
const cors = require('cors')
app.use(cors)
app.use(express.urlencoded({ extended: false }))
app.listen(8000, () => { console.log('api server running at http://127.0.0.1:8000') })
|
6.4 初始化路由相关的文件夹
- 在项目根目录中,新建
router
文件夹,用来存放所有的 路由 模块
- 路由模块中,只存放客户端的请求与处理函数之间的映射关系
- 在项目根目录中,新建
router_handler
文件夹,用来存放所有的 路由处理函数模块
- 路由处理函数模块中,专门负责存放每个路由对应的处理函数
6.5 初始化用户路由模块
- 在
router
文件夹中,新建 user.js
文件,作为用户的路由模块
const express = require('express')
const router = express.Router()
router.post('/reguser', (req, res) => { res.send('reguser Ok') })
router.post('/login', (req, res) => { res.send('Login Ok') })
module.exports = router
|
- 在
app.js
中,导入并使用 用户路由模块
const express = require('express')
const app = express()
const cors = require('cors')
app.use(cors)
app.use(express.urlencoded({ extended: false }))
const userRouter = require('./router/user') app.use('/api', userRouter)
app.listen(8000, () => { console.log('api server running at http://127.0.0.1:8000') })
|
6.6 抽离用户路由模块中的处理函数
目的:为了保证 路由模块 的纯粹性,所有的 路由处理函数 ,必须抽离到对应的 路由处理函数模块 中
- 在
/router_handler/user.js
中,使用 exports
对象,分别向外共享如下两个路由处理函数
exports.regUser = (req, res) => { res.send('reguser Ok') }
exports.login = (req, res) => { res.send('login Ok') }
|
- 将
/router/user.js
中的代码修改为如下结构
const express = require('express')
const router = express.Router()
const userHandle = require('../router_handler/user')
router.post('/reguser', userHandle.regUser)
router.post('/login', userHandle.login)
module.exports = router
|
7. 注册新用户
7.1 创建用户表
- 在
my_db_01
数据库中,新建 ev_users
表
7.2 安装配置 mysql 模块
在 API 接口项目中,需要安装并配置 mysql 这个第三方模块,来连接和操作 MySQL 数据库
- 安装
mysql
模块
- 在项目根目录中新建
/db/index.js
文件,在此自定义模块中创建数据库的连接对象
const mysql = require('mysql')
const db = mysql.createPool({ host: '127.0.0.1', user: 'root', password: 'admin123', database: 'my_db_01' })
module.exports = db
|
7.3 检测表单数据是否合法
7.3.1 注册功能实现步骤
- 检测表单数据是否合法
- 检测用户名是否被占用
- 对密码进行加密处理
- 插入新用户
7.3.2 检测表单数据是否合法
- 判断用户名和密码是否为空
exports.regUser = (req, res) => { const userinfo = req.body
if (!userinfo.username || !userinfo.password) { return res.send({ status: 1, message: '用户名和密码不正确' }) } res.send('reguser Ok') }
|
7.4 检测用户名是否合法
- 导入数据库操作模块
const db = require('../db/index')
|
- 定义 SQL 语句
const sql = `select * from ev_users where username=?`
|
- 执行 SQL 语句并根据结果判断用户名是否被占用
db.query(sql, userinfo.username, (err, results) => { if (err) { return res.send({ status: 1, message: err.message }) }
if (results.length > 0) { return res.send({ status: 1, message: '用户名被占用,请更换其他用户名' }) }
})
|
- 完整代码
const db = require('../db/index')
exports.regUser = (req, res) => { const userinfo = req.body
if (!userinfo.username || !userinfo.password) { return res.send({ status: 1, message: '用户名和密码不正确' }) }
const sql = `select * from ev_users where username=?` db.query(sql, userinfo.username, (err, results) => { if (err) { return res.send({ status: 1, message: err.message }) } if (results.length > 0) { return res.send({ status: 1, message: '用户名被占用,请更换其他用户名' }) }
})
res.send('reguser Ok') }
|
7.5 对密码进行加密处理
7.5.1 什么需要对密码进行加密以及 bcryptjs 的优点
- 为了保证密码的安全性,不建议在数据库以 明文 的形式保存用户密码,推荐对密码进行 加密存储
- 在当前项目中,使用
bcryptjs
对用户密码进行加密,优点:
- 加密之后的密码,无法被逆向破解
- 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
7.5.2 使用 bcryptjs 对密码进行加密
- 安装指定版本的
bcryptjs
- 在
/router_handler/user.js
中,导入 bcryptjs
const bcrypt = require('bcryptjs')
|
- 在注册用户的处理函数中,确认用户名可用之后,调用
bcrypt.hashSync
(明文密码, 随机盐的长度) 方法,对用户的密码进行加密处理
exports.regUser = (req, res) => { db.query(sqlStr, userinfo.username, (err, results) => { if (err) { return res.send({ status: 1, message: err.message }) }
if (results.length > 0) { return res.send({ status: 1, message: '用户名被占用,请更换其他用户名' }) }
console.log(userinfo) userinfo.password = bcrypt.hashSync(userinfo.password, 10) console.log(userinfo) res.send('reguser Ok') }) }
|
7.6 插入新用户
- 定义插入用户的
SQL
语句
const sql = 'insert into ev_users set ?'
|
- 调用
db.query()
执行 SQL
语句,插入新用户
exports.regUser = (req, res) => { db.query(sqlStr, userinfo.username, (err, results) => { if (err) { return res.send({ status: 1, message: err.message }) }
if (results.length > 0) { return res.send({ status: 1, message: '用户名被占用,请更换其他用户名' }) }
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
const sql = 'insert into ev_users set ?'
db.query(sql, { username: userinfo.username, password: userinfo.password }, (err, results) => { if (err) return res.send({ status: 1, message: err.message }) if (results.affectedRows !== 1) return res.send({ status: 1, message: '注册用户失败,请稍后再试!' }) res.send({ status: 0, message: '注册成功' }) }) }) }
|
8. 优化
8.1 优化 res.send() 代码
在处理函数中,需要多次调用 res.send() 向客户端响应 处理失败 的结果,为了简化代码,可以手动封装一个 res.cc() 函数
- 在
app.js
中,所有路由之前,声明一个全局中间件,为 res
对象挂载一个 res.cc()
函数
app.use(express.urlencoded({ extended: false }))
app.use((req, res, next) => { res.cc = (err, status = 1) => { res.send({ status, message: err instanceof Error ? err.message : err }) }
next() })
const userRouter = require('./router/user') app.use('/api', userRouter)
|
- 将
router_handle/user.js
中的文件修改成 res.cc
的方式
const db = require('../db/index') const bcrypt = require('bcryptjs')
exports.regUser = (req, res) => { const userinfo = req.body console.log(userinfo.username)
if (!userinfo.username || !userinfo.password) { return res.cc('用户名和密码不正确') }
const sqlStr = 'select * from ev_users where username=?' db.query(sqlStr, userinfo.username, (err, results) => { if (err) { return res.cc(err) }
if (results.length > 0) { return res.cc('用户名被占用,请更换其他用户名') }
userinfo.password = bcrypt.hashSync(userinfo.password, 10)
const sql = 'insert into ev_users set ?'
db.query(sql, { username: userinfo.username, password: userinfo.password }, (err, results) => { if (err) return res.cc(err) if (results.affectedRows !== 1) return res.cc('注册用户失败,请稍后再试!') res.send({ status: 0, message: '注册成功' }) }) }) }
exports.login = (req, res) => { res.send('login Ok') }
|
8.2 了解数据验证的原则
表单验证的原则:前端验证为辅,后端验证为主,后端永远不要相信前端提交过来的任何内容
- 在实际开发中,前后端都需要对表单的数据进行合法性的验证,而且,后端做为数据合法性校验的最后一个关口,在拦截非法数据方面,起到了至关重要的作用
- 单纯的使用
if...else...
的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,
推荐使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性,让后端程序员把更多的精
力放在核心业务逻辑的处理上
8.3 介绍如何定义验证规则对象
- 安装
@hapi/joi
包,为表单中携带的每个数据项,定义验证规则
npm install @hapi/joi@17.1.0 -S
|
- 安装
@escook/express-joi
中间件,来实现自动对表单数据进行验证的功能
npm i @escook/express-joi -S
|
- 相关验证规则的含义
var getUsersSchema = { limit: expressJoi.Joi.types.Number().integer().min(1).max(25), offset: expressJoi.Joi.types.Number().integer().min(0).max(25), name: expressJoi.Joi.types.String().alphanum().min(2).max(25) }
|
8.4 了解如何使用数验证的中间件
- 在路由中使用局部中间件
- 在局部中间件中通过
expressJoi(userSchema)
的方式调用中间件进行参数验证
- 验证通过以后,将结果转交给后面的回调函数
- 验证失败以后,就会报一个全局的错误,只需要在错误级别中间件捕获错误,然后做下一步处理
8.5 定义验证规则模块
- 新建
/schema/user.js
用户信息验证规则模块,并初始化代码
const joi = require('@hapi/joi')
const username = joi.string().alphanum().min(10).max(10).required() const passoword = joi.string().pattern(/^[\S]{6, 12}$/).required()
exports.reg_login_schema = { body: { username, passoword } }
|
8.6 实现对表单数据验证方式的改造
- 将原始的
if
验证方式注释
- 在
router.js
中导入验证数据的中间件以及验证规则对象,在需要验证的路由中使用局部中间件添加校验规则
const express = require('express')
const router = express.Router()
const userHandle = require('../router_handler/user')
const expressJoi = require('@escook/express-joi')
const { reg_login_schema } = require('../schema/user')
router.post('/reguser', expressJoi(reg_login_schema), userHandle.regUser)
router.post('/login', userHandle.login)
module.exports = router
|
- 验证可能会失败,所以在全局进行捕获
app.use((err, req, res, next) => { if (err instanceof joi.ValidationError) return res.cc(err) res.cc(err) })
|