chore(learn): Applied MDX format to Chinese curriculum files (#40462)

This commit is contained in:
Randell Dawson
2020-12-16 00:37:30 -07:00
committed by GitHub
parent 873fce02a2
commit 9ce4a02a41
1665 changed files with 58741 additions and 88042 deletions

View File

@@ -1,16 +1,13 @@
---
id: 589fc832f9fc0f352b528e78
title: 宣布新用户
challengeType: 2
forumTopicId: 301546
title: 宣布新用户
---
## Description
# --description--
<section id='description'>
许多聊天室都有这个功能:所有已连接到服务器的在线用户都会看到有人加入或退出的提醒。我们已经写好了处理连接和断开事件的代码,只要对这个方法稍作修改就可以实现这个功能。在事件中,我们需要发送这三条信息:连接或断开的用户名、当前用户数量、事件类型(即需要知道用户究竟是连接还是断开)。
请将事件名称更改为 <code>'user'</code>,其中应包含如下字段:'name'、'currentUsers'、'connected'(布尔值,连接上即为 <code>true</code>,断开则是 <code>false</code>)。记得要修改之前我们写好的处理 'user count' 的那部分代码,现在我们应在用户连接时传入布尔值 <code>true</code>;在用户断开连接是传入布尔值 <code>false</code>
许多聊天室都有这个功能:所有已连接到服务器的在线用户都会看到有人加入或退出的提醒。我们已经写好了处理连接和断开事件的代码,只要对这个方法稍作修改就可以实现这个功能。在事件中,我们需要发送这三条信息:连接或断开的用户名、当前用户数量、事件类型(即需要知道用户究竟是连接还是断开)。 请将事件名称更改为 `'user'`,其中应包含如下字段:'name'、'currentUsers'、'connected'(布尔值,连接上即为 `true`,断开则是 `false`)。记得要修改之前我们写好的处理 'user count' 的那部分代码,现在我们应在用户连接时传入布尔值 `true`;在用户断开连接是传入布尔值 `false`
```js
io.emit('user', {
@@ -20,8 +17,7 @@ io.emit('user', {
});
```
现在,我们的客户端已经有足够的信息显示现有用户数量和发送用户上下线通知。接下来我们需要在客户端监听 'user' 事件,然后使用 jQuery 把 <code>#num-users</code> 节点的文本内容更新为 '{NUMBER} users online'。同时,我们需要为 <code>&#60;ul&#62;</code> 添加一个 id 为 'messages' 且带有 '{NAME} has {joined/left} the chat.' 文本的<code>&#60;li&#62;</code>
一种实现方式如下:
现在,我们的客户端已经有足够的信息显示现有用户数量和发送用户上下线通知。接下来我们需要在客户端监听 'user' 事件,然后使用 jQuery 把 `#num-users` 节点的文本内容更新为 '{NUMBER} users online'。同时,我们需要为 `<ul>` 添加一个 id 为 'messages' 且带有 '{NAME} has {joined/left} the chat.' 文本的`<li>`。 一种实现方式如下:
```js
socket.on('user', data => {
@@ -33,46 +29,50 @@ socket.on('user', data => {
});
```
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/bf95a0f74b756cf0771cd62c087b8286) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: <code>'user'</code> 事件应发送包含 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: 客户端应处理和显示 <code>'user'</code> 对象中的信息
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
`'user'` 事件应发送包含 name、currentUsers、connected 字段的对象。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
客户端应处理和显示 `'user'` 对象中的信息
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,16 +1,15 @@
---
id: 5895f70df9fc0f352b528e68
title: 身份验证策略
challengeType: 2
forumTopicId: 301547
title: 身份验证策略
---
## Description
<section id='description'>
# --description--
现在,我们需要构建验证用户的策略,策略的选择有很多。比如,如果我们已经让用户在注册时填写了用户信息,那我们就可以基于这些信息验证;或者也可以引入第三方登录,如 Google 或者 Github。为此你可以参考 <a href='http://passportjs.org/'>Passports 中提供的策略插件</a>。对于这个项目的验证策略,我们会采用自己搭建的方式完成。
现在,我们需要构建验证用户的策略,策略的选择有很多。比如,如果我们已经让用户在注册时填写了用户信息,那我们就可以基于这些信息验证;或者也可以引入第三方登录,如 Google 或者 Github。为此你可以参考 [Passports 中提供的策略插件](http://passportjs.org/)。对于这个项目的验证策略,我们会采用自己搭建的方式完成。
首先,我们需要引入 <em>passport-local</em> 作为依赖,然后将它添加到服务器,就像这样:<code>const LocalStrategy = require('passport-local');</code>
首先,我们需要引入 *passport-local* 作为依赖,然后将它添加到服务器,就像这样:`const LocalStrategy = require('passport-local');`
然后,我们需要让 passport 使用实例化的本地策略对象。请注意,接下来的所有代码都应写在连接数据库的回调中,因为它们的执行都依赖数据库。
@@ -28,46 +27,56 @@ passport.use(new LocalStrategy(
));
```
这就是我们的用户验证逻辑:首先根据用户输入的用户名在数据库中寻找用户;然后检查密码是否匹配;最后如果没有发生错误,那么就会返回用户对象并通过验证。
我们也可以采用上面链接中提供的验证策略,一般来说,根据该策略仓库中的 README 来进行配置就足够了。一个很好的例子是 Github 策略,在该策略中,我们不需要写用户名或密码的相关验证逻辑,因为用户将被引导到 Github 页面进行验证。只要他们登录并同意Github 就会返回他们的个人信息供我们使用。
以上就是本次挑战的内容。在下一个挑战中,我们会基于表单数据调用上面写好的验证策略。
完成上述要求后,你可以在下方提交你的页面链接。
这就是我们的用户验证逻辑:首先根据用户输入的用户名在数据库中寻找用户;然后检查密码是否匹配;最后如果没有发生错误,那么就会返回用户对象并通过验证。 我们也可以采用上面链接中提供的验证策略,一般来说,根据该策略仓库中的 README 来进行配置就足够了。一个很好的例子是 Github 策略,在该策略中,我们不需要写用户名或密码的相关验证逻辑,因为用户将被引导到 Github 页面进行验证。只要他们登录并同意Github 就会返回他们的个人信息供我们使用。 以上就是本次挑战的内容。在下一个挑战中,我们会基于表单数据调用上面写好的验证策略。 完成上述要求后,你可以在下方提交你的页面链接。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 你的项目需要使用 <code>passport-local</code> 作为依赖。
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: 应正确地引入和设置 <code>passport-local</code>。
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
你的项目需要使用 `passport-local` 作为依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
应正确地引入和设置 `passport-local`
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,17 +1,15 @@
---
id: 589fc831f9fc0f352b528e77
title: 使用Socket.IO进行身份验证
challengeType: 2
forumTopicId: 301548
title: 使用Socket.IO进行身份验证
---
## Description
# --description--
<section id='description'>
目前,我们还无法确定连接到服务器的用户身份。虽然 `req.user` 包含用户信息,但这个只在用户直接与服务器交互(即不通过 web socket 访问服务器资源)时产生。当我们的用户通过 web socket 与服务器连接时,由于不存在 `req` 对象,因此我们无法获取用户数据。解决这个问题的方法之一是通过读取和解析请求中包含 passport session 的 cookie然后反序列化进而获取用户信息对象。幸运的是npm 上有可以让这个复杂的流程简单化的库。
目前,我们还无法确定连接到服务器的用户身份。虽然 <code>req.user</code> 包含用户信息,但这个只在用户直接与服务器交互(即不通过 web socket 访问服务器资源)时产生。当我们的用户通过 web socket 与服务器连接时,由于不存在 <code>req</code> 对象,因此我们无法获取用户数据。解决这个问题的方法之一是通过读取和解析请求中包含 passport session 的 cookie然后反序列化进而获取用户信息对象。幸运的是npm 上有可以让这个复杂的流程简单化的库。
添加 <code>passport.socketio</code><code>connect-mongo</code><code>cookie-parser</code> 作为依赖,把它们分别赋值给 <code>passportSocketIo</code><code>MongoStore</code><code>cookieParser</code>。同时,我们需要从之前引入的 <code>express-session</code> 中开辟新的内存空间,就像接下来这样:
添加 `passport.socketio``connect-mongo``cookie-parser` 作为依赖,把它们分别赋值给 `passportSocketIo``MongoStore``cookieParser`。同时,我们需要从之前引入的 `express-session` 中开辟新的内存空间,就像接下来这样:
```js
const MongoStore = require('connect-mongo')(session);
@@ -34,11 +32,11 @@ io.use(
);
```
记得要把 <code>key</code><code>store</code> 加到 app 的 <code>session</code> 中间件。这样SocketIO 才知道该对哪个 session 执行此配置。
记得要把 `key``store` 加到 app 的 `session` 中间件。这样SocketIO 才知道该对哪个 session 执行此配置。
<hr>
接下来,我们可以定义 <code>success</code><code>fail</code> 的回调函数:
接下来,我们可以定义 `success``fail` 的回调函数:
```js
function onAuthorizeSuccess(data, accept) {
@@ -54,7 +52,7 @@ function onAuthorizeFail(data, message, error, accept) {
}
```
现在,我们可以通过 socket 对象通过 <code>socket.request.user</code> 访问 <code>user</code> 对象。为此,你可以这样做:
现在,我们可以通过 socket 对象通过 `socket.request.user` 访问 `user` 对象。为此,你可以这样做:
```js
console.log('user ' + socket.request.user.name + ' connected');
@@ -62,51 +60,83 @@ console.log('user ' + socket.request.user.name + ' connected');
这样,我们可以在 console 里看到谁连接到了我们的服务器。
完成上述要求后,你可以在下方提交你的页面链接。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。 完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/1414cc9433044e306dd7fd0caa1c6254) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应添加 <code>passport.socketio</code> 作为依赖。
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: 应添加 <code>cookie-parser</code> 作为依赖。
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\(\s*\w+\.authorize\(/, 'You should register "passport.socketio" as socket.io middleware and provide it correct options'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应添加 `passport.socketio` 作为依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
应添加 `cookie-parser` 作为依赖。
```js
(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);
}
);
```
应正确引入 passportSocketIo。
```js
(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);
}
);
```
应正确配置 passportSocketIo。
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,16 +1,13 @@
---
id: 589690e6f9fc0f352b528e6e
title: 使用模块清理项目
challengeType: 2
forumTopicId: 301549
title: 使用模块清理项目
---
## Description
<section id='description'>
# --description--
目前,我们把所有的代码都放到了 server.js 文件里,这会导致代码难以维护,且扩展性差。
现在让我们来创建两个新文件:<code>routes.js</code><code>auth.js</code>
在每个文件的开头,我们都需要写上这段代码:
目前,我们把所有的代码都放到了 server.js 文件里,这会导致代码难以维护,且扩展性差。 现在让我们来创建两个新文件:`routes.js``auth.js` 在每个文件的开头,我们都需要写上这段代码:
```js
module.exports = function (app, myDataBase) {
@@ -18,51 +15,40 @@ module.exports = function (app, myDataBase) {
}
```
然后,在 server.js 文件的开头,我们需要像这样引入文件:<code>const routes = require('./routes.js');</code>
在成功连接数据库之后,我们需要像这样进行实例化:<code>routes(app, myDataBase)</code>
最后,我们需要把所有路由相关的代码从 server.js 移动到新文件 routes.js。不要忘了<code>ensureAuthenticated</code> 方法的定义也要移动到新文件中,这个是我们在之前的挑战中,为在路由中判断用户是否已登录创建的函数。然后,我们还需要在 routes.js 文件开头添加所需要的依赖,如:<code>const passport = require('passport');</code>
然后,在 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 中调用<code>auth(app,myDataBase)</code>。另外,由于我们的注册路由依赖 passport所以我们需要先调用<code>auth(app, myDataBase)</code>,再调用<code>routes(app, myDataBase)</code>
现在,我们来把 server.js 中与验证相关的代码分离到 auth.js 中,例如序列化,设置验证策略等。请正确添加依赖,并在 server.js 中调用`auth(app,myDataBase)`。另外,由于我们的注册路由依赖 passport所以我们需要先调用`auth(app, myDataBase)`,再调用`routes(app, myDataBase)`
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92) 的答案。
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 <a href='https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92' target='_blank'>here</a>.
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](https://gist.github.com/camperbot/2d06ac5c7d850d8cf073d2c2c794cc92).
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应正确引入新文件。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require\s*\(('|")\.\/routes(\.js)?\1\)/gi, 'You should have required your new files'); assert.match(data, /client.db[^]*routes/gi, 'Your new modules should be called after your connection to the database'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应正确引入新文件。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/require\s*\(('|")\.\/routes(\.js)?\1\)/gi,
'You should have required your new files'
);
assert.match(
data,
/client.db[^]*routes/gi,
'Your new modules should be called after your connection to the database'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
# --solutions--

View File

@@ -1,13 +1,11 @@
---
id: 589fc831f9fc0f352b528e75
title: 通过 Emitting 通信
challengeType: 2
forumTopicId: 301550
title: 通过 Emitting 通信
---
## Description
<section id='description'>
# --description--
<dfn>Emit</dfn> 是你会用到的,最常见的通信方式。如果我们从服务器发送信息给 'io',就相当于把事件的名称和数据发送给了所有处于连接状态的 socket。我们可以利用这个特性实现这样的功能只要有新用户连接到服务器我们就可以把目前连接的总用户数发给所有已连接的用户这样所有用户随时都可以看到实时的在线人数。
@@ -23,13 +21,13 @@ let currentUsers = 0;
++currentUsers;
```
最后在监听连接的地方发出emit该事件即可。这个事件应命名为 'user count',且数据应该为 <code>currentUsers</code>
最后在监听连接的地方发出emit该事件即可。这个事件应命名为 'user count',且数据应该为 `currentUsers`
```js
io.emit('user count', currentUsers);
```
接下来,我们还需要让客户端监听从服务端发出的事件。为此,我们还是需要用到 <em>on</em> 这个方法:
接下来,我们还需要让客户端监听从服务端发出的事件。为此,我们还是需要用到 *on* 这个方法:
```js
socket.on('user count', function(data) {
@@ -39,48 +37,63 @@ socket.on('user count', function(data) {
现在你可以尝试启动你的 app 并登录,你会看到在客户端的控制台打印出了 1这就表示目前连接到服务器的用户数为 1。你可以试着通过打开多个 app 来验证数量是否会增加。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/28ef7f1078f56eb48c7b1aeea35ba1f5) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应定义 currentUsers。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js').then(data => {assert.match(data, /currentUsers/gi, 'You should have variable currentUsers defined');}, xhr => { throw new Error(xhr.statusText); })
- text: 服务器应在有新的连接时 emit 当前用户数量。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /io.emit.*('|")user count('|").*currentUsers/gi, 'You should emit "user count" with data currentUsers'); }, xhr => { throw new Error(xhr.statusText); })
- text: 客户端应监听 'user count' 事件。
testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|")user count('|")/gi, 'Your client should be connection to server with the connection defined as socket'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应定义 currentUsers。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/currentUsers/gi,
'You should have variable currentUsers defined'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
服务器应在有新的连接时 emit 当前用户数量。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/io.emit.*('|")user count('|").*currentUsers/gi,
'You should emit "user count" with data currentUsers'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
客户端应监听 'user count' 事件。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/public/client.js').then(
(data) => {
assert.match(
data,
/socket.on.*('|")user count('|")/gi,
'Your client should be connection to server with the connection defined as socket'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,16 +1,15 @@
---
id: 5895f70df9fc0f352b528e6a
title: 创建新的中间件
challengeType: 2
forumTopicId: 301551
title: 创建新的中间件
---
## Description
<section id='description'>
# --description--
无论是否登录,或者哪怕用户试图访问其他页面,目前都会跳转到 <code>/profile</code>。为了解决这个问题,我们需要在 profile 页面渲染之前进行用户验证,创建中间件就可以实现这个功能。
无论是否登录,或者哪怕用户试图访问其他页面,目前都会跳转到 `/profile`。为了解决这个问题,我们需要在 profile 页面渲染之前进行用户验证,创建中间件就可以实现这个功能。
这个挑战的目标是创建<code>ensureAuthenticated(req, res, next)</code>中间件方法,通过在 <em>request</em> 上调用 passports 的<code>isAuthenticated</code> 方法,我们可以检查 <em>req.user</em> 是否定义,从而确定用户是否通过认证。如果用户已通过验证,就会调用 <em>next()</em>,否则我们应重定向到主页并让用户登录。该中间件的实现如下:
这个挑战的目标是创建`ensureAuthenticated(req, res, next)`中间件方法,通过在 *request* 上调用 passports 的`isAuthenticated` 方法,我们可以检查 *req.user* 是否定义,从而确定用户是否通过认证。如果用户已通过验证,就会调用 *next()*,否则我们应重定向到主页并让用户登录。该中间件的实现如下:
```js
function ensureAuthenticated(req, res, next) {
@@ -21,7 +20,7 @@ function ensureAuthenticated(req, res, next) {
};
```
然后,我们需要把 <em>ensureAuthenticated</em> 中间件添加到处理请求的回调之前:
然后,我们需要把 *ensureAuthenticated* 中间件添加到处理请求的回调之前:
```js
app
@@ -31,43 +30,50 @@ app
});
```
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ae49b8778cab87e93284a91343da0959) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: <code>ensureAuthenticated</code> 中间件应添加到 <code>/profile</code>路由中。
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: 如果没有通过验证,对 /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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
`ensureAuthenticated` 中间件应添加到 `/profile`路由中。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
如果没有通过验证,对 /profile 的 GET 请求应重定向到 /
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,13 +1,11 @@
---
id: 589fc831f9fc0f352b528e76
title: 处理连接断开
challengeType: 2
forumTopicId: 301552
title: 处理连接断开
---
## Description
<section id='description'>
# --description--
你也许注意到,目前为止我们只处理用户数量的增加,没有处理减少。事实上,处理用户断开连接也很简单。区别在于,新连接的监听是发生在整个服务器上,但连接断开的监听是发生在每个 socket 上。
@@ -21,48 +19,43 @@ socket.on('disconnect', () => {
为确保客户端可以看到实时的用户数量,显然,我们应该在用户断开时让 currentUsers 减 1然后发送 'user count' 事件,并使用修改后的用户数量。
<strong>注意:</strong><code>'disconnect'</code> 类似,所有 socket 可以发送到服务器的事件,我们都应该在有 'socket' 定义的连接监听器里处理。
**注意:**`'disconnect'` 类似,所有 socket 可以发送到服务器的事件,我们都应该在有 'socket' 定义的连接监听器里处理。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ab1007b76069884fb45b215d3c4496fa) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 服务器应处理断开 socket 连接的事件。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /socket.on.*('|")disconnect('|")/gi, ''); }, xhr => { throw new Error(xhr.statusText); })
- text: 客户端应监听 'user count' 事件。
testString: getUserInput => $.get(getUserInput('url')+ '/public/client.js') .then(data => { assert.match(data, /socket.on.*('|")user count('|")/gi, 'Your client should be connection to server with the connection defined as socket'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
服务器应处理断开 socket 连接的事件。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(data, /socket.on.*('|")disconnect('|")/gi, '');
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
客户端应监听 'user count' 事件。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/public/client.js').then(
(data) => {
assert.match(
data,
/socket.on.*('|")user count('|")/gi,
'Your client should be connection to server with the connection defined as socket'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,20 +1,19 @@
---
id: 58a25c98f9fc0f352b528e7f
title: 哈希密码
challengeType: 2
forumTopicId: 301553
title: 哈希密码
---
## Description
<section id='description'>
# --description--
回过头来看信息安全,你也许记得在数据库中存储明文密码是<em>绝对</em>禁止的。现在,我们需要引入 BCrypt 来解决这个问题。
回过头来看信息安全,你也许记得在数据库中存储明文密码是*绝对*禁止的。现在,我们需要引入 BCrypt 来解决这个问题。
添加 BCrypt 作为依赖,并通过<code>require</code>添加到服务器代码中。你需要在两个步骤中使用哈希运算:注册和保存新账户,以及登录时检查密码是否正确。
添加 BCrypt 作为依赖,并通过`require`添加到服务器代码中。你需要在两个步骤中使用哈希运算:注册和保存新账户,以及登录时检查密码是否正确。
目前处理注册的路由中,我们是这样把密码添加到数据库的:<code>password: req.body.password</code>。我们可以通过这段代码创建哈希值:<code>var hash = bcrypt.hashSync(req.body.password, 12);</code>,然后就可以把<code>passsword: req.body.password</code>替换为<code>password: hash</code>
目前处理注册的路由中,我们是这样把密码添加到数据库的:`password: req.body.password`。我们可以通过这段代码创建哈希值:`var hash = bcrypt.hashSync(req.body.password, 12);`,然后就可以把`passsword: req.body.password`替换为`password: hash`
最后,在验证逻辑中,我们已经有这样一段代码执行检查:<code>if (password !== user.password) { return done(null, false); }</code>。但我们现在存储的密码<code>user.password</code>已经是哈希值了。由于目前的检测机制是密码不匹配时就返回未认证,因此修改后,用于比对用户密码哈希值的代码应该是这样:
最后,在验证逻辑中,我们已经有这样一段代码执行检查:`if (password !== user.password) { return done(null, false); }`。但我们现在存储的密码`user.password`已经是哈希值了。由于目前的检测机制是密码不匹配时就返回未认证,因此修改后,用于比对用户密码哈希值的代码应该是这样:
```js
if (!bcrypt.compareSync(password, user.password)) {
@@ -24,43 +23,56 @@ if (!bcrypt.compareSync(password, user.password)) {
当你需要存储密码时,这样做可以有效地提升网站的安全性。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/dc16cca09daea4d4151a9c36a1fab564) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应存在 BCrypt 依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'bcrypt', 'Your project should list "bcrypt" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: BCrypt 应正确地引入和调用。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require.*("|')bcrypt\1/gi, 'You should have required bcrypt'); assert.match(data, /bcrypt.hashSync/gi, 'You should use hash the password in the registration'); assert.match(data, /bcrypt.compareSync/gi, 'You should compare the password to the hash in your strategy'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应存在 BCrypt 依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'bcrypt',
'Your project should list "bcrypt" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
BCrypt 应正确地引入和调用。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/require.*("|')bcrypt\1/gi,
'You should have required bcrypt'
);
assert.match(
data,
/bcrypt.hashSync/gi,
'You should use hash the password in the registration'
);
assert.match(
data,
/bcrypt.compareSync/gi,
'You should compare the password to the hash in your strategy'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,54 +1,39 @@
---
id: 5895f70ef9fc0f352b528e6b
title: 如何将 Profile 放在一起
challengeType: 2
forumTopicId: 301554
title: 如何将 Profile 放在一起
---
## Description
<section id='description'>
# --description--
现在,只有通过验证的用户才能进入 <em>/profile</em> 页面,这样我们就可以在页面上使用 'req.user' 里的信息了。
现在,只有通过验证的用户才能进入 */profile* 页面,这样我们就可以在页面上使用 'req.user' 里的信息了。
请在变量中包含 <em>username</em> 键,值为 'req.user.username',并通过 render 方法传给 profile 页面。然后在 'profile.pug' 页面,添加这行 <code>h2.center#welcome Welcome, #{username}!</code> 代码来创建 class 为 <code>center</code>、id 为 <code>welcome</code> 且文本内容为 'Welcome, ' 后加用户名的 h2 元素。
请在变量中包含 *username* 键,值为 'req.user.username',并通过 render 方法传给 profile 页面。然后在 'profile.pug' 页面,添加这行 `h2.center#welcome Welcome, #{username}!` 代码来创建 class 为 `center`、id 为 `welcome` 且文本内容为 'Welcome, ' 后加用户名的 h2 元素。
以及,请在 profile 里添加 <em>/logout</em> 链接,后续会用于处理用户退出登录的逻辑:<code>a(href='/logout') Logout</code>
以及,请在 profile 里添加 */logout* 链接,后续会用于处理用户退出登录的逻辑:`a(href='/logout') Logout`
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/136b3ad611cc80b41cab6f74bb460f6a) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应在 Pug render 中给 /profile 传一个变量。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /username:( |)req.user.username/gi, 'You should be passing the variable username with req.user.username into the render function of the profile page'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应在 Pug render 中给 /profile 传一个变量。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/username:( |)req.user.username/gi,
'You should be passing the variable username with req.user.username into the render function of the profile page'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
# --solutions--

View File

@@ -1,60 +1,71 @@
---
id: 5895f70df9fc0f352b528e69
title: 如何使用 Passport 策略
challengeType: 2
forumTopicId: 301555
title: 如何使用 Passport 策略
---
## Description
<section id='description'>
# --description--
在提供的 index.pug 文件里有一个登录表单。因为这个表单中存在行内 JavaScript 代码 <code>if showLogin</code>,因此它是隐藏的。因为 showLogin 未定义,所以表单不会渲染。如果在该页面的 <code>res.render()</code> 里添加一个包含 <code>showLogin: true</code> 的对象,你就可以在刷新页面后看到表单。当你点击 login 时,表单会向服务器的 <em>/login</code> 发送 POST 请求,此时服务器端就可以接受 POST 请求信息并进行用户验证。
在提供的 index.pug 文件里有一个登录表单。因为这个表单中存在行内 JavaScript 代码 `if showLogin`,因此它是隐藏的。因为 showLogin 未定义,所以表单不会渲染。如果在该页面的 `res.render()` 里添加一个包含 `showLogin: true` 的对象,你就可以在刷新页面后看到表单。当你点击 login 时,表单会向服务器的 */login 发送 POST 请求,此时服务器端就可以接受 POST 请求信息并进行用户验证。*
在这个挑战中,你需要为 POST 请求添加路由<code>/login</code>。为了用这个路由进行验证,你需要添加一个中间件,中间件应作为参数添加到用于处理请求的回调函数 <code>function(req,res)</code> 之前。对于 passport 的验证中间件,应这样调用:<code>passport.authenticate('local')</code>
在这个挑战中,你需要为 POST 请求添加路由`/login`。为了用这个路由进行验证,你需要添加一个中间件,中间件应作为参数添加到用于处理请求的回调函数 `function(req,res)` 之前。对于 passport 的验证中间件,应这样调用:`passport.authenticate('local')`
<em>passport.authenticate</em> 也接收选项作为参数,这些选项用于设置验证,例如<code>{ failureRedirect: '/' }</code>就很有用,请记得添加到你的代码中。如果中间件验证通过,我们就应该提供相应的后续处理。在这个挑战中,我们需要让用户重定到 <em>/profile</em>,这样 <code>profile.pug</code> 页面就会渲染。
*passport.authenticate* 也接收选项作为参数,这些选项用于设置验证,例如`{ failureRedirect: '/' }`就很有用,请记得添加到你的代码中。如果中间件验证通过,我们就应该提供相应的后续处理。在这个挑战中,我们需要让用户重定到 */profile*,这样 `profile.pug` 页面就会渲染。
如果验证通过,用户对象将会储存到 <em>req.user</em> 中。
如果验证通过,用户对象将会储存到 *req.user* 中。
这时,如果你在表单里输入了用户名和密码,路由将会重定向到主页 <em>/</em>,在服务端将会打印 'User {USERNAME} attempted to log in.',由于现在我们还没有实现注册功能,因此所有登录尝试都会失败。
这时,如果你在表单里输入了用户名和密码,路由将会重定向到主页 */*,在服务端将会打印 'User {USERNAME} attempted to log in.',由于现在我们还没有实现注册功能,因此所有登录尝试都会失败。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/7ad011ac54612ad53188b500c5e99cb9) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: server.js 中应正确执行所有步骤。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /showLogin:( |)true/gi, 'You should be passing the variable "showLogin" as true to your render function for the homepage'); assert.match(data, /failureRedirect:( |)('|")\/('|")/gi, 'Your code should include a failureRedirect to the "/" route'); assert.match(data, /login[^]*post[^]*local/gi, 'You should have a route for login which accepts a POST and passport.authenticates local'); }, xhr => { throw new Error(xhr.statusText); })
- text: 到 /login 的 POST 请求应重定向到 /
testString: getUserInput => $.post(getUserInput('url')+ '/login') .then(data => { assert.match(data, /Looks like this page is being rendered from Pug into HTML!/gi, 'A login attempt at this point should redirect to the homepage since we do not have any registered users'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
server.js 中应正确执行所有步骤。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/showLogin:( |)true/gi,
'You should be passing the variable "showLogin" as true to your render function for the homepage'
);
assert.match(
data,
/failureRedirect:( |)('|")\/('|")/gi,
'Your code should include a failureRedirect to the "/" route'
);
assert.match(
data,
/login[^]*post[^]*local/gi,
'You should have a route for login which accepts a POST and passport.authenticates local'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
到 /login 的 POST 请求应重定向到 /
```js
(getUserInput) =>
$.post(getUserInput('url') + '/login').then(
(data) => {
assert.match(
data,
/Looks like this page is being rendered from Pug into HTML!/gi,
'A login attempt at this point should redirect to the homepage since we do not have any registered users'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,19 +1,17 @@
---
id: 5895f70cf9fc0f352b528e67
title: 实现 Passport 用户的序列化
challengeType: 2
forumTopicId: 301556
title: 实现 Passport 用户的序列化
---
## Description
<section id='description'>
# --description--
截至目前,我们还没有配置完数据库,因此还无法加载用户数据。实现这个的方式很多,但对于我们的项目,一旦服务器启动,那么只要有 app 实例在运行,数据库就应一直处于连接状态。
为此,你需要在环境变量 <code>MONGO_URI</code> 中添加你的数据库地址(比如:<code>mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority</code>),我们会在 <em>connection.js</em> 中调用它。
为此,你需要在环境变量 `MONGO_URI` 中添加你的数据库地址(比如:`mongodb+srv://:@cluster0-jvwxi.mongodb.net/?retryWrites=true&w=majority`),我们会在 *connection.js* 中调用它。
_你可以在 <a href='https://www.mongodb.com/cloud/atlas' target='_blank'>MongoDB Atlas</a> 创建一个免费的数据库。_
*你可以在 [MongoDB Atlas](https://www.mongodb.com/cloud/atlas) 创建一个免费的数据库。*
在连接数据库之后,我们才能让服务器开始监听请求,这样做可以保证服务器在数据库连接前或数据库发生错误时不接受任何请求。为此,我们需要这样写:
@@ -41,48 +39,47 @@ myDB(async client => {
// app.listen out here...
```
记得要取消 deserializeUser 中 <code>myDataBase</code> 的注释,并把 <code>doc</code> 添加到 <code>done(null, null)</code>
记得要取消 deserializeUser 中 `myDataBase` 的注释,并把 `doc` 添加到 `done(null, null)`
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/175f2f585a2d8034044c7e8857d5add7) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- 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: 反序列化应正确使用,且应正确调用 <code>done(null, null)</code>。
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应存在数据库连接。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
反序列化应正确使用,且应正确调用 `done(null, null)`
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,16 +1,15 @@
---
id: 589a69f5f9fc0f352b528e71
title: 实现社交账号登陆 (2)
challengeType: 2
forumTopicId: 301557
title: 实现社交账号登陆 (2)
---
## Description
<section id='description'>
# --description--
设置 GitHub 验证的最后一步是创建策略本身。为此,你需要在 <code>auth.js</code><code>require</code>'passport-github',且实例化为 GithubStrategy<code>const GithubStrategy = require('passport-github').Strategy;</code>。别忘了在 <code>dotenv</code> 中修改环境变量。
设置 GitHub 验证的最后一步是创建策略本身。为此,你需要在 `auth.js``require`'passport-github',且实例化为 GithubStrategy`const GithubStrategy = require('passport-github').Strategy;`。别忘了在 `dotenv` 中修改环境变量。
为了设置 GitHub 策略,我们需要在 <b>passport</b> 中使用实例化的 <b>GithubStrategy</b>,它可以接收两个参数:一个对象(包括 <em>clientID</em>, <em>clientSecret</em><em>callbackURL</em>),以及一个回调函数。在这个回调函数中,我们要处理验证成功时,判断用户是否已经在数据库中存在的逻辑,还有如果数据库中不存在,把用户数据添加到数据库的代码。这种处理方式适用于绝大部分第三方验证策略,但有些策略会需要我们提供更多的信息,详情请参考相关策略的 README。例如Google 的验证策略会要求你提供一个 <em>scope</em>,用于标示用户成功登录后,你需要从返回的对象中获取那些信息。以及,这也需要经过用户同意,你才可以获取到。你可以在 <a href='https://github.com/jaredhanson/passport-github/' target='_blank'>这里</a> 了解当前我们使用的验证策略的用法,不过我们也会在这里进行详细讲解。
为了设置 GitHub 策略,我们需要在 **passport** 中使用实例化的 **GithubStrategy**,它可以接收两个参数:一个对象(包括 *clientID*, *clientSecret**callbackURL*),以及一个回调函数。在这个回调函数中,我们要处理验证成功时,判断用户是否已经在数据库中存在的逻辑,还有如果数据库中不存在,把用户数据添加到数据库的代码。这种处理方式适用于绝大部分第三方验证策略,但有些策略会需要我们提供更多的信息,详情请参考相关策略的 README。例如Google 的验证策略会要求你提供一个 *scope*,用于标示用户成功登录后,你需要从返回的对象中获取那些信息。以及,这也需要经过用户同意,你才可以获取到。你可以在 [这里](https://github.com/jaredhanson/passport-github/) 了解当前我们使用的验证策略的用法,不过我们也会在这里进行详细讲解。
你的新策略应该这样去实现:
@@ -29,45 +28,79 @@ passport.use(new GitHubStrategy({
目前,你的验证部分不会成功。由于没有数据库的逻辑和回调函数,你的代码目前还会报错。但如果你试一试,就可以在右边的控制台里看到输出了你的 GitHub 的个人信息。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/ff3a1166684c1b184709ac0bee30dee6) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应正确添加依赖 passport-github。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'passport-github', 'Your project should list "passport-github" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: 应正确引入依赖 passport-github。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/auth.js') .then(data => { assert.match(data, /require.*("|')passport-github("|')/gi, 'You should have required passport-github'); }, xhr => { throw new Error(xhr.statusText); })
- text: 到目前为止Github 策略应正确设置。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/auth.js') .then(data => { assert.match(data, /passport\.use.*new GitHubStrategy/gi, 'Passport should use a new GitHubStrategy'); assert.match(data, /callbackURL:\s*("|').*("|')/gi, 'You should have a callbackURL'); assert.match(data, /process.env.GITHUB_CLIENT_SECRET/g, 'You should use process.env.GITHUB_CLIENT_SECRET'); assert.match(data, /process.env.GITHUB_CLIENT_ID/g, 'You should use process.env.GITHUB_CLIENT_ID'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应正确添加依赖 passport-github。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'passport-github',
'Your project should list "passport-github" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
应正确引入依赖 passport-github。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/auth.js').then(
(data) => {
assert.match(
data,
/require.*("|')passport-github("|')/gi,
'You should have required passport-github'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
到目前为止Github 策略应正确设置。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/auth.js').then(
(data) => {
assert.match(
data,
/passport\.use.*new GitHubStrategy/gi,
'Passport should use a new GitHubStrategy'
);
assert.match(
data,
/callbackURL:\s*("|').*("|')/gi,
'You should have a callbackURL'
);
assert.match(
data,
/process.env.GITHUB_CLIENT_SECRET/g,
'You should use process.env.GITHUB_CLIENT_SECRET'
);
assert.match(
data,
/process.env.GITHUB_CLIENT_ID/g,
'You should use process.env.GITHUB_CLIENT_ID'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,14 +1,13 @@
---
id: 589a8eb3f9fc0f352b528e72
title: 实现社交账号登陆 (3)
challengeType: 2
forumTopicId: 301558
title: 实现社交账号登陆 (3)
---
## Description
<section id='description'>
# --description--
验证策略的最后一部分是处理从 GitHub 返回的个人信息。如果用户存在,我们就需要从数据库中读取用户数据并在 profile 页面加载否则我们需要把用户信息添加到数据库。GitHub 在用户信息中为我们提供了独一无二的 <em>id</em>,我们可以通过序列化的 id 在数据库中搜索用户(已实现)。以下是这个逻辑的实现示例,我们应该把它传到新策略的第二个参数,就是目前 <code>console.log(profile);</code> 的下方:
验证策略的最后一部分是处理从 GitHub 返回的个人信息。如果用户存在,我们就需要从数据库中读取用户数据并在 profile 页面加载否则我们需要把用户信息添加到数据库。GitHub 在用户信息中为我们提供了独一无二的 *id*,我们可以通过序列化的 id 在数据库中搜索用户(已实现)。以下是这个逻辑的实现示例,我们应该把它传到新策略的第二个参数,就是目前 `console.log(profile);` 的下方:
```js
myDataBase.findAndModify(
@@ -33,45 +32,36 @@ myDataBase.findAndModify(
);
```
<code>findAndModify</code> 的作用是在数据库中查询对象并更新,如果对象不存在,我们也可以 <code>upsert</code>upsert 可以理解为 update + insert然后我们可以在回调方法里获取到插入数据后的新对象。在这个例子中我们会把 last_login 设置成为 now而且总会为 login_count 加 1。只有在插入一个新对象新用户我们才会初始化这些字段。另外还需要注意默认值的使用。有时返回的用户信息可能不全可能是因为用户没有填写也可能是因为用户选择不公开一部分信息。在这种情况下我们需要进行相应的处理以防我们的 app 报错。
`findAndModify` 的作用是在数据库中查询对象并更新,如果对象不存在,我们也可以 `upsert`upsert 可以理解为 update + insert然后我们可以在回调方法里获取到插入数据后的新对象。在这个例子中我们会把 last_login 设置成为 now而且总会为 login_count 加 1。只有在插入一个新对象新用户我们才会初始化这些字段。另外还需要注意默认值的使用。有时返回的用户信息可能不全可能是因为用户没有填写也可能是因为用户选择不公开一部分信息。在这种情况下我们需要进行相应的处理以防我们的 app 报错。
你现在应该可以登录你的应用了,试试吧。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/183e968f0e01d81dde015d45ba9d2745) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: GitHub 策略应配置完成。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/auth.js') .then(data => { assert.match(data, /GitHubStrategy[^]*myDataBase/gi, 'Strategy should use now use the database to search for the user'); assert.match(data, /GitHubStrategy[^]*return cb/gi, 'Strategy should return the callback function "cb"'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
GitHub 策略应配置完成。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/auth.js').then(
(data) => {
assert.match(
data,
/GitHubStrategy[^]*myDataBase/gi,
'Strategy should use now use the database to search for the user'
);
assert.match(
data,
/GitHubStrategy[^]*return cb/gi,
'Strategy should return the callback function "cb"'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
# --solutions--

View File

@@ -1,18 +1,23 @@
---
id: 589a69f5f9fc0f352b528e70
title: 实现社交账号登陆 (1)
challengeType: 2
forumTopicId: 301559
title: 实现社交账号登陆 (1)
---
## Description
<section id='description'>
# --description--
第三方用户验证的实现逻辑如下:<ol><li>在用户点击按钮或者链接后,进入验证页面,通过第三方平台(如 GitHub来进行用户验证。</li><li>需要在路由中调用<code>passport.authenticate('github')</code>,跳转至 GitHub 验证页面。</li><li>页面跳转到 GitHub 上,如果用户未登录 GitHub就需要在这里进行登录。登录成功后会出现向用户申请访问权限的确认页。</li><li>如果用户同意访问,则用户会回到我们提供的回调地址,带着 GitHub 那边提供的用户信息回到我们的 app 中。</li><li>验证已完成。在我们的应用中,我们需要查询这个用户是否已经存在。如果是新用户,那我们需要把他的用户信息存储到数据库。</li></ol>
第三方用户验证的实现逻辑如下:
在 OAuth 验证策略中,我们至少需要提供 <em>Client ID</em><em>Client Secret</em>,这样第三方平台就会获悉验证请求的来源,以及这个来源是否有效。为此,需要去我们使用的第三方验证平台(如 GitHub获取这两个字段的值。注意,我们获取到的这个值是唯一的,且仅对我们的当前 app 有效——<b>因此,千万不要分享给别人</b>,更不要上传到公共仓库或者直接写在代码里。通常,我们会在 <em>.env</em> 文件里配置,并在 Node.js 里通过:<code>process.env.GITHUB_CLIENT_ID</code>获取。对于这次挑战,我们将会使用 GitHub 作为验证平台
1. 在用户点击按钮或者链接后,进入验证页面,通过第三方平台(如 GitHub来进行用户验证
2. 需要在路由中调用`passport.authenticate('github')`,跳转至 GitHub 验证页面。
3. 页面跳转到 GitHub 上,如果用户未登录 GitHub就需要在这里进行登录。登录成功后会出现向用户申请访问权限的确认页。
4. 如果用户同意访问,则用户会回到我们提供的回调地址,带着 GitHub 那边提供的用户信息回到我们的 app 中。
5. 验证已完成。在我们的应用中,我们需要查询这个用户是否已经存在。如果是新用户,那我们需要把他的用户信息存储到数据库。
首先,你需要进入账户设置里的 <a href='https://github.com/settings/developers'>Developer settings</a>板块,然后在 OAuth Apps 获取 <em>Client ID</em><em>Client Secret</em>。点击 'Register a new application',设置你的应用名称,然后把你的 glitch 主页地址(<b>注意,不是项目代码的地址</b>)粘贴到 Homepage URL。然后回调 url 需要设置成上面 Homepage URL 里你粘贴的地址,但后面要加上 '/auth/github/callback'。这样在用户通过 Github 验证后才能跳转到我们指定的页面。别忘了,我们还需要在 .env 文件里配置好 'GITHUB_CLIENT_ID' 和 'GITHUB_CLIENT_SECRET'
在 OAuth 验证策略中,我们至少需要提供 *Client ID**Client Secret*,这样第三方平台就会获悉验证请求的来源,以及这个来源是否有效。为此,需要去我们使用的第三方验证平台(比如 GitHub获取这两个字段的值。注意我们获取到的这个值是唯一的且仅对我们的当前 app 有效——**因此,千万不要分享给别人**,更不要上传到公共仓库或者直接写在代码里。通常,我们会在 *.env* 文件里配置,并在 Node.js 里通过:`process.env.GITHUB_CLIENT_ID`获取。对于这次挑战,我们将会使用 GitHub 作为验证平台
首先,你需要进入账户设置里的 [Developer settings](https://github.com/settings/developers)板块,然后在 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'。跳转的逻辑与上一个项目中的逻辑一样。
@@ -25,43 +30,45 @@ app.route('/login')
});
```
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 路由 /auth/github 应正确配置。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/routes.js') .then(data => { assert.match(data, /('|")\/auth\/github('|")[^]*get.*passport.authenticate.*github/gi, 'Route auth/github should only call passport.authenticate with github'); }, xhr => { throw new Error(xhr.statusText); })
- text: 路由 /auth/github/callback 应正确配置。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/routes.js') .then(data => { assert.match(data, /('|")\/auth\/github\/callback('|")[^]*get.*passport.authenticate.*github.*failureRedirect:( |)("|')\/\2/gi, 'Route auth/github/callback should accept a get request and call passport.authenticate for github with a failure redirect to home'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
路由 /auth/github 应正确配置。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/routes.js').then(
(data) => {
assert.match(
data,
/('|")\/auth\/github('|")[^]*get.*passport.authenticate.*github/gi,
'Route auth/github should only call passport.authenticate with github'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
路由 /auth/github/callback 应正确配置。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/routes.js').then(
(data) => {
assert.match(
data,
/('|")\/auth\/github\/callback('|")[^]*get.*passport.authenticate.*github.*failureRedirect:( |)("|')\/\2/gi,
'Route auth/github/callback should accept a get request and call passport.authenticate for github with a failure redirect to home'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,16 +1,15 @@
---
id: 58965611f9fc0f352b528e6c
title: 用户退出登录
challengeType: 2
forumTopicId: 301560
title: 用户退出登录
---
## Description
<section id='description'>
# --description--
创建退出登录的逻辑是比较简单的。只要用户尝试退出登录,路由就应重定向到主页,而不应该显示任何其他页面。
在 passport 里,只需要在重定向前调用 <code>req.logout();</code> 即可完成用户的退出登录。
在 passport 里,只需要在重定向前调用 `req.logout();` 即可完成用户的退出登录。
```js
app.route('/logout')
@@ -30,43 +29,45 @@ app.use((req, res, next) => {
});
```
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/c3eeb8a3ebf855e021fd0c044095a23b) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: <code>req.Logout</code> 应在 <code>/logout</code> 中调用。
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: 退出登录后应重定向到主页 /
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
`req.Logout` 应在 `/logout` 中调用。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
退出登录后应重定向到主页 /
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,18 +1,17 @@
---
id: 58966a17f9fc0f352b528e6d
title: 注册新用户
challengeType: 2
forumTopicId: 301561
title: 注册新用户
---
## Description
<section id='description'>
# --description--
现在我们需要为新用户添加注册帐号的功能,首先我们需要在主页的 res.render 接收的变量对象中添加 <code>showRegistration: true</code>。此时刷新页面,你会看到页面上已经显示了我们在 index.pug 文件中定义的注册表单。这个表单设置了请求路径 <em>/register</em>,并将请求方法设置成 <b>POST</b>,所以我们需要在服务器接受 <b>POST</b> 请求,且在数据库中创建用户对象。
现在我们需要为新用户添加注册帐号的功能,首先我们需要在主页的 res.render 接收的变量对象中添加 `showRegistration: true`。此时刷新页面,你会看到页面上已经显示了我们在 index.pug 文件中定义的注册表单。这个表单设置了请求路径 */register*,并将请求方法设置成 **POST**,所以我们需要在服务器接受 **POST** 请求,且在数据库中创建用户对象。
用户注册的逻辑如下:注册新用户 > 认证新用户 > 重定向到 /profile。
对于步骤一的注册新用户,详细逻辑如下:用 findOne 命令查询数据库 > 如果返回了用户对象,则表示用户存在,然后返回主页;如果用户未定义且没有报错,则会将包含用户名和密码的用户对象通过 <code>insertOne</code> 添加到数据库,只要没有报错则会继续下一步:认证新用户——我们已经在 /login 路由的 POST 请求中写好了这部分逻辑。
对于步骤一的注册新用户,详细逻辑如下:用 findOne 命令查询数据库 > 如果返回了用户对象,则表示用户存在,然后返回主页;如果用户未定义且没有报错,则会将包含用户名和密码的用户对象通过 `insertOne` 添加到数据库,只要没有报错则会继续下一步:认证新用户——我们已经在 /login 路由的 POST 请求中写好了这部分逻辑。
```js
app.route('/register')
@@ -47,105 +46,152 @@ app.route('/register')
);
```
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/b230a5b3bbc89b1fa0ce32a2aa7b083e) 的答案。
**注意:**接下来的挑战可能会在运行 _picture-in-picture_(画中画)模式的浏览器中出现问题。如果你使用的线上 IDE 提供在 IDE 内预览 app 的功能,请考虑打开新的标签页预览。
**注意:** 接下来的挑战可能会在运行 *picture-in-picture*(画中画)模式的浏览器中出现问题。如果你使用的线上 IDE 提供在 IDE 内预览 app 的功能,请考虑打开新的标签页预览。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 注册路由和显示主页。
testString: "getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /showRegistration:( |)true/gi, 'You should be passing the variable showRegistration as true to your render function for the homepage'); assert.match(data, /register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi, 'You should have a route accepted a post request on register that querys the db with findone and the query being username: req.body.username'); }, xhr => { throw new Error(xhr.statusText); })"
- text: 注册功能应可以正常运行。
testString: "async getUserInput => {
const url = getUserInput('url');
const user = `freeCodeCampTester${Date.now()}`;
const xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
test(this);
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttp.open('POST', url+'/register', true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send(`username=${user}&password=${user}`);
function test(xhttpRes) {
const data = xhttpRes.responseText;
assert.match(data, /Profile/gi, 'Register should work, and redirect successfully to the profile.');
}
}
"
- text: 登录功能应可以正常运行。
testString: "async getUserInput => {
const url = getUserInput('url');
const user = `freeCodeCampTester${Date.now()}`;
const xhttpReg = new XMLHttpRequest();
xhttpReg.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
login();
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttpReg.open('POST', url+'/register', true);
xhttpReg.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttpReg.send(`username=${user}&password=${user}`);
function login() {
const xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
test(this);
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttp.open('POST', url+'/login', true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send(`username=${user}&password=${user}`);
}
function test(xhttpRes) {
const data = xhttpRes.responseText;
assert.match(data, /Profile/gi, 'Login should work if previous test was done successfully and redirect successfully to the profile.');
assert.match(data, new RegExp(user, 'g'), 'The profile should properly display the welcome to the user logged in');
}
}
"
- text: 退出登录功能应可以正常运行。
testString: "getUserInput => $.ajax({url: getUserInput('url')+ '/logout', type: 'GET', xhrFields: { withCredentials: true }}) .then(data => { assert.match(data, /Home/gi, 'Logout should redirect to home'); }, xhr => { throw new Error(xhr.statusText); })"
- text: 退出登录后profile 页面应无法访问。
testString: "getUserInput => $.ajax({url: getUserInput('url')+ '/profile', type: 'GET', crossDomain: true, xhrFields: { withCredentials: true }}) .then(data => { assert.match(data, /Home/gi, 'Profile should redirect to home when we are logged out now again'); }, xhr => { throw new Error(xhr.statusText); })"
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
注册路由和显示主页。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/showRegistration:( |)true/gi,
'You should be passing the variable showRegistration as true to your render function for the homepage'
);
assert.match(
data,
/register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi,
'You should have a route accepted a post request on register that querys the db with findone and the query being username: req.body.username'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
注册功能应可以正常运行。
```js
async (getUserInput) => {
const url = getUserInput('url');
const user = `freeCodeCampTester${Date.now()}`;
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
test(this);
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttp.open('POST', url + '/register', true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send(`username=${user}&password=${user}`);
function test(xhttpRes) {
const data = xhttpRes.responseText;
assert.match(
data,
/Profile/gi,
'Register should work, and redirect successfully to the profile.'
);
}
};
```
登录功能应可以正常运行。
```js
async (getUserInput) => {
const url = getUserInput('url');
const user = `freeCodeCampTester${Date.now()}`;
const xhttpReg = new XMLHttpRequest();
xhttpReg.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
login();
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttpReg.open('POST', url + '/register', true);
xhttpReg.setRequestHeader(
'Content-type',
'application/x-www-form-urlencoded'
);
xhttpReg.send(`username=${user}&password=${user}`);
function login() {
const xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
test(this);
} else {
throw new Error(`${this.status} ${this.statusText}`);
}
};
xhttp.open('POST', url + '/login', true);
xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhttp.send(`username=${user}&password=${user}`);
}
function test(xhttpRes) {
const data = xhttpRes.responseText;
assert.match(
data,
/Profile/gi,
'Login should work if previous test was done successfully and redirect successfully to the profile.'
);
assert.match(
data,
new RegExp(user, 'g'),
'The profile should properly display the welcome to the user logged in'
);
}
};
```
退出登录功能应可以正常运行。
```js
(getUserInput) =>
$.ajax({
url: getUserInput('url') + '/logout',
type: 'GET',
xhrFields: { withCredentials: true }
}).then(
(data) => {
assert.match(data, /Home/gi, 'Logout should redirect to home');
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
退出登录后profile 页面应无法访问。
```js
(getUserInput) =>
$.ajax({
url: getUserInput('url') + '/profile',
type: 'GET',
crossDomain: true,
xhrFields: { withCredentials: true }
}).then(
(data) => {
assert.match(
data,
/Home/gi,
'Profile should redirect to home when we are logged out now again'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,13 +1,11 @@
---
id: 589fc832f9fc0f352b528e79
title: 发送和显示聊天消息
challengeType: 2
forumTopicId: 301562
title: 发送和显示聊天消息
---
## Description
<section id='description'>
# --description--
现在,我们可以开始实现聊天室功能了。整体逻辑很简单,只需要获取用户发给服务端的消息,再通过服务端给所有客户端发送信息就可以了。在 client.js 文件里,你应该已经注意到了这段提交表单的代码:
@@ -17,58 +15,57 @@ $('form').submit(function() {
});
```
现在我们需要处理事件的 emit它应该发生在定义 <code>messageToSend</code> 之后,以及清除 <code>#m</code> 中的文本之前。我们称这个事件叫 <code>'chat message'</code>,需发送的数据叫 <code>messageToSend</code>
现在我们需要处理事件的 emit它应该发生在定义 `messageToSend` 之后,以及清除 `#m` 中的文本之前。我们称这个事件叫 `'chat message'`,需发送的数据叫 `messageToSend`
```js
socket.emit('chat message', messageToSend);
```
在服务端,我们需要监听包含 <code>message</code><code>'chat message'</code> 事件。一旦事件发生,我们就通过<code>io.emit</code> 把包含 <code>name</code><code>message</code><code>'chat message'</code> 事件发送给所有已连接的 socket。
在服务端,我们需要监听包含 `message``'chat message'` 事件。一旦事件发生,我们就通过`io.emit` 把包含 `name``message``'chat message'` 事件发送给所有已连接的 socket。
回到客户端,我们需要监听 <code>'chat message'</code> 事件。只要接收到这个事件,就把包含名字和消息的内容(注意:需要在名字后添加冒号)通过 <code>&#60;li&#62;</code> 添加到 <code>#messages</code>
回到客户端,我们需要监听 `'chat message'` 事件。只要接收到这个事件,就把包含名字和消息的内容(注意:需要在名字后添加冒号)通过 `<li>` 添加到 `#messages`
至此,我们已经完成发送信息到所有客户端的功能。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/d7af9864375207e254f73262976d2016) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 服务端应监听 <code>'chat message'</code>,且应在监听到后 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: 客户端应正确处理和展示从 <code>'chat message'</code> 事件发来的新数据。
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); })'
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
服务端应监听 `'chat message'`,且应在监听到后 emit。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
客户端应正确处理和展示从 `'chat message'` 事件发来的新数据。
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,19 +1,17 @@
---
id: 5895f70cf9fc0f352b528e66
title: 用户对象的序列化
challengeType: 2
forumTopicId: 301563
title: 用户对象的序列化
---
## Description
# --description--
<section id='description'>
序列化和反序列化在身份认证中是很重要的概念。序列化一个对象就是将其内容转换成一个体积很小的 *key*,后续可以通过这个 *key* 反序列化为原始对象。这样,服务器就可以在用户未登录时识别用户,或者说给这个用户一个唯一标识,用户也不需要在每次访问不同页面时都给服务器发送用户名和密码。
序列化和反序列化在身份认证中是很重要的概念。序列化一个对象就是将其内容转换成一个体积很小的 <em>key</em>,后续可以通过这个 <em>key</em> 反序列化为原始对象。这样,服务器就可以在用户未登录时识别用户,或者说给这个用户一个唯一标识,用户也不需要在每次访问不同页面时都给服务器发送用户名和密码
我们需要用到序列化和反序列化的方法来进行配置。passport 为我们提供了 `passport.serializeUser( OURFUNCTION )``passport.deserializeUser( OURFUNCTION )` 两个方法
我们需要用到序列化和反序列化的方法来进行配置。passport 为我们提供了 <code>passport.serializeUser( OURFUNCTION )</code><code>passport.deserializeUser( OURFUNCTION )</code> 两个方法。
code>serializeUser</code>方法接收两个参数,分别是表示用户的对象和一个回调函数。其中,回调函数的返回值应为这个用户的唯一标识符:最简单的写法就是让它返回用户的<code>_id</code>,这个<code>_id</code>属性是 MongoDB 为用户创建的唯一字段。类似地,反序列化也接收两个参数,分别是在序列化时生成的标识符以及一个回调函数。在回调函数里,我们需要根据根据传入的标识符(比如 _id返回表示用户的对象。为了在 MongoDB 中通过 query查询语句获取 <code>_id</code> 字段,首先我们需要引入 MongoDB 的<code>ObjectID</code>方法:<code>const ObjectID = require('mongodb').ObjectID;</code>;然后调用它:<code>new ObjectID(THE_ID)</code>。当然,这一切的前提都是先引入 MongoDB 作为依赖。你可以在下面的例子中看到:
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) => {
@@ -27,52 +25,99 @@ passport.deserializeUser((id, done) => {
});
```
注意:在完全配置好 MongoDB 前,<code>deserializeUser</code> 会抛出错误。因此,现在请先注释掉上面的代码,在 <code>deserializeUser</code> 中仅调用 <code>done(null, null)</code> 即可。
注意:在完全配置好 MongoDB 前,`deserializeUser` 会抛出错误。因此,现在请先注释掉上面的代码,在 `deserializeUser` 中仅调用 `done(null, null)` 即可。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/7068a0d09e61ec7424572b366751f048) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应存在正确的 <code>serializeUser</code> 方法。
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: 应存在正确的 <code>deserializeUser</code> 方法。
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: 注释掉的代码中应包含 <code>ObjectId</code>。
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应存在正确的 `serializeUser` 方法。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(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);
}
);
```
</section>
应存在正确的 `deserializeUser` 方法。
```js
(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);
}
);
```
MongoDB 应作为项目的依赖。
```js
(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);
}
);
```
注释掉的代码中应包含 `ObjectId`
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,72 +1,102 @@
---
id: 5895f700f9fc0f352b528e63
title: 设置模板引擎
challengeType: 2
forumTopicId: 301564
title: 设置模板引擎
---
## Description
# --description--
<section id='description'>
请注意,本项目在 [这个 Repl.it 项目](https://repl.it/github/freeCodeCamp/boilerplate-mochachai) 的基础上进行开发。你也可以从 [GitHub](https://repl.it/github/freeCodeCamp/boilerplate-mochachai) 上克隆。
请注意,本项目在 <a href="https://repl.it/github/freeCodeCamp/boilerplate-mochachai">这个 Repl.it 项目</a> 的基础上进行开发。你也可以从 <a href='https://repl.it/github/freeCodeCamp/boilerplate-mochachai'>GitHub</a> 上克隆
你可以在应用的模版引擎中使用静态模板文件(如那些写在 *Pug* 里的)。在运行时,模版引擎会用服务端的真实数据替换掉模版文件中的变量,然后将模版转译成发送给客户端的 HTML 静态文件。这样可以轻松地构造 HTML 页面,允许在页面直接显示变量内容而不需要发送 API 请求
你可以在应用的模版引擎中使用静态模板文件(如那些写在 <em>Pug</em> 里的)。在运行时,模版引擎会用服务端的真实数据替换掉模版文件中的变量,然后将模版转译成发送给客户端的 HTML 静态文件。这样可以轻松地构造 HTML 页面,允许在页面直接显示变量内容而不需要发送 API 请求
为了在项目中使用 *Pug*,你需要在 package.json 中添加依赖 `"pug": "^0.1.0"`。注意,依赖的名称和版本号都要添加
为了在项目中使用 <em>Pug</em>,你需要在 package.json 中添加依赖 <code>"pug": "^0.1.0"</code>。注意,依赖的名称和版本号都要添加
为了在 Express 中使用 pug 作为模版引擎,你需要在 express 中将 **app** 的 "view-engine" 设置为 "pug",就像这样:`app.set('view engine', 'pug')`
为了在 Express 中使用 pug 作为模版引擎,你需要在 express 中将 <b>app</b> 的 "view-engine" 设置为 "pug",就像这样:<code>app.set('view engine', 'pug')</code>
如果没有正确的 *render* *'views/pug'* 路径下的 index 文件,页面将不会被加载
如果没有正确的 <em>render</em> <em>'views/pug'</em> 路径下的 index 文件,页面将不会被加载
最后, 你需要修改 <code>res.render()</code> 方法,设置 <code>/</code> 的响应。<code>res.render()</code> 方法接收一个文件路径作为参数,这个路径既可以是相对路径(相对于 views也可以是绝对路径。而且我们不需要给它添加文件扩展名文件后缀名
最后, 你需要修改 `res.render()` 方法,设置 `/` 的响应。`res.render()` 方法接收一个文件路径作为参数,这个路径既可以是相对路径(相对于 views也可以是绝对路径。而且我们不需要给它添加文件扩展名文件后缀名
如果一切顺利,刷新一下应用的主页就不会看到 "Pug template is not defined." 的报错了,而是会看到 Pug 成功加载的提示。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/3515cd676ea4dfceab4e322f59a37791) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 项目中应使用 Pug 作为依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'pug', 'Your project should list "pug" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: 项目中应使用 Pug 作为模版引擎。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /('|")view engine('|"),( |)('|")pug('|")/gi, 'Your project should set Pug as a view engine'); }, xhr => { throw new Error(xhr.statusText); })
- text: 在 Response 里使用正确的 ExpressJS 方法渲染主页面。
testString: getUserInput => $.get(getUserInput('url')+ '/') .then(data => { assert.match(data, /FCC Advanced Node and Express/gi, 'You successfully rendered the Pug template!'); }, xhr => { throw new Error(xhr.statusText); })
- text: Pug 应该生效。
testString: getUserInput => $.get(getUserInput('url')+ '/') .then(data => { assert.match(data, /pug-success-message/gi, 'Your projects home page should now be rendered by pug with the projects .pug file unaltered'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
项目中应使用 Pug 作为依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'pug',
'Your project should list "pug" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
项目中应使用 Pug 作为模版引擎。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/('|")view engine('|"),( |)('|")pug('|")/gi,
'Your project should set Pug as a view engine'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
在 Response 里使用正确的 ExpressJS 方法渲染主页面。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/').then(
(data) => {
assert.match(
data,
/FCC Advanced Node and Express/gi,
'You successfully rendered the Pug template!'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
Pug 应该生效。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/').then(
(data) => {
assert.match(
data,
/pug-success-message/gi,
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,20 +1,19 @@
---
id: 5895f70cf9fc0f352b528e65
title: 设置 Passport
challengeType: 2
forumTopicId: 301565
title: 设置 Passport
---
## Description
<section id='description'>
# --description--
现在我们来创建 <em>Passport</em>,最终我们需要用它来实现用户注册和登录。除了 Passport我们还会用 express-session 来处理 session会话。在客户端我们可以用这个中间件把 session id 储存到 cookie。同时我们可以在服务器上通过这个 id 访问 session 数据。通过这种方式,我们无需把用户的个人信息存到 cookie 来只完成用户的验证,只需要用这个 id 作为 <em>key</em> 来访问服务器上用户的数据即可。
现在我们来创建 *Passport*,最终我们需要用它来实现用户注册和登录。除了 Passport我们还会用 express-session 来处理 session会话。在客户端我们可以用这个中间件把 session id 储存到 cookie。同时我们可以在服务器上通过这个 id 访问 session 数据。通过这种方式,我们无需把用户的个人信息存到 cookie 来只完成用户的验证,只需要用这个 id 作为 *key* 来访问服务器上用户的数据即可。
为了在你的项目中使用 Passport首先你需要在 package.json 文件中添加依赖:<code>"passport": "^0.3.2"</code>
为了在你的项目中使用 Passport首先你需要在 package.json 文件中添加依赖:`"passport": "^0.3.2"`
此外,还需要添加 express-session 作为依赖,就像这样:<code>"express-session": "^1.15.0"</code>。express-session 有许多高级特性,但我们暂时只需要了解其基础功能。
此外,还需要添加 express-session 作为依赖,就像这样:`"express-session": "^1.15.0"`。express-session 有许多高级特性,但我们暂时只需要了解其基础功能。
现在,我们需要配置 session 并初始化 Passport。请先创建变量 <code>session</code><code>passport</code> 来引入 express-session 和 passport。
现在,我们需要配置 session 并初始化 Passport。请先创建变量 `session``passport` 来引入 express-session 和 passport。
为了让 express 应用可以使用 session我们需要添加一些基础选项。请在 .env 文件中添加字段 'SESSION_SECRET',并给它赋一个随机值,便于加密 cookie、计算哈希。
@@ -27,49 +26,99 @@ app.use(session({
}));
```
还有,我们需要让 express 使用 <code>passport.initialize()</code><code>passport.session()</code>。为此,你可以这样写:<code>app.use(passport.initialize());</code>
还有,我们需要让 express 使用 `passport.initialize()``passport.session()`。为此,你可以这样写:`app.use(passport.initialize());`
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/4068a7662a2f9f5d5011074397d6788c) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应添加 Passort 和 express-session 作为依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'passport', 'Your project should list "passport" as a dependency'); assert.property(packJson.dependencies, 'express-session', 'Your project should list "express-session" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: 依赖应正确引入。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /require.*("|')passport("|')/gi, 'You should have required passport'); assert.match(data, /require.*("|')express-session("|')/gi, 'You should have required express-session'); }, xhr => { throw new Error(xhr.statusText); })
- text: express 应调用 passport 的方法。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /passport.initialize/gi, 'Your express app should use "passport.initialize()"'); assert.match(data, /passport.session/gi, 'Your express app should use "passport.session()"'); }, xhr => { throw new Error(xhr.statusText); })
- text: 应正确设置 session 和 session secret。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /secret:( |)process.env.SESSION_SECRET/gi, 'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应添加 Passort 和 express-session 作为依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'passport',
'Your project should list "passport" as a dependency'
);
assert.property(
packJson.dependencies,
'express-session',
'Your project should list "express-session" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
依赖应正确引入。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/require.*("|')passport("|')/gi,
'You should have required passport'
);
assert.match(
data,
/require.*("|')express-session("|')/gi,
'You should have required express-session'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
express 应调用 passport 的方法。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/passport.initialize/gi,
'Your express app should use "passport.initialize()"'
);
assert.match(
data,
/passport.session/gi,
'Your express app should use "passport.session()"'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
应正确设置 session 和 session secret。
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/secret:( |)process.env.SESSION_SECRET/gi,
'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
# --solutions--

View File

@@ -1,26 +1,24 @@
---
id: 589fc830f9fc0f352b528e74
title: 设置环境
challengeType: 2
forumTopicId: 301566
title: 设置环境
---
## Description
# --description--
<section id='description'>
在接下来的挑战中,我们将会用到 `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`
在接下来的挑战中,我们将会用到 <code>chat.pug</code> 文件。首先,你需要在你的 <code>routes.js</code> 文件中为 <code>/chat</code> 添加一个处理 GET 请求的路由,并给它传入 <code>ensureAuthenticated</code>。在回调函数中,我们需要让它 render <code>chat.pug</code> 文件,并在响应中包含 <code>{ user: req.user }</code> 信息。现在,请修改 <code>/auth/github/callback</code> 路由,让它可以像这样设置 user_id<code>req.session.user_id = req.user.id</code>,并在设置完成后重定向至 <code>/chat</code>
我们还需要添加 <code>http</code><code>socket.io</code> 两个依赖项,并且像这样引入:
我们还需要添加 `http``socket.io` 两个依赖项,并且像这样引入:
```javascript
const http = require('http').createServer(app);
const io = require('socket.io')(http);
```
现在我们的 _express_ 应用已经包含了 _http_ 服务,接下来我们需要监听 _http_ 服务的事件。为此,我们需要把 <code>app.listen</code> 更新为 <code>http.listen</code>
现在我们的 *express* 应用已经包含了 *http* 服务,接下来我们需要监听 *http* 服务的事件。为此,我们需要把 `app.listen` 更新为 `http.listen`
我们需要处理的第一件事是监听从客户端发出的连接事件,我们可以调用 <dfn>on</dfn> 方法来监听具体的事件。它接收两个参数:一个是发出的事件的标题字符串,另一个是后续用来传递数据的回调函数。在这个回调函数中,我们用 <em>socket</em> 来代表它所包含的数据。简单来说socket 就是指已连接到服务器的客户端。
我们需要处理的第一件事是监听从客户端发出的连接事件,我们可以调用 <dfn>on</dfn> 方法来监听具体的事件。它接收两个参数:一个是发出的事件的标题字符串,另一个是后续用来传递数据的回调函数。在这个回调函数中,我们用 *socket* 来代表它所包含的数据。简单来说socket 就是指已连接到服务器的客户端。
为了可以监听服务器的连接事件,我们在数据库连接的部分加入如下代码:
@@ -30,65 +28,113 @@ io.on('connection', socket => {
});
```
对于发出连接事件的客户端,只需要在 <code>client.js</code> 中添加以下内容:
对于发出连接事件的客户端,只需要在 `client.js` 中添加以下内容:
```js
/*global io*/
let socket = io();
```
注意,这个 <code>client.js</code> 文件是在用户通过验证后才加载到客户端的。在这个文件中,我们没有定义 io 变量,但第一行的注释会阻止运行时产生的报错。不过,我们在 chat.pug 的页面上已经为你添加好了 Socket.IO 库的 CDN。
注意,这个 `client.js` 文件是在用户通过验证后才加载到客户端的。在这个文件中,我们没有定义 io 变量,但第一行的注释会阻止运行时产生的报错。不过,我们在 chat.pug 的页面上已经为你添加好了 Socket.IO 库的 CDN。
现在你可以重启一下你的 app尝试一下验证用户然后你应该会看到服务器的 console 里输出了 'A user has connected'。
<strong>注意:</strong>只有在连接到处于同一个 url/server 上的 socket 时,<code>io()</code>才可以正常执行。如果需要连接到外部的 socket就需要这样调用<code>io.connect('URL');</code>
**注意:** 只有在连接到处于同一个 url/server 上的 socket 时,`io()`才可以正常执行。如果需要连接到外部的 socket就需要这样调用`io.connect('URL');`
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/aae41cf59debc1a4755c9a00ee3859d1) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: 应添加 Socket.IO 作为依赖。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/package.json') .then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'socket.io', 'Your project should list "socket.io" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: 应正确引入 <code>http</code>,并示例化为 <code>http</code>。
testString: getUserInput => $.get(getUserInput('url')+ '/_api/server.js') .then(data => { assert.match(data, /http.*=.*require.*('|")http\1/gi, 'Your project should list "http" as a dependency'); }, xhr => { throw new Error(xhr.statusText); })
- text: 应正确引入 <code>socket.io</code>,并示例化为 <code>io</code>。
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); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
应添加 Socket.IO 作为依赖。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/_api/package.json').then(
(data) => {
var packJson = JSON.parse(data);
assert.property(
packJson.dependencies,
'socket.io',
'Your project should list "socket.io" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
应正确引入 `http`,并示例化为 `http`
```js
(getUserInput) =>
$.get(getUserInput('url') + '/_api/server.js').then(
(data) => {
assert.match(
data,
/http.*=.*require.*('|")http\1/gi,
'Your project should list "http" as a dependency'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
应正确引入 `socket.io`,并示例化为 `io`
```js
(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);
}
);
```
Socket.IO 应监听连接。
```js
(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);
}
);
```
客户端应连接到服务器。
```js
(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);
}
);
```
# --solutions--

View File

@@ -1,62 +1,47 @@
---
id: 5895f70bf9fc0f352b528e64
title: 使用模板引擎
challengeType: 2
forumTopicId: 301567
title: 使用模板引擎
---
## Description
<section id='description'>
# --description--
模版引擎最大的特点之一就是在 HTML 页面展示之前,可以从服务端传变量到模版文件。
在 Pug 文件中,你可以用变量名来调用变量,比如写成 <code>#{variable_name}</code> 来实现行内调用,或像 <code>p= variable_name</code> 把元素与变量直接写在一起,这表示 p 元素的内容等价于这个变量。
在 Pug 文件中,你可以用变量名来调用变量,比如写成 `#{variable_name}` 来实现行内调用,或像 `p= variable_name` 把元素与变量直接写在一起,这表示 p 元素的内容等价于这个变量。
建议大家在 <a href='https://github.com/pugjs/pug' target='_blank'>Pug 的 README</a> 里看看它的语法和用法这样你写出的代码会相对简练。另外要注意Pug 使用缩进来表示嵌套的代码块。
建议大家在 [Pug 的 README](https://github.com/pugjs/pug) 里看看它的语法和用法这样你写出的代码会相对简练。另外要注意Pug 使用缩进来表示嵌套的代码块。
在 pug 的 'index.pug' 文件中,我们使用了 <em>title</em><em>message</em> 两个变量。
在 pug 的 'index.pug' 文件中,我们使用了 *title**message* 两个变量。
为了从服务器传递这些信息,你需要给 <em>res.render</em> 的第二个参数传入一个对象,其中包含变量对应的值。比如,如果你想传递对象 <code>{title: 'Hello', message: 'Please login'</code> 到你的主页,那么应该这样写:
为了从服务器传递这些信息,你需要给 *res.render* 的第二个参数传入一个对象,其中包含变量对应的值。比如,如果你想传递对象 `{title: 'Hello', message: 'Please login'` 到你的主页,那么应该这样写:
<code>res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});</code>
`res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});`
刷新页面,如果页面中数据显示正确,你就可以提交你的页面了。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 <a href='https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871' target='_blank'>这里</a> 的答案。
完成上述要求后,你可以在下方提交你的页面链接。如果你遇到了问题,可以参考 [这里](https://gist.github.com/camperbot/4af125119ed36e6e6a8bb920db0c0871) 的答案。
</section>
# --hints--
## Instructions
<section id='instructions'>
</section>
## Tests
<section id='tests'>
```yml
tests:
- text: Pug 应正确地展示变量。
testString: getUserInput => $.get(getUserInput('url')+ '/') .then(data => { assert.match(data, /pug-variable("|')>Please login/gi, 'Your projects home page should now be rendered by pug with the projects .pug file unaltered'); }, xhr => { throw new Error(xhr.statusText); })
```
</section>
## Challenge Seed
<section id='challengeSeed'>
</section>
## Solution
<section id='solution'>
Pug 应正确地展示变量。
```js
/**
Backend challenges don't need solutions,
because they would need to be tested against a full working project.
Please check our contributing guidelines to learn more.
*/
(getUserInput) =>
$.get(getUserInput('url') + '/').then(
(data) => {
assert.match(
data,
/pug-variable("|')>Please login/gi,
'Your projects home page should now be rendered by pug with the projects .pug file unaltered'
);
},
(xhr) => {
throw new Error(xhr.statusText);
}
);
```
</section>
# --solutions--