毫无疑问,几乎全部的应用都会涉及到数据存储。可是 Express 框架自己只能经过程序变量来保存数据,它并不提供数据持久化功能。而仅仅经过内存来保存数据是没法应对真实场景的。由于内存自己并不适用于大规模的数据储存并且服务中止后这些数据也会消失。虽然咱们还能够经过文件的形式保存数据,可是文件中的数据对于查询操做明显不友好。全部,接下来咱们将学习如何在 Express 中经过 MongoDB 数据库的形式来对数据进行持久化存储。css
本文包含的主要内容有:html
对于 Web 应用来讲,一般数据库的选择能够划分为两大类:关系型和非关系型。其中前者优势类型于电子表格,它的数据是结构化而且伴随着严格规定。典型的关系型数据库包括:MySQL、 SQL Server 以及 PostgreSQL。然后者一般也被称为 NoSQL 数据库,它的结构相对更加灵活,而这一点与 JS 很是相似。node
可是为何 Node 开发者会特别中意 NoSQL 中的 Mongo 数据库,还造成了流行的 MEAN 技术栈呢?web
第一个缘由是:Mongo 是 NoSQL 类型数据里最流行的一个。这也让网上关于 Mogon 的资料很是丰富,全部你在实际使用过程当中可能会遇到的坑大概率都能找到答案。并且做为一个成熟的项目,Mongo 也已经被大公司承认和应用。sql
另外一个缘由则是 Mongo 自身很是可靠、有特点。它使用高性能的 C++ 进行底层实现,也让它赢得了大量的用户信赖。mongodb
虽然 Mongo 不是用 JavaScript 实现的,可是原生的 shell 却使用的是 JavaScript 语言。这意味着可使用 JavaScript 在控制台操做 Mongo 。另外,对于 Node 开发者来讲它也减小了学习新语言的成本。shell
固然,Mongo 并非全部 Express 应用的正确选择,关系数据库依然占据着很是重要的地位。顺便提一下,NoSQL 中的 CouchDB 功能也很是强大。数据库
注意:虽然本文只会介绍 Mongo 以及 Mongoose 类库的使用。可是若是你和我同样对 SQL 很是熟悉而且但愿在 Express 使用关系数据库的话,你能够去查看 Sequelize。它为不少关系型数据库提供了良好的支持。express
在正式使用 Mongo 前,咱们先来看看 Mongo 是如何工做的。npm
对于大多数应用来讲都会在服务器中使用 Mongo 这样的数据库来进行持久化工做。虽然,你能够在一个应用中建立多个数据库,可是绝大多数都只会使用一个。
若是你想正常访问这些数据库的话,首先你须要运行一个 Mongo 服务。客户端经过给服务端发送指令来实现对数据库的各类操做。而链接客户端与服务端的程序一般都被称为数据库驱动。对于 Mongo 数据库来讲它在 Node 环境下的数据库驱动程序是 Mongoose。
每一个数据库都会有一个或多个相似于数组同样的数据集合。例如,一个简单的博客应用,可能就会有文章集合、用户集合。可是这些数据集合的功能远比数组来的强大。例如,你能够查询集合中 18 岁以上的用户。
而每个集合里面存储了 JSON 形式的文档,虽然在技术上并无采用 JSON。每个文档都对应一条记录,而每一条记录都包含若干个字段属性。另外,同一集合里的文档记录并不必定拥有同样的字段属性。这也是 NoSQL 与 关系型数据库最大的区别之一。
实际上文档在技术上采用的是简称为 BSON 的 Binary JSON。在实际写代码过程当中,咱们并不会直接操做 BSON 。多数状况下会将其转化为 JavaScript 对象。另外,BSON 的编码和解码方式与 JSON 也有不一样。BSON 支持的类型也更多,例如,它支持日期、时间戳。下图展现了应用中数据库使用结构:
最后还有一点很是重要:Mongo 会给每一个文档记录添加一个 _id 属性,用于标示该记录的惟一性。若是两个同类型的文档记录的 id 属性一致的话,那么就能够推断它们是同一记录。
若是你有关系型数据库的知识背景的话,其实你会发现 Mongo 不少概念是和 SQL 意义对应的。
首先, Mongo 中的文档概念其实就至关于 SQL 中的一行记录。在应用的用户系统中,每个用户在 Mongo 中是一个文档而在 SQL 中则对应一条记录。可是与 SQL 不一样的是,在数据库层 Mongo 并无强制的 schema,因此一条没有用户名和邮件地址的用户记录在 Mongo 中是合法的。
其次,Mongo 中的集合对应 SQL 中的表,它们都是用来存储同一类型的记录。
一样,Mongo 中的数据库也和 SQL 数据库概念很是类似。一般一个应用只会有一个数据库,而数据库内部则能够包含多个集合或者数据表。
更多的术语对应表能够去查看官方的这篇文档。
在使用以前,首要的任务固然就是机器上安装 Mongo 数据库并拉起服务了。若是你的机器是 macOS 系统而且不喜欢命令行模式的话,你能够经过安装 Mongo.app 应用完成环境搭建。若是你熟悉命令行交互的话能够经过 Homebrew 命令 brew install mongodb 进行安装。
Ubuntu 系统能够参照文档,同时 Debian 则能够参照文档 进行 Mongo 安装。
另外,在本书中咱们会假设你安装是使用的 Mongo 数据库的默认配置。也就是说你没有对 Mongo 的服务端口号进行修改而是使用了默认的 27017 。
安装 Mongo 后接下来问题就是如何在 Node 环境中操做数据库。这里最佳的方式就是使用官方的 Mongoose类库。其官方文档描述为:
Mongoose 提供了一个直观并基于 schema 的方案来应对程序的数据建模、类型转换、数据验证等常见数据库问题。
换句话说,除了充当 Node 和 Mongo 之间的桥梁以外,Mongoose 还提供了更多的功能。下面,咱们经过构建一个带用户系统的简单网站来熟悉 Mongoose 的特性。
为了更好的学习本文的内容,下面咱们会开发一个简单的社交应用。该应用将会实现用户注册、我的信息编辑、他人信息的浏览等功能。这里咱们将它称为 Learn About Me 或者简称为 LAM 。应用中主要包含如下页面:
和以前同样,首先咱们须要新建工程目录并编辑 package.json 文件中的信息:
{
"name": "learn-about-me",
"private": true,
"scripts": {
"start": "node app"
},
"dependencies": {
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.6.5",
"connect-flash": "^0.1.1",
"cookie-parser": "^1.3.2",
"ejs": "^1.0.0",
"express": "^4.0.0",
"express-session": "^1.7.6",
"mongoose": "^3.8.15",
"passport": "^0.2.0",
"passport-local": "^1.0.0"
}
}复制代码
接下来,运行 npm install 安装这些依赖项。在后面的内容中将会一一对这些依赖项的做用进行介绍。
须要注意的是,这里咱们引入了一个纯 JS 实现的加密模块 bcrypt-nodejs 。其实 npm 中还有一个使用 C 语言实现的加密模块 bcrypt 。虽然 bcrypt 性能更好,可是由于须要编译 C 代码全部安装起来没 bcrypt-nodejs 简单。不过,这两个类库功能一致能够进行自由切换。
前面说过 Mongo 是以 BSON 形式进行数据存储的。例如,Hello World 的 BSON 表现形式为:
\x16\x00\x00\x00\x02hello\x00\x06\x00\x00\x00world\x00\x00复制代码
虽然计算机彻底可以理解 BSON 格式,可是很明显 BSON 对人类来讲并非一种易于阅读的格式。所以,开发者发明了更易于理解的数据库模型概念。数据库模型以一种近似人类语言的方式对数据库对象作出了定义。一个模型表明了一个数据库记录,一般也表明了编程语言中的对象。例如,这里它就表明一个 JavaScript 对象。
除了表示数据库的一条记录以外,模型一般还伴随数据验证、数据拓展等方法。下面经过具体示例来见识下 Mongoose 中的这些特性。
在示例中,咱们将建立一个用户模型,该模型带有如下属性:
在 Mongoose 中咱们使用 schema 来定义用户模型。除了包含上面的属性以外,以后还会在其中添加一些类型方法。在项目的根目录建立 models 文件夹,而后在其中建立一个名为 user.js 的文件并复制下面代码:
var mongoose = require("mongoose");
var userSchema = mongoose.Schema({
username: { type: String, require: true, unique: true },
password: { type: String, require: true },
createdAt: {type: Date, default: Date.now },
displayName: String,
bio: String
});复制代码
从上面的代码中,咱们能看到属性字段的定义很是简单。同时咱们还对字段的数据类型、惟一性、缺省、默认值做出了约定。
当模型定义好以后,接下来就是在模型中定义方法了。首先,咱们添加一个返回用户名称的简单方法。若是用户定义了昵称则返回昵称不然直接返回用户名。代码以下:
...
userSchema.methods.name = function() {
return this.displayName || this.username;
}复制代码
一样,为了确保数据库中用户信息安全,密码字段必须以密文形式存储。这样即便出现数据库泄露或者入侵行为也能载必定程度上确保用户信息的安全。这里咱们将会使用对 Bcrypt 程序对用户密码进行单向哈希散列,而后在数据库中存储加密后的结果。
首先,咱们须要在 user.js 文件头部引入 Bcrypt 类库。在使用过程当中咱们能够经过增长哈希次数来提升数据的安全性。固然,哈希操做是很是操做,因此咱们应该选取一个相对适中的数值。例如,下面的代码中咱们将哈希次数设定为了 10 。
var bcrypt = require("bcrypt-nodejs");
var SALT_FACTOR = 10;复制代码
固然,对密码的哈希操做应该在保存数据以前。因此这部分代码应该在数据保存以前的回调函数中完成,代码以下:
...
var noop = function() {};
// 保存操做以前的回调函数
userSchema.pre("save", function(done) {
var user = this;
if (!user.isModified("password")) {
return done();
}
bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
if (err) {
return done(err);
}
bcrypt.hash(user.password, salt, noop,
function(err, hashedPassword) {
if (err) {
return done(err);
}
user.password = hashedPassword;
done();
}
);
});
});复制代码
该回调函数会在每次进行数据库保存以前被调用,因此它能确保你的密码会以密文形式获得保存。
处理须要对密码进行加密处理以外,另外一个常见需求就是用户受权验证了。例如,在用户登陆操做时的密码验证操做。
...
userSchema.methods.checkPassword = function(guess, done) {
bcrypt.compare(guess, this.password, function(err, isMatch) {
done(err, isMatch);
});
}复制代码
出于安全缘由,这里咱们使用的是 bcrypt.compare 函数而不是简单的相等判断 === 。
完成模型定义和通用方法实现后,接下来咱们就须要将其暴露出来供其余代码使用了。不过暴露模型的操做很是简单只需两行代码:
...
var User = mongoose.model("User", userSchema);
module.exports = User;复制代码
models/user.js 文件中完整的代码以下:
// 代码清单 8.8 models/user.js编写完成以后
var bcrypt = require("bcrypt-nodejs");
var SALT_FACTOR = 10;
var mongoose = require("mongoose");
var userSchema = mongose.Schema({
username: { type: String, require: true, unique: true },
password: { type: String, require: true },
createdAt: {type: Date, default: Date.now },
displayName: String,
bio: String
});
userSchema.methods.name = function() {
return this.displayName || this.username;
}
var noop = function() {};
userSchema.pre("save", function(done) {
var user = this;
if (!user.isModified("password")) {
return done();
}
bcrypt.genSalt(SALT_FACTOR, function(err, salt) {
if (err) { return done(err); }
bcrypt.hash(user.password, salt, noop,
function(err, hashedPassword) {
if (err) { return done(err); }
user.password = hashedPassword;
done();
}
);
});
});
userSchema.methods.checkPassword = function(guess, done) {
bcrypt.compare(guess, this.password, function(err, isMatch) {
done(err, isMatch);
});
}
var User = mongoose.model("User", userSchema);
module.exports = User;复制代码
模型定义好以后,接下来就是在主页、编辑页面、注册等页面进行使用了。相比于以前的模型定义,使用过程相对来讲要更简单。
首先,在项目根目录建立主入口文件 app.js 并复制下面的代码:
var express = require("express");
var mongoose = require("mongoose");
var path = require("path");
var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var session = require("express-session");
var flash = require("connect-flash");
var routes = require("./routes");
var app = express();
// 链接到你MongoDB服务器的test数据库
mongoose.connect("mongodb://localhost:27017/test");
app.set("port", process.env.PORT || 3000);
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs");
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: "TKRv0IJs=HYqrvagQ#&!F!%V]Ww/4KiVs$s,<<MX",
resave: true,
saveUninitialized: true
}));
app.use(flash());
app.use(routes);
app.listen(app.get("port"), function() {
console.log("Server started on port " + app.get("port"));
});复制代码
接下来,咱们须要实现上面使用到的路由中间件。在根目录新建 routes.js 并复制代码:
var express = require("express");
var User = require("./models/user");
var router = express.Router();
router.use(function(req, res, next) {
res.locals.currentUser = req.user;
res.locals.errors = req.flash("error");
res.locals.infos = req.flash("info");
next();
});
router.get("/", function(req, res, next) {
User.find()
.sort({ createdAt: "descending" })
.exec(function(err, users) {
if (err) { return next(err); }
res.render("index", { users: users });
});
});
module.exports = router;复制代码
这两段代码中,首先,咱们使用 Mongoose 进行了数据库链接。而后,在路由中间件中经过 User.find 异步获取用户列表并将其传递给了主页视图模版。
接下来,咱们就轮到主页视图的实现了。首先在根目录建立 views 文件夹,而后在文件夹中添加第一个模版文件 _header.ejs :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Learn About Me</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Learn About Me</a>
</div>
<!--
若是用户已经登录了则对导航条进行相应的改变。
一开始你的代码中并不存在currentUser,因此总会显示一个状态
-->
<ul class="nav navbar-nav navbar-right">
<% if (currentUser) { %>
<li>
<a href="/edit">
Hello, <%= currentUser.name() %>
</a>
</li>
<li><a href="/logout">Log out</a></li>
<% } else { %>
<li><a href="/login">Log in</a></li>
<li><a href="/signup">Sign up</a></li>
<% } %>
</ul>
</div>
</div>
<div class="container">
<% errors.forEach(function(error) { %>
<div class="alert alert-danger" role="alert">
<%= error %>
</div>
<% }) %>
<% infos.forEach(function(info) { %>
<div class="alert alert-info" role="alert">
<%= info %>
</div>
<% }) %>复制代码
你可能注意到了这些文件的名字是如下划线开始的。这是一个社区约定,全部组件模版都会如下划线进行区分。
接下来,添加第二个通用组件模版 _footer.js:
</div>
</body>
</html>复制代码
最后,咱们添加主页视图模版文件。该视图模版会接受中间件中传入的 users 变量并完成渲染:
<% include _header %>
<h1>Welcome to Learn About Me!</h1>
<% users.forEach(function(user) { %>
<div class="panel panel-default">
<div class="panel-heading">
<a href="/users/<%= user.username %>">
<%= user.name() %>
</a>
</div>
<% if (user.bio) { %>
<div class="panel-body"><%= user.bio %></div>
<% } %>
</div>
<% }) %>
<% include _footer %>复制代码
确保代码无误后,接下来启动 Mongo 数据库服务并使用 npm start 拉起工程。而后,经过浏览器访问 localhost:3000 就能类型下图的主页界面:
固然,由于此时数据库中并无任何记录全部这里并无出现任何用户信息。
接下来,咱们就来实现用户用户注册和登陆功能。不过在此以前,咱们须要在 app.js 中引入 body-parser 模块并用于后面请求参数的解析。
var bodyParser = require("body-parser");
...
app.use(bodyParser.urlencoded({ extended: false }));
…复制代码
为了提升安全性,这里咱们将 body-parser 模块的 extended 设置为 false 。接下来,咱们在 routes.js 添加 sign-up 功能的中间件处理函数:
var passport = require("passport");
...
router.get("/signup", function(req, res) {
res.render("signup");
});
router.post("/signup", function(req, res, next) {
// 参数解析
var username = req.body.username;
var password = req.body.password;
// 调用findOne只返回一个用户。你想在这匹配一个用户名
User.findOne({ username: username }, function(err, user) {
if (err) { return next(err); }
// 判断用户是否存在
if (user) {
req.flash("error", "User already exists");
return res.redirect("/signup");
}
// 新建用户
var newUser = new User({
username: username,
password: password
});
// 插入记录
newUser.save(next);
});
// 进行登陆操做并实现重定向
}, passport.authenticate("login", {
successRedirect: "/",
failureRedirect: "/signup",
failureFlash: true
}));复制代码
路由中间件定义完成后,下面咱们就来实现视图模版 signup.ejs 文件。
// 拷贝代码到 views/signup.ejs
<% include _header %>
<h1>Sign up</h1>
<form action="/signup" method="post">
<input name="username" type="text" class="form-control" placeholder="Username" required autofocus>
<input name="password" type="password" class="form-control" placeholder="Password" required>
<input type="submit" value="Sign up" class="btn btn-primary btn-block">
</form>
<% include _footer %>复制代码
若是你成功建立用户并再次访问主页的话,你就能看见一组用户列表:
而注册页的 UI 大体以下:
在实现登陆功能以前,咱们先把我的信息展现功能先补充完整。在 routes.js 添加以下中间件函数:
...
router.get("/users/:username", function(req, res, next) {
User.findOne({ username: req.params.username }, function(err, user) {
if (err) { return next(err); }
if (!user) { return next(404); }
res.render("profile", { user: user });
});
});
...复制代码
接下来编写视图模版文件 profile.ejs :
// 保存到 views 文件夹中
<% include _header %>
<!--
参考变量currentUser来判断你的登录状态。不过如今它总会是false状态
-->
<% if ((currentUser) && (currentUser.id === user.id)) { %>
<a href="/edit" class="pull-right">Edit your profile</a>
<% } %>
<h1><%= user.name() %></h1>
<h2>Joined on <%= user.createdAt %></h2>
<% if (user.bio) { %>
<p><%= user.bio %></p>
<% } %>
<% include _footer %>复制代码
若是如今你经过首页进入用户详情页话,那么你就会出现相似下图的界面:
除了上面这些基本功能以外,User 模型作重要的功能实际上是登陆以及权限认证。而这也是 User 模型与其余模型最大的区别。因此接下来的任务就是实现登陆页并进行密码和权限认证。
为了减小不少没必要要的工做量,这里咱们会使用到第三方的 Passport 模块。该模版是特意为请求进行验证而设计处理的 Node 中间件。经过该中间件只需一小段代码就能实现复杂的身份认证操做。不过 Passport 并无指定如何进行用户身份认证,它只是提供了一些模块化函数。
Passport 的设置过程主要有三件事:
首先,在初始化 Passport 环境时,你须要在工程中引入一些其余中间件。它们分别为:
其中前面 4 个中间件已经引入过了。它们的做用分别为: body-parser 用于参数解析;cookie-parser 处理从浏览器中获取的cookies;express-session 用于处理用户 session;而 connect-flash 则用户展现错误信息。
最后,咱们须要在 app.js 中引入 Passport 模块并在后面调用其中的两个中间件函数。
var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var flash = require("connect-flash");
var passport = require("passport");
var session = require("express-session");
...
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
// 须要一串随机字母序列,字符串不必定须要跟此处同样
secret: "TKRv0IJs=HYqrvagQ#&!F!%V]Ww/4KiVs$s,<<MX",
resave: true,
saveUninitialized: true
}));
app.use(flash());
app.use(passport.initialize());
app.use(passport.session());
...复制代码
代码中,咱们使用一串随机字符串来对客户端的 session 进行编码。这样就能在必定程度上增长 cookies 的安全性。而将 resave 设置为 true 则保证了即便 session 没有被修改也依然会被刷新。
接下来就是第二步操做:设置 Passport 对 User 模型的序列化和反序列化操做了。这样 Passport 就能实现 session 和 user 对象的互相转化了。Passport 文档对这一操做的描述为:
在标准的 web 应用中,只有当客户端发送了登陆请求才会须要对用户进行身份认证。若是认证经过的话,二者之间就会新建一个 session 并将其保存到 cookie 中进行维护。任何后续操做都不会再进行认证操做,取而代之的是使用 cookie 中惟一指定的 session 。因此,Passport 须要经过序列化和反序列化实现 session 和 user 对象的互相转化。
为了后期代码维护方便,这里咱们新建一个名为 setuppassport.js 的文件并将序列化和反序列化的代码放入其中。最后,咱们将其引入到 app.js 中:
…
var setUpPassport = require("./setuppassport");
…
var app = express();
mongoose.connect("mongodb://localhost:27017/test");
setUpPassport();
…复制代码
下面就是 setuppassport.js 中的代码实现了。由于 User 对象都有一个 id 属性做为惟一标识符,因此咱们就根据它来进行 User 对象的序列化和反序列化操做:
// setuppassport.js 文件中的代码
var passport = require("passport");
var User = require("./models/user");
module.exports = function() {
passport.serializeUser(function(user, done) {
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
}复制代码
接下来就是最难的部分了,如何进行身份认证?
在开始进行认证前,还有一个小工做须要完成:设置认证策略。虽然 Passport 附带了 Facebook 、Google 的身份认证策略,可是这里咱们须要的将其设置为 local strategy 。由于验证部分的规则和代码是由咱们本身来实现的。
首先,咱们在 setuppassport.js 中引入 LocalStrategy
...
var LocalStrategy = require("passport-local").Strategy;
…复制代码
接下来,按照下面的步骤使用 LocalStrategy 来进行具体的验证:
下面就是将这些步骤转化为具体的代码:
// setuppassport.js 验证代码
...
passport.use("login", new LocalStrategy(function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if(err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that username!" });
}
user.checkPassword(password, function(err, isMatch) {
if (err) { return done(err); }
if (isMatch) {
return done(null, user);
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
...复制代码
完成策略定义后,接下来就能够在项目的任何地方进行调用。
最后,咱们还须要完成一些视图和功能:
首先,咱们实现登陆界面视图。在 routes.js 中添加登陆路由中间件:
...
router.get("/login", function(req, res) {
res.render("login");
});
...复制代码
在登陆视图 login.ejs 中,咱们会接收一个用户名和一个密码,而后发送登陆的 POST 请求:
<% include _header %>
<h1>Log in</h1>
<form action="/login" method="post">
<input name="username" type="text" class="form-control" placeholder="Username" required autofocus>
<input name="password" type="password" class="form-control" placeholder="Password" required>
<input type="submit" value="Log in" class="btn btn-primary btn-block">
</form>
<% include _footer %>复制代码
接下来,咱们就须要处理该 POST 请求。其中就会使用到 Passport 的身份认证函数。
// routes.js 中登录功能代码
var passport = require("passport");
...
router.post("/login", passport.authenticate("login", {
successRedirect: "/",
failureRedirect: "/login",
failureFlash: true
}));
...复制代码
其中 passport.authenticate 函数会返回一个回调。该函数会根据咱们的指定对不一样的验证结果分别进行重定向。例如,登陆成功会重定向到首页,而失败则会重定向到登陆页。
登出操做相对来讲要简单得多,代码以下
// routes.js 登出部分
...
router.get("/logout", function(req, res) {
req.logout();
res.redirect("/");
});
...复制代码
Passport 还附加了 req.user 和 connect-flash 信息。再回顾一下前面的这段代码,相信你能有更深的体会。
...
router.use(function(req, res, next) {
// 为你的模板设置几个有用的变量
res.locals.currentUser = req.user;
res.locals.errors = req.flash("error");
res.locals.infos = req.flash("info");
next();
});
...复制代码
登陆和登出玩抽,下面就该轮到我的信息编辑功能了。
首先,咱们来实现一个通用的中间件工具函数 ensureAuthenticated 。该中间件函数会对当前用户的权限进行检查,若是检查不经过则会重定向到登陆页。
// routes.js 中的 ensureAuthenticated 中间件
...
function ensureAuthenticated(req, res, next) {
// 一个Passport提供的函数
if (req.isAuthenticated()) {
next();
} else {
req.flash("info", "You must be logged in to see this page.");
res.redirect("/login");
}
}
...复制代码
接下来,咱们会在编辑中间件中调用该函数。由于咱们须要确保在开始编辑以前,当前用户拥有编辑权限。
// GET /edit(在router.js中)
...
// 确保用户被身份认证;若是它们没有被重定向的话则运行你的请求处理
router.get("/edit", ensureAuthenticated, function(req, res) {
res.render("edit");
});
...复制代码
接下来咱们须要实现 edit.ejs 视图模版文件。该视图模版的内容很是简单,只包含用户昵称和简介的修改。
// views/edit.ejs
<% include _header %>
<h1>Edit your profile</h1>
<form action="/edit" method="post">
<input name="displayname" type="text" class="form-control" placeholder="Display name" value="<%= currentUser.displayName || "" %>">
<textarea name="bio" class="form-control" placeholder="Tell us about yourself!"> <%= currentUser.bio || "" %></textarea>
<input type="submit" value="Update" class="btn btn-primary btn-block">
</form>
<% include _footer %>复制代码
最后,咱们须要对修改后提交的请求做出处理。在进行数据库更新以前,这里一样须要进行权限认证。
// POST /edit(在routes.js中)
...
// 一般,这会是一个PUT请求,不过HTML表单仅仅支持GET和POST
router.post("/edit", ensureAuthenticated, function(req, res, next) {
req.user.displayName = req.body.displayname;
req.user.bio = req.body.bio;
req.user.save(function(err) {
if (err) {
next(err);
return;
}
req.flash("info", "Profile updated!");
res.redirect("/edit");
});
});
...复制代码
该代码仅仅只是对数据库对应记录的字段进行了更新。最终渲染的编辑视图以下:
最后,你能够建立一些测试数据对示例应用的全部功能进行一遍验证。
本文包含的内容有:
原文地址