Node.js + Express + mongodb 的博客项目之模板引擎的配置及用户登录、注册(三)

前言

在这一节中,我们将会进行模板引擎的配置,以及博客前台的界面设计、并且设计博客的用户数据结构及连接数据库,实现用户的注册、登录。写得比较的详细,所以篇幅会较长,


模板引擎的配置

模板引擎是为了实现模板文件与业务数据的结合,实现界面与数据的分离。同时,还可以进行组件化开发,减少我们的代码量,可复用。

在 Express 中配置模板引擎的过程一般如下

  1. views, 放模板文件的目录,比如: app.set(“views”, “./views”)
  2. view engine, 模板引擎,比如: app.set(“view engine”, “ejs”)
    一旦 view engine 设置成功,就不需要显式指定引擎,或者在应用中加载模板引擎模块,Express 已经在内部加载。
    在 app.js 添加代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 引入路径模块
    const path = require("path");
    /*
    配置模板引擎
    */
    // 设置模板引擎的存放目录为当前被执行的js文件的相对目录views
    app.set("views", path.join(__dirname, "views"));
    // 设置模板引擎为ejs
    app.set("view engine", "ejs");

配置完模板引擎,就可以使用模板引擎进行数据渲染了,通过 res.render() 调用其导出方法 __express(filePath, options, callback) 渲染模板。


这个时候我们在 views 目录下新建一个文件 main/index.ejs 作为博客的主页

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>TFLIN'BLOG</title>
</head>
<body>
<h1>欢迎来到我的博客</h1>
</body>
</html>


接着在 app.js 文件修改为如下代码,使用 res.render() 后端渲染模板。

1
2
3
4
5
// 给app绑定路由,所有通过"/"的url都将通过以下方法
app.get("/", (req, res, next) => {
// 发送内容至客户端
res.send("<h1>TFL BLOG</h1>");
});

修改为

1
2
3
4
5
// 给app绑定路由,所有通过"/"的url都将通过以下方法
app.get("/", (req, res, next) => {
// 渲染admin/index模板
res.render("main/index");
});

这个时候,node app 启动项目,然后在浏览器中输入地址,就可以看到模板已经被渲染出来了
渲染模板


配置服务器静态资源库

在 Node.js 服务器中,没有其他类似其他服务器一样有个专门存放静态资源的地方,我们需要对此进行相应的配置,实现对应的功能。在原生 Node.js 中,我们实现这个过程需要考虑很多种情况,而 express 框架,用 express.static 一行代码就可以达到目的了。
在 app.js 添加如下代码,配置服务器静态资源库

1
2
// 使用中间件设置静态资源库的目录
app.use("/public", express.static(path.join(__dirname, "/public")));

这样我们就可以在 /public 目录下存放相关的静态资源了,我们这个项目是便捷开发,所以我们先在静态资源库中存放 jQuery、Bootstrap 相关文件。
静态资源


我们再对 main/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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!--引用bootstrap样式-->
<link rel="stylesheet" href="/public/css/bootstrap.min.css">
<title>TFLIN'BLOG</title>
</head>
<body>
<header>
<header>
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="/">TFL'BLOG</a>
</div>
<div class="container">
<ul class="nav navbar-nav">
<li class="active"><a href="/">首页</a></li>
<li><a href="javascript:;">Java</a></li>
<li><a href="javascript:;">Python</a></li>
<li><a href="javascript:;">Node.js</a></li>
</ul>
</div>
</nav>
</header>
</header>
</body>
</html>

重启服务器,再次查看浏览器
静态资源库样式
看到之前我们设置的样式已经生效了,说明静态资源库设置成功。


路由配置

我们在实现相应的功能之前,要进行相应的路由配置,也就是实现分模块开发。在 /routes 目录下新建以下文件,用于路由配置。

  1. api.js –> 提供登录,注册,登出接口
  2. main.js –> 前台路由文件
  3. admin.js –> 后台管理路由文件
    路由文件

然后在 app.js 将

1
2
3
4
5
// 给app绑定路由,所有通过"/"的url都将通过以下方法
app.get("/", (req, res, next) => {
// 渲染admin/index模板
res.render("main/index");
});

修改为以下代码,实现路由分模块处理

1
2
3
4
5
/*
路由处理
*/
// 所有通过"/"的url,都由./routes/main.js文件进行处理
app.use("/", require("./routes/main.js"));

再修改 main.js 首页路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 引入相关模块
const express = require("express");

// 实例化Router对象
const router = express.Router();

// 首页路由配置
router.get("/", (req, res) => {
// 渲染首页模板
res.render("main/index");
});

// 将其暴露给外部使用
module.exports = router;

最后在重启服务器,测试,依旧成功!
路由文件


注册、登录、登出的实现

