Node 第 7 天
1. 学习目标
◆ 完成登录功能的开发
◆ 能够获取用户的信息并重置密码和头像
2. 登录功能
2.1 实现步骤
- 检测表单数据是否合法
- 根据用户名查询用户的数据
- 判断用户输入的密码是否正确
- 生成
JWT
的 Token
字符串
2.2 检测表单数据是否合法
- 将
/router/user.js
中 登录 的路由代码修改进行修改
router.post('/reguser', expressJoi(reg_login_schema), userHandle.regUser)
router.post('/login', expressJoi(reg_login_schema), userHandle.login)
|
2.3 根据用户名查询用户的数据
- 接收表单的数据
const userInfo = req.body
|
- 定义
sql
语句
const sql = `select * from ev_users where username=?`
|
- 执行
sql
语句,查询用户的数据
db.query(sql, userInfo.username, (err, results) => { if (err) return res.cc(err) if (results.length !== 1) return res.cc('登录失败') res.send('login Ok') })
|
- 完整代码
exports.login = (req, res) => { const userInfo = req.body const sql = `select * from ev_users where username=?` db.query(sql, userInfo.username, (err, results) => { if (err) return res.cc(err) if (results.length !== 1) return res.cc('登录失败') res.send('login Ok') }) }
|
2.4 判断用户输入的密码是否正确
核心实现思路:调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码)
方法比较密码是否一致,
返回值是布尔值(true 一致、false 不一致)
- 实现代码
const compareResult = bcrypt.compareSync(userInfo.password, results[0].password)
if (!compareResult) return res.cc('登录失败!')
|
完整代码
exports.login = (req, res) => { const userInfo = req.body const sql = `select * from ev_users where username=?` db.query(sql, userInfo.username, (err, results) => { if (err) return res.cc(err) if (results.length !== 1) return res.cc('登录失败')
const compareResult = bcrypt.compareSync(userInfo.password, results[0].password) if (!compareResult) return res.cc('登录失败!') res.send('login Ok') }) }
|
2.5 分析生成 Token
字符串的步骤
- 通过
ES6
的高级语法,快速剔除 密码
和 头像
的值
- 运行如下的命令,安装生成
Token
字符串的包
- 在
/router_handler/user.js
模块的头部区域,导入 jsonwebtoken
包
- 创建
config.js
文件,并向外共享 加密 和 还原 Token
的 jwtSecretKey
字符串
- 将用户信息对象加密成
Token
字符串
- 将生成的
Token
字符串响应给客户端
2.6 生成 JWT
的 Token
步骤
- 通过
ES6
的高级语法,快速剔除 密码
和 头像
的值
const user = { ...results[0], password: '', user_pic: '' }
|
- 运行如下的命令,安装生成
Token
字符串的包
cnpm i jsonwebtoken@8.5.1 -S
|
- 在
/router_handler/user.js
模块的头部区域,导入 jsonwebtoken
包
const jwt = require('jsonwebtoken')
|
- 创建
config.js
文件,并向外共享 加密 和 还原 Token
的 jwtSecretKey
字符串
module.exports = { jwtSecretKey: 'itheima No1. ^_^', expiresIn: '10h' }
|
- 将用户信息对象加密成
Token
字符串
const config = require('../config')
|
const tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: config.expiresIn})
|
- 将生成的
Token
字符串响应给客户端
res.send({ status: 0, message: '登录成功!', token: 'Bearer ' + tokenStr, })
|
- 完整代码
const db = require('../db/index') const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const config = require('../config')
exports.regUser = (req, res) => {}
exports.login = (req, res) => { const userInfo = req.body const sql = `select * from ev_users where username=?` db.query(sql, userInfo.username, (err, results) => { if (err) return res.cc(err) if (results.length !== 1) return res.cc('登录失败')
const compareResult = bcrypt.compareSync(userInfo.password, results[0].password) if (!compareResult) return res.cc('登录失败!')
const user = { ...results[0], password: '', user_pic: '' }
const tokenStr = jwt.sign(user, config.jwtSecretKey, {expiresIn: config.expiresIn})
res.send({ status: 0, message: '登录成功!', token: 'Bearer ' + tokenStr, }) }) }
|
2.7 配置解析 Token
的中间件
- 运行如下的命令,安装解析
Token
的中间件
cnpm i express-jwt@5.3.3 -S
|
- 在
app.js
中注册路由之前,配置解析 Token
的中间件
const config = require('./config')
const expressJWT = require('express-jwt')
app.use(expressJWT({ secret: config.jwtSecretKey })).unless({ path: [/^\/api\//] })
const userRouter = require('./router/user') app.use('/api', userRouter)
|
- 在
app.js
中的 错误级别中间件 里面,捕获并处理 Token
认证失败后的错误
app.use((err, req, res, next) => { if (err instanceof joi.ValidationError) return res.cc(err) if (err.name === 'UnauthorizedError') return res.cc('身份认证失败!') res.cc(err) })
|
3. 获取用户的基本信息
3.1 实现步骤
- 初始化 路由 模块
- 初始化 路由处理函数 模块
- 获取用户的基本信息
3.2 初始化路由模块
- 创建
/router/userinfo.js
路由模块,并初始化如下的代码结构
const express = require('express') const { route } = require('./user')
const router = express.Router()
router.get('/userinfo', (req, res) => { res.send('ok') })
module.exports = router
|
- 在
app.js
中导入并使用个人中心的路由模块
const userRouter = require('./router/user')
const userinfoRouter = require('./router/userinfo')
app.use('/api', userRouter) app.use('/my', userinfoRouter)
|
3.3 初始化 路由处理函数 模块
- 创建
/router_handler/userinfo.js
路由处理函数模块,并初始化如下的代码结构
exports.getUserInfo = (req, res) => { res.send('Ok') }
|
- 修改
/router/userinfo.js
中的代码
const express = require('express') const { route } = require('./user')
const router = express.Router()
const userinfo_handler = require('../router_handler/userinfo')
router.get('/userinfo', userinfo_handler.getUserInfo)
module.exports = router
|
3.4 获取用户的基本信息
- 在
/router_handler/userinfo.js
头部导入数据库操作模块
const db = require('../db/index')
|
- 定义
SQL
语句
const sql = `select id, username, nickname, email, user_pic from ev_users where id=?
|
- 调用
db.query()
执行 SQL
语句
db.query(sql, req.user.id, (err, results) => { if (err) return res.cc(err)
if (results.length !== 1) return res.cc('获取用户信息失败!')
res.send({ status: 0, message: '获取用户基本信息成功!', data: results[0], }) })
|
- 完成代码
const db = require('../db/index')
exports.getUserInfo = (req, res) => { const sql = `select id, username, nickname, email, user_pic from ev_users where id=?`
db.query(sql, req.user.id, (err, results) => { if (err) return res.cc(err) if (results.length !== 1) return res.cc('获取用户信息失败!')
res.send({ status: 0, message: '获取用户基本信息成功!', data: results[0], }) }) }
|
4. 更新用户的基本信息
4.1 实现步骤
- 定义路由和处理函数
- 验证表单数据
- 实现更新用户基本信息的功能
4.2 定义路由和处理函数
- 在
/router/userinfo.js
模块中,新增 更新用户基本信息 的路由
router.post('/userinfo', userinfo_handler.updateUserInfo)
|
- 在
/router_handler/userinfo.js
模块中,定义并向外共享 更新用户基本信息 的路由处理函数
exports.updateUserInfo = (req, res) => { res.send('Ok') }
|
4.3 定义验证规则对象
- 在
/schema/user.js
验证规则模块中,定义 id
, nickname
, email
的验证规则
const id = joi.number().integer().min(1).required() const nickname = joi.string().required() const email = joi.string().email().required()
|
- 并使用
exports
向外共享如下的 验证规则对象
exports.update_userinfo_schema = { body: { id, nickname, email, } }
|
- 完整代码
const joi = require('@hapi/joi')
const username = joi.string().alphanum().min(1).max(10).required() const password = joi.string().pattern(/^[\S]{6,12}$/).required()
const id = joi.number().integer().min(1).required() const nickname = joi.string().required() const email = joi.string().email().required()
exports.reg_login_schema = { body: { username, password, } }
exports.update_userinfo_schema = { body: { id, nickname, email, } }
|
4.4 验证数据规则的合法性
- 在
/router/userinfo.js
模块中,导入验证数据合法性的中间件
const expressJoi = require('@escook/express-joi')
|
- 在
/router/userinfo.js
模块中,导入需要的验证规则对象
const expressJoi = require('@escook/express-joi')
const { update_userinfo_schema } = require('../schema/user')
|
- 在
/router/userinfo.js
模块中,修改 更新用户的基本信息 的路由如下
router.post('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)
|
- 完整代码
const express = require('express') const { route } = require('./user')
const expressJoi = require('@escook/express-joi')
const { update_userinfo_schema } = require('../schema/user')
const router = express.Router()
const userinfo_handler = require('../router_handler/userinfo')
router.get('/userinfo', userinfo_handler.getUserInfo)
router.post('/userinfo', expressJoi(update_userinfo_schema), userinfo_handler.updateUserInfo)
module.exports = router
|
4.4 实现更新用户基本信息的功能
- 定义待执行的
SQL
语句
const sql = `update ev_users set ? where id=?`
|
- 调用
db.query()
执行 SQL
语句并传参
db.query(sql, [req.body, req.body.id], (err, results) => { if (err) return res.cc(err)
if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!')
return res.cc('修改用户基本信息成功!', 0) })
|
- 完整代码
exports.updateUserInfo = (req, res) => { const sql = `update ev_users set ? where id=?`
db.query(sql, [req.body, req.body.id], (err, results) => { if (err) return res.cc(err)
if (results.affectedRows !== 1) return res.cc('修改用户基本信息失败!')
return res.cc('修改用户基本信息成功!', 0) }) }
|
5. 重置密码
5.1 实现步骤
- 定义路由和处理函数
- 验证表单数据
- 实现重置密码的功能
5.2 定义路由和处理函数
- 在
/router/userinfo.js
模块中,新增 重置密码 的路由
router.post('/updatepwd', userinfo_handler.updatePassword)
|
在 /router_handler/userinfo.js
模块中,定义并向外共享 重置密码 的路由处理函数
exports.updatePassword = (req, res) => { res.send('ok') }
|
5.3 验证数据表单
核心验证思路:旧密码与新密码,必须符合密码的验证规则,并且新密码不能与旧密码一致
- 在
/schema/user.js
模块中,使用 exports
向外共享如下的 验证规则对象
joi.ref('oldPwd')
表示 newPwd
的值必须和 oldPwd
的值保持一致
joi.not(joi.ref('oldPwd'))
表示 newPwd
的值不能等于 oldPwd
的值
.concat()
用于合并 joi.not(joi.ref('oldPwd'))
和 password
这两条验证规则
exports.update_password_schema = { body: { oldPwd: password, newPwd: joi.not(joi.ref('oldPwd')).concat(password), } }
|
- 在
/router/userinfo.js
模块中,导入需要的验证规则对象
const { update_userinfo_schema, update_password_schema } = require('../schema/user')
|
- 并在 重置密码的路由 中,使用 update_password_schema 规则验证表单的数据
router.post('/updatepwd', expressJoi(update_password_schema), userinfo_handler.updatePassword)
|
5.4 实现重置密码的功能
- 根据
id
查询用户是否存在
exports.updatePassword = (req, res) => { const sql = `select * from ev_users where id=?`
db.query(sql, req.user.id, (err, results) => { if (err) return res.cc(err)
if (results.length !== 1) return res.cc('用户不存在!')
res.cc('ok') }) }
|
5.5 判断提交的 旧密码 是否正确
- 在头部区域导入
bcryptjs
const bcrypt = require('bcryptjs')
|
- 使用
bcrypt.compareSync(提交的密码,数据库中的密码)
方法验证密码是否正确
compareSync()
函数的返回值为布尔值,true
表示密码正确,false
表示密码错误
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password) if (!compareResult) return res.cc('旧密码错误!')
|
- 完整密码
exports.updatePassword = (req, res) => { const sql = `select * from ev_users where id=?`
db.query(sql, req.user.id, (err, results) => { if (err) return res.cc(err)
if (results.length !== 1) return res.cc('用户不存在!')
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password) if (!compareResult) return res.cc('旧密码错误!')
res.cc('ok') }) }
|
5.6 实现重置密码的功能
- 定义更新用户密码的
SQL
语句
const sql = `update ev_users set password=? where id=?`
|
- 对新密码进行加密处理
const newPwd = bcrypt.hashSync(req.body.newPwd, 10)
|
- 执行
SQL
语句
db.query(sql, [newPwd, req.user.id], (err, results) => { if (err) return res.cc(err)
if (results.affectedRows !== 1) return res.cc('更新密码失败!') res.cc('更新密码成功!', 0) })
|
- 完整代码
exports.updatePassword = (req, res) => { const sql = `select * from ev_users where id=?`
db.query(sql, req.user.id, (err, results) => { if (err) return res.cc(err)
if (results.length !== 1) return res.cc('用户不存在!')
const compareResult = bcrypt.compareSync(req.body.oldPwd, results[0].password) if (!compareResult) return res.cc('旧密码错误!')
const sql = `update ev_users set password=? where id=?`
const newPwd = bcrypt.hashSync(req.body.newPwd, 10) db.query(sql, [newPwd, req.user.id], (err, results) => { if (err) return res.cc(err)
if (results.affectedRows !== 1) return res.cc('更新密码失败!') res.cc('更新密码成功!', 0) }) }) }
|
6. 更换头像
6.1 实现步骤
- 定义路由和处理函数
- 验证表单数据
- 实现更新用户头像的功能
6.2 定义路由和处理函数
- 在
/router/userinfo.js
模块中,新增 更新用户头像 的路由
router.post('/update/avatar', userinfo_handler.updateAvatar)
|
- 在
/router_handler/userinfo.js
模块中,定义并向外共享 更新用户头像 的路由处理函数
exports.updateAvatar = (req, res) => { res.send('ok') }
|
6.3 验证表单数据
- 在
/schema/user.js
验证规则模块中,定义 avatar 的验证规则
dataUri()
指的是如下格式的字符串数据
data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=
const avatar = joi.string().dataUri().required()
exports.update_avatar_schema = { body: { avatar } }
|
- 并使用
exports
向外共享如下的 验证规则对象
const { update_avatar_schema } = require('../schema/user')
router.post('/update/avatar', expressJoi(update_avatar_schema), userinfo_handler.updateAvatar)
|
6.4 实现更新用户头像的操作
- 定义更新用户头像的
SQL
语句
const sql = 'update ev_users set user_pic=? where id=?'
|
- 调用
db.query()
执行 SQL
语句,更新对应用户的头像
exports.updateAvatar = (req, res) => { const sql = 'update ev_users set user_pic=? where id=?'
db.query(sql, [req.body.avatar, req.user.id], (err, results) => { if (err) return res.cc(err)
if (results.affectedRows !== 1) return res.cc('更新头像失败!')
return res.cc('更新头像成功!', 0) }) }
|