node项目实战-01实现注册登录
node项目实战-01实现注册登录
1.项目准备
前面我们已经搭建了一个基础的项目模版,现在我们就用这个项目模版来做一个简单的项目。首先我们安装mongoose模块:
npm install mongoose --save
mongoose
是MongoDB的Node.js驱动程序,它为我们提供了一种在Node.js中使用MongoDB的简单方法。后面我们会详细介绍他的使用方法。
注册接口
我们先来实现一个注册接口,这个接口接收一个参数
username
、password
、phone
,然后将这个参数保存到数据库中。
首先我们分别创建关于用户功能的router
和controller
;新建/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')
-
crypto.createHash('md5')
: 创建一个用于 MD5 散列的 Hash 对象。md5
表示采用 MD5 算法。 -
.update('lonjin' + str)
: 使用给定的数据进行更新,这里的数据是'lonjin' + str
,即将字符串'lonjin'
与参数str
连接后的结果,这样可避免被撞库的风险。 -
.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
中,我们定义了一些公共字段,比如createTime
和updateTime
,在很多场景下我们可能都需要这两个字段,所以我们将这两个字段也抽离出来。
新建:/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接口规范;当然我们可以根据一些业务需求做对应的调整。