前言
在这一节中,我们将会进行模板引擎的配置,以及博客前台的界面设计、并且设计博客的用户数据结构及连接数据库,实现用户的注册、登录。写得比较的详细,所以篇幅会较长,
模板引擎的配置
模板引擎是为了实现模板文件与业务数据的结合,实现界面与数据的分离。同时,还可以进行组件化开发,减少我们的代码量,可复用。
在 Express 中配置模板引擎的过程一般如下
- views, 放模板文件的目录,比如: app.set(“views”, “./views”)
- 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");
app.set("views", path.join(__dirname, "views"));
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"> <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 目录下新建以下文件,用于路由配置。
- api.js –> 提供登录,注册,登出接口
- main.js –> 前台路由文件
- 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"> <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"); const $loginDiv = $("#login"); 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
| (() => { $regBtn.on("click", () => { $.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
|
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 来保存登录状态,保存登录状态会在下一节完成。