“真实世界”全栈开发-3.7-用户公开信息API
在本教程的第二部分,我们定义的“用户公开信息”就是除去用户邮箱、密码及当前JWT令牌的其它信息:
{
"profile": {
"username": "jake",
"bio": "I work at statefarm",
"image": "https://static.productionready.io/images/smiley-cyrus.jpg",
"following": false
}
}
从数据库读取用户的公开信息
打开models/User.js
,添加返回用户公开信息的方法:
// +++
UserSchema.methods.toProfileJSONFor = function () {
return {
username: this.username,
bio: this.bio,
image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg',
following: false // “关注”功能将在之后的章节里实现
};
};
// +++
mongoose.model('User', UserSchema);
因为我们还没有实现“关注”用户的功能,所以following
的值现在固定为false
。
如果用户没有设定头像(从数据库返回的this.image
的值为null
),我们也为image
设定一个默认URL。
数据库那的任务解决了,现在我们来实现属于服务器的部分。就同之前的功能一样,这部分可以分为三大块:端点(已经在教程的第二部分规定好)、中间件(待写)以及路由(待写)。
负责用户公开信息的路由
我们先来定义负责用户公开信息的路由。它负责将端点映射到对应的中间件。
新建routes/api/profiles.js
文件,写入:
const router = require('express').Router();
const mongoose = require('mongoose');
const User = mongoose.modle('User');
const auth = require('../auth');
module.exports = router;
接下来,按老规则,我们要把上面创建的router
登记到API主路由上,好让后端应用使用。这个router
的根URL将是/profiles
,符合的端点在API设计里有三个,这一讲我们只实现其中“获取用户公开信息”的功能,把“关注用户”和“取消关注用户”的功能留给后面的章节。
打开routes/api/index.js
,加入如下的一行代码:
router.use('/users', require('./users'));
// +++
router.use('/profiles', require('./profiles'));
// +++
路由对象创建好了,剩下要作的就是定义相关中间件并且把它们与端点联接起来。
我们需要在URL中指定用户名,好从数据库中获取该用户的公开信息。Express的参数中间件能够截取URL中的参数,以供后续的中间件使用。所谓参数中间件,就是在req
、res
、next
之后还有其它参数,这些参数都是从URL中抽取出来的。
往routes/api/profiles.js
里添加:
// +++
router.param('username', (req, res, next, username) => {
User.findOne({username: username}).then(user => {
if (!user)
return res.status(404).json({errors: {username: `no such username: ${username}`}});
res.locals.profile = user;
return next();
}).catch(next);
});
// +++
module.exports = router
当该路由对象处理的URL模板里含有:username
(冒号之后的与上面第一个字符串参数一致)时,上面的中间件就会利用该用户名从数据库获取用户对象,并存到res.locals.profile
。
实现并装配获取用户公开信息的中间件
往routes/api/profiles.js
继续加入:
// +++
router.get('/:username', auth.optional, (req, res, next) => {
return res.json({profile: res.locals.profile.toProfileJSONFor()});
});
// +++
module.exports = router;
当后端收到方法为GET
、URL形如/api/profiles/xxxxx
的请求时,前面定义的参数中间件会首先被调用,抽取出用户名为xxxxx
,并以此获取对应的用户对象(如果用户名存在的话),存在res.locals.profile
里,然后才依次调用后面的两个中间件。最后的那个中间件调用该用户对象的toProfileJSONFor
方法,把它的公开信息返回给前端。
为以后做准备
不过,做软件开发必须要多看一步。等到后面实现“关注、取消关注”的功能时,toProfileJSONFor
还需要考察当前登录的用户为谁,以此来判断following
的值到底是true
还是false
。这就意味着这个方法至少要接受一个参数(当前的用户对象)。
我们在models/User.js
里做如下更改:
// ***
UserSchema.methods.toProfileJSONFor = function (user) {
// ***
我们这里仅仅改变该方法的签名,方法体的改变留到实现“关注、取消关注”的时候。
路由方面,如果获取用户公开信息的请求里含有令牌,Passport会把当前用户的ID解析成req.payload.id
。有了ID(或者没有),我们可以用loadCurrentUser
来载入当前用户,并把它传给更改后的profile.toProfileJSONFor
。如果令牌不存在,就传入null
,返回的following
则为false
。
将上面的路由做如下更改:
// ***
const loadCurrentUser = require('./user').loadCurrentUser;
router.get('/:username', auth.optional, loadCurrentUser, (req, res, next) => {
const user = req.locals.profile || null;
return res.json({profile: req.locals.profile.toProfileJSONFor(user)});
});
// ***
module.exports = router;
上面的代码再次体现了Express中间件的灵活性。
到此为止,我们就实现了获取用户公开信息的API。