【nodejs】大事件后台管理项目(三)——文章管理接口

上一节

5. 文章管理

5.1 新建 ev_articles 表

CREATE TABLE `my_db_01`.`ev_articles`  (
  `Id` int(0) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL COMMENT '文章标题',
  `content` text NOT NULL COMMENT '文章内容',
  `cover_img` varchar(255) NOT NULL COMMENT '封面图片',
  `pub_date` varchar(255) NOT NULL COMMENT '文章发表日期',
  `state` varchar(255) NOT NULL COMMENT '文章发布状态',
  `is_delete` tinyint(255) NOT NULL DEFAULT 0 COMMENT '0未删除,1相反',
  `cate_id` int(0) NOT NULL COMMENT '分类id',
  `author_id` int(0) NOT NULL COMMENT '作者id',
  PRIMARY KEY (`Id`),
  UNIQUE INDEX(`Id`)
) COMMENT = '文章表';

5.2 发布新文章

5.2.0 实现步骤

  1. 初始化路由模块
  2. 初始化路由处理函数模块
  3. 使用 multer 解析表单数据
  4. 验证表单数据
  5. 实现发布文章的功能

5.2.1 初始化路由模块

  1. 创建 /router/article.js 路由模块,并初始化如下的代码结构:
// 导入 express
const express = require('express')
// 创建路由对象
const router = express.Router()

// 发布新文章
router.post('/add', (req, res) => {
  res.send('ok')
})

// 向外共享路由对象
module.exports = router
  1. app.js 中导入并使用文章的路由模块:
// 导入并使用文章路由模块
const articleRouter = require('./router/article')
// 为文章的路由挂载统一的访问前缀 /my/article
app.use('/my/article', articleRouter)

5.2.2 初始化路由处理函数模块

  1. 创建 /router_handler/article.js 路由处理函数模块,并初始化如下的代码结构:
// 发布新文章的处理函数
exports.addArticle = (req, res) => {
  res.send('ok')
}
  1. 修改 /router/article.js 中的代码如下:
const express = require('express')
const router = express.Router()

// 导入文章的路由处理函数模块
const article_handler = require('../router_handler/article')

// 发布新文章
router.post('/add', article_handler.addArticle)

module.exports = router

5.2.3 使用 multer 解析表单数据

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

南涧网站制作公司哪家好,找创新互联!从网页设计、网站建设、微信开发、APP开发、响应式网站建设等网站项目制作,到程序开发,运营维护。创新互联从2013年创立到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联。

当前项目,推荐使用 multer 来解析 multipart/form-data 格式的表单数据。https://www.npmjs.com/package/multer

  1. 运行如下的终端命令,在项目中安装 multer
npm i multer@1.4.2
  1. /router_handler/article.js 模块中导入并配置 multer
// 导入解析 formdata 格式表单数据的包
const multer = require('multer')
// 导入处理路径的核心模块
const path = require('path')

// 创建 multer 的实例对象,通过 dest 属性指定文件的存放路径
const upload = multer({ dest: path.join(__dirname, '../uploads') })
  1. 修改 发布新文章 的路由如下:
// 发布新文章的路由
// upload.single() 是一个局部生效的中间件,用来解析 FormData 格式的表单数据
// 将文件类型的数据,解析并挂载到 req.file 属性中
// 将文本类型的数据,解析并挂载到 req.body 属性中
router.post('/add', upload.single('cover_img'), article_handler.addArticle)
  1. /router_handler/article.js 模块中的 addArticle 处理函数中,将 multer 解析出来的数据进行打印:
// 发布新文章的处理函数
exports.addArticle = (req, res) => {
  console.log(req.body) // 文本类型的数据
  console.log('--------分割线----------')
  console.log(req.file) // 文件类型的数据

  res.send('ok')
}

测试

在认证条件下输入cover_img,查看返回值

5.2.4 验证表单数据

实现思路:通过 express-joi 自动验证 req.body 中的文本数据;通过 if 判断手动验证 req.file 中的文件数据;

  1. 创建 /schema/article.js 验证规则模块,并初始化如下的代码结构:
// 导入定义验证规则的模块
const joi = require('joi')

// 定义 标题、分类Id、内容、发布状态 的验证规则
const title = joi.string().required()
const cate_id = joi.number().integer().min(1).required()
const content = joi.string().required().allow('')
const state = joi.string().valid('已发布', '草稿').required()

// 验证规则对象 - 发布文章
exports.add_article_schema = {
  body: {
    title,
    cate_id,
    content,
    state,
  },
}
  1. /router/article.js 模块中,导入需要的验证规则对象,并在路由中使用:
// 导入验证数据的中间件
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)
  1. /router_handler/article.js 模块中的 addArticle 处理函数中,通过 if 判断客户端是否提交了 封面图片
// 发布新文章的处理函数
exports.addArticle = (req, res) => {
    // 手动判断是否上传了文章封面
  if (!req.file || req.file.fieldname !== 'cover_img') return res.cc('文章封面是必选参数!')

  // TODO:表单数据合法,继续后面的处理流程...
})

5.2.5 实现发布文章的功能

以下的内容放到/router_handler/article.js的处理函数中

  1. 整理要插入数据库的文章信息对象:
// 导入处理路径的 path 核心模块
const path = require('path')

//注意:放入到处理函数内
const articleInfo = {
  // 标题、内容、状态、所属的分类Id
  ...req.body,
  // 文章封面在服务器端的存放路径
  cover_img: path.join('/uploads', req.file.filename),
  // 文章发布时间
  pub_date: new Date(),
  // 文章作者的Id
  author_id: req.auth.id,
}
  1. 定义发布文章的 SQL 语句:
const sql = `insert into ev_articles set ?`
  1. 调用 db.query() 执行发布文章的 SQL 语句:
// 导入数据库操作模块
const db = require('../db/index')

// 执行 SQL 语句
db.query(sql, articleInfo, (err, results) => {
  // 执行 SQL 语句失败
  if (err) return res.cc(err)

  // 执行 SQL 语句成功,但是影响行数不等于 1
  if (results.affectedRows !== 1) return res.cc('发布文章失败!')

  // 发布文章成功
  res.cc('发布文章成功', 0)
})
  1. app.js 中,使用 express.static() 中间件,将 uploads 目录中的图片托管为静态资源:
// 托管静态资源文件
app.use('/uploads', express.static('./uploads'))

测试

我的踩坑点:

  1. 原来之所以使用multipart/form-data的格式提交数据是因为cover_img不承认提交网络图片链接的形式,而是要选择本地文件,不然是无法读取到文件类型的,点击File即可,postman的我不清楚。
  2. articleInfo对象中的req.user一定要改成req.auth,不然它会一直提示Cannot read properties of undefined (reading 'id')的错误,现在就可以看到我提交成功了。

    可以看到我在uploads文件夹中存放着以二进制流的形式存放图片数据。

5.3 查看文章列表

/router_handler/article.js

//文章列表显示处理函数
exports.getArticle = (req, res) => {
	const sql = 'select * from ev_articles where is_delete=0 order by id asc'

	db.query(sql, (err, results) => {
		// 1. 执行 SQL 语句失败
		if (err) return res.cc(err)

		// 2. 执行 SQL 语句成功
		res.send({
			status: 0,
			message: '获取文章列表成功!',
			data: results,
		})
	})
	// res.send('ok')
}

/router/article.js

// 获取文章的列表数据
router.get('/list', article_handler.getArticle)

测试:

5.4 根据Id删除文章

/router/article.js

//根据ID删除文章的路由
router.get('/delete/:Id', expressJoi(delete_schema), article_handler.deleteById)

/router_handler/article.js

// 删除文章分类的处理函数
exports.deleteById = (req, res) => {
	const sql = `update ev_articles set is_delete=1 where Id=?`

	db.query(sql, req.params.Id, (err, results) => {
		// 执行 SQL 语句失败
		if (err) return res.cc(err)

		// SQL 语句执行成功,但是影响行数不等于 1
		if (results.affectedRows !== 1) return res.cc('删除文章失败!')

		// 删除文章分类成功
		res.cc('删除文章成功!', 0)
	})
}

/schema/article.js

// 定义 文章Id 的校验规则
const Id = joi.number().integer().min(1).required()
// 校验规则对象 - 删除分类
exports.delete_schema = {
	params: {
		Id,
	},
}

注意大事件项目的art_list文件的这个Id不要改成了id,因为数据库里面写的也是Id

测试

5.5 根据Id查找文章

/router/article.js

//根据Id查询文章
router.get(
	'/:Id',
	expressJoi(get_articleById_schema),
	article_handler.getArticleById
)

/router_handler/article.js

//根据Id查询文章的处理函数
exports.getArticleById = (req, res) => {
	const sql = `select * from ev_articles where Id=?`

	db.query(sql, req.params.Id, (err, results) => {
		// 执行 SQL 语句失败
		if (err) return res.cc(err)

		// SQL 语句执行成功,但是没有查询到任何数据
		if (results.length !== 1) return res.cc('获取文章数据失败!')

		// 把数据响应给客户端
		res.send({
			success: true,
			status: 0,
			message: '获取文章数据成功!',
			data: results[0],
		})
	})
}

/schema/article.js

// 校验规则对象 - 根据 id 获取文章
exports.get_articleById_schema = {
	params: {
		Id,
	},
}

测试

5.6 更新文章内容

/router/article.js

//更新文章的路由
router.post(
	'/edit',
	uploads.single('cover_img'),
	expressJoi(update_article_schema),
	article_handler.editArticle
)

/router_handler/article.js

//更新文章的处理函数
exports.editArticle = (req, res) => {
	//先查询有没有这个文章有没有名称撞车
	const sqlStr = `select * from ev_articles where Id != ? and title = ?`
	//执行查重操作
	db.query(sqlStr, [req.body.Id, req.body.title], (err, results) => {
		if (err) return res.cc(err)
		if (results[0]) {
			return res.cc('文章标题不能重复!')
		}
		if (!req.file || req.file.fieldname !== 'cover_img')
			return res.cc('文章封面是必选参数!')
		// 证明数据都是合法的,可以进行后续业务逻辑的处理
		// 处理文章的信息对象
		const articleInfo = {
			// 标题、内容、发布状态、所属分类的Id
			...req.body,
			// 文章封面的存放路径
			cover_img: path.join('/uploads', req.file.filename),
			// 文章的发布时间
			pub_date: new Date(),
			// 文章作者的Id
			author_id: req.auth.id,
		}
		const sql = `update ev_articles set ? where Id=?`
		db.query(sql, [articleInfo, req.body.Id], (err, results) => {
			if (err) return res.cc(err)
			if (results.affectedRows !== 1) return res.cc('编辑文章失败!')
			res.cc('编辑文章成功!', 0)
		})
	})
}

/schema/article.js

// 校验规则对象 — 更新文章
exports.update_article_schema = {
	body: {
		Id,
		title,
		cate_id,
		content,
		state,
	},
}

测试


当前标题:【nodejs】大事件后台管理项目(三)——文章管理接口
文章链接:http://azwzsj.com/article/dsojjdp.html