前台界面设计

要实现登录注册等,我们先在前端写出相应内容,然后在通过 ajax 的方式提交请求到后端。
修改 main/inde.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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!--引用bootstrap样式-->
<link rel="stylesheet" href="/public/css/bootstrap.min.css">
<title>TFLIN'BLOG</title>
</head>
<body>
<!-- 页头 -->
<header>
<header>
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="/">TFL'BLOG</a>
</div>
<div class="container">
<ul class="nav navbar-nav">
<li class="active"><a href="/">首页</a></li>
<li><a href="javascript:;">Java</a></li>
<li><a href="javascript:;">Python</a></li>
<li><a href="javascript:;">Node.js</a></li>
</ul>
</div>
</nav>
</header>
</header>
<!-- 主体 -->
<div class="container">
<!-- 内容部分 -->
<div class="col-md-8">
<div class="well">
<h3>博客标题</h3>
<p>内容简介</p>
<a href="javascript:;" class="btn btn-info">阅读全文</a>
<span class="text-info pull-right">点击量 时间 作者 分类</span>
</div>
<div class="well">
<h3>博客标题</h3>
<p>内容简介</p>
<a href="javascript:;" class="btn btn-info">阅读全文</a>
<span class="text-info pull-right">点击量 时间 作者 分类</span>
</div>
<div class="well">
<h3>博客标题</h3>
<p>内容简介</p>
<a href="javascript:;" class="btn btn-info">阅读全文</a>
<span class="text-info pull-right">点击量 时间 作者 分类</span>
</div>
</div>
<!-- 登录、注册、用户面板部分 -->
<div class="col-md-4">
<!-- 登录面板 -->
<div id="login">
<h2>登录</h2>
<div class="input-group">
<span class="input-group-addon">用户</span>
<input type="text" class="form-control" placeholder="请输入用户名">
</div>
<br>
<div class="input-group">
<span class="input-group-addon">密码</span>
<input type="password" class="form-control" placeholder="请输入密码">
</div>
<br>
<button type="button" name="button" class="btn btn-primary form-control">登录</button>
<br><br>
<a href="javascripts:;" class="col-md-offset-4">没有账号?点击注册</a>
<!-- 警告框 -->
<div class="alert alert-success alert-dismissable hide" role="alert">
<button class="close" type="button">×</button>
<span>恭喜您操作成功!</span>
</div>
</div>
<!-- 注册面板 -->
<div id="reg">
<h2>注册</h2>
<div class="input-group">
<span class="input-group-addon">用户名称</span>
<input type="text" class="form-control" placeholder="请输入用户名">
</div>
<br>
<div class="input-group">
<span class="input-group-addon">输入密码</span>
<input type="password" class="form-control" placeholder="请输入密码">
</div>
<br>
<div class="input-group">
<span class="input-group-addon">确认密码</span>
<input type="password" class="form-control" placeholder="请再次输入密码">
</div>
<br>
<button type="button" name="button" class="btn btn-primary form-control">注册</button>
<br><br>
<a href="javascripts:;" class="col-md-offset-4">已有账号?点击登录</a>
<!-- 警告框 -->
<div class="alert alert-success alert-dismissable hide" role="alert">
<button class="close" type="button">×</button>
<span>恭喜您操作成功!</span>
</div>
</div>
<!-- 用户面板 -->
<div id="user-info">
<div class="panel panel-primary">
<div class="panel-heading">用户面板</div>
<div class="panel-body">
<h3>欢迎,xxx</h3>
<p>
<button type="button" name="button" class="btn btn-danger form-control">注销</button>
</p>
</div>
</div>
</div>
</div>
</div>
</body>
<script src="/public/js/3.3.1-jquery-min.js"></script>
<script src="/public/js/bootstrap.min.js"></script>
</html>

在浏览器中刷新,效果如下:
前台界面


用户表结构的设计及连接数据库

在写用户模块之前,我们先对用户的表结构进行设计。在 schemas 目录下,新建一个 users.js 的文件,用来设计用户的数据结构。
schemas/users.js 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引入mongoose模块,驱动mongodb数据库
const mongoose = require("mongoose");

/*
用户的数据结构
{
用户名,字符串类型
用户密码,字符串类型
是否为管理员,布尔类型,默认为false
}
*/
module.exports = new mongoose.Schema({
username: String,
password: String,
isadmin: {
type: Boolean,
default: false
}
});

创建完用户的 Schema 对象以后,我们在 models 中创建用户模型,通过用户模型类操作数据库.
models/user.js 代码如下:

1
2
3
4
5
6
7
// 引入mongoose模块
const mongoose = require("mongoose");
// 引入user的schema
const userSchema = require("../schemas/users.js");

