持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
项目要求
通过一个小项目实践 Node.js 各个知识点。麻雀虽小,五脏俱全,这个项目会从 0~1,逐步完成所有需求。该项目实现接口有注册、登录、获取用户信息等等
登录注册
新建 ev_users 表
安装并配置 mysql 模块
在 API 接口项目中,需要安装并配置 mysql 这个第三方模块,来连接和操作 MySQL 数据库
-
运行如下命令,安装 mysql 模块
npm install mysql@2.18.1 -
在项目根目录中新建
/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: '用户名或密码不能为空'
})
}
检测用户名是否被占用
-
导入数据库操作模块
const db = require('../db/index') -
定义 SQL 语句
const sqlStr = 'select * from ev_users where username=?' -
执行 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:'用户名被占用,请更换其他用户名' }) } // 用户名可以使用。。。 }) -
对密码进行加密处理
-
为了保证密码的安全性,不建议在数据库以明文的形式保存用户密码,推荐对密码进行加密存储
-
在当前项目中,使用
bcryptjs对用户密码进行加密, -
优点:
- 加密之后的密码,无法被逆向破解
- 同一明文密码多次加密,得到的加密结果各不相同,保证了安全性
-
运行如下命令,安装指定的版本
bcryptjsnpm install bcryptjs@2.4.3 -
在
/router_handler/user.js中, 导入bcryptjsconst bcrypt = require('bcryptjs') -
在注册用户的处理函数中,确认用户名可用之后,调用
bcryptjs方法,对用户的密码进行加密处理// 对用户的密码进行加密,返回值是加密之后的密码字符串 userinfo.password = bcrypt.hashSync(userinfo.password,10)
-
-
插入新用户
-
定义插入用户的 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() 函数
-
在
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() })
优化代码-优化表单数据验证
-
表单验证的原则
前端验证为辅,后端验证为主,后端永远不要相信前端提交过来的任何内容。在实际开发中,前后端都需要对表单数据进行合法性验证,而且,后端作为数据合法校验的最后一个关口,在拦截非法数据方面,起到了至关重要的作用
-
单纯使用
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) })
-