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)   }) }
 
 
  |