// 创建user对象模型,并暴露出去
module.exports = mongoose.model("user", userSchema);

然后我们在项目入口文件 app.js 进行数据库连接,再引入 mongoose 模块后,将 app.js 中监听端口号的部分改为:

1
2
3
4
5
6
7
8
9
10
11
// 连接数据库
mongoose.connect("mongodb://localhost:27017/blog_db", (err) => {
if (!err) {
// 如果没出错,监听端口号
app.listen(8080);
} else {
// 出错则抛出异常
console.log("数据库连接成功!");
throw err;
}
});

这个时候,我们先在命令行使用 mongod 命令启动 mongodb 服务,当然也可以将该服务设置成系统服务,就不用每次都手动启动服务了。
启动数据库服务
mongodb 服务启动完成后,就可以 重启服务器了,node app:
重启服务器
可以看到,此时我们的数据库已经连接成功了。


前端交互设计

在写相关接口前,先把博客前台的交互简单处理一下,就是先显示登录的界面,将注册的界面给隐藏起来,由用户点击切换相关面板,同时通过 ajax 提交数据到相关接口进行处理。
我们先把用户注册面板和用户信息面板隐藏起来,在 views/main/index.ejs 中给它们加上一个 .hide 的 class。
在 /public/js 目录下新建一个 main.js 用于处理博客首页的相关交互,main.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
$(() => {
// 获取点击注册的连接
const $regLink = $("#login a");
// 获取点击登录的连接
const $loginLink = $("#reg a");
// 获取登录的div
const $loginDiv = $("#login");
// 获取注册的div
const $regDiv = $("#reg");
// 获取登录按钮
const $loginBtn = $("#login-btn");
// 获取注册按钮
const $regBtn = $("#reg-btn");
// 获取警告框
const $warningBox = $(".alert");

// 切换到登录/注册界面的方法
(() => {
// 注册点击事件
$regLink.on("click", () => {
// 隐藏登录界面
$loginDiv.hide();
// 显示注册界面
$regDiv.removeClass("hide");
$regDiv.addClass("show");
});
// 注册点击事件
$loginLink.on("click", () => {
// 隐藏注册界面
$regDiv.hide();
$regDiv.removeClass("show");
// 显示登录界面
$loginDiv.show();
});
})();

// 警告框的关闭
(() => {
$("button[class='close']").bind("click", (e) => {
$warningBox.addClass("hide");
});
})();
});

这样我们就可以得到一个简答的交互效果,如下图
登录注册切换


用户注册的接口

博客的相关接口我们在 api.js 文件中写,在 api.js 中,我们先定义一个返回给前端的 json 数据,里面包含有请求的状态码,以及返回的提示消息。而前端的登录与注册是通过 ajax 与后端进行相应的交互,所以我们通过 POST 的方式请求。我们想要获取解析前端通过 POST 提交的数据,就需要 Express 的一个中间模块 body-parser 。body-parser 提供了 bodyParser.json(), bodyParser.raw(), bodyParser.text(), bodyParser.urlencoded() 四种解析数据的方法,其中 bodyParser.urlencoded() 支持utf-8 的解析方式。该方法提供了多个参数,而我们用到的是 extended ,当 extended 的值为 false 的时候会使用 querystring 来解析 url 格式的数据,值为 true 则会以 qs 库来解析,默认为 true。这里有一篇文章对该中间件进行了讲解,可以去看看 –> Express 中间件—-body-parser
我们在 app.js 中添加如下代码对 body-parser 进行相应的配置:

1
2
3
4
5
6
7
8
9
10
// 引入body-parser模块
const bodyParser = require("body-parser");

// body-parser配置
app.use(bodyParser.urlencoded({extended: true}))

// 注意:中间件要写在路由配置前,否则容易出错

// 所有通过"/api"的url,都由./routes/api.js文件进行处理
app.use("/api", require("./routes/api.js"));

配置完 body-parser 后我们就可以使用 req.body 来获取前端传过来的post信息。接下来在 api.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
89
90
// 引入相关模块
const express = require("express");
// 引入用户数据模型,进行相应的操作
const userModel = require("../models/user");

// 实例化Router对象
const router = express.Router();

// 定义一个返回给前端的消息对象
let responseData;
// 通过中间件对其进行初始化,这样每一次请求都会进行初始化
router.use((req, res, next) => {
responseData = {
// 状态码
code: 0,
// 返回的消息
message: ""
};
next();
});

