Node.js + Express + mongodb 的博客项目之博客内容的后台管理(七)

前言

博客内容的管理,与博客文章分类的管理基本上类似。都是分别有几个路由对应相应的功能,GET /admin/content 对应文章内容的首页,GET /admin/content/add 对应内容的添加,GET /admin/content/edit 是内容的修改编辑,GET /admin/content/delete 则为博客内容的删除路由。


博客内容在数据库中的存储结构与模型

文章的内容的数据结构主要有文章的标题,分类,作者,添加时间,阅读量,简介,正文部分。其中分类和作者是引用其对应的集合而来,添加时间则是通过时间格式化模块 moment 格式化后转化为字符串。
在 /schemas 文件夹中新建一个 contents.js 文件,用来存放文档结构,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 引入mongoose模块
const mongoose = require("mongoose");
// 引入时间格式化模块
const moment = require("moment");

/*
文章内容的数据结构
{
标题,字符串类型
分类id,引用对象
作者,引用对象
添加时间,字符串
阅读量,字符串
简介,字符串
正文,字符串
}
*/
module.exports = new mongoose.Schema({
title: String,
category: {
type: mongoose.Schema.Types.ObjectId,
ref: "category"
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "user"
},
addTime: {
type: String,
default: moment().format('YYYY-MM-DD h:mm:ss a')
},
views: {
type: Number,
default: 0
},
description: {
type: String,
default: ""
},
content: {
type: String,
default: ""
}
});

然后在 /models 中添加内容的模型 content.js

1
2
3
4
5
6
// 引入相关模块
const mongoose = require("mongoose");
const contentSchema = require("../schemas/contents.js");

// 文章内容的模型对象
module.exports = mongoose.model("content", contentSchema);


内容相关的静态模板

在视图文件夹 /views 中的 /admin/ 新建一个 /content 文件夹用来存放文章内容相关的视图文件。


管理首页模板

在 /views/admin/content 中新建一个 index.ejs 作为内容管理的首页,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!-- 引入页头模板 -->
<%- include("../header") %>
<!-- 内容 -->
<div class="container-fluid">
<ol class="breadcrumb">
<li><a href="/admin">管理首页</a></li>
<li class="active">内容首页</li>
</ol>
<table class="table table-hover table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>分类名称</th>
<th>标题</th>
<th>作者</th>
<th>添加时间</th>
<th>阅读量</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<%for(let i = 0, len = docs.length; i < len; i++){%>
<tr>
<td><%=docs[i]._id%></td>
<td><%=docs[i].category.name%></td>
<td><%=docs[i].title%></td>
<td><%=docs[i].author.username%></td>
<td><%=docs[i].addTime%></td>
<td><%=docs[i].views%></td>
<td>
<a href="/admin/docs/edit?id=<%=docs[i]._id%>" class="btn btn-default">修改</a>
<a href="/admin/docs/delete?id=<%=docs[i]._id%>" class="btn btn-danger">删除</a>
</td>
</tr>
<%}%>
</tbody>
</table>
<!-- 引入分页模板 -->
<%- include("../../pagination") %>
</body>
</html>


内容添加页面模板

接着再新建一个内容添加页面 /views/admin/content/add.ejs 内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- 引入页头模板 -->
<%- include("../header") %>
<!-- 内容 -->
<div class="container-fluid">
<ol class="breadcrumb">
<li><a href="/admin">管理首页</a></li>
<li><a href="/admin/content">内容首页</a></li>
<li class="active">内容添加</li>
</ol>
<form method="post">
<div class="form-group">
<label for="title">分类</label>
<select name="category" id="category" class="form-control">
<%for(let i = 0, len = categories.length; i < len; i++){%>
<option value="<%=categories[i]._id%>"><%=categories[i].name%></option>
<%}%>
</select>
</div>
<div class="form-group">
<label for="title">标题</label>
<input type="text" class="form-control" id="title" name="title" placeholder="请输入标题">
</div>
<div class="form-group">
<label for="description">简介</label>
<input type="text" class="form-control" id="description" name="description" placeholder="请输入简介">
</div>
<div class="form-group">
<label for="content">正文</label>
<textarea name="content" rows="9" class="form-control" placeholder="请输入正文"></textarea>
</div>
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</body>
</html>


内容修改界面模板

最后一个界面则是博客内容的修改界面 /views/content/edit.ejs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!-- 引入页头模板 -->
<%- include("../header") %>
<!-- 内容 -->
<div class="container-fluid">
<ol class="breadcrumb">
<li><a href="/admin">管理首页</a></li>
<li><a href="/admin/content">内容首页</a></li>
<li class="active">内容修改</li>
</ol>
<form method="post">
<div class="form-group">
<label for="title">分类</label>
<select name="category" id="category" class="form-control">
<%for(let i = 0, len = categorys.length; i < len; i++){%>
<%if((content.category).toString() === categorys[i]._id.toString()){%>
<option value="<%=categorys[i]._id%>" selected="selected"><%=categorys[i].name%></option>
<%}else{%>
<option value="<%=categorys[i]._id%>"><%=categorys[i].name%></option>
<%}%>

<%}%>
</select>
</div>
<div class="form-group">
<label for="title">标题</label>
<input type="text" class="form-control" id="title" name="title" placeholder="请输入新标题" value="<%=content.title%>">
</div>
<div class="form-group">
<label for="description">简介</label>
<input type="text" class="form-control" id="description" name="description" placeholder="请输入新简介" value="<%=content.description%>">
</div>
<div class="form-group">
<label for="content">正文</label>
<textarea name="content" rows="9" class="form-control" placeholder="请输入新正文"><%=content.content%></textarea>
</div>
<button type="submit" class="btn btn-default">修改</button>
</form>
</div>
</body>
</html>


