io.emit('user', {name: socket.request.user.name, currentUsers, connected: true});
现在,您的客户端将拥有所有必要信息,以便在用户连接或断开连接时正确显示当前用户数和通知!要在客户端处理此事件,我们应该监听“用户”,然后使用jQuery将#num-users
的文本更改为“{NUMBER}在线用户”,并附加<li>
,以更新当前用户数<li>
使用ID为'messages'的无序列表,其中'{NAME}已{加/左}聊天。'。此实现可能如下所示: socket.on('user',function(data){ - $('#num-users')。text(data.currentUsers +'users online'); - var message = data.name; - if(data.connected){ - 消息+ ='已加入聊天。'; - } else { - 消息+ ='离开了聊天。'; - } - $('#messages')。append($('<li>')。html('<b>'+ message +'<\ / b>')); -});当您认为自己已经做对时,请提交您的页面。
'user'
,其中应包含如下字段:'name'、'currentUsers'、'connected'(布尔值,连接上即为 true
,断开则是 false
)。记得要修改之前我们写好的处理 'user count' 的那部分代码,现在我们应在用户连接时传入布尔值 true
;在用户断开连接是传入布尔值 false
。
+
+```js
+io.emit('user', {
+ name: socket.request.user.name,
+ currentUsers,
+ connected: true
+});
+```
+
+现在,我们的客户端已经有足够的信息显示现有用户数量和发送用户上下线通知。接下来我们需要在客户端监听 'user' 事件,然后使用 jQuery 把 #num-users
节点的文本内容更新为 '{NUMBER} users online'。同时,我们需要为 <ul>
添加一个 id 为 'messages' 且带有 '{NAME} has {joined/left} the chat.' 文本的<li>
。
+一种实现方式如下:
+
+```js
+socket.on('user', data => {
+ $('#num-users').text(data.currentUsers + ' users online');
+ let message =
+ data.name +
+ (data.connected ? ' has joined the chat.' : ' has left the chat.');
+ $('#messages').append($(''user'
事件应发送包含 name、currentUsers、connected 字段的对象。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js').then(data => { assert.match(data, /io.emit.*('|")user\1.*name.*currentUsers.*connected/gis, 'You should have an event emitted named user sending name, currentUsers, and connected'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 客户端应处理和显示 'user'
对象中的信息
+ testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|")user\1[^]*num-users/gi, 'You should change the text of "#num-users" within on your client within the "user" event listener to show the current users connected'); assert.match(data, /socket.on.*('|")user\1[^]*messages.*li/gi, 'You should append a list item to "#messages" on your client within the "user" event listener to announce a user came or went'); }, xhr => { throw new Error(xhr.statusText); })
```
const LocalStrategy = require('passport-local');
现在,您必须告诉护照使用实例化的LocalStartegy对象,并定义一些设置。确保这一点以及此时的所有内容都封装在数据库连接中,因为它依赖于它! passport.use(新的LocalStrategy( - function(用户名,密码,已完成){ - db.collection('users')。findOne({username:username},function(err,user){ - console.log('用户'+用户名+'试图登录。'); - if(err){return done(err); } - if(!user){return done(null,false); } - if(password!== user.password){return done(null,false); } - return done(null,user); - }); +这是我们尝试在本地验证某人时要采取的过程。首先,它尝试使用输入的用户名在我们的数据库中查找用户,然后检查要匹配的密码,最后如果没有弹出我们检查过的错误,如错误的密码,则返回用户对象,它们是认证。许多策略都是使用不同的设置设置的,一般来说,根据该策略库中的README很容易设置它。一个很好的例子是Github策略,我们不需要担心用户名或密码,因为用户将被发送到Github的auth页面进行身份验证,只要他们登录并同意,然后Github返回他们的个人资料我们用。在下一步中,我们将设置如何实际调用身份验证策略以根据表单数据验证用户!如果您认为自己已经完成了这一点,请提交您的页面。+ +现在,我们需要构建验证用户的策略,策略的选择有很多。比如,如果我们已经让用户在注册时填写了用户信息,那我们就可以基于这些信息验证;或者也可以引入第三方登录,如 Google 或者 Github。为此,你可以参考 Passports 中提供的策略插件。对于这个项目的验证策略,我们会采用自己搭建的方式完成。 + +首先,我们需要引入 passport-local 作为依赖,然后将它添加到服务器,就像这样: const LocalStrategy = require('passport-local');
+ +然后,我们需要让 passport 使用实例化的本地策略对象。请注意,接下来的所有代码都应写在连接数据库的回调中,因为它们的执行都依赖数据库。 + +```js +passport.use(new LocalStrategy( + function(username, password, done) { + myDataBase.findOne({ username: username }, function (err, user) { + console.log('User '+ username +' attempted to log in.'); + if (err) { return done(err); } + if (!user) { return done(null, false); } + if (password !== user.password) { return done(null, false); } + return done(null, user); + }); } -));
passport-local
作为依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'passport-local', 'Your project should list "passport-local " as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- - text: Passport-local正确需要和设置
+ - text: 应正确地引入和设置 passport-local
。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require.*("|')passport-local("|')/gi, 'You should have required passport-local'); assert.match(data, /new LocalStrategy/gi, 'You should have told passport to use a new strategy'); assert.match(data, /findOne/gi, 'Your new local strategy should use the findOne query to find a username based on the inputs'); }, xhr => { throw new Error(xhr.statusText); })
```
@@ -46,7 +64,11 @@ tests:
io.use(passportSocketIo.authorize({ - cookieParser:cookieParser, - key:'express.sid', - secret:process.env.SESSION_SECRET, - store:sessionStore -}));您还可以选择将“成功”和“失败”与在客户端尝试连接时身份验证过程完成后调用的函数一起传递。现在可以在套接字对象上以
socket.request.user
访问用户对象。例如,现在您可以添加以下内容: console.log('user ' + socket.request.user.name + ' connected');
它将登录已连接的服务器控制台!当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在此处查看项目。 req.user
包含用户信息,但这个只在用户直接与服务器交互(即不通过 web socket 访问服务器资源)时产生。当我们的用户通过 web socket 与服务器连接时,由于不存在 req
对象,因此我们无法获取用户数据。解决这个问题的方法之一是通过读取和解析请求中包含 passport session 的 cookie,然后反序列化,进而获取用户信息对象。幸运的是,npm 上有可以让这个复杂的流程简单化的库。
+
+添加 passport.socketio
、connect-mongo
、cookie-parser
作为依赖,把它们分别赋值给 passportSocketIo
、MongoStore
、cookieParser
。同时,我们需要从之前引入的 express-session
中开辟新的内存空间,就像接下来这样:
+
+```js
+const MongoStore = require('connect-mongo')(session);
+const URI = process.env.MONGO_URI;
+const store = new MongoStore({ url: URI });
+```
+
+现在我们只需要让 Socket.IO 调用它并进行相应的配置即可。请注意,以上这些都必须放在现有的 socket 相关代码之前,而且不能放到连接的监听回调函数里。你的服务器代码应类似于这样:
+
+```js
+io.use(
+ passportSocketIo.authorize({
+ cookieParser: cookieParser,
+ key: 'express.sid',
+ secret: process.env.SESSION_SECRET,
+ store: store,
+ success: onAuthorizeSuccess,
+ fail: onAuthorizeFail
+ })
+);
+```
+
+记得要把 key
和 store
加到 app 的 session
中间件。这样,SocketIO 才知道该对哪个 session 执行此配置。
+
+success
与 fail
的回调函数:
+
+```js
+function onAuthorizeSuccess(data, accept) {
+ console.log('successful connection to socket.io');
+
+ accept(null, true);
+}
+
+function onAuthorizeFail(data, message, error, accept) {
+ if (error) throw new Error(message);
+ console.log('failed connection to socket.io:', message);
+ accept(null, false);
+}
+```
+
+现在,我们可以通过 socket 对象通过 socket.request.user
访问 user
对象。为此,你可以这样做:
+
+```js
+console.log('user ' + socket.request.user.name + ' connected');
+```
+
+这样,我们可以在 console 里看到谁连接到了我们的服务器。
+
+完成上述要求后,你可以在下方提交你的页面链接。
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+passport.socketio
作为依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'passport.socketio', 'Your project should list "passport.socketio" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- - text: passportSocketIo是正确需要的
+ - text: 应添加 cookie-parser
作为依赖。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'cookie-parser', 'Your project should list "cookie-parser" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 应正确引入 passportSocketIo。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js').then(data => { assert.match(data, /require\((['"])passport\.socketio\1\)/gi, 'You should correctly require and instantiate "passport.socketio"');}, xhr => { throw new Error(xhr.statusText); })
- - text: passportSocketIo已正确设置
- testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /io\.use\(.+\.authorize\(/gi, 'You should register "passport.socketio" as socket.io middleware and provide it correct options'); }, xhr => { throw new Error(xhr.statusText); })
-
+ - text: 应正确配置 passportSocketIo。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /io\.use\(\s*\w+\.authorize\(/, 'You should register "passport.socketio" as socket.io middleware and provide it correct options'); }, xhr => { throw new Error(xhr.statusText); })
```
module.exports = function(app,db){ -现在位于服务器文件的顶部,需要这样的文件:}
const routes = require('./routes.js');
在您与数据库建立成功连接之后,实例化它们中的每一个如下: routes(app, db)
最后,获取服务器中的所有路由并将它们粘贴到新文件中并从服务器文件中删除它们。也可以使用ensureAuthenticated,因为我们专门为路由创建了中间件功能。您现在必须正确添加所使用的依赖项,例如const passport = require('passport');
,在routes.js文件中导出行的最上方。继续添加它们直到不再存在错误,并且您的服务器文件不再有任何路由!现在在auth.js文件中执行相同的操作,其中包含与身份验证相关的所有内容,例如序列化和本地策略的设置,并从服务器文件中删除它们。确保添加依赖项并在同一位置调用服务器中的auth(app,db)
。确保routes(app, db)
auth(app, db)
之前有auth(app, db)
routes(app, db)
因为我们的注册路由取决于发起的护照!恭喜 - 您已经处于Advanced Node和Express的这一部分的末尾,并且有一些漂亮的代码可供展示!当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在此处查看已完成项目的示例。 routes.js
和 auth.js
+在每个文件的开头,我们都需要写上这段代码:
+
+```js
+module.exports = function (app, myDataBase) {
+
+}
+```
+
+然后,在 server.js 文件的开头,我们需要像这样引入文件:const routes = require('./routes.js');
+在成功连接数据库之后,我们需要像这样进行实例化:routes(app, myDataBase)
。
+最后,我们需要把所有路由相关的代码从 server.js 移动到新文件 routes.js。不要忘了,ensureAuthenticated
方法的定义也要移动到新文件中,这个是我们在之前的挑战中,为在路由中判断用户是否已登录创建的函数。然后,我们还需要在 routes.js 文件开头添加所需要的依赖,如:const passport = require('passport');
。
+
+如果在这些步骤后没有报错,那么恭喜你,你已成功地从 server.js 文件中分离出了路由文件(**尽管 catch 那部分还是包含了路由的逻辑**)。
+
+现在,我们来把 server.js 中与验证相关的代码分离到 auth.js 中,例如序列化,设置验证策略等。请正确添加依赖,并在 server.js 中调用auth(app,myDataBase)
。另外,由于我们的注册路由依赖 passport,所以我们需要先调用auth(app, myDataBase)
,再调用routes(app, myDataBase)
。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+Submit your page when you think you've got it right. If you're running into errors, you can check out an example of the completed project here.
+
+var currentUsers = 0;
现在当有人连接时,你应该在发出计数之前递增计数,这样你就可以在连接监听器中添加增量器。 ++currentUsers;
最后,在递增计数后,您应该发出事件(仍在连接侦听器中)。该事件应命名为“用户计数”,数据应该只是'currentUsers'。 io.emit('user count', currentUsers);
socket.on('user count',function(data){ - 的console.log(数据); -});现在尝试加载您的应用并进行身份验证,您应该在客户端控制台中看到“1”代表当前用户数!尝试加载更多客户端并进行身份验证以查看数量是否上升。当您认为自己已经做对时,请提交您的页面。
currentUsers
:
+
+```js
+io.emit('user count', currentUsers);
+```
+
+接下来,我们还需要让客户端监听从服务端发出的事件。为此,我们还是需要用到 on 这个方法:
+
+```js
+socket.on('user count', function(data) {
+ console.log(data);
+});
+```
+
+现在你可以尝试启动你的 app 并登录,你会看到在客户端的控制台打印出了 1,这就表示目前连接到服务器的用户数为 1。你可以试着通过打开多个 app 来验证数量是否会增加。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+ensureAuthenticated(req, res, next)
,它将检查用户是否通过调用护照进行身份验证isAuthenticated对请求进行检查,然后检查req.user是否定义。如果是,那么应该调用next() ,否则我们只需通过重定向到我们的主页来回复请求即可登录。该中间件的实现是: function ensureAuthenticated(req,res,next){ - if(req.isAuthenticated()){ - return next(); +现在,在包含呈现页面的函数的get请求的参数之前,将ensureAuthenticated作为中间件添加到配置文件页面的请求中。+ +无论是否登录,或者哪怕用户试图访问其他页面,目前都会跳转到 /profile
。为了解决这个问题,我们需要在 profile 页面渲染之前进行用户验证,创建中间件就可以实现这个功能。 + +这个挑战的目标是创建ensureAuthenticated(req, res, next)
中间件方法,通过在 request 上调用 passports 的isAuthenticated
方法,我们可以检查 req.user 是否定义,从而确定用户是否通过认证。如果用户已通过验证,就会调用 next(),否则我们应重定向到主页并让用户登录。该中间件的实现如下: + +```js +function ensureAuthenticated(req, res, next) { + if (req.isAuthenticated()) { + return next(); } - res.redirect( '/'); -};
app.route( '/简档') - .get(ensureAuthenticated,(req,res)=> { - res.render(process.cwd()+'/ views / pug / profile'); - });当您认为自己已经做对时,请提交您的页面。
ensureAuthenticated
中间件应添加到 /profile
路由中。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /ensureAuthenticated[^]*req.isAuthenticated/gi, 'Your ensureAuthenticated middleware should be defined and utilize the req.isAuthenticated function'); assert.match(data, /profile[^]*get[^]*ensureAuthenticated/gi, 'Your ensureAuthenticated middleware should be attached to the /profile route'); }, xhr => { throw new Error(xhr.statusText); })
- - text: 正确的Get请求/配置文件重定向到/因为我们未经过身份验证
+ - text: 如果没有通过验证,对 /profile 的 GET 请求应重定向到 /
testString: getUserInput => $.get(getUserInput('url')+ '/profile') .then(data => { assert.match(data, /Home page/gi, 'An attempt to go to the profile at this point should redirect to the homepage since we are not logged in'); }, xhr => { throw new Error(xhr.statusText); })
```
@@ -44,7 +64,11 @@ tests:
socket.on('disconnect', () => { /*anything you want to do on disconnect*/ });
要确保客户端持续获得当前用户的更新计数,您应该在断开连接时将currentUsers减少1,然后使用更新的计数发出'user count'事件! 注意 'disconnect'
类似,所有 socket 可以发送到服务器的事件,我们都应该在有 'socket' 定义的连接监听器里处理。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+password: req.body.password
。实现保存哈希的一种简单方法是在数据库逻辑var hash = bcrypt.hashSync(req.body.password, 12);
之前添加以下内容var hash = bcrypt.hashSync(req.body.password, 12);
并使用password: hash
替换数据库保存中的req.body.password
。最后在我们的身份验证策略中,我们在完成流程之前在代码中检查以下内容: if (password !== user.password) { return done(null, false); }
。完成之前的更改后,现在user.password
是一个哈希。在更改现有代码之前,请注意语句如何检查密码是否不相等,然后返回未经过身份验证的密码。考虑到这一点,您的代码可能如下所示,以正确检查针对哈希输入的密码: if (!bcrypt.compareSync(password, user.password)) { return done(null, false); }
这是所有需要实现的最重要的安全功能之一,当你有来存储密码!当您认为自己已经做对时,请提交您的页面。 require
添加到服务器代码中。你需要在两个步骤中使用哈希运算:注册和保存新账户,以及登录时检查密码是否正确。
+
+目前处理注册的路由中,我们是这样把密码添加到数据库的:password: req.body.password
。我们可以通过这段代码创建哈希值:var hash = bcrypt.hashSync(req.body.password, 12);
,然后就可以把passsword: req.body.password
替换为password: hash
。
+
+最后,在验证逻辑中,我们已经有这样一段代码执行检查:if (password !== user.password) { return done(null, false); }
。但我们现在存储的密码user.password
已经是哈希值了。由于目前的检测机制是密码不匹配时就返回未认证,因此修改后,用于比对用户密码哈希值的代码应该是这样:
+
+```js
+if (!bcrypt.compareSync(password, user.password)) {
+ return done(null, false);
+}
+```
+
+当你需要存储密码时,这样做可以有效地提升网站的安全性。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+h2.center#welcome Welcome, #{username}!
行h2.center#welcome Welcome, #{username}!
使用“center”类创建h2元素,并使用包含文本“Welcome”和用户名的id“welcome”创建!同样在配置文件中,添加指向/ logout的链接。该路由将托管用于取消身份验证用户的逻辑。 a(href='/logout') Logout
当您认为自己正确时,请提交您的页面。 h2.center#welcome Welcome, #{username}!
代码来创建 class 为 center
、id 为 welcome
且文本内容为 'Welcome, ' 后加用户名的 h2 元素。
+
+以及,请在 profile 里添加 /logout 链接,后续会用于处理用户退出登录的逻辑:a(href='/logout') Logout
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+if showLogin
的形式缩进后。在showLogin作为变量从未定义之前,它从未呈现包含该表单的代码块。继续在该页面的res.render上向对象showLogin: true
添加一个新变量。刷新页面时,您应该看到表单!此表单设置为POST on / login,因此我们应该设置此接受POST并验证用户身份。对于此挑战,您应添加路由/登录以接受POST请求。要在此路由上进行身份验证,您需要添加中间件才能发送响应。这是通过在您的function(req,res)
之前使用中间件传递另一个参数来完成的!要使用的中间件是passport.authenticate('local')
。 passport.authenticate也可以将一些选项作为参数,例如: { failureRedirect: '/' }
这非常有用,所以一定要添加它。作为使用中间件后的响应(只有在身份验证中间件通过时才会调用)应该是将用户重定向到/ profile,并且该路由应该呈现视图'profile.pug'。如果身份验证成功,则用户对象将保存在req.user中 。现在,如果您在表单中输入用户名和密码,它应该重定向到主页/并且在服务器的控制台中应该是'用户{USERNAME}尝试登录'。因为我们目前无法登录未注册的用户。当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在这里查看到目前为止完成的项目。 if showLogin
,因此它是隐藏的。因为 showLogin 未定义,所以表单不会渲染。如果在该页面的 res.render()
里添加一个包含 showLogin: true
的对象,你就可以在刷新页面后看到表单。当你点击 login 时,表单会向服务器的 /login 发送 POST 请求,此时服务器端就可以接受 POST 请求信息并进行用户验证。
+
+在这个挑战中,你需要为 POST 请求添加路由/login
。为了用这个路由进行验证,你需要添加一个中间件,中间件应作为参数添加到用于处理请求的回调函数 function(req,res)
之前。对于 passport 的验证中间件,应这样调用:passport.authenticate('local')
。
+
+passport.authenticate 也接收选项作为参数,这些选项用于设置验证,例如{ failureRedirect: '/' }
就很有用,请记得添加到你的代码中。如果中间件验证通过,我们就应该提供相应的后续处理。在这个挑战中,我们需要让用户重定到 /profile,这样 profile.pug
页面就会渲染。
+
+如果验证通过,用户对象将会储存到 req.user 中。
+
+这时,如果你在表单里输入了用户名和密码,路由将会重定向到主页 /,在服务端将会打印 'User {USERNAME} attempted to log in.',由于现在我们还没有实现注册功能,因此所有登录尝试都会失败。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+const mongo = require('mongodb').MongoClient;
)现在我们想要连接到我们的数据库,然后开始侦听请求。这样做的目的是在连接数据库之前或者出现数据库错误时不允许请求。要实现此目的,您需要在以下内容中包含序列化和应用程序侦听器: mongo.connect(process.env.DATABASE,(err,db)=> { - if(错误){ - console.log('数据库错误:'+错误); - } else { - console.log('成功的数据库连接'); -您现在可以在deserializeUser中取消注释该块并删除您的-//serialization and app.listen
}});
done(null, null)
。确保将.env文件中的DATABASE设置为数据库的连接字符串(例如: DATABASE=mongodb://admin:pass@mlab.com:12345/my-project
)。您可以在mLab上设置免费数据库。恭喜 - 您已完成序列化设置!当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在这里查看到目前为止完成的项目。 MONGO_URI
中添加你的数据库地址(比如:mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority
),我们会在 connection.js 中调用它。
+
+_你可以在 MongoDB Atlas 创建一个免费的数据库。_
+
+在连接数据库之后,我们才能让服务器开始监听请求,这样做可以保证服务器在数据库连接前或数据库发生错误时不接受任何请求。为此,我们需要这样写:
+
+```js
+myDB(async client => {
+ const myDataBase = await client.db('database').collection('users');
+
+ // Be sure to change the title
+ app.route('/').get((req, res) => {
+ //Change the response to render the Pug template
+ res.render('pug', {
+ title: 'Connected to Database',
+ message: 'Please login'
+ });
+ });
+
+ // Serialization and deserialization here...
+
+ // Be sure to add this...
+}).catch(e => {
+ app.route('/').get((req, res) => {
+ res.render('pug', { title: e, message: 'Unable to login' });
+ });
+});
+// app.listen out here...
+```
+
+记得要取消 deserializeUser 中 myDataBase
的注释,并把 doc
添加到 done(null, null)
。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+done(null, null)
- testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.notMatch(data, /null,( |)null/gi, 'The callback in deserializeUser of (null, null) should be completely removed for the db block uncommented out'); }, xhr => { throw new Error(xhr.statusText); })
-
+ - text: 应存在数据库连接。
+ testString: getUserInput => $.get(getUserInput('url')+ '/') .then(data => { assert.match(data, /Connected to Database/gi, 'You successfully connected to the database!'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 反序列化应正确使用,且应正确调用 done(null, null)
。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /null,\s*doc/gi, 'The callback in deserializeUser of (null, null) should be altered to (null, doc)'); }, xhr => { throw new Error(xhr.statusText); })
```
const GitHubStrategy = require('passport-github').Strategy;
。要设置Github策略,你必须告诉护照 使用实例化的GithubStrategy ,它接受2个参数:一个对象(包含clientID , clientSecret和callbackURL )和一个在成功验证用户时要调用的函数,我们将确定如果用户是新用户以及最初要保存在用户数据库对象中的字段。这在许多策略中很常见,但有些可能需要更多信息,如特定策略的github README所述;例如,Google也需要一个范围来确定您的请求所返回的信息类型,并要求用户批准此类访问。我们正在实施的当前策略在此处列出了它的用法,但我们将在freeCodeCamp上完成所有这些工作!以下是您的新战略应该如何看待这一点: passport.use(new GitHubStrategy({ - clientID:process.env.GITHUB_CLIENT_ID, - clientSecret:process.env.GITHUB_CLIENT_SECRET, - callbackURL:/ * INSERT CALLBACK URL在这里输入GITHUB * / - }, - function(accessToken,refreshToken,profile,cb){ - 的console.log(配置文件); - //这里的数据库逻辑带有包含我们用户对象的回调 +您的身份验证还不会成功,并且实际上会抛出错误,没有数据库逻辑和回调,但如果您尝试它,它应该将您的Github配置文件记录到您的控制台!当您认为自己已经做对时,请提交您的页面。+ +设置 GitHub 验证的最后一步是创建策略本身。为此,你需要在 auth.js
中require
'passport-github',且实例化为 GithubStrategy:const GithubStrategy = require('passport-github').Strategy;
。别忘了在dotenv
中修改环境变量。 + +为了设置 GitHub 策略,我们需要在 passport 中使用实例化的 GithubStrategy,它可以接收两个参数:一个对象(包括 clientID, clientSecret 和 callbackURL),以及一个回调函数。在这个回调函数中,我们要处理验证成功时,判断用户是否已经在数据库中存在的逻辑,还有如果数据库中不存在,把用户数据添加到数据库的代码。这种处理方式适用于绝大部分第三方验证策略,但有些策略会需要我们提供更多的信息,详情请参考相关策略的 README。例如,Google 的验证策略会要求你提供一个 scope,用于标示用户成功登录后,你需要从返回的对象中获取那些信息。以及,这也需要经过用户同意,你才可以获取到。你可以在 这里 了解当前我们使用的验证策略的用法,不过我们也会在这里进行详细讲解。 + +你的新策略应该这样去实现: + +```js +passport.use(new GitHubStrategy({ + clientID: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + callbackURL: /*INSERT CALLBACK URL ENTERED INTO GITHUB HERE*/ +}, + function(accessToken, refreshToken, profile, cb) { + console.log(profile); + //Database logic here with callback containing our user object } -));
console.log(profile);
目前是: db.collection( 'socialusers')。findAndModify( - {id:profile.id}, - {}, - {$ setOnInsert:{ - id:profile.id, - name:profile.displayName || 'John Doe', - 照片:profile.photos [0] .value || ” - 电子邮件:profile.emails [0] .value || '没有公开电子邮件', - created_on:new Date(), - provider:profile.provider || “ - } $设置:{ - last_login:新日期() - } $ INC { - login_count:1 - }}, - {upsert:true,new:true}, - (错误,doc)=> { - return cb(null,doc.value); - } -);使用findAndModify,它允许您搜索对象并对其进行更新,如果对象不存在则将其置换,并在每次回调函数中接收新对象。在这个例子中,我们总是将last_login设置为now,我们总是将login_count增加1,并且只有当我们插入一个新对象(新用户)时,我们才会填充大部分字段。需要注意的是使用默认值。有时,返回的个人资料不会填写所有信息,或者用户会选择保留私密信息;所以在这种情况下我们必须处理它以防止错误。你现在应该可以登录你的应用了 - 试试吧!当您认为自己已经做对时,请提交您的页面。如果你正在运行到错误,您可以检查出的这个小项目的完成代码的例子在这里 。
console.log(profile);
的下方:
+
+```js
+myDataBase.findAndModify(
+ {id: profile.id},
+ {},
+ {$setOnInsert:{
+ id: profile.id,
+ name: profile.displayName || 'John Doe',
+ photo: profile.photos[0].value || '',
+ email: Array.isArray(profile.emails) ? profile.emails[0].value : 'No public email',
+ created_on: new Date(),
+ provider: profile.provider || ''
+ },$set:{
+ last_login: new Date()
+ },$inc:{
+ login_count: 1
+ }},
+ {upsert:true, new: true},
+ (err, doc) => {
+ return cb(null, doc.value);
+ }
+);
+```
+
+findAndModify
的作用是在数据库中查询对象并更新,如果对象不存在,我们也可以 upsert
(注,upsert 可以理解为 update + insert)它,然后我们可以在回调方法里获取到插入数据后的新对象。在这个例子中,我们会把 last_login 设置成为 now,而且总会为 login_count 加 1。只有在插入一个新对象(新用户)时,我们才会初始化这些字段。另外,还需要注意默认值的使用。有时返回的用户信息可能不全,可能是因为用户没有填写,也可能是因为用户选择不公开一部分信息。在这种情况下,我们需要进行相应的处理,以防我们的 app 报错。
+
+你现在应该可以登录你的应用了,试试吧。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+passport.authenticate('github')
,将其重定向到Github。 process.env.GITHUB_CLIENT_ID
。对于这个挑战,我们将使用Github策略。 从Github获取您的客户ID和密码是在“开发者设置”下的帐户配置文件设置中完成的,然后是“ OAuth应用程序 ”。点击“注册一个新的应用程序”,为您的应用命名,将网址粘贴到您的故障主页( 不是项目代码的网址 ),最后为回调网址,粘贴到与主页相同的网址,但使用'/ auth / github / callback'已添加。这是用户将被重定向到我们在Github上进行身份验证后处理的地方。将返回的信息保存为.env文件中的“GITHUB_CLIENT_ID”和“GITHUB_CLIENT_SECRET”。在重新混合的项目中,创建2条接受GET请求的路由:/ auth / github和/ auth / github / callback。第一个应该只调用护照来验证'github',第二个应该调用护照来验证'github',失败重定向到'/'然后如果成功重定向到'/ profile'(类似于我们的上一个项目)。 '/ auth / github / callback'应该如何看待的示例与我们在上一个项目中处理正常登录的方式类似: app.route( '/登录') - .post(passport.authenticate('local',{failureRedirect:'/'}),(req,res)=> { - res.redirect( '/简档'); - });当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在此处查看项目。
passport.authenticate('github')
,跳转至 GitHub 验证页面。process.env.GITHUB_CLIENT_ID
获取。对于这次挑战,我们将会使用 GitHub 作为验证平台。
+
+首先,你需要进入账户设置里的 Developer settings板块,然后在 OAuth Apps 获取 Client ID 和 Client Secret。点击 'Register a new application',设置你的应用名称,然后把你的 glitch 主页地址(注意,不是项目代码的地址)粘贴到 Homepage URL。然后,回调 url 需要设置成上面 Homepage URL 里你粘贴的地址,但后面要加上 '/auth/github/callback'。这样在用户通过 Github 验证后才能跳转到我们指定的页面。别忘了,我们还需要在 .env 文件里配置好 'GITHUB_CLIENT_ID' 和 'GITHUB_CLIENT_SECRET'。
+
+然后,请在你现在的项目里,为 /auth/github 和 /auth/github/callback 创建两个接收 GET 请求的路由。第一个只需要通过 passport 来调用 'github' 验证,第二个需要调用 passport 来验证 'github',但需要在失败时跳转回主页 '/',成功是跳转到用户页面 '/profile'。跳转的逻辑与上一个项目中的逻辑一样。
+
+例如 '/auth/github/callback' 应该像我们处理在上一个项目中一般的登录一样:
+
+```js
+app.route('/login')
+ .post(passport.authenticate('local', { failureRedirect: '/' }), (req,res) => {
+ res.redirect('/profile');
+ });
+```
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+req.logout();
认证用户就像调用req.logout();
一样简单req.logout();
在重定向之前。 app.route( '/注销') - .get((req,res)=> { - req.logout(); - res.redirect( '/'); - });您可能已经注意到我们也没有处理丢失的页面(404),在Node中处理此问题的常用方法是使用以下中间件。继续在所有其他路线之后添加:
app.use((req,res,next)=> { - res.status(404) - .TYPE( '文本') - .send('未找到'); -});当您认为自己已经做对时,请提交您的页面。
req.logout();
即可完成用户的退出登录。
+
+```js
+app.route('/logout')
+ .get((req, res) => {
+ req.logout();
+ res.redirect('/');
+});
+```
+
+你可能注意到我们还没有处理 404 错误,这个错误码代表页面无法找到。在 Node.js 中我们通常会用如下的中间件来处理,请在所有路由之后添加这段代码:
+
+```js
+app.use((req, res, next) => {
+ res.status(404)
+ .type('text')
+ .send('Not Found');
+});
+```
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+req.Logout
应在 /logout
中调用。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /req.logout/gi, 'You should be calling req.logout() in your /logout route'); }, xhr => { throw new Error(xhr.statusText); })
- - text: 注销应该重定向到主页/
+ - text: 退出登录后应重定向到主页 /
testString: getUserInput => $.get(getUserInput('url')+ '/logout') .then(data => { assert.match(data, /Home page/gi, 'When a user logs out they should be redirected to the homepage'); }, xhr => { throw new Error(xhr.statusText); })
```
@@ -44,7 +63,11 @@ tests:
showRegistration: true
。刷新页面时,您应该会看到已在index.pug文件中创建的注册表单!此表单设置为POST on / register,因此我们应该设置此接受POST并在数据库中创建用户对象。注册路由的逻辑应如下所示:注册新用户>验证新用户>重定向到/配置文件步骤1的逻辑,注册新用户,应如下所示:使用findOne命令查询数据库>如果用户返回然后它存在并重定向回到主页或者如果用户未定义且没有发生错误,则使用用户名和密码将“insertOne”输入数据库,只要没有错误发生,然后调用next转到步骤2,验证新的user,我们已经在POST / login路由中编写了逻辑。 app.route( '/寄存器') - .post((req,res,next)=> { - db.collection('users')。findOne({username:req.body.username},function(err,user){ - if(错误){ - 下一个(ERR); - } else if(user){ - res.redirect( '/'); - } else { - db.collection( '用户')。insertOne( - {username:req.body.username, - 密码:req.body.password}, - (错误,doc)=> { - if(错误){ - res.redirect( '/'); - } else { - next(null,user); - } - } - ) +当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在这里查看到目前为止完成的项目。+ +现在我们需要为新用户添加注册帐号的功能,首先我们需要在主页的 res.render 接收的变量对象中添加 showRegistration: true
。此时刷新页面,你会看到页面上已经显示了我们在 index.pug 文件中定义的注册表单。这个表单设置了请求路径 /register,并将请求方法设置成 POST,所以我们需要在服务器接受 POST 请求,且在数据库中创建用户对象。 + +用户注册的逻辑如下:注册新用户 > 认证新用户 > 重定向到 /profile。 + +对于步骤一的注册新用户,详细逻辑如下:用 findOne 命令查询数据库 > 如果返回了用户对象,则表示用户存在,然后返回主页;如果用户未定义且没有报错,则会将包含用户名和密码的用户对象通过insertOne
添加到数据库,只要没有报错则会继续下一步:认证新用户——我们已经在 /login 路由的 POST 请求中写好了这部分逻辑。 + +```js +app.route('/register') + .post((req, res, next) => { + myDataBase.findOne({ username: req.body.username }, function(err, user) { + if (err) { + next(err); + } else if (user) { + res.redirect('/'); + } else { + myDataBase.insertOne({ + username: req.body.username, + password: req.body.password + }, + (err, doc) => { + if (err) { + res.redirect('/'); + } else { + // The inserted document is held within + // the ops property of the doc + next(null, doc.ops[0]); + } } - })}, - passport.authenticate('local',{failureRedirect:'/'}), - (req,res,next)=> { - res.redirect( '/简档'); + ) + } + }) + }, + passport.authenticate('local', { failureRedirect: '/' }), + (req, res, next) => { + res.redirect('/profile'); } -);
$('form').submit(function(){ /*logic*/ });
) #m
之前发出事件。该事件应命名为“聊天消息”,数据应为“messageToSend”。 socket.emit('chat message', messageToSend);
现在,在您的服务器上,您应该收听事件“聊天消息”的套接字,并将数据命名为“message”。一旦接收到事件,应该然后发射所述事件“聊天消息”到所有插座io.emit
与数据为含“名称”和“报文”的对象。现在再次在您的客户端上,您现在应该监听事件“聊天消息”,并在收到时,将一个列表项追加到#messages
,其名称为冒号和消息!此时聊天功能齐全,并在所有客户端发送消息!当您认为自己已经做对时,请提交您的页面。如果您遇到错误,可以在此处检查项目到此时为服务器和客户端 。 messageToSend
之后,以及清除 #m
中的文本之前。我们称这个事件叫 'chat message'
,需发送的数据叫 messageToSend
:
+
+```js
+socket.emit('chat message', messageToSend);
+```
+
+在服务端,我们需要监听包含 message
的 'chat message'
事件。一旦事件发生,我们就通过io.emit
把包含 name
和 message
的 'chat message'
事件发送给所有已连接的 socket。
+
+回到客户端,我们需要监听 'chat message'
事件。只要接收到这个事件,就把包含名字和消息的内容(注意:需要在名字后添加冒号)通过 <li>
添加到 #messages
。
+
+至此,我们已经完成发送信息到所有客户端的功能。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+'chat message'
,且应在监听到后 emit。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*name.*message/gis, 'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: '客户端应正确处理和展示从 'chat message'
事件发来的新数据。
+ testString: 'getUserInput => $.get(getUserInput(''url'')+ ''/public/client.js'') .then(data => { assert.match(data, /socket.on.*(''|")chat message(''|")[^]*messages.*li/gis, ''You should append a list item to #messages on your client within the "chat message" event listener to display the new message''); }, xhr => { throw new Error(xhr.statusText); })'
```
passport.serializeUser( OURFUNCTION )
和passport.deserializeUser( OURFUNCTION )
创建它们。使用2个参数调用serializeUser,完整的用户对象和护照使用的回调。在回调中返回应该是唯一的键来标识该用户 - 最容易使用的用户是对象中的用户_id,因为它应该是MongoDb生成的唯一用户。类似地,使用该密钥和护照的回调函数调用deserializeUser,但这次我们必须获取该密钥并将用户完整对象返回到回调。要进行查询搜索Mongo _id,您必须创建const ObjectID = require('mongodb').ObjectID;
,然后使用它调用new ObjectID(THE_ID)
。一定要将MongoDB添加为依赖项。您可以在以下示例中看到: passport.serializeUser((user,done)=> { - done(null,user._id); - });
passport.deserializeUser((id,done)=> { - db.collection( '用户')。findOne( - {_id:new ObjectID(id)}, - (错误,doc)=> { - 完成(null,doc); - } - ); - });注意:这个deserializeUser将抛出一个错误,直到我们在下一步中设置数据库,因此注释掉整个块并在函数deserializeUser中调用
done(null, null)
。当您认为自己已经做对时,请提交您的页面。 passport.serializeUser( OURFUNCTION )
和 passport.deserializeUser( OURFUNCTION )
两个方法。
+
+code>serializeUser方法接收两个参数,分别是表示用户的对象和一个回调函数。其中,回调函数的返回值应为这个用户的唯一标识符:最简单的写法就是让它返回用户的_id
,这个_id
属性是 MongoDB 为用户创建的唯一字段。类似地,反序列化也接收两个参数,分别是在序列化时生成的标识符以及一个回调函数。在回调函数里,我们需要根据根据传入的标识符(比如 _id)返回表示用户的对象。为了在 MongoDB 中通过 query(查询语句)获取 _id
字段,首先我们需要引入 MongoDB 的ObjectID
方法:const ObjectID = require('mongodb').ObjectID;
;然后调用它:new ObjectID(THE_ID)
。当然,这一切的前提都是先引入 MongoDB 作为依赖。你可以在下面的例子中看到:
+
+```js
+passport.serializeUser((user, done) => {
+ done(null, user._id);
+});
+
+passport.deserializeUser((id, done) => {
+ myDataBase.findOne({ _id: new ObjectID(id) }, (err, doc) => {
+ done(null, null);
+ });
+});
+```
+
+注意:在完全配置好 MongoDB 前,deserializeUser
会抛出错误。因此,现在请先注释掉上面的代码,在 deserializeUser
中仅调用 done(null, null)
即可。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+serializeUser
方法。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /passport.serializeUser/gi, 'You should have created your passport.serializeUser function'); assert.match(data, /null,\s*user._id/gi, 'There should be a callback in your serializeUser with (null, user._id)'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 应存在正确的 deserializeUser
方法。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /passport.deserializeUser/gi, 'You should have created your passport.deserializeUser function'); assert.match(data, /null,\s*null/gi, 'There should be a callback in your deserializeUser with (null, null) for now'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: MongoDB 应作为项目的依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'mongodb', 'Your project should list "mongodb" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- - text: Mongodb正确要求包括ObjectId
- testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require.*("|')mongodb("|')/gi, 'You should have required mongodb'); assert.match(data, /new ObjectID.*id/gi, 'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'); }, xhr => { throw new Error(xhr.statusText); })
-
+ - text: 注释掉的代码中应包含 ObjectId
。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require.*("|')mongodb\1/gi, 'You should have required mongodb'); assert.match(data, /new ObjectID.*id/gi, 'Even though the block is commented out, you should use new ObjectID(id) for when we add the database'); }, xhr => { throw new Error(xhr.statusText); })
```
"pug": "^0.1.0"
现在告诉Node / Express使用模板引擎,你必须告诉你的快递应用程序 将 'pug' 设置为'view-engine'。 app.set('view engine', 'pug')
最后,你应该改变请求您响应该指数路线res.render
与路径视图意见/哈巴狗/ index.pug。如果一切按计划进行,您应该刷新应用程序主页并看到一条小消息,说您已成功从我们的Pug文件中删除Pug!当您认为自己已经做对时,请提交您的页面。 "pug": "^0.1.0"
。注意,依赖的名称和版本号都要添加。
+
+为了在 Express 中使用 pug 作为模版引擎,你需要在 express 中将 app 的 "view-engine" 设置为 "pug",就像这样:app.set('view engine', 'pug')
。
+
+如果没有正确的 render 'views/pug' 路径下的 index 文件,页面将不会被加载。
+
+最后, 你需要修改 res.render()
方法,设置 /
的响应。res.render()
方法接收一个文件路径作为参数,这个路径既可以是相对路径(相对于 views),也可以是绝对路径。而且,我们不需要给它添加文件扩展名(文件后缀名)。
+
+如果一切顺利,刷新一下应用的主页就不会看到 "Pug template is not defined." 的报错了,而是会看到 Pug 成功加载的提示。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+"passport": "^0.3.2"
此外,现在还要将Express-session添加为依赖项。 Express-session拥有大量可以使用的高级功能,但现在我们只是要使用基础知识! "express-session": "^1.15.0"
您需要立即设置会话设置并初始化Passport。一定要先创建变量'session'和'passport',分别要求'express-session'和'passport'。要设置您要使用的快速应用程序使用会话,我们将仅定义几个基本选项。请务必将“SESSION_SECRET”添加到.env文件中,并为其提供随机值。这用于计算用于加密cookie的哈希值! app.use(会话({ - secret:process.env.SESSION_SECRET, - resave:是的, - saveUninitialized:true, -}));您也可以继续告诉您的快递应用程序使用 'passport.initialize()'和'passport.session()'。 (例如,
app.use(passport.initialize());
)当您认为自己正确时,请提交您的页面。如果您遇到错误,可以在这里查看到目前为止完成的项目。 "passport": "^0.3.2"
+
+此外,还需要添加 express-session 作为依赖,就像这样:"express-session": "^1.15.0"
。express-session 有许多高级特性,但我们暂时只需要了解其基础功能。
+
+现在,我们需要配置 session 并初始化 Passport。请先创建变量 session
和 passport
来引入 express-session 和 passport。
+
+为了让 express 应用可以使用 session,我们需要添加一些基础选项。请在 .env 文件中添加字段 'SESSION_SECRET',并给它赋一个随机值,便于加密 cookie、计算哈希。
+
+```js
+app.use(session({
+ secret: process.env.SESSION_SECRET,
+ resave: true,
+ saveUninitialized: true,
+ cookie: { secure: false }
+}));
+```
+
+还有,我们需要让 express 使用 passport.initialize()
和 passport.session()
。为此,你可以这样写:app.use(passport.initialize());
。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+const io = require('socket.io')(http);
需要处理的第一件事是从客户端侦听新连接。 on关键字就是这样 - 监听特定事件。它需要2个参数:一个包含所发出事件标题的字符串,以及一个用于传递数据的函数。在我们的连接侦听器的情况下,我们使用socket来定义第二个参数中的数据。套接字是连接的个人客户端。要在我们的服务器上侦听连接,请在项目中的注释之间添加以下内容: io.on('connection',socket => { - console.log('用户已连接'); -});现在,对于客户端进行连接,您只需要将以下内容添加到client.js中,该客户端经过身份验证后由页面加载:
/ * global io * / -var socket = io();注释会抑制您通常会看到的错误,因为文件中未定义“io”。我们已经在chat.pug页面上的Socket.IO库中添加了一个可靠的CDN。现在尝试加载您的应用并进行身份验证,您应该在服务器控制台中看到“用户已连接”! 注意
io()
仅在连接到同一URL /服务器上托管的套接字时起作用。要连接到其他地方托管的外部套接字,您可以使用io.connect('URL');
。当您认为自己已经做对时,请提交您的页面。 chat.pug
文件。首先,你需要在你的 routes.js
文件中为 /chat
添加一个处理 GET 请求的路由,并给它传入 ensureAuthenticated
。在回调函数中,我们需要让它 render chat.pug
文件,并在响应中包含 { user: req.user }
信息。现在,请修改 /auth/github/callback
路由,让它可以像这样设置 user_id:req.session.user_id = req.user.id
,并在设置完成后重定向至 /chat
。
+
+我们还需要添加 html
和 socket.io
两个依赖项,并且像这样引入:
+
+```javascript
+const http = require('http').createServer(app);
+const io = require('socket.io')(http);
+```
+
+现在我们的 _express_ 应用已经包含了 _http_ 服务,接下来我们需要监听 _http_ 服务的事件。为此,我们需要把 app.listen
更新为 http.listen
。
+
+我们需要处理的第一件事是监听从客户端发出的连接事件,我们可以调用 on 方法来监听具体的事件。它接收两个参数:一个是发出的事件的标题字符串,另一个是后续用来传递数据的回调函数。在这个回调函数中,我们用 socket 来代表它所包含的数据。简单来说,socket 就是指已连接到服务器的客户端。
+
+为了可以监听服务器的连接事件,我们在数据库连接的部分加入如下代码:
+
+```javascript
+io.on('connection', socket => {
+ console.log('A user has connected');
+});
+```
+
+对于发出连接事件的客户端,只需要在 client.js
中添加以下内容:
+
+```js
+/*global io*/
+let socket = io();
+```
+
+注意,这个 client.js
文件是在用户通过验证后才加载到客户端的。在这个文件中,我们没有定义 io 变量,但第一行的注释会阻止运行时产生的报错。不过,我们在 chat.pug 的页面上已经为你添加好了 Socket.IO 库的 CDN。
+
+现在你可以重启一下你的 app,尝试一下验证用户,然后你应该会看到服务器的 console 里输出了 'A user has connected'。
+
+注意:只有在连接到处于同一个 url/server 上的 socket 时,io()
才可以正常执行。如果需要连接到外部的 socket,就需要这样调用:io.connect('URL');
。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+http
,并示例化为 http
。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /http.*=.*require.*('|")http\1/gi, 'Your project should list "html" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 应正确引入 socket.io
,并示例化为 io
。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js').then(data => {assert.match(data, /io.*=.*require.*('|")socket.io\1.*http/gi, 'You should correctly require and instantiate socket.io as io.');}, xhr => { throw new Error(xhr.statusText); })
+ - text: Socket.IO 应监听连接。
+ testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /io.on.*('|")connection\1.*socket/gi, 'io should listen for "connection" and socket should be the 2nd arguments variable'); }, xhr => { throw new Error(xhr.statusText); })
+ - text: 客户端应连接到服务器。
testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.*=.*io/gi, 'Your client should be connection to server with the connection defined as socket'); }, xhr => { throw new Error(xhr.statusText); })
-
```
#{variable_name}
与元素上的其他文本内联,或者在元素上使用相等的一侧而不使用空格(例如p= variable_name
来设置该p= variable_name
。 p元素文本等于变量。我们强烈建议在他们的Githubs自述文件中查看 Pug的语法和结构。 Pug就是使用空格和制表符来显示嵌套元素,并减少制作漂亮网站所需的代码量。查看项目中包含的我们的pug文件'index.pug',我们使用变量title和message要从我们的服务器单独传递它们,您需要将一个对象作为第二个参数添加到res.render中 ,并带有变量和他们的价值。例如,传递此对象以设置索引视图的变量: {title: 'Hello', message: 'Please login'
它应该看起来像: res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});
现在刷新页面,您应该在视图中呈现的那些值在index.pug文件中列出的正确位置!当您认为自己已经做对时,请提交您的页面。 #{variable_name}
来实现行内调用,或像 p= variable_name
把元素与变量直接写在一起,这表示 p 元素的内容等价于这个变量。
+
+建议大家在 Pug 的 README 里看看它的语法和用法,这样你写出的代码会相对简练。另外要注意,Pug 使用缩进来表示嵌套的代码块。
+
+在 pug 的 'index.pug' 文件中,我们使用了 title 和 message 两个变量。
+
+为了从服务器传递这些信息,你需要给 res.render 的第二个参数传入一个对象,其中包含变量对应的值。比如,如果你想传递对象 {title: 'Hello', message: 'Please login'
到你的主页,那么应该这样写:
+
+res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});
+
+刷新页面,如果页面中数据显示正确,你就可以提交你的页面了。
+
+完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 这里 的答案。
+
+