【综合项目】api系统——基于Node.js、express、mysql等技术

目录

[0 前言](#0 前言)

[1 初始化](#1 初始化)

[2 注册登录](#2 注册登录)

[2.1 注册](#2.1 注册)

[2.1.1 功能:密码加密(2.3.3)](#2.1.1 功能:密码加密(2.3.3))

[2.1.1.1 操作](#2.1.1.1 操作)

[2.1.1.2 bcryptjs详解](#2.1.1.2 bcryptjs详解)

[2.1.2 插入新用户(2.3.4)](#2.1.2 插入新用户(2.3.4))

[2.1.3 优化:表单数据验证(2.5)](#2.1.3 优化:表单数据验证(2.5))

[2.1.3.1 过时代码修正](#2.1.3.1 过时代码修正)

[2.1.3.2 关键操作](#2.1.3.2 关键操作)

[2.2 登录](#2.2 登录)

[2.2.1 判断密码是否正确(2.6.3)](#2.2.1 判断密码是否正确(2.6.3))

[2.2.2 生成 JWT 的 Token 字符串的注意点(2.6.4)](#2.2.2 生成 JWT 的 Token 字符串的注意点(2.6.4))

[3 个人中心](#3 个人中心)

[3.1 更新用户基本信息](#3.1 更新用户基本信息)

[3.1.1 验证表单数据(3.2.2)](#3.1.1 验证表单数据(3.2.2))

[3.2 重置密码](#3.2 重置密码)

[3.2.1 验证表单数据(3.3.2)](#3.2.1 验证表单数据(3.3.2))

[3.3 更新头像](#3.3 更新头像)

[3.3.1 验证表单数据(3.4.2)](#3.3.1 验证表单数据(3.4.2))

[4 文章分类管理](#4 文章分类管理)

[4.1 根据Id更新文章分类数据](#4.1 根据Id更新文章分类数据)

[4.1.1 查询分类名称与别名是否被占用(4.5.4)](#4.1.1 查询分类名称与别名是否被占用(4.5.4))

[5 文章管理](#5 文章管理)

[5.1 发布新文章](#5.1 发布新文章)

[5.1.1 使用 multer 解析表单数据(5.2.3)](#5.1.1 使用 multer 解析表单数据(5.2.3))

[5.1.2 验证表单数据(5.2.4)](#5.1.2 验证表单数据(5.2.4))

[5.1.3 新建数据对象(5.2.5)](#5.1.3 新建数据对象(5.2.5))


0 前言

本章仅记录部分功能代码(以前文章中未涉及的新内容)以及所遇到的问题

详细内容见 项目首页 - api系统 - GitCode 中的指导文档

❗❗❗最终完整代码也会上传至GitCode中,收藏方便查找

注:部分标题后面跟了原文档中的序号,方便查看


1 初始化

此部分暂无内容


2 注册登录

2.1 注册

2.1.1 功能:密码加密(2.3.3)

2.1.1.1 操作

使用 bcryptjs 对用户密码进行加密

安装指定版本的 bcryptjs :

CoffeeScript 复制代码
npm i bcryptjs@2.4.3

导入 bcryptjs :

javascript 复制代码
const bcrypt = require('bcryptjs')

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

javascript 复制代码
// 对用户的密码,进行 bcrype 加密,返回值是加密之后的密码字符串
userinfo.password = bcrypt.hashSync(userinfo.password, 10)

2.1.1.2 bcryptjs详解

bcrypt.hashSync 是 `bcrypt` 库中用于同步生成哈希值的函数,在 Node.js 中经常使用该库来对密码等敏感信息进行哈希处理。以下详细介绍 bcrypt.hashSync 函数的参数:

函数

javascript 复制代码
bcrypt.hashSync(data, saltOrRounds);

参数说明

  1. data

类型:string

描述:需要进行哈希处理的数据,通常是用户的密码。这是一个必需的参数,代表你要加密的原始文本。
2. saltOrRounds

类型:string 或 number

描述:该参数可以是一个盐值(string 类型),也可以是生成盐的轮数(number 类型)。
当 saltOrRounds 为 number 类型时

它代表生成盐的轮数,也称为成本因子(cost factor)。这个值越大,生成盐和哈希值所花费的时间就越长,安全性也相对更高。推荐的值通常在 10 - 12 之间。

bcrypt 会根据这个轮数自动生成一个随机的盐值,然后使用这个盐值对 data 进行哈希处理。

当 saltOrRounds 为 string 类型时

它代表一个预先定义好的盐值。使用自定义盐值时,每次使用相同的盐和数据进行哈希处理,会得到相同的哈希结果。

一般情况下,不建议手动指定盐值,因为 bcrypt 自动生成的随机盐值可以更好地保证安全性。

返回值

bcrypt.hashSync 函数会返回一个包含盐值和哈希值的字符串,这个字符串可以安全地存储在数据库中,用于后续的密码验证。


2.1.2 插入新用户(2.3.4)

判断插入是否成功

javascript 复制代码
if (results.affectedRows !== 1) {
return res.send({ status: 1, message: '注册用户失败,请稍后再试!' })
}

即判断影响数据行数是否为1


2.1.3 优化:表单数据验证(2.5)

2.1.3.1 过时代码修正

const joi = require('@hapi/joi') 的写法现在已经失效,

应该这样导入:const joi = require('joi')


2.1.3.2 关键操作

安装 @hapi/joi 包,为表单中携带的每个数据项,定义验证规则:

CoffeeScript 复制代码
npm install @hapi/joi@17.1.0

安装 @escook/express-joi 中间件,来实现自动对表单数据进行验证的功能:

CoffeeScript 复制代码
 npm i @escook/express-joi

常用验证规则:

除此之外,本文下方的验证表单数据中还包含一些其他常用规则

javascript 复制代码
 /**
 * string() 值必须是字符串
 * alphanum() 值只能是包含 a-zA-Z0-9 的字符串
 * min(length) 最小长度
 * max(length) 最大长度
 * required() 值是必填项,不能为 undefined
 * pattern(正则表达式) 值必须符合正则表达式的规则
 */

注意:一定要先指定一种数据类型string()或者其他(包括any()),然后才能执行后续操作


导出规则

javascript 复制代码
// 注册和登录表单的验证规则对象
exports.reg_login_schema = {
// 表示需要对 req.body 中的数据进行验证
  body: {
    username,
    password,
  },
 }

使用规则

javascript 复制代码
 // 1. 导入验证表单数据的中间件
const expressJoi = require('@escook/express-joi')
 // 2. 导入需要的验证规则对象
const { reg_login_schema } = require('../schema/user')


//...


router.post('/reguser', expressJoi(reg_login_schema), userHandler.regUser)

2.2 登录

2.2.1 判断密码是否正确(2.6.3)

使用加密密码的包bcrypt

返回值是布尔值(true 一致、false 不一致)

javascript 复制代码
bcrypt.compareSync(用户提交的密码, 数据库中的密码)

示例:

javascript 复制代码
// 拿着用户输入的密码,和数据库中存储的密码进行对比
const compareResult = bcrypt.compareSync(userinfo.password, results[0].password)
// 如果对比的结果等于 false, 则证明用户输入的密码错误
if (!compareResult) {
	return res.cc('登录失败!')
}
// TODO:登录成功,生成 Token 字符串

2.2.2 生成 JWT 的 Token 字符串的注意点(2.6.4)

核心注意点:在生成 Token 字符串的时候,一定要剔除 密码 和 头像 的值

通过 ES6 的高级语法,快速剔除 密码 和 头像 的值:

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

3 个人中心

3.1 更新用户基本信息

3.1.1 验证表单数据(3.2.2)

记录新属性

javascript 复制代码
 const id = joi.number().integer().min(1).required()
 const nickname = joi.string().required()
 const email = joi.string().email().required()

integer() 方法用于验证一个值是否为整数


3.2 重置密码

3.2.1 验证表单数据(3.3.2)

javascript 复制代码
body: {
      // 使用 password 这个规则,验证 req.body.oldPwd 的值
    oldPwd: password,
    newPwd: joi.not(joi.ref('oldPwd')).concat(password),
  },

使用 joi.not(joi.ref('oldPwd')).concat(password) 规则,验证 req.body.newPwd 的值

解读:

  1. joi.ref('oldPwd') 表示 newPwd 的值必须和 oldPwd 的值保持一致

  2. joi.not(joi.ref('oldPwd')) 表示 newPwd 的值不能等于 oldPwd 的值

  3. .concat() 用于合并 joi.not(joi.ref('oldPwd')) 和 password 这两条验证规则


3.3 更新头像

3.3.1 验证表单数据(3.4.2)

javascript 复制代码
// dataUri() 指的是如下格式的字符串数据:
// data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=
 const avatar = joi.string().dataUri().required()

dataUrl() 方法用于验证一个字符串是否符合数据 URL 的格式


4 文章分类管理

4.1 根据Id更新文章分类数据

4.1.1 查询分类名称与别名是否被占用(4.5.4)

javascript 复制代码
SELECT * FROM ev_article_cate WHERE Id <> ? AND (name = ? or alias = ?)

注意:已有数据的情况下进行更新内容,需要排除当前数据的内容,否做无法做到部分内容修改!


5 文章管理

5.1 发布新文章

5.1.1 使用 multer 解析表单数据(5.2.3)

URL-encoded:适用于传输简单的文本数据,例如表单中的文本字段

multipart/form-data:适用于上传文件或包含二进制数据的表单

注意:使用 express.urlencoded() 中间件无法解析 multipart/form-data 格式的请求体数据

当前项目,推荐使用 multer 来解析 multipart/form-data 格式的表单数据

安装:

CoffeeScript 复制代码
 npm i multer@1.4.2

创建与使用:

javascript 复制代码
// 导入解析 formdata 格式表单数据的包
const multer = require('multer')
 // 导入处理路径的核心模块
const path = require('path')
// 创建 multer 的实例对象,通过 dest 属性指定文件的存放路径
const upload = multer({ dest: path.join(__dirname, '../uploads') })



// 发布新文章的路由
// upload.single() 是一个局部生效的中间件,用来解析 FormData 格式的表单数据
// 将文件类型的数据,解析并挂载到 req.file 属性中
// 将文本类型的数据,解析并挂载到 req.body 属性中
router.post('/add', upload.single('cover_img'), article_handler.addArticle)

之后文本类型的数据,即字段会通过joi来进行规则验证,但是文件类型的数据不行,得额外用if判断


5.1.2 验证表单数据(5.2.4)

注意:先后顺序一定不能变,因为multer会将其他字段挂载到req.body上,如果在joi之后,会导致部分字段不会被joi检测

javascript 复制代码
// 导入验证数据的中间件
const expressJoi = require('@escook/express-joi')
 // 导入文章的验证模块
const { add_article_schema } = require('../schema/article')
 // 发布新文章的路由
// 注意:在当前的路由中,先后使用了两个中间件:
//       先使用 multer 解析表单数据
//       再使用 expressJoi 对解析的表单数据进行验证
router.post('/add', upload.single('cover_img'), expressJoi(add_article_schema), 
article_handler.addArticle)

验证文件类型:

javascript 复制代码
 // 发布新文章的处理函数
exports.addArticle = (req, res) => {
    // 手动判断是否上传了文章封面
  if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必选
参数!')
  // TODO:表单数据合法,继续后面的处理流程...
 })

5.1.3 新建数据对象(5.2.5)

javascript 复制代码
 const articleInfo = {
  // 标题、内容、状态、所属的分类Id
  ...req.body,
  // 文章封面在服务器端的存放路径
  cover_img: path.join('/uploads', req.file.filename),
  // 文章发布时间
  pub_date: new Date(),
  // 文章作者的Id
  author_id: req.user.id,
 }

断更...

相关推荐
飞30036 分钟前
腾讯集团软件开发-后台开发方向内推
java·mysql·业界资讯
暴怒的代码3 小时前
云原生监控篇——全链路可观测性与AIOps实战
数据库·sql·mysql
m0_748246358 小时前
最新最详细的配置Node.js环境教程
node.js
WannaRunning10 小时前
MySQL中的共享锁和排他锁
数据库·mysql
zyplayer-doc10 小时前
MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
数据库·mysql
m0_7482500311 小时前
数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(一).创建基础表
数据库·mysql·oracle
m0_7482333612 小时前
深入了解 MySQL 中的 JSON_CONTAINS
数据库·mysql·json
GzlAndy12 小时前
MySQL整体架构
mysql
南城巷陌12 小时前
HTTP 协议的发展历程:从 HTTP/1.0 到 HTTP/2.0
前端·网络·网络协议·http·node.js
咖啡の猫12 小时前
http 模块
后端·node.js