博客内容管理的相关内容处理

在处理完博客内容管理的先关静态模板后,就进行相关路由的处理。

博客内容首页的路由

在 admin.js 中,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 引入内容的模型,用来操作数据库
const contentModel = require("../models/content");

// 博客内容管理首页
router.get("/content", (req, res, next) => {
// 调用自定义的分页渲染方法
pagination({
// 每页显示的条数
limit: 10,
// 需要操作的数据库模型
model: contentModel,
// 需要控制分页的url
url: "/admin/content",
// 渲染的模板页面
ejs: "admin/content/index",
// 查询的条件
where: {},
// 需要跨集合查询的条件
populate: ["category", "author"],
res: res,
req: req
});
});


博客内容添加界面保存路由

在 admin.js 中添加如下代码,实现对 /admin/content/add 路由的 GEt 和 POST 处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 博客内容的添加界面
router.get("/content/add", (req, res, next) => {
// 从数据中读取分类信息
categoryModel.find({}, (err, categories) => {
if (!err) {
res.render("admin/content/add", {
userInfo: req.userInfo,
categories: categories
});
return;
} else {
throw err;
return;
}
});
});

// 内容添加的保存
router.post("/content/add", (req, res, next) => {
let title = req.body.title;
let category = req.body.category;
let description = req.body.description;
let content = req.body.content;
// 后端进行简单的验证
if (title === "") {
// 如果标题为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "标题不能为空"
});
return;
} else if (description === "") {
// 如果简介为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "简介不能为空"
});
return;
} else if (content === "") {
// 如果正文为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "正文不能为空"
});
return;
} else {
// 一切正常,存入数据库
contentModel.create({
title: title,
category: category,
author: req.userInfo.userid.toString(),
description: description,
content: content
}, (err) => {
if (!err) {
// 保存成功
res.render("admin/success", {
url: "/admin/content",
userInfo: req.userInfo,
message: "提交成功!"
});
} else {
throw err;
}
});
}
});


内容的修改界面及保存修改的路由

内容的修改是由前端通过 GET 方式向后端传递需要修改的内容的 ID ,后端再从数据库中查询出相应数据进行更新,并将处理结果返回给前端。
在 admin.js 中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// 内容修改的界面
router.get("/content/edit", (req, res, next) => {
// 获取需要修改内容的id
let id = req.query.id;
// 从数据库中查询
contentModel.findById(id, (err, content) => {
if (content) {
// 如果数据存在,从数据库中查询出所有分类
categoryModel.find({}, (err, categories) => {
if (!err) {
// 渲染修改模板视图
res.render("admin/content/edit", {
userInfo: req.userInfo,
categories: categories,
content: content
});
} else {
throw err;
}
});
} else {
// 如果该内容不存在
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "该内容不存在!"
});
}
});
});

// 内容的修改保存
router.post("/content/edit", (req, res, next) => {
// 获取数据
let title = req.body.title;
let category = req.body.category;
let description = req.body.description;
let content = req.body.content;
let id = req.query.id;

// 后端进行简单的验证
if (title === "") {
// 如果标题为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "标题不能为空!"
});
return;
} else if (description === "") {
// 如果简介为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "简介不能为空!"
});
return;
} else if (content === "") {
// 如果正文为空,渲染错误页面
res.render("admin/error", {
url: null,
userInfo: req.userInfo,
message: "正文不能为空!"
});
return;
} else {
// 一切正常,更新数据库
contentModel.update({
_id: id
}, {
title: title,
category: category,
description: description,
content: content
}, (err) => {
if (!err) {
// 保存成功
res.render("admin/success", {
url: "/admin/content",
userInfo: req.userInfo,
message: "修改成功!"
});
} else {
throw err;
}
});
}
});


内容删除的处理

删除同样是前端通过 GET 方式传递需要删除的内容的 ID 过来,然后服务器再从数据库根据 ID 删除数据。
在 admin.js 中添加如下代码,处理删除内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 内容的删除
router.get("/content/delete", (req, res, next) => {
// 获取id
let id = req.query.id;
// 根据id删除数据
contentModel.remove({
_id: id
}, (err) => {
if (!err) {
// 删除成功
res.render("admin/success", {
url: "/admin/content",
userInfo: req.userInfo,
message: "删除成功!"
});
} else {
// 出错
res.render("admin/error", {
url: "/admin/content",
userInfo: req.userInfo,
message: "删除失败!"
});
}
});
});


内容管理的测试

最后进行内容管理的相关测试:
内容管理

------ 本文结束 ------

版权声明

Tflin's Blog by Tan Feng Lin is licensed under a Creative Commons BY-NC-ND 4.0 International License.
谭丰林创作并维护的Tflin's Blog博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Tflin's Blog 博客( http://tflin.com ),版权所有,侵权必究。