• Node.js的基本使用(四)项目实战——项目初始化及用户注册登录接口的实现


    初始化

    创建项目
    1. 初始化包管理配置文件npm init -y
    2. 运行如下的命令,安装特定版本的 express
    3. 在项目根目录中新建 app.js 作为整个项目的入口文件,并初始化如下的代码
    // 导入 express 模块
    const express = require('express')
    // 创建 express 的服务器实例
    const app = express()
    
    // write your code here...
    
    // 调用 app.listen 方法,指定端口号并启动web服务器
    app.listen(3007, function () {
      console.log('api server running at http://127.0.0.1:3007')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    配置cors跨域
    1. 运行如下的命令,安装 cors 中间件npm i cors@2.8.5
    2. app.js 中导入并配置 cors 中间件
    // 导入 cors 中间件
    const cors = require('cors')
    // 将 cors 注册为全局中间件
    app.use(cors())
    
    • 1
    • 2
    • 3
    • 4
    配置解析表单数据的中间件

    只能解析application/x-www-form-urlencoded

    app.use(express.urlencoded({ extended: false }))
    
    • 1
    初始化路由相关的文件夹
    1. 在项目根目录中,新建 router 文件夹,用来存放所有的路由模块
    2. 在项目根目录中,新建 router_handler 文件夹,用来存放所有的 路由处理函数模块
    初始化用户路由模块
    1. 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. app.js 中,导入并使用 用户路由模块
    // 导入并注册用户路由模块
    const userRouter = require('./router/user')
    app.use('/user', userRouter)
    
    • 1
    • 2
    • 3
    抽离用户路由模块中的处理函数

    为了保证 路由模块 的纯粹性,所有的 路由处理函数,必须抽离到对应的 路由处理函数模块

    1. 在router_handle中的user.js中定义路由处理函数
    exports.register = (req,resp)=>{
        resp.send("发送成功")
        console.log('注册成功');
    }
    exports.login =(req,resp)=>{
        resp.send("登录成功!")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 在router中的user.js中使用定义的处理函数模块
    const express = require("express")
    const router = express.Router()
    
    const userHandle = require("../router_handle/user.js")
    
    router.get("/register",userHandle.register)
    router.post("/login",userHandle.login)
    
    module.exports = router
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image-20220829134235810

    安装配置mysql
    1. 使用npm安装mysql模块npm install mysql
    2. 配置mysql模块
    const mysql = require("mysql")
    const db = mysql.createPool({
        host:'127.0.0.1',
        user:'root',
        password:'root',
        database:'node_demo'
    })
    // 向外共享 db 数据库连接对象
    module.exports = db
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    登录注册

    初始化数据表
    1. 新增user表用来存放用户信息

    1. 检查表单数据是否合法
    • 获取表单数据 res.body 需要安装body-parser中间件,并且需要在引入路由之前引用
    //app.js
    var bodyParser = require('body-parser')
    app.use(bodyParser.urlencoded({ extended: false }))
    app.use(bodyParser.json())
    
    //router_handle/user.js
    exports.register = (req, resp) => {
        // 接收表单数据
        const userinfo = req.body
        // 判断数据是否合法
        if (!userinfo.username || !userinfo.password) {
            return res.send({
                status: 500,
                message: '用户名或密码不能为空!'
            })
        }    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 验证用户名是否存在
        // 检测用户名是否被占用
        db.query('select * from user where username = ?',userinfo.username,function(err,result){
            if(err){
                return res.send({ code:500,msg:err.message})
            }
            if(result.length > 0){
                return res.send({ code:-1,msg:"用户名已被占用,请更换其他用户名!"})
            }
            res.send("新增成功")
        })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 对密码进行加密,在当前项目中,使用 bcryptjs 对用户密码进行加密,优点:
    • 加密之后的密码,无法被逆向破解
    • 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性

    运行如下命令,安装指定版本的 bcryptjs

    npm i bcryptjs@2.4.3
    
    • 1

    /router_handler/user.js 中,导入 bcryptjs

    const bcrypt = require('bcryptjs')
    
    • 1

    在注册用户的处理函数中,确认用户名可用之后,调用 bcrypt.hashSync(明文密码, 随机盐的长度) 方法,对用户的密码进行加密处理

    // 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
    userinfo.password = bcrypt.hashSync(userinfo.password, 10)
    
    • 1
    • 2
    优化res.send()代码

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

    app.use(function(req,res,next){
        res.cc = function (err,code = 500){
            res.send({
                code,
                msg:err instanceof Error ? err.message : err
            })
        }
        next()
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时,注册代码简化为

    exports.register = (req, res) => {
        // 接收表单数据
        const userinfo = req.body
        // 判断数据是否合法
        if (!userinfo.username || !userinfo.password) {
            return res.cc('用户名或密码不能为空!')
        }
        // 检测用户名是否被占用
        db.query('select * from user where username = ?',userinfo.username,function(err,result){
            if(err){
                return res.cc(err)
            }
            if(result.length > 0){
                return res.cc("用户名已被占用,请更换其他用户名!",-1)
            }
        })
        //对用户密码进行加密
        userinfo.password = bcrypt.hashSync(userinfo.password, 10)
        userinfo['nickName'] = '新用户'
        userinfo['userPic'] = ''
        // 插入新用户
        db.query("insert into user set ?",userinfo,function(err,result){
            if(err){
                return res.cc(err)
            }
            if(result.affectedRows != 1){
                return res.cc("注册用户失败,请稍后重试!")
            }
            return res.cc("新增用户成功",200)
        })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    优化表单数据验证

    单纯的使用 if...else... 的形式对数据合法性进行验证,效率低下、出错率高、维护性差。因此,推荐使用第三方数据验证模块,来降低出错率、提高验证的效率与可维护性。

    1. 安装 @hapi/joi 包,为表单中携带的每个数据项,定义验证规则:
    npm install @hapi/joi@17.1.0
    
    • 1
    1. 安装 @escook/express-joi 中间件,来实现自动对表单数据进行验证的功能:
    npm i @escook/express-joi
    
    • 1
    1. 新建 /schema/user.js 用户信息验证规则模块,并初始化代码如下:
    const Joi = require('@hapi/joi')
    const joi = require('@hapi/joi')
    
    /**
     * string() 值必须是字符串
     * alphanum() 值只能是包含 a-zA-Z0-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,
        repassword:Joi.ref('password')
      },
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    1. 修改 /router/user.js 中的代码如下:

      // 1. 导入验证表单数据的中间件
      const expressJoi = require('@escook/express-joi')
      // 2. 导入需要的验证规则对象
      const { reg_login_schema } = require('../schema/user')
      
      router.post("/register",expressJoi(reg_login_schema),userHandle.register)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. app.js 的全局错误级别中间件中,捕获验证失败的错误,并把验证失败的结果响应给客户端:

    // 错误中间件
    app.use(function (err, req, res, next) {
        // 数据验证失败
        if (err instanceof joi.ValidationError) return res.cc(err)
        // 未知错误
        res.cc(err)
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    登录判断用户输入的密码是否正确

    调用 bcrypt.compareSync(用户提交的密码, 数据库中的密码) 方法比较密码是否一致,返回值为布尔类型

    if(!bcrypt.compareSync(userinfo.password,result[0].password)){
        return res.cc('密码错误,请重新输入','-1')
    }
    
    • 1
    • 2
    • 3
    生成 JWT 的 Token 字符串

    在生成 Token 字符串的时候,一定要剔除 密码头像 的值

    // 剔除完毕之后,user 中只保留了用户的 id, username, nickname, email 这四个属性的值
    const user = { ...results[0], password: '', user_pic: '' }
    
    • 1
    • 2

    知识链接—扩展运算符(…)

    对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中

    let bar = { a: 1, b: 2 };
    let baz = { …bar }; // { a: 1, b: 2 }

    如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉

    let bar = {a: 1, b: 2};
    let baz = {…bar, …{a:2, b: 4}}; // {a: 2, b: 4}

    如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

    1. 运行如下的命令,安装生成 Token 字符串的包

      npm i jsonwebtoken@8.5.1
      
      • 1
    2. /router_handler/user.js 模块的头部区域,导入 jsonwebtoken 包:

    // 用这个包来生成 Token 字符串
    const jwt = require('jsonwebtoken')
    
    • 1
    • 2
    1. 创建 config.js 文件,并向外共享 加密还原 Token 的 jwtSecretKey 字符串
    module.exports = {
      jwtSecretKey: 'abcdefg. ^_^',
    }
    
    • 1
    • 2
    • 3
    1. 将用户信息对象加密成 Token 字符串
    // 导入配置文件
    const config = require('../config')
    
    // 生成 Token 字符串
    const tokenStr = jwt.sign(user, config.jwtSecretKey, {
      expiresIn: '10h', // token 有效期为 10 个小时
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 将生成的 Token 字符串响应给客户端
    res.send({
      status: 200,
      message: '登录成功!',
      // 为了方便客户端使用 Token,在服务器端直接拼接上 Bearer 的前缀
      token: 'Bearer ' + tokenStr,
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    C语言版本和GCC版本
    2023华为杯数学建模研赛思路分享——最全版本E题深度解析
    Go微服务实战 - 用户服务开发(gRPC+Protocol Buffer)
    资料分析-笔记
    操作系统OS/存储管理/内存管理/内存管理的主要功能_基本原理_要求
    《嵌入式 – GD32开发实战指南》第20章 GD32的存储结构
    【区块链 | 智能合约】Ethereum源代码(10)- 以太坊Downloader源码分析
    SkeyeVSS输煤智能视频识别安全监控解决方案
    从mybatis-plus-generator看如何编写代码生成器
    单源广度优先搜索 (leetcode经典例题 C++实现)
  • 原文地址:https://blog.csdn.net/qq_46258819/article/details/126587711