node项目实战-01实现注册登录

1.项目准备

前面我们已经搭建了一个基础的项目模版,现在我们就用这个项目模版来做一个简单的项目。首先我们安装mongoose模块:

npm install mongoose --save

mongoose是MongoDB的Node.js驱动程序,它为我们提供了一种在Node.js中使用MongoDB的简单方法。后面我们会详细介绍他的使用方法。

注册接口

我们先来实现一个注册接口,这个接口接收一个参数usernameemailpasswordphone,然后将这个参数保存到数据库中。

首先我们分别创建关于用户功能的routercontroller;新建/router/user.js/controller/userController.js

/router/user.js内容如下:

const express = require('express')
const router = express.Router()
const userController = require('../controller/userController')

router
    .post('/register', userController.register)
module.exports = router

/controller/userController.js内容如下,顺便打印一下我们接受到参数:

exports.register = async (req, res) => {
    console.log(req.body)
}

然后我们在/router/index.js中引入这个路由:

const express = require('express')
const router = express.Router()
const user = require('./user')

router.use('/user', user)
module.exports = router

运行项目:

npm run dev

当我们使用Postman或者apifox访问http://localhost:3000/user/register并传入参数,控制台就会打印我们传入的参数:

{
  "username": "test",
  "email": "test@test.com",
  "password": "123456",
  "phone": "12345678901"
}

拿到参数以后我们需要把这些数据存入到数据库中,这时候就需要用到开头提到的mongoose进行处理。

使用mongoose存储数据

首先新建/model/index.js,引入mongoose,同时进行连接数据库的操作:

const mongoose = require('mongoose')

async function main() {
    await mongoose.connect('mongodb://localhost:27017/nodeDemo')
}

main().then(res => {
    console.log('mongo连接成功')
}).catch(error => {
    console.log('mongo连接失败', error)
})

然后我们在当前文件夹中再建立一个userModel.js文件,在这个文件中定义一个userSchema,最后导出

const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
    username: {
        type: String, //数据类型
        required: true, //数据是否必须
        trim: true, //去除两边空格
        minlength: 4, //最小长度
        maxlength: 18, //最大长度
        validate: {
            validator: function (v) {
                return /^[a-zA-Z0-9_-]{4,18}$/.test(v)
            },
            message: '{VALUE}不是一个合法的用户名'
        }
    },
    email: {
        type: String,
        required: true,
        trim: true,
        validate: {
            validator: function (v) {
                return /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(v)
            },
            message: '{VALUE}不是一个合法的邮箱'
        }
    },
    password: {
        type: String,
        required: true,
    },
    phone: {
        type: String,
        required: true,
        unique: true,
        trim: true,
        validate: {
            validator: function (v) {
                return /^1[34578]\d{9}$/.test(v)
            },
            message: '{VALUE}不是一个合法的手机号'
        }
    },
    avatar: {
        type: String,
        default: null
    },
    createTime: {
        type: Date,
        default: Date.now
    },
    updateTime: {
        type: Date,
        default: Date.now
    }
})

module.exports = userSchema

关于mongoose.Schema的详细使用方法可以参考mongoose文档

导出之后,我们还是要在/model/index.js中进行集中的导出,到时候我们就可以在其他地方进行导入使用了,最终的/model/index.js内容如下:

const mongoose = require('mongoose')

async function main() {
    await mongoose.connect('mongodb://localhost:27017/nodeDemo')
}

main().then(res => {
    console.log('mongo连接成功')
}).catch(error => {
    console.log('mongo连接失败', error)
})

//导出模块
module.exports = {
    User: mongoose.model('User', require('./userModel'))
}

再回到刚才的/controller/userController.js中,我们需要进行修改,将register方法中的console.log(req.body)改为:

const { User } = require('../model/index')
exports.register = async (req, res) => {
    console.log(req.body)
    const userModel = new User(req.body)
    const dbBack = await userModel.save()
    res.status(201).json(dbBack)
}

