对于对populate 有疑问并希望这样做的人:
- 用简单的文字和快速回复(气泡)聊天
- 4 个用于聊天的数据库集合:
clients、users、rooms、messasges。
- 相同的消息数据库结构适用于 3 种类型的发件人:机器人、用户和客户端
-
refPath 或 dynamic reference
-
populate 与 path 和 model 选项
- 使用
findOneAndReplace/replaceOne 和$exists
- 如果获取的文档不存在,则创建一个新文档
上下文
目标
- 将新的简单文本消息保存到数据库并使用用户或客户端数据填充它(2 个不同的模型)。
- 将新的 quickReplies 消息保存到数据库并使用用户或客户端数据填充它。
- 保存每封邮件的发件人类型:
clients、users & bot。
- 仅使用其 Mongoose 模型填充发件人为
clients 或 users 的邮件。 _sender 类型客户端模型是clients,对于用户是users。
消息架构:
const messageSchema = new Schema({
room: {
type: Schema.Types.ObjectId,
ref: 'rooms',
required: [true, `Room's id`]
},
sender: {
_id: { type: Schema.Types.Mixed },
type: {
type: String,
enum: ['clients', 'users', 'bot'],
required: [true, 'Only 3 options: clients, users or bot.']
}
},
timetoken: {
type: String,
required: [true, 'It has to be a Nanosecond-precision UTC string']
},
data: {
lang: String,
// Format samples on https://docs.chatfuel.com/api/json-api/json-api
type: {
text: String,
quickReplies: [
{
text: String,
// Blocks' ids.
goToBlocks: [String]
}
]
}
}
mongoose.model('messages', messageSchema);
解决方案
我的服务器端 API 请求
我的代码
实用函数(在chatUtils.js 文件上)获取您要保存的消息类型:
/**
* We filter what type of message is.
*
* @param {Object} message
* @returns {string} The type of message.
*/
const getMessageType = message => {
const { type } = message.data;
const text = 'text',
quickReplies = 'quickReplies';
if (type.hasOwnProperty(text)) return text;
else if (type.hasOwnProperty(quickReplies)) return quickReplies;
};
/**
* Get the Mongoose's Model of the message's sender. We use
* the sender type to find the Model.
*
* @param {Object} message - The message contains the sender type.
*/
const getSenderModel = message => {
switch (message.sender.type) {
case 'clients':
return 'clients';
case 'users':
return 'users';
default:
return null;
}
};
module.exports = {
getMessageType,
getSenderModel
};
我的服务器端(使用Nodejs)获取保存消息的请求:
app.post('/api/rooms/:roomId/messages/new', async (req, res) => {
const { roomId } = req.params;
const { sender, timetoken, data } = req.body;
const { uuid, state } = sender;
const { type } = state;
const { lang } = data;
// For more info about message structure, look up Message Schema.
let message = {
room: new ObjectId(roomId),
sender: {
_id: type === 'bot' ? null : new ObjectId(uuid),
type
},
timetoken,
data: {
lang,
type: {}
}
};
// ==========================================
// CONVERT THE MESSAGE
// ==========================================
// Convert the request to be able to save on the database.
switch (getMessageType(req.body)) {
case 'text':
message.data.type.text = data.type.text;
break;
case 'quickReplies':
// Save every quick reply from quickReplies[].
message.data.type.quickReplies = _.map(
data.type.quickReplies,
quickReply => {
const { text, goToBlocks } = quickReply;
return {
text,
goToBlocks
};
}
);
break;
default:
break;
}
// ==========================================
// SAVE THE MESSAGE
// ==========================================
/**
* We save the message on 2 ways:
* - we replace the message type `quickReplies` (if it already exists on database) with the new one.
* - else, we save the new message.
*/
try {
const options = {
// If the quickRepy message is found, we replace the whole document.
overwrite: true,
// If the quickRepy message isn't found, we create it.
upsert: true,
// Update validators validate the update operation against the model's schema.
runValidators: true,
// Return the document already updated.
new: true
};
Message.findOneAndUpdate(
{ room: roomId, 'data.type.quickReplies': { $exists: true } },
message,
options,
async (err, newMessage) => {
if (err) {
throw Error(err);
}
// Populate the new message already saved on the database.
Message.populate(
newMessage,
{
path: 'sender._id',
model: getSenderModel(newMessage)
},
(err, populatedMessage) => {
if (err) {
throw Error(err);
}
res.send(populatedMessage);
}
);
}
);
} catch (err) {
logger.error(
`#API Error on saving a new message on the database of roomId=${roomId}. ${err}`,
{ message: req.body }
);
// Bad Request
res.status(400).send(false);
}
});
提示:
对于数据库:
- 每条消息本身就是一个文档。
- 我们不使用
refPath,而是使用populate() 上使用的实用程序getSenderModel。这是因为机器人。 sender.type 可以是:users 有他的数据库,clients 有他的数据库,bot 没有数据库。 refPath 需要真正的模型引用,如果没有,Mongooose 会抛出错误。
-
sender._id 可以为用户和客户端输入 ObjectId,或者为机器人输入 null。
对于 API 请求逻辑:
- 我们替换了
quickReply 消息(消息数据库必须只有一个快速回复,但您需要多少简单的文本消息都可以)。我们使用findOneAndUpdate 而不是replaceOne 或findOneAndReplace。
- 我们执行查询操作(
findOneAndUpdate)和populate操作,每个操作都带有callback。如果您不知道是否使用 async/await、then()、exec() 或 callback(err, document),这一点很重要。如需更多信息,请查看Populate Doc。
- 我们将快速回复消息替换为
overwrite 选项且不带 $set 查询运算符。
- 如果我们没有找到快速回复,我们会创建一个新回复。您必须使用
upsert 选项告诉 Mongoose。
- 我们只填充一次,用于替换消息或新保存的消息。
- 我们返回回调,无论我们使用
findOneAndUpdate 和populate() 保存的消息是什么。
- 在
populate 中,我们使用getSenderModel 创建自定义动态模型引用。我们可以使用 Mongoose 动态引用,因为 bot 的 sender.type 没有任何 Mongoose 模型。我们使用 Populating Across Database 和 model 和 path optins。
我花了很多时间在这里和那里解决小问题,我希望这会对某人有所帮助! ?