Lesson-1 关注与粉丝需求分析
细化关注与粉丝功能点
- 关注、取消关注
- 获取关注人、粉丝列表(用户-用户多对多关系)
Lesson-2 关注与粉丝的 schema 设计
操作步骤
- 分析关注与粉丝的数据结构
- 设计关注与粉丝 schema
分析关注与粉丝的数据结构
因为 mongoose 有限制,假设用户是一个大V,拥有百万级粉丝,那么对应用户某个属性中存储一百万粉丝是不合理,因为粉丝字段将超过 4M ,在 mongoose 中会认为是设计不合理的,所以不能直接把粉丝做在 Schema 中,但是关注可以,因为每个用户最多只能关注一千人,这个数量并没多少,而粉丝只需要查看一下所有用户的关注者里是否有关注该用户即可
设计关注与粉丝 schema
// models/users.js
const userSchema = new Schema({
__v: { type: Number, select: false },
name: { type: String, required: true },
password: { type: String, required: true, select: false },
avatar_url: { type: String }, // 用户头像
gender: { type: String, enum: ['male', 'female'], default: 'male', required: true }, // enum 可枚举,性别
headline: { type: String }, // 一句话简介
locations: { type: [{ type: String }], select: false }, // 可枚举的字符串数组,居住地
business: { type: String, select: false }, // 公司
employments: { // 职业经历
type: [{
company: { type: String },
job: { type: String }
}],
select: false
},
educations: { // 教育经历
type: [{
school: { type: String },
major: { type: String },
diploma: { type: Number, enum: [1, 2, 3, 4, 5] }, // 文凭:初中,高中,大专,本科,本科以上
entrance_year: { type: Number },
graduation_year: { type: Number }
}],
select: false
},
following: { // 关注的人
type: [{
type: Schema.Types.ObjectId, // 用户ID,这里属于特殊类型,必须用Schema提供的类型
ref: 'User' // 引用 User = require('../models/users') 数据库模型
}],
select: false
}
});
Lesson-3&4 RESTful 风格的关注与粉丝接口
操作步骤
- 实现获取关注人和粉丝列表接口
- 实现关注和取消关注接口
- 使用 Postman 测试
实现获取关注人和粉丝列表接口 & 关注和取消关注
实现一个关注方法,需要用到 populate 来实现关联(填充),否则就需要使用关注对象的id再去查询一次以获取关注对象的信息,这样效率太低(从理解上就是相当于遍历了两次数据库)。根据 RESTFul api最佳实践,获取关注列表是通过该用户id+方法名的方式,关注某人是使用put方法,以 following+id 的方式来进行关注别人。取消关注跟关注实际为同一个做法,只是把following中的schema对象从数组中移除
这里再简单补充一下 populate,原本我们保存的是一个__id,但是对到一些场景中,我们希望拿到的不是单纯的id,而是对应id的内容,那么这个时候就需要使用填充,在查询的时候,将这个id替换为对应内容,这就是populate的作用,实在还是不懂那就只能找篇文章看看了Mongoose中文文档-指南之填充
// routes/users.js
// 获取关注列表
router.get('/:id/following', listFollowing);
// 获取粉丝
router.get('/:id/followers', listFollowers)
// 关注某人
router.put('/following/:id', auth, follow); // 这里需要用到当前用户的信息,也就是token,所以需要加auth中间件
// 取消关注某人
router.put('/unfollowing/:id', auth, unfollow);
// controllers/users.js
async listFollowing (ctx) {
const user = await User.findById(ctx.params.id).select('+following').populate('following');
if(!user) ctx.throw(404);
ctx.body = user.following;
}
async listFollowers (ctx) {
const users = await User.find({ following: ctx.params.id }); // 查找following包含自己id的用户
ctx.body = users;
}
async follow (ctx) {
const me = await User.findById(ctx.state.user._id).select('+following');
// me 会拿到当前用户信息,拿到后再去查询一下是否关注的对象是否已经在关注数组里,如果没有就添加进关注数组里并保存
// 否则关注者的数组里会存在多个已关注对象
// 由于following数组里保存的是 schema 对象,所以需要使用将其变换成字符串来查重
if(!me.following.map(id => id.toString()).includes(ctx.params.id)) {
me.following.push(ctx.params.id);
me.save();
}
ctx.status = 204;
}
async unfollow (ctx) {
const me = await User.findById(ctx.state.user._id).select('+following');
const index = me.following.map(id => id.toString()).indexOf(ctx.params.id);
if(index > -1) {
me.following.splice(index, 1);
me.save();
}
ctx.status = 204;
}
使用 Postman 测试
Lesson-5 编写校验用户存在与否的中间件
操作步骤
- 编写校验用户存在与否的中间件
- 使用 Postman 测试接口(感觉放测试图也没什么用,后面除非必要,否则就不要这项了)
原因:如果直接调取接口,去关注与取消一个不存在的用户肯定是需要拦截并报错的。做成中间件的原因是因为可复用,没什么特别强悍的理由,就酱紫
// routes/users.js
// 关注某人
router.put('/following/:id', auth, checkUserExist, follow);
// 取消关注某人
router.put('/unfollowing/:id', auth, checkUserExist, unfollow);
async checkUserExist (ctx, next) {
const user = await User.findById(ctx.params.id);
if(!user) ctx.throw(404, '用户不存在');
await next();
}