这样一来,当我们使用Postman或者apifox访问http://localhost:3000/user/register并传入参数,控制台就会打印我们传入的参数,并且将这些参数保存到数据库中。

密码加密

在上面的代码中,我们将密码明文存入了数据库中,这样显然是不安全的,我们需要对密码进行加密处理。这里我们使用crypto中的createHash函数对密码进行加密,然后将加密后的密码存入数据库中。

新建util/md5.js;并引入crypto:

const crypto = require('crypto')

/**
 * md5加密
 * @param str
 * @returns {string}
 */
module.exports = str => crypto.createHash('md5').update('lonjin' + str).digest('hex')
  1. crypto.createHash('md5'): 创建一个用于 MD5 散列的 Hash 对象。md5 表示采用 MD5 算法。

  2. .update('lonjin' + str): 使用给定的数据进行更新,这里的数据是 'lonjin' + str,即将字符串 'lonjin' 与参数 str 连接后的结果,这样可避免被撞库的风险。

  3. .digest('hex'): 返回 Hash 对象的二进制数据的十六进制表示。这就是最终的 MD5 散列值。

然而,请注意,MD5 不再被推荐用于安全目的,因为它已经被发现存在一些弱点。在密码存储或其他需要高度安全性的场景中,更推荐使用更强大的散列算法,比如 SHA-256,并且结合一些附加的安全措施,例如加盐(salting)和适当的迭代次数(iteration)。具体方式如下:

const crypto = require('crypto');

function sha256Hash(str) {
    const hash = crypto.createHash('sha256')
    const hashedStr = hash.update('lonjin' + str).digest('hex')
    return hashedStr
}
module.exports = sha256Hash

处理公共字段

在上面的userSchema.js中,我们定义了一些公共字段,比如createTimeupdateTime,在很多场景下我们可能都需要这两个字段,所以我们将这两个字段也抽离出来。

新建:/model/baseModel.js:

module.exports = {
    createTime: {
        type: Date,
        default: Date.now
    },
    updateTime: {
        type: Date,
        default: Date.now
    }
}

然后在/model/userModel.js中引入并使用:

const baseModel = require('./baseModel')

const userSchema = new mongoose.Schema({
    //...原有逻辑
    avatar: {
        type: String,
        default: null
    },
    ...baseModel
})

数据验证

express-validator是一个非常好用的表单验证中间件,它可以帮助我们在表单提交的时候进行数据验证,从而避免了前端的一些常见的错误。

首先我们需要安装express-validator:

npm install express-validator --save

还是已我们上面提到的注册接口为例子;首先我们封装一下验证规则:

新建middleware/validator/userValidator.js,用来处理用户相关的验证规则,同时我们再建立一个文件夹,去统一处理错误逻辑;新建middleware/validator/errorBack.js

middleware/validator/userValidator.js内容如下:

const { body } = require('express-validator')
const validator = require('./errorBack')

module.exports.register = validator(
    [
        body('username')
            .notEmpty().withMessage('用户名不能为空').bail()
            .isLength({ min: 3, max: 10 }).withMessage('用户名长度必须在3-10之间'),
        body('email')
            .notEmpty().withMessage('邮箱不能为空').bail()
            .isEmail().withMessage('邮箱格式不正确'),
        body('password')
            .notEmpty().withMessage('密码不能为空').bail()
            .isLength({ min: 6, max: 16 }).withMessage('密码长度必须在6-16之间')
    ]
) 

middleware/validator/errorBack.js文件内容如下:

const { validationResult } = require('express-validator')

module.exports = validator => {
    return async (req, res, next) => {
        await Promise.all(validator.map(i => i.run(req)))
        //获取错误信息
        const errors = validationResult(req)
        if (!errors.isEmpty()) {
            // 处理错误信息
            return res.status(401).json({ error: errors.array() })
        }
        next()
    }
}

