12.4 消息与关注接口
消息中心的接口大多数是对已有集合的数据查询,但是需要添加一个消息的已读和未读逻辑。为了维护用户的消息状态,我们还要再新建一个消息集合。消息集合的字段如下:
- user_id:接受消息的用户 ID。
- source_id:评论或收藏或关注的 ID。
- type:消息类型,1 表示评论,2 表示收藏和赞,3 表示关注。
- status:状态,0 表示未读,1 表示已读。
- created_at:创建时间。
新建文件 model/messages.js 文件,将上面的字段编写为模型,代码如下:
// model/messages.js
const mongoose = require("mongoose");
const { ObjectId } = mongoose.Types;
const messagesSchema = new mongoose.Schema({
user_id: { type: ObjectId, required: true },
source_id: { type: ObjectId, required: true },
type: { type: Number, enum: [1, 2, 3], required: true },
status: { type: Number, enum: [0, 1], default: 0, required: true },
created_at: { type: Date, default: Date.now },
});
const Model = mongoose.model("messages", messagesSchema);
12.4.1 消息列表接口
继续创建 router/messages.js 文件并编写路由代码,然后写一个获取消息列表的接口。
router.get("/lists", async (req, res, next) => {
let { user_id } = req.query;
try {
let result = await MessModel.aggregate([
{ $match: { user_id: ObjectId(user_id), status: 0 } },
{
$group: {
_id: "$type",
count: {
$sum: 1,
},
},
},
]);
let rsinfo = Object.fromEntries(
result.map((json) => ["type" + json._id, json.count])
);
let resjson = {
comment: rsinfo["type1"] || 0,
praise: rsinfo["type2"] || 0,
follow: rsinfo["type3"] || 0,
total: result.reduce((a, b) => a.count + b.count),
};
res.send(resjson);
} catch (err) {
next(err);
}
});
上面代码中首次使用了 $group 管道阶段,主要用于分组统计查询。我们要分别统计评论、点赞和关注的未读数量,因此要分组统计。统计后再经过处理,将其组合为一个对象返回。
在路由配置中注册该路由如下:
// config/router.js
const messRouter = require('../router/messages.js')
const router = (app) => {
...
app.use('/messages', messRouter)
};
然后我们测试该接口,运行结果如图所示:
图中的返回结果,准确统计了评论、点赞、关注的未读数量以及总的未读数量,满足我们展示消息的要求。
有了消息列表,还需要创建消息的接口吗?其实不需要,只要我们在创建评论、创建点赞/收藏、添加关注的接口中添加一下 MessModel.create() 方法,在这些接口调用成功后自动创建消息即可。
比如在创建评论之后插入消息,我们修改 router/comments.js 中的创建数据路由如下:
router.post('/create', async (req, res, next) => {
let body = req.body
try {
let result = await CommsModel.create(body)
await MessModel.create({
source_id: result._id,
type: 1,
user_id: body.target_user,
})
res.send(result)
} catch (err) {
...
}
})
12.4.2 关注与取消关注接口
消息中心还有个关注者列表的展示,并且用户可以快速关注粉丝,页面如图所示:
因此我们需要一套用户关注的逻辑,这需要一个新的集合,不过结构简单。创建 model/follows.js 文件,编写关注集合的模型如下:
const { ObjectId } = mongoose.Types;
const followsSchema = new mongoose.Schema({
user_id: { type: ObjectId, required: true }, // 用户 ID
fans_id: { type: ObjectId, required: true }, // 粉丝 ID
created_at: { type: Date, default: Date.now },
});
const Model = mongoose.model("follows", followsSchema);
只需要三个字段,用户 ID、粉丝 ID 和创建时间。然后编写路由文件 router/follows.js,在这里编写关注和取消关注的接口。
和点赞与取消点赞的逻辑类似,关注也不存在一个人对另一个人关注两次的情况,因此这里也非常适合将关注与取消关注放在一个接口里实现,根据查询结果自动执行创建或删除。
创建路由名为 “/toggle”,方法为 POST,代码如下:
// router/follows.js
var FollowsModel = require("../model/follows");
var MessModel = require("../model/messages");
router.post("/toggle", async (req, res, next) => {
let body = req.body;
try {
let { user_id, fans_id } = body;
if (!user_id || !fans_id) {
return res.status(400).send({ message: "参数缺失" });
}
let action = "delete";
let result = await FollowsModel.findOneAndDelete(body);
if (!result) {
action = "create";
result = await FollowsModel.create(body);
await MessModel.create({
source_id: result._id,
type: 3,
user_id,
});
}
res.send({
action,
message: action == "create" ? "关注成功" : "取消关注成功",
});
} catch (err) {
next(err);
}
});
代码中首先尝试删除操作(即取消关注),没有返回值表示没有找到数据,此时进入创建数据(关注)的逻辑。并且因为关注有消息通知,所以还要向消息集合添加数据。
接着我们注册该路由,关注和取消关注的接口就可以用了。
// config/router.js
const follRouter = require('../router/follows.js')
const router = app => {
...
app.use('/follows', follRouter)
}
测试下执行结果,首先添加一个关注,如图所示。
相同参数再调用一次,可以看到关注已取消,如图所示。
12.4.3 关注者列表接口
有了关注和取消关注的功能,现在就可以获取某个用户的粉丝列表了。创建列表路由,代码如下:
router.get('/lists', async (req, res, next) => {
let { user_id } = req.query
try {
let result = await FollowsModel.aggregate([
{
$match: {
user_id: ObjectId(user_id),
},
},
{
$lookup: {
from: 'users',
localField: 'fans_id',
foreignField: '_id',
as: 'fans_info',
},
},
{
$lookup: {
from: 'follows',
let: {
uid: '$user_id',
fid: '$fans_id',
},
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$user_id', '$$fid'] },
{ $eq: ['$fans_id', '$$uid'] },
],
},
},
},
],
as: 'is_follow',
},
},
{
$addFields: {
fans_info: {
$first: '$fans_info',
},
is_follow: {
$gt: [{ $size: '$is_follow' }, 0], // 判断数组长度大于0
},
},
},
])
res.send(result)
} catch (err) {
...
}
})
上面代码中,特别要介绍下第三条管道,它是 $lookup 管道操作符的高级用法,可以自定义关联筛选数据的细节,而不是默认将关联数据都列出来。新的两个属性含义如下:
- let:设置自定义变量。
- pipeline:设置关联查询时的管道操作细节。
我们将两个 follows 集合关联查询,使 user_id 与 fans_id 互相比较,从而查询出该粉丝是否被关注。测试列表接口,返回结果如下: