一、实现思路
注册功能简单来说就是提供一个路由,命中路由后把用户名和密码插入数据库。但是也有一些细节需要注意
- 对前端传过来的值需要先进行合法性校验。
- 规定用户名必须唯一,因此在往数据库插入用户信息前需要先检查用户名是否存在,若存在则响应客户端告知更改用户名。
- 考虑到密码的安全性,我们不直接将密码明文保存在数据库,而是加密以后再存入数据库,可以利用一些第三方的npm包来实现这个需求。
- 我们可以直接在
a
p
p
app
app上挂在路由,但是考虑到我们将来要书写众多的路由,我们可以把路由抽离出来,使用
express.Router()
得到router
对象,在 r o u t e r router router对象上挂在路由,然后再通过app.use()
使用该路由对象,从而实现模块化开发。
二、项目目录
项目目录结构如下
---- api_server
---- router
---- user.js
---- router_handler
---- user.js
----app.js
---- schema
---- user.js
router里存放路由关系,router_handler里存放路由处理函数。
schema用于放置数据校验规则的配置文件。
三、代码编写
1、基本代码
---- router
---- user.js
const express = require('express')
// 导入路由处理函数
const userHandler = require('../router_handler/user.js')
//调用express.Router()方法,该方法返回一个对象
const router = express.Router()
// 注册路由
router.post('/register', userHandler.register)
module.exports = router
---- app.js
// 导入user路由
const userRouter = require('./router/user.js')
// 使用路由,当第一个参数传入一个路由前缀时,会给所有userRouter上的路由路径加上该前缀,即我们访问的是 /api/login、/api/register
app.use('/api', userRouter)
---- router_handler
---- user.js
const register = (req , res) => {
res.send('命中注册路由!')
}
module.exports = {
login
}
到了这一步,我们可以先使用postman等
a
p
i
api
api测试工具,访问 http://127.0.0.1/api/login
,如果成功返回 命中注册路由
, 说明接口已经跑通,可以进行接下来的编写了。
2、数据合法性校验
这一步我们通过 req.body(express.urlencoded解析数据后挂在在req.body上的)
可以拿到前端传过来的数据,最简单的就是使用 if...else...
判断使得用户名和密码符合我们的要求。
const register = (req, res) => {
const userInfo = req.body
if(!userInfo.username || !userInfo.password) return res.send({status: 1 , message: '注册失败!'})
//其他的条件...
}
上面只是一个示例,验证了用户名或密码为空的情况,但是还有很多其他的校验还没做,如果都用 if...else...
进行判断,代码冗余,可读性变差,因此我们可以借助一些第三方的包来做这个功能。
这里使用joi这个包来实现数据校验功能。
// ./schema/user.js
// 导入joi包用于数据校验
const Joi = require('joi')
// 定义登录注册的校验规则
const reg_login_schema = {
//string()字符串,alphaum()数字和字母的组合,
//min(1)长度最小为1,max(10)最大为10,required()这个值是必须有的
username: Joi.string().alphanum().min(1).max(10).required(),
password: Joi.string()
.pattern(/^[\S]{6,12}$/)
.required()
}
上面的代码导入了
j
o
i
joi
joi这个包,然后定义了一个对象,该对象里分别对用户名和密码做出一些约束。
由于登录和注册都需要对用户名密码进行合法性校验,所有我们可以封装一个自定义的中间件。
// ./schema/user.js
// 定义登录注册数据校验中间件
const joiExpress = (req, res, next) => {
//根据文档使用Joi即可
const schema = Joi.object(reg_login_schema)
const userInfo = req.body ? req.body : {}
//当匹配成功时,err的值是undefined
const { error } = schema.validate(userInfo)
if (error) res.cc(error)
//自定义中间件一定记得调用next()方法,否则服务可能会在这里停止
next()
}
// 导出该中间件
module.exports = joiExpress
// ./router/user.js
// 导入自定义的登录注册数据校验中间件
const joiExpress = require('../schema/user')
// 在该路由下使用中间件
router.post('/register', joiExpress, userHandler.register)
3、注册路由处理函数
基本代码如下
// ./router_handler/user.js
// 注册路由处理函数
const register = (req, res) => {
const userInfo = req.body
// 检查用户名是否已存在
let sql = `select * from ${usersTable} where username=?`
db.query(sql, [userInfo.username], (err, result) => {
if (err) return res.send({status: 1 , message: err.message})
if (result.length > 0) return res.send({status: 1 , message: '用户名已存在,请更换用户名!'})
// 执行注册操作
let insertSql = `insert into ${usersTable} set ?`
// 将用户信息存入数据库
db.query(insertSql, { username: userInfo.username, password: userInfo.password }, (err, result) => {
if (err) return res.cc(err)
if (result.affectedRows >= 1) res.send({status: 1 , message: '用户名已存在,请更换用户名!'})
else res.send({status: 1 , message: '注册失败!请稍后重试~'})
})
})
}
上面的代码我们重复书写了很多次res.send()
,且返回的数据对象有统一的格式,因此我们可以再进行一层封装,编写一个自定义中间件。
// ./app.js
// 自定义处理错误中间件
app.use((req, res, next) => {
//在res对象上挂载cc函数,之后的req上都会拥有该cc函数
res.cc = (err, status = 1) => {
err = err instanceof Error ? err.message : err
res.send({
status,
message: err
})
}
next()
})
// 使用路由,上面的中间件必须在下面这一行挂在路由的前面引入
app.use('/api', userRouter)
有了这个中间件,将代码更改一下
// ./router_handler/user.js
const register = (req, res) => {
const userInfo = req.body
// 检查用户名是否已存在
let sql = `select * from ${usersTable} where username=?`
db.query(sql, [userInfo.username], (err, result) => {
if (err) return res.cc(err)
if (result.length > 0) return res.cc('用户名已存在,请更换用户名!')
// 执行注册操作
let insertSql = `insert into ${usersTable} set ?`
// 将用户信息存入数据库
db.query(insertSql, { username: userInfo.username, password: userInfo.password }, (err, result) => {
if (err) return res.cc(err)
if (result.affectedRows >= 1) res.cc('注册成功!', 1)
else res.cc('注册失败,请稍后重试~')
})
})
}
4、密码加密
// ./router_handler/user.js
// 导入bcryptjs用于密码加密
const bcrypt = require('bcryptjs')
// 注册路由处理函数
const register = (req, res) => {
const userInfo = req.body
// 检查用户名是否已存在
let sql = `select * from ${usersTable} where username=?`
db.query(sql, [userInfo.username], (err, result) => {
if (err) return res.cc(err)
if (result.length > 0) return res.cc('用户名已存在,请更换用户名!')
// 执行注册操作
let insertSql = `insert into ${usersTable} set ?`
// 将明文密码进行加密
userInfo.password = bcrypt.hashSync(userInfo.password, 10)
// 将用户信息存入数据库
db.query(insertSql, { username: userInfo.username, password: userInfo.password }, (err, result) => {
if (err) return res.cc(err)
if (result.affectedRows >= 1) res.cc('注册成功!', 1)
else res.cc('注册失败,请稍后重试~')
})
})
}