下面是一些在 express-validator 中常用的验证规则,以及它们的简要说明:

验证器 描述
.notEmpty() 检查字段是否为空。
.isLength({ min, max }) 检查字段的字符长度是否在指定的范围内。
.isEmail() 检查字段是否是有效的电子邮件地址。
.isURL() 检查字段是否是有效的URL。
.isInt() 检查字段是否为整数。
.isNumeric() 检查字段是否为数字。
.isFloat() 检查字段是否为浮点数。
.isDate() 检查字段是否为有效的日期。
.isBoolean() 检查字段是否为布尔值。
.isIn(values) 检查字段值是否在指定的数组中。
.isNotIn(values) 检查字段值是否不在指定的数组中。
.equals(value) 检查字段值是否与指定的值相等。
.not() 反转前面的验证条件。如果之前的验证条件为真,将变为假,反之亦然。
.custom(validatorFunction) 使用自定义验证函数进行验证。
.withMessage(message) 在验证失败时设置错误消息。
.bail() 在验证失败时停止执行后续验证链。

这些是一些常见的验证规则,我们可以根据具体的需求组合它们,构建强大而灵活的验证规则链。

接下来我们在/controller/userController.js中引入验证器并使用:

const express = require('express')
const router = express.Router()
const userController = require('../controller/userController')
//引入验证器
const validator = require('../middleware/validator/userValidator')

// 使用validator中间件
router
    .post('/register',
        validator.register,
        userController.register)

module.exports = router

这样在用户注册的时候,就能对用户提交的数据进行验证,从而避免了前端的一些常见的错误。

这时候我们使用apifox发起注册请求,不传任何参数,就会返回错误信息:

{
    "error": [
        {
            "type": "field",
            "value": "",
            "msg": "用户名不能为空",
            "path": "username",
            "location": "body"
        },
        {
            "type": "field",
            "msg": "邮箱不能为空",
            "path": "email",
            "location": "body"
        },
        {
            "type": "field",
            "msg": "密码不能为空",
            "path": "password",
            "location": "body"
        }
    ]
}

数据唯一性验证

在上面的例子中,我们做了对参数的一些验证,但是还是缺少一个对数据唯一性的验证。比如注册时候我们可能要求手机号和邮箱必须是唯一的。我们这里还是借助express-validator的自定义验证函数custom来实现;当然我们需要连接到数据库进行数据查找。

首先我们在middleware/validator/userValidator.js中添加一个自定义验证函数:

const { body } = require('express-validator')
const validator = require('./errorBack')
// 1.引入User model 
const { User } = require('../../model/index')

module.exports.register = validator(
    [
        body('username')
            .notEmpty().withMessage('用户名不能为空').bail()
            .isLength({ min: 3, max: 10 }).withMessage('用户名长度必须在3-10之间'),
        body('email')
            .notEmpty().withMessage('邮箱不能为空').bail()
            .isEmail().withMessage('邮箱格式不正确')
            .custom(async val => {
                // 2.查询数据库
                const emailValidator = await User.findOne({ email: val })
                // 3.判断是否存在
                if (emailValidator) {
                    return Promise.reject('邮箱已被注册')
                }
            }).bail(),
        body('password')
            .notEmpty().withMessage('密码不能为空').bail()
            .isLength({ min: 6, max: 16 }).withMessage('密码长度必须在6-16之间'),
        body('phone')
            .notEmpty().withMessage('手机号不能为空').bail()
            .isLength({ min: 11 }).withMessage('手机号格式不正确')
            .custom(async val => {
                // 同理
                const phonelValidator = await User.findOne({ phone: val })
                if (phonelValidator) {
                    return Promise.reject('手机号已被注册')
                }
            }).bail(),
    ]
) 

Restful接口规范

在项目中,我们需要对接口进行规范,比如接口的路径,请求方式,参数,返回值等。这里我们可以参考restfulapi接口规范;当然我们可以根据一些业务需求做对应的调整。