目录
1 一些注意
2 创建数据库
3 项目结构
4 配置文件 config.js
5 参数规则包 @hapi/joi与@escook/express-joi
5.1 安装
5.2 文档中的demo
5.2.1 定义规则
5.2.2 使用规则
5.3 项目中的使用
5.3.1 定义信息规则
5.3.2 使用规则
6 密码加密包 bcrypt.js
6.1 加密
6.2 对比
7 处理form-data数据包 multer
7.1 安装
7.2 项目中的使用
8 入口文件 main.js
8.1 约束一些请求
8.2 注册所有路由模块
8.3 处理请求体
1 一些注意
- 不要认为接口中的message没什么用就不写了,写上在排错与前端调用的时候都比较方便
- 这里面记录了该项目后端的详细实现方式 api_server_ev 在这个链接中关于文章列表后面几个视图没有写
- 如果只看接口文档可以看这个 ShowDoc
- 在实际开发的时候express有时会出现错误后服务就断掉,且与其他模块可能会出现协同性的问题(不是一家开发的,比如mysql出现了问题,给express错误处理中间件服务也会断掉)
- 实际开发中应该将路由与视图不在同一个文件写符合模块化开发的要求,我这里就写在一起了,因为对于一个小项目来讲,一共就十几个url,摊到每个模块也就四五个url,你过度封装反而会导致你维护的时候不好找,一会儿点开这个一会儿点开那个。分开写的原因是当路由多的时候可以一个人搞路由一个人搞视图,分开写的缺点是从主函数到视图函数会出现多级的调用,对于单人开发就显得不是很友好
- 大事件项目后端视频 07.项目-初始化项目_哔哩哔哩_bilibili P77-P96
- 后端代码链接 链接:百度网盘 请输入提取码 提取码:wd4n
2 创建数据库
数据库使用的是mysql,数据表有三个,分别是 用户信息数据表,文章分类数据表,文章内容数据表
三个数据表的这些信息都是一样的
用户信息数据表,其中user_pic是用户头像,要求为base64字符串,我们的Datatype可以写为TEXT
文章信息数据表
文章分类数据表
三张表都是没有外键的,我们让用户在上传文章的时候要上传用户id(后端根据token自动添加参数),然后显示的时候根据用户id进行筛选(后端根据token自动筛选),从而用户只会看到自己的文章
文章分类没有给author_id,实际上也可以再加一个author_id,让每一个用户都有自己专属的分类
3 项目结构
项目结构如下,最上面的uploads放前端传过来的文章封面,config.js中有一些配置,main.js是入口文件,运行的时候node main.js
router是四个模块的路由
4 配置文件 config.js
我使用了一个config.js文件作为配置文件,里面有很多的全局变量,config文件内容没有做导出,在使用的时候直接引入config就行了
config.js中有需要导入的库,token密钥,数据库配置,文章封面存放的文件夹,以及一些验证规则
// 需要导入的库
joi = require('joi')
mysql = require('mysql')
express = require('express')
jsonwebtoken = require('jsonwebtoken')
bcryptjs = require('bcryptjs')
expressJoi = require('@escook/express-joi')
cors = require('cors')
multer = require('multer')
path = require('path')
// token密钥
secretKey = 'xifgnmuioherawbt'
// 数据库
db = mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'12345678',
database:'big_things'
})
// 文章封面存放的文件夹
uploads = multer({dest:path.join(__dirname,'../uploads')})
// id的验证规则都是相同的,所以可以统一来写
id_rule = joi.number().integer().min(1).required()
// 用户信息表 验证规则
userinfo_username_rule = joi.string().alphanum().min(1).max(10).required()
userinfo_password_rule = joi.string().pattern(/^[\S]{6,12}$/)
userinfo_nickname_rule = joi.string().required()
userinfo_email_rule = joi.string().email().required()
userinfo_avatar_rule = joi.string().dataUri().required()
// 文章分类表 验证规则
category_name_rule = joi.string().required()
category_alias_rule = joi.string().alphanum().required()
// 文章列表 验证规则
article_title_rule = joi.string().required()
article_content_rule = joi.string().required().allow('')
// 分类id与状态 有的时候需要必选,有的时候不需要必选,所以在总的规则中取消了required(),需要的时候你再加上就行了
article_cate_id_rule = joi.number().integer().min(1)
article_state_rule = joi.string().valid('已发布', '草稿')
// 文章列表要做分页,pagenum是要看第几页
article_pagenum_rule = joi.number().integer().min(1).required()
// pagesize是每页显示多少条数据
article_pagesize_rule = joi.number().integer().min(1).required()
简单看一下导入的库
- joi与express-joi 前端传入信息规则验证
- mysql 数据库
- express 后端框架
- jsonwebtoken 做token的,用于前后端跨域信息验证
- bcryptjs 密码加密
- cors 做跨域的
- multer 处理前端传入的form-data数据的
- path 路径拼接
其中 mysql在这里有具体用法 8.mysql模块_Suyuoa的博客-CSDN博客
express,jsonwebtoken,cors在这里有具体用法 7.Express模块基础用法_express 引入模块_Suyuoa的博客-CSDN博客
5 参数规则包 @hapi/joi与@escook/express-joi
后端是一定要加数据验证的,不要因为前端做了验证后端就不做了,宁可多做不要少做
我们如果每个验证都写if else那样的话代码量较多,维护起来比较麻烦,我们可以借助一些第三方的包来搞
如果规则较简单的时候也可以不用,轮子太多也不利于项目的维护,有时候搞来搞去发现轮子不好用,最好还有查轮子的文档,比如我就遇到了这个问题,还得在网上查一下怎么搞定这个错误 【node.js】报错Cannot mix different versions of joi schemas解决方法_前端小二哥的博客-CSDN博客
@hapi/joi 是定义规则用的,@escook/express-joi 是验证规则用的
@hapi/joi是@escook/express-joi的依赖,在导入的时候要先导入@hapi/joi再导入@escook/express-joi
5.1 安装
5.2 文档中的demo
在npm官网上搜索@escook/express-joi会出现它的简易demo
5.2.1 定义规则
我们看userSchema这个对象,其中有body,query,params,分别的意思就是req.body,req.query,req.params
有什么写什么就行,比如只验证req.body,就只写body就行
body,query,params这三个名称固定
下面看body中的内容,里面username这些是参数名,后面跟的是验证规则。可以给验证规则自己定义一个变量方便复用
验证规则的名字一般我们保持与接收的参数名一致就行,如果不一致你就需要这样写
- 实则还是遵循键值的格式
Joi.string()是先让其变成字符串形式,Joi.number().integer()是让其先变成数字型然后变为整形
alphanum()表示这个字符串中只能包含 a-z A-Z 0-9
min()与max()是字符串的最小,最大长度,如果类型是数字那么就代表最大值最小值
required()表示是必选参数
patter()内加正则表达式用于验证规则
repassword这个就是重新输入密码的参数名,joi.ref('password')的意思是必须与password保持一致
只有写在body中的内容才会被接收,如果你多给了是存不到req.body中的,比如上面我给了id,nickname.email,如果你再多给一个password,最后你查req.body中是没有password的
5.2.2 使用规则
userSchema是我们刚刚定义的规则,如果在路由中出现了验证不通过的情况就会抛出一个错误,抛出错误后下面定义一个错误级别中间件,这个错误级别中间件会把错误的信息返回给客户端
5.3 项目中的使用
5.3.1 定义信息规则
我们下面看一下传入信息的规则,规则都放在config.js中,搞的全局变量,后面在验证的时候直接使用这个全局变量就好
- number() 必须为数字
- integer() 必须为整形
- min(1) 当值为数字时,最小值为1,当值为字符时,最小长度为1
- required() 必须传入
- string() 必须为字符串
- alphanum() 只能包含 a-z,A-Z,0-9
- max(10) 当值为数字时,最大值为10,当值为字符时,最大长度为10
- pattern(/^[\S]{6,12}$/) pattern中是正则的用法,这里是6-12位非空字符串
- email() 必须为邮箱格式
- dataUri() 比如是dataUri格式,比如 'data:image/png;base64,VE9PTUFOWVNFQ1JFVFM=' 项目中用其处理base64图像字符串
- allow('') 允许为空
- valid('已发布', '草稿') 值只能为 已发布 或 草稿
5.3.2 使用规则
首先定义一个对象,如果是get的查询字符串传递就写query
如果是post就写body,form-data与xxx-www-form-urlencoded都用body
如果是拼接路由就用params
当有不符合规则的信息时,在main.js中加入一个中间件,提示客户端错误信息
6 密码加密包 bcrypt.js
可以通过 beryptjs 来对密码部分进行加密,首先定义一个密钥,这个密钥就随便写就行,密码会根据这个密钥进行加密与解密,我在config.js中定义了一个全局变量secretKey
6.1 加密
在注册的时候,加密后存储数据库,加密方法为 bcryptjs.hashSync(),第一个参数为加密的内容,第二个参数是哈希烟的长度
- 即使是相同的密码也会得到不同的加密结果
6.2 对比
使用bcyptjs.compareSync()将传入的密码与数据库中存储的真实密码进行比对
7 处理form-data数据包 multer
multer是处理 multipart/form-data 表单上传的数据用的(Multer 不会处理任何非 multipart/form-data 类型的表单数据)
7.1 安装
7.2 项目中的使用
我们先看几个关于multer的信息
传上来的文件被存储在uploads文件夹中,每一次成功传输会生成一个这样的文件
在mysql中会存储文件的路径
下面我们来看使用
在config.js中,我们指定了一个文件夹用来放文章封面,dest这个键就是放在那个位置的意思
- 这里只是管把文件存在哪个实际的地方,和数据库中的数据无关
使用single()方法,参数为传递的文件参数名,single()方法会让cover_img存在req.file中,我们打印出 req.file.filename 看一下
打印出来的结果只有文件名
下面用path.join()只和数据库中的信息有关,和实际文件存在哪里无关
8 入口文件 main.js
const config = require('./config.js')
const login_register_router = require('./router/login_register.js')
const personal_center_router = require('./router/personal_center.js')
const article_category_management_router = require('./router/article_category_management.js')
const article_list_management_router = require('./router/article_list_management.js')
const { expressjwt: jwt } = require("express-jwt")
const app = express()
app.use(function (req, res, next) {
res.setTimeout(5*1000, function () {
res.send("请求超时")
});
next();
});
app.use(
jwt({
secret: secretKey,
algorithms: ["HS256"],
}).unless({ path: [/^\/api\//] })
)
app.use(cors())
app.use(express.urlencoded({extended:false}))
app.use(login_register_router)
app.use(personal_center_router)
app.use(article_category_management_router)
app.use(article_list_management_router)
app.use((err,req,res,next) => {
// 判断用户提交的信息
if (err instanceof joi.ValidationError) return res.send({
status:1,
message:err.message
})
// 判断token
if (err.name === 'UnauthorizedError') {
return res.send({status:1,message:'无效的token'})
}
// 其他错误
return res.send({status:1,message:err.message})
})
app.listen(80,() => {
console.log('服务在80端口启动')
})
首先引入配置文件和所有的路由模块
引入jwt,只有在main.js中需要,所以就在这里引入了,而且这个库的引入方式有一点奇特,不方便在config.js中引入
初始化一个app
8.1 约束一些请求
放一个中间件,当请求时间超过5秒时,返回请求超时
放一个中间件,只有使用/api开头的路由不需要token,其余均需要token
支持跨域
支持前端post发送一些信息
8.2 注册所有路由模块
8.3 处理请求体
第一个if用来处理用户提交的信息(我们上面用joi定义的那些规则),第二个if用来判断token
在80端口启动