/*
用户注册的接口:
提交的验证:
1.用户名不能为空
2.密码不能为空
3.两次密码必须一致
数据库验证:
1.用户名是否已存在
*/
router.post("/user/register", (req, res, next) => {
// 用户名
let username = req.body.username;
// 密码
let password = req.body.password;
// 确认密码
let repassword = req.body.repassword;

// 用户名不能为空
if (username === "") {
// 设置数据
responseData.code = 1;
responseData.message = "用户名不能为空!";
// 给客户端发送一个json的相应
res.json(responseData);

return;
}
// 密码不能为空
if (password === "" || repassword === "") {
responseData.code = 2;
responseData.message = "密码或确认密码不能为空!";
res.json(responseData);
return;
}
// 两次密码必须一致
if (password !== repassword) {
responseData.code = 3;
responseData.message = "两次密码不一致!";
res.json(responseData);
return;
}

// 向数据库中查询用户名是否已存在
userModel.findOne({username: username}, (err, user) => {
// 如果用户名已存在
if (user) {
responseData.code = 4;
responseData.message = "该用户名已存在!";
res.json(responseData);
return;
} else {
// 用户名不存在,则新建用户
userModel.create({
username: username,
password: password
}, (err) => {
if (!err) {
responseData.message = "注册成功!";
res.json(responseData);
} else {
console.log(err);
}
});
}
});
});

// 将其暴露给外部
module.exports = router;

此时后台的用户注册接口已经写好了,我们在前端通过 ajax 去请求该接口,在博客首页的 js 文件 public/js/main.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
// ajax请求
(() => {
// 用户注册
$regBtn.on("click", () => {
// 通过ajax提交数据
$.ajax({
type: "post",
url: "/api/user/register",
data: {
username: $("#reg [name='username']").val(),
password: $("#reg [name='password']").val(),
repassword: $("#reg [name='repassword']").val()
},
dataType: "json",
success: (result) => {
if (result.code) {
$warningBox.find("span").html("警告:" + result.message);
$warningBox.addClass("alert-danger")
$warningBox.removeClass("hide alert-success");
} else {
$warningBox.find("span").html("恭喜您" + result.message);
$warningBox.addClass("alert-success")
$warningBox.removeClass("hide alert-danger");
}
}
});
});
})();

后端的用户注册接口已经写完了,前端的 ajax 请求也写完了,我们就可以重启服务器进行测试了,我们先在数据库中新建一个用户叫 admin 以便进行用户名冲突测试,使用 node app 重启服务器,在客户端中刷新网页,进行测试:
注册测试
前端和服务器方面没有问题,我们再到数据库中看看,发现新注册的用户已经存入数据库中了
注册数据库
到这里用户注册算是基本完成了。


用户登录的接口

用户登录的实现基本上和用户注册的逻辑差不多,前端 ajax 提交数据到后端,后端进行相应的验证,并将验证结果返回给前端。后端的登录接口设计为 POST api/user/login。
在 routes/api.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
/*
用户登录的接口
提交的验证:
1.用户名不能为空
2.密码不能为空
数据库的验证:
1.用户名是否存在
2.若存在密码是否正确
*/
router.post("/user/login", (req, res, next) => {
// 用户名
let username = req.body.username;
// 密码
let password = req.body.password;

// 用户名不能为空
if (username === "") {
responseData.code = 1;
responseData.message = "用户名不能为空!";
res.json(responseData);
return;
}
// 密码不能为空
if (password === "") {
responseData.code = 2;
responseData.message = "密码不能为空!";
res.json(responseData);
return;
}
// 向数据库查询,用户是否存在
userModel.findOne({username: username}, (err, user) => {
// 如果存在该用户
if (user) {
// 验证密码是否正确
if (user.password === password) {
responseData.message = "登录成功!";
res.json(responseData);
return;
} else {
responseData.code = 5;
responseData.message = "密码不正确!";
res.json(responseData);
return;
}
} else {
responseData.code = 6;
responseData.message = "该用户名不存在!";
res.json(responseData);
return;
}
});
});

后端的登录接口完成后,我们在前端的 Js 代码中使用 ajax 向该接口提交数据
public/js/main.js 的 ajax 请求方法中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 用户登录
$loginBtn.on("click", () => {
$.ajax({
type: "post",
url: "/api/user/login",
data: {
username: $("#login [name='username']").val(),
password: $("#login [name='password']").val()
},
dataType: "json",
success: (result) => {
if (result.code) {
$warningBox.find("span").html("警告:" + result.message);
$warningBox.addClass("alert-danger")
$warningBox.removeClass("hide alert-success");
} else {
// 登录成功,隐藏登录界面
$loginDiv.hide();
// 显示用户界面
$("#user-info").removeClass("hide");
}
}
});
});

重启服务器,刷新客户端,我们来进行相应的测试:
登录测试
没发现什么问题,此时博客的用户登录暂时完成。当然这只是简单的登录,并没有使用 cookie 来保存登录状态,保存登录状态会在下一节完成。


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

版权声明

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 ),版权所有,侵权必究。