MongoDB聊天集合查询:获取最近联系人、最后消息及未读消息数
问题描述
我的MongoDB chats集合结构如下:
{"_id":"654f4d7cbb027c35a0b90529","msg":"test message","createdat":"2023-11-11T09:46:41.872Z","dateseen":"2023-11-11T09:55:56.596Z","fromid":"eb78a1f79938f5eb193b63fc13b3250","toid":"7b987d98718041abb3b9a85b5c96214a","deleted1":false,"deleted1at":null,"deleted2":false,"deleted2at":null,"type":1}
需要编写聚合查询,传入指定userID后,获取该用户的最近聊天联系人、最后一条消息以及未读消息数量。期望输出格式如下:
{"body":[{"id":"c5e8b02a94984fe38e7ff35131890b55","unSeenMsgCount":0,"lastMessage":{"fromId":"eb78a1f74f274353b63fc13b3250","toId":"c5e8b02a94984fe7ff35131890b55","message":"string","dateSent":1704889808607,"type":1}}],"header":null}
我尝试了以下聚合代码,但存在未读消息统计错误、同一联系人生成两条聊天记录的问题,请求修正:
db.collection.aggregate([ {"$match":{"$or":[{"fromid":userID,"deleted1":false},{"toid":userID,"deleted2":false}]}}, {"$group":{ "_id":{"$cond":{ "if":{"$or":[ {"$and":[{"$eq":["$fromid",userID]},{"$ne":["$toid",userID]}]}, {"$and":[{"$eq":["$toid",userID]},{"$ne":["$fromid",userID]}]} ]}, "then":"$toid","else":"$fromid" }}, "lastMessage":{"$last":{"$cond":{ "if":{"$or":[ {"$and":[{"$eq":["$toid",userID]},{"$ne":["$fromid",userID]}]}, {"$and":[{"$eq":["$fromid",userID]},{"$ne":["$toid",userID]}]} ]}, "then":"$$ROOT","else":null }}}, "unseenCount":{"$sum":{"$cond":{ "if":{"$and":[{"$eq":["$toid",userID]},{"$eq":["$dateseen",null]}]}, "then":1,"else":0 }}} }}, {"$sort":{"lastMessage.createdat":-1}}, {"$replaceRoot":{"newRoot":{"$mergeObjects":["$lastMessage",{"unSeenMsgCount":"$unseenCount"}]}}}, {"$skip":offset},{"$limit":limit} ])
修正后的聚合查询
db.chats.aggregate([ // 过滤当前用户相关且未被自己删除的消息 { "$match": { "$or": [ {"fromid": userID, "deleted1": false}, {"toid": userID, "deleted2": false} ] } }, // 提前计算对方ID和未读标记 { "$addFields": { "peerId": { "$cond": [{"$eq": ["$fromid", userID]}, "$toid", "$fromid"] }, "isUnseen": { "$and": [ {"$eq": ["$toid", userID]}, {"$eq": ["$dateseen", null]} ] } } }, // 按联系人分组,取最新消息、统计未读数量 { "$group": { "_id": "$peerId", "lastMessage": {"$max": {"date": "$createdat", "doc": "$$ROOT"}}, "unSeenMsgCount": {"$sum": {"$cond": ["$isUnseen", 1, 0]}} } }, // 提取最新消息的原始文档 { "$addFields": { "lastMessage": "$lastMessage.doc" } }, // 按消息时间倒序排序 { "$sort": {"lastMessage.createdat": -1} }, // 映射为期望的输出字段格式 { "$project": { "_id": 0, "id": "$_id", "unSeenMsgCount": "$unSeenMsgCount", "lastMessage": { "fromId": "$lastMessage.fromid", "toId": "$lastMessage.toid", "message": "$lastMessage.msg", "dateSent": {"$toLong": {"$toDate": "$lastMessage.createdat"}}, "type": "$lastMessage.type" } } }, // 分页处理 {"$skip": offset}, {"$limit": limit}, // 包裹成指定的输出结构 { "$group": { "_id": null, "body": {"$push": "$$ROOT"} } }, { "$project": { "_id": 0, "body": 1, "header": null } } ])
修正说明
- 解决联系人重复问题:通过
$addFields提前计算统一的peerId(对方用户ID),确保每个联系人只会生成一条分组记录,避免原逻辑中冗余条件导致的分组错误。 - 修正未读消息统计:新增
isUnseen字段明确标记未读消息(当前用户为接收方且dateseen为空),统计时直接累加该标记,避免原逻辑的判断遗漏。 - 准确获取最新消息:改用
$max结合消息时间戳获取最新消息,比依赖聚合顺序的$last更可靠,确保拿到的是时间最晚的消息。 - 对齐输出格式:通过
$project严格映射字段名(如msg转message、createdat转时间戳dateSent),最后通过分组包裹成body和header的指定结构。
内容的提问来源于stack exchange,提问作者Mevo
相关产品推荐
相关产品推荐

