【Node.js实战】博客系统逻辑编写-Express (终章)
前言
哈喽,大家好,这里是胖芙,这个系列的文章会从零开始教你怎么使用 Express.js 去搭建一个能用的博客系统,手把手的那种!!!让你打通 JS 前后端任督二脉!
如想要及时收到文章推送,欢迎关注公众号《泡芙学前端》,同步更新文章中...
最近因为去了一趟新疆净化心灵,封面图就是在新疆拍的,玩结束后太累了,有半个月没更文章了~ 拒绝当懒狗,开始继续更文辣~
来来来,继续书接上文
上一篇文章中我们完成了上传文件到本地、上传文件到阿里云 OSS、还有接口的 token 鉴权服务
那么这里也是本系列的最后一章,本章我们主要就来完成下列的接口
标签管理:
- 创建标签
- 查询所有标签
- 删除标签
博客管理:
- 创建博客
- 分页获取博客列表
- 根据博客 id 获取博客的详情
- 根据博客的 id 更新博客内容
- 根据标签的 id 查找所有关联了该 id 的博客
- 删除博客
标签逻辑编写
首先先来定义一下路由
// routes/tags.js
const express = require('express');
const router = express.Router();
const tagController = require('../controllers/tagController');
// 创建标签
router.post('/create', tagController.createTag);
// 查询所有标签
router.get('/query', tagController.getTags);
// 根据 id 删除标签
router.delete('/delete/:id', tagController.deleteTag);
module.exports = router;
路由编写完成后开始进入控制器中编写逻辑
现在让我们先进入到 根目录/controllers/tagController.js
这个文件中
首先导入 Tag 模型
const Tag = require("../models/Tag");
创建标签
async function createTag(req, res) {
try {
const { name } = req.body;
const oldTag = await Tag.findOne({
where: {
name,
}
});
// 之前创建过这个标签
if (oldTag) {
// 标签没删除
if (!oldTag.isDeleted) {
return res.json({ msg: "标签已存在" });
}
// 标签如果已经删除了则直接将其恢复并更新创建时间
oldTag.isDeleted = false;
const newDate = +new Date();
oldTag.createdAt = newDate;
oldTag.updatedAt = newDate;
await oldTag.save();
return res.json(oldTag);
}
// 之前没创建过则直接创建
const newTag = await Tag.create({ name });
res.json(newTag);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
查询标签
这里时一次性查询所有的标签回来,没做分页,如果需要可以自己加下分页逻辑
async function getTags(req, res) {
try {
const tags = await Tag.findAll({
where: { isDeleted: false },
// 从返回结果中移除 isDeleted 字段
attributes: { exclude: ["isDeleted"] },
});
res.json(tags);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
删除标签
这里的删除都是软删除,不会删除数据库中的数据,只是把 isDeleted 置为 true
async function deleteTag(req, res) {
try {
const { id } = req.params;
const tag = await Tag.findByPk(id);
if (!tag) {
return res.status(404).json({ error: "Tag not found" });
}
tag.isDeleted = true;
await tag.save();
res.json({ status: "OK" });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
测试接口
上面的逻辑编写完成后,我们使用 Postman 测试一下。在测试之前别忘了先进行登录,我们上一章中给所有的主逻辑接口都加上了鉴权校验。具体的逻辑在上一章我们也提到过。
然后在调用后续我们所有编写的接口之前都要在这里加上登录获得的 token
如果是在前端中调用,可以在 axios 拦截器中统一给请求头带上该 token
import axios from 'axios';
axios.interceptors.request.use(
config => {
if (localStorage.token) {
config.headers.Authorization = 'Bearer ' + '登录获得的 token, 一般存在 localStorage.token 字段中';
}
return config;
},
err => {
return Promise.reject(err);
}
);
创建新标签
此时可以看到数据库中已经插入了该字段
如果这时候再点击一次重复创建就会返回 "标签已存在"
查询所有标签
此时我们去调用查询接口,可以看到刚刚创建的那一条数据返回了
删除标签
我们把刚刚创建的那个标签的 id 20 传到删除接口中,删除成功后会返回 OK
这时候我们再去查询一下标签看到返回了空数组,说明已经删除成功了
标签基本的主逻辑到这里就已经编写完成了~ 好啦,接下来我们就来看下博客逻辑的部分
博客逻辑编写
博客的逻辑分为增删改查四部分
首先先来定义好路由
// routes/blogs.js
const express = require("express");
const router = express.Router();
const blogController = require("../controllers/blogController");
// 创建博客
router.post("/create", blogController.createBlog);
// 查询博客列表
router.get("/query", blogController.getBlogList);
// 根据 id 查询博客详情
router.get("/query/:id", blogController.getBlogById);
// 根据 id 修改博客
router.patch("/update/:id", blogController.updateBlog);
// 根据 id 删除博客
router.delete("/delete/:id", blogController.deleteBlog);
// 根据标签id查找关联了该标签的博客
router.get("/queryByTag/:tagId", blogController.queryByTag);
module.exports = router;
编写完路由后,进入到博客控制器文件中 根目录/controllers/blogController.js
首先在文件开头导入这三个模型,一会需要用
const Blog = require("../models/Blog");
const Tag = require("../models/Tag");
const User = require("../models/User");
当你在下面的代码中看到 attributes.include/exclude 时,只需要知道它们是用来处理返回结果中的字段的即可,比如返回结果中需要包含数据库中的某个字段或移除某个字段,through.attributes 则是用来控制关联模型中的字段的
创建博客
async function createBlog(req, res) {
try {
const { title, content, coverImage, tags } = req.body;
// 查询标签是否存在
const existingTags = await Tag.findAll({
where: { name: tags },
});
if (existingTags.length === 0) {
return res.status(404).json({ error: "Tags not found" });
}
// 创建博客记录
const newBlog = await Blog.create({
title,
content,
coverImage,
// 在鉴权中间件中我们挂载了用户信息在 req 中
userId: req.user.userId,
isDeleted: false,
});
// 关联标签和博客
await newBlog.setTags(existingTags);
// 返回创建的博客记录
res.json(newBlog);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
分页获取博客列表
这里我们加了分页的逻辑
async function getBlogList(req, res) {
try {
const { page = 1, pageSize = 10 } = req.query;
const _page = parseInt(page);
const _pageSize = parseInt(pageSize);
// 查询所有博客并返回数量
const blogs = await Blog.findAndCountAll({
offset: (_page - 1) * _pageSize,
limit: _pageSize,
where: { isDeleted: false },
distinct: true,
attributes: {
exclude: ["isDeleted"],
},
include: [
{
model: Tag,
as: "tags",
attributes: {
exclude: ["isDeleted", "createdAt", "updatedAt"],
},
through: {
attributes: [],
},
},
{
model: User,
as: "user",
attributes: ["nickname", "id"],
},
],
});
res.json(blogs);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
根据博客 id 获取博客的详情
async function getBlogById(req, res) {
try {
const { id } = req.params;
const blog = await Blog.findOne({
where: { id, isDeleted: false },
attributes: {
exclude: ["isDeleted"],
},
include: [
{
model: Tag,
as: "tags",
attributes: {
exclude: ["isDeleted", "createdAt", "updatedAt"],
},
through: {
attributes: [],
},
},
{
model: User,
as: "user",
attributes: ["nickname", "id"],
},
],
});
if (!blog) {
return res.status(404).json({ error: "Blog not found" });
}
res.json(blog);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
根据博客的 id 更新博客内容
async function updateBlog(req, res) {
try {
const { id } = req.params;
const { title, content, coverImage, tags } = req.body;
const blog = await Blog.findByPk(id);
if (!blog) {
return res.status(404).json({ error: "Blog not found" });
}
// 更新博客记录
blog.title = title;
blog.content = content;
blog.coverImage = coverImage;
// 在鉴权中间件中我们挂载了用户信息在 req 中
blog.userId = req.user.userId;
blog.updatedAt = +new Date();
await blog.save();
// 更新关联的标签
const existingTags = await Tag.findAll({
where: { name: tags },
});
if (existingTags.length === 0) {
return res.status(404).json({ error: "Tags not found" });
}
await blog.setTags(existingTags);
res.json(blog);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
根据标签的 id 查找所有关联了该 id 的博客
async function queryByTag(req, res) {
try {
const { tagId } = req.params;
const result = await Tag.findAll({
where: {
id: tagId,
},
attributes: {
exclude: ["isDeleted", "createdAt", "updatedAt"],
},
include: {
model: Blog,
as: "blogs",
where: {
isDeleted: false,
},
attributes: {
exclude: ["isDeleted"],
},
through: {
attributes: [],
},
},
});
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
删除博客
async function deleteBlog(req, res) {
try {
const { id } = req.params;
const blog = await Blog.findByPk(id);
if (!blog) {
return res.status(404).json({ error: "Blog not found" });
}
// 标记博客记录为已删除状态
blog.isDeleted = true;
await blog.save();
res.json({ msg: "OK" });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
上面有一些代码是可以复用的,比如查询博客列表和根据 id 查找博客详情的那一堆过滤字段,如果觉得有必要的话可以自己封装一下。
测试接口
创建博客
这里的 tags 我们上传标签名数组,上一小节中我们创建过一个 React 标签,封面图可以调用我们上一章中完成的上传文件接口,上传到 OSS 后会得到一个在线地址,然后把这个地址塞到这里就行了
创建完成后我们看到接口把创建的信息返回了
获取博客列表
这里我们可以在请求 url 上加上分页信息,如果不加就是默认第一页和10条数据
请求后可以看到把博客数量,标签信息、用户信息、博客信息都返回来了
根据博客 id 获取博客的详情
这里我们在 url 中加上刚刚创建的那条博客的 id,可以看到只返回了这个博客的信息
根据博客的 id 更新博客内容
url 中带上对应需要更新的博客的 id,tags 如果需要更新的话需要额外多创建几个 tag
我们这里多添加一个 JS tag
然后我们这里更新了 title、content、coverImage、tags 字段, tags 中多加了一个 JS
因为这里我们没返回 tags 的信息,所以我们再去查询一下这条博客,可以看到 tags 已经被更新了
根据标签的 id 查找所有关联了该 id 的博客
我们这里多创建一个博客 id 为21,然后把 React 标签也关联进去,这时候再调用这个接口,React 标签下应该会有两条博客,id 为别为 20 和 21 那么就是对的
这时候我们再去调用这个查找关联标签的博客这个接口,url 入参带上 React 这个标签的 id。 我们看到结果中确实返回了2条数据,后续数据多了的话也可以给这个接口加上分页
删除博客
我们这里把 id 为 21 的博客给删一下,删除完成后返回了 ok,然后我们再去调用一下查询接口看看
再去查询时发现 count 变为 1 了,数据也只返回了一条,说明软删除成功了
总结
想要及时收到推送,欢迎关注公众号《泡芙学前端》,同步更新文章中...
前面三章的内容在这里:
再加上本章内容,刚好完成了四个章节,全文约 3W5+ 字
我们完成的内容有:
- Mysql 数据库搭建
- Docker 管理容器
- 登录注册及接口鉴权
- 文件本地上传/阿里云OSS上传
- 标签管理、博客管理
该系列暂时先告一段落了~ 说实话,不知道是不是因为写的不好,这个系列的文章没啥人看,还是说大家都已经会了这些了,我写的太简单了哈哈哈,没关系,这也是我给自己以前学习的知识做一下总结。在总结的过程中我还是有了解到一些新的东西的。
远方的风景很好,但也别忘偶尔驻足看看眼前的景色,有时候你会有意想不到的收获。
后续应该会去写一些 Nest.js 相关的内容了,学习 Express.js 对于学习 Nest.js 也有很大的好处,因为 Nest.js 底层其实还是 Express~ 好了,那就祝大家后续学习愉快~
转载自:https://juejin.cn/post/7250875810792521788