Node.js - 项目实践之登录注册(二)

250 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

项目要求

通过一个小项目实践 Node.js 各个知识点。麻雀虽小,五脏俱全,这个项目会从 0~1,逐步完成所有需求。该项目实现接口有注册、登录、获取用户信息等等

截屏2022-05-28 下午11.35.33.png


登录注册

新建 ev_users 表

Snip20220426_1.png

安装并配置 mysql 模块

在 API 接口项目中,需要安装并配置 mysql 这个第三方模块,来连接和操作 MySQL 数据库

  1. 运行如下命令,安装 mysql 模块

    npm install mysql@2.18.1
    
  2. 在项目根目录中新建 /db/index.js 文件,在这个自定义模块中创建数据库的连接对象

    // 导入 mysql 模块
    const mysql = require('mysql')
    
    // 创建数据库连接对象
    const db = mysql.createPool({
        host:'127.0.0.1',
        user:'root',
        password:'admin123',
        database:'my_db_01'
    })
    
    // 向外共享 db 数据库连接对象
    module.exports = db
    

注册

检测表单数据是否合法

判断用户名和密码是否为空

// 获取客户端提交到服务器的用户信息
const userinfo = req.body

// 对表单中的数据,进行合法性的校验
if (!userinfo.username || !userinfo.password){
    return res.send({
        status: 1,
        message: '用户名或密码不能为空'
    })
}
检测用户名是否被占用
  1. 导入数据库操作模块

    const db = require('../db/index')
    
  2. 定义 SQL 语句

    const sqlStr = 'select * from ev_users where username=?'
    
  3. 执行 SQL 语句并根据结果判断用户名是否被占用

    db.query(sqlStr, userinfo.username, (err, results)=>{
    
        // 执行 SQL 语句失败
        if(err){
            return res.send({
                status:1,
                message: err.message
            })
        }
    
        // 判断用户名是否被占用
        if(results.length > 0){
            return res.send({
                status: 1,
                message:'用户名被占用,请更换其他用户名'
            })
        }
        
        // 用户名可以使用。。。
    })
    
  4. 对密码进行加密处理

    • 为了保证密码的安全性,不建议在数据库以明文的形式保存用户密码,推荐对密码进行加密存储

    • 在当前项目中,使用 bcryptjs 对用户密码进行加密,

    • 优点:

      • 加密之后的密码,无法被逆向破解
      • 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
    • 运行如下命令,安装指定的版本 bcryptjs

      npm install bcryptjs@2.4.3
      
    • /router_handler/user.js 中, 导入 bcryptjs

      const bcrypt = require('bcryptjs')
      
    • 在注册用户的处理函数中,确认用户名可用之后,调用 bcryptjs 方法,对用户的密码进行加密处理

      // 对用户的密码进行加密,返回值是加密之后的密码字符串      
      userinfo.password = bcrypt.hashSync(userinfo.password,10)
      
  5. 插入新用户

    • 定义插入用户的 SQL 语句

      const sql = 'insert into ev_users set ?'
      
    • 调用 db.query 执行 SQL 语句,插入新用户

      db.query(sql, {username: userinfo.username, password:userinfo.password} , (err, results) => {
      
          // 判断 SQL 语句是否执行成功
          if(err) return res.send({
              status: 1,
              message: err.message
          })
      
      
          // 判断影响行数是否为 1
          if (results.affectedRows != 1){
              return res.send({
                  status: 1,
                  message:'注册用户失败,请稍后再试'
              })
          }
          
          // 注册成功
          res.send({
              status:0,
              message:'注册成功'
          })
      })
      
优化代码-优化 res.send() 代码

在处理函数中,需要多次调用 res.send() 向客户端响应处理失败的结果,为了简化代码,可以手动封装一个 res.cc() 函数

  1. app.js 中,所有路由之前,声明一个全局中间件,为 res 对象挂载一个 res.cc() 函数

    // 响应数据的中间件
    app.use((req, res, next) => {
        // status = 0 为成功, status = 1 为失败,默认将 status 的值设置为 1, 方便处理失败的情况
        res.cc = function (err, status = 1) {
            res.send({
                // 状态
                status,
                // 描述信息,判断 err 是 错误对象 还是 字符串
                message: err instanceof Error ? err.message : err
            })
        }
        next()
    })
    
优化代码-优化表单数据验证
  1. 表单验证的原则

    前端验证为辅,后端验证为主,后端永远不要相信前端提交过来的任何内容。在实际开发中,前后端都需要对表单数据进行合法性验证,而且,后端作为数据合法校验的最后一个关口,在拦截非法数据方面,起到了至关重要的作用

  2. 单纯使用 if...else... 的形式对数据合法性进行验证,效率低下,出错率高,维护性差,因此,推荐使用第三方数据验证模块来降低出错率,提高验证的效率与可维护性,让后端程序员把更多的精力放在业务逻辑处理上。步骤如下

    • 安装 @hapi/joi 包,为表单中携带的每个数据项定义验证规则

      npm install @hapi/joi@17.1.0
      
    • 安装 @escook/express-joi 中间件,来实现自动对表单数据进行验证的功能

      npm install @escook/express-joi
      
    • 新建 /schema/user.js 用户信息验证规则模块,并初始化代码如下

      // 导入定义验证规则的包
      const joi = require('@hapi/joi')
      
      // 定义用户名和密码的验证规则
      /*
      string() 值必须是字符串
      alphanum() 值只能是包含 a-z A-Z 0-9 的字符串
      min(length)最小长度
      max(length)最大长度
      required() 值是必须项,不能为 undefined
      pattern(正则表达式) 值必须符合正则表达式的规则
      */
      const username = joi.string().alphanum().min(1).max(10).required
      const password = joi.string().pattern(/^[\s]{6,12}$/).required
      
      
      // 定义验证注册和登录表单数据的规则对象
      exports.reg_login_schema = {
          // 表示需要对 req.body 中的数据进行验证
          body: {
              username,
              password,
          },
      }
      
    • 修改 /router/user.js 中的代码如下

      const express = require('express')
      
      // 创建路由对象
      const router = express.Router()
      
      // 导入用户路由处理函数的对应的模块
      const userHandler = require('../router_handler/user')
      
      // 1、导入验证数据的中间件
      const expressJoi = require('@escook/express-joi')
      
      // 2. 导入需要的验证规则对象
      const { reg_login_schema } = require('../schema/user')
      
      // 注册新用户
      /*
         在注册新用户的路由中,声明局部中间件,对当前请求中携带的数据进行验证
         数据验证通过后,会把这次请求流转给后面的路由处理函数
         数据验证失败后,终止后续代码的执行,并抛出一个全局的 Error 错误,进入全局错误级别中间件进行处理
      */
      router.post('/reguser', expressJoi(reg_login_schema) , userHandler.reguser)
      
      // 用户登录
      router.post('/login', userHandler.login)
      
      // 将路由对象共享出去
      module.exports = router
      
    • app.js 的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端

      const joi = require('@hapi/joi')
      
      // 定义错误级别的中间件
      app.use((err, req, res, next) => {
          // 验证失败导致的错误
          if (err instanceof joi.ValidationError) return res.cc(err)
          // 未知的错误
          res.cc(err)
      })