|
|
@@ -199,7 +199,17 @@ async function flushMongoUserPrefBuffer() {
|
|
|
async function handleMessageEvent(eventData, eventType) {
|
|
|
const msgId = eventData.msgid;
|
|
|
const fcmId = eventData.fcmid;
|
|
|
- const eventTime = (0, dayjs_1.default)(eventData.t).toDate();
|
|
|
+ // Ensure 't' or 'create_at' is used for event time, default to current time
|
|
|
+ let eventTime;
|
|
|
+ if (eventData.t) {
|
|
|
+ eventTime = (0, dayjs_1.default)(eventData.t).toDate();
|
|
|
+ }
|
|
|
+ else if (eventData.create_at) {
|
|
|
+ eventTime = (0, dayjs_1.default)(eventData.create_at).toDate();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ eventTime = new Date();
|
|
|
+ }
|
|
|
const inForeground = eventData.inforeground === "true" || eventData.inforeground === true;
|
|
|
try {
|
|
|
let updateFields;
|
|
|
@@ -231,7 +241,7 @@ async function handleMessageEvent(eventData, eventType) {
|
|
|
query = { fcmReceipt: fcmId };
|
|
|
}
|
|
|
else {
|
|
|
- console.warn(`[Ingestor Service] Missing msgid or fcmid for event type: ${eventType}. Event: ${JSON.stringify(eventData)}`);
|
|
|
+ console.warn(`[MongoDB-MessageRecord] Missing msgid or fcmid for event type: ${eventType}. Event: ${JSON.stringify(eventData)}`);
|
|
|
return;
|
|
|
}
|
|
|
// Perform the update
|
|
|
@@ -241,7 +251,11 @@ async function handleMessageEvent(eventData, eventType) {
|
|
|
console.log(`[MongoDB-MessageRecord] Updated record for ${eventType} event. Matched: ${result.matchedCount}, Modified: ${result.modifiedCount}`);
|
|
|
}
|
|
|
else {
|
|
|
- console.warn(`[MongoDB-MessageRecord] No matching record found for ${eventType} event. Query: ${JSON.stringify(query)}`);
|
|
|
+ // 消息打开(message_open)可能比消息接收(message_receive)先到达,
|
|
|
+ // 对于message_open找不到记录是正常的,因为message_receive是创建记录的源头。
|
|
|
+ if (eventType === "message_receive") {
|
|
|
+ console.warn(`[MongoDB-MessageRecord] No matching record found for ${eventType} event. Query: ${JSON.stringify(query)}`);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
catch (error) {
|
|
|
@@ -264,6 +278,30 @@ async function processMessage(msg) {
|
|
|
amqpChannel.reject(msg, false); // Reject malformed message, do not re-queue
|
|
|
return;
|
|
|
}
|
|
|
+ // Determine event type field name based on project_id and event source
|
|
|
+ // Assuming 'type' for Android-like events, 'name' for iOS-like events,
|
|
|
+ const eventType = eventData.type || eventData.name;
|
|
|
+ // --- 1. Handle Message-Specific Events First ---
|
|
|
+ // 无论是 message_receive 还是 message_open,都需要先即时处理 MongoDB MessageRecord。
|
|
|
+ if (["message_receive", "message_open"].includes(eventType)) {
|
|
|
+ // Note: This function includes its own error handling and logging.
|
|
|
+ await handleMessageEvent(eventData, eventType);
|
|
|
+ // 【关键改动】对于 message_receive 事件:
|
|
|
+ // 1. 已在 handleMessageEvent 中处理完毕 MessageRecord。
|
|
|
+ // 2. 需求是不存入 ClickHouse,也不更新 User.lastActiveAt。
|
|
|
+ // 3. 因此,处理完毕后,直接 Acknowledge 消息并返回。
|
|
|
+ if (eventType === "message_receive") {
|
|
|
+ amqpChannel.ack(msg);
|
|
|
+ return; // 立即返回,跳过后续的 ClickHouse 和 User 更新逻辑
|
|
|
+ }
|
|
|
+ // message_open 将继续向下流转,以便被记录到 ClickHouse 和更新 User 表
|
|
|
+ }
|
|
|
+ // Filter by allowed event types (excluding message_receive now as it was handled above)
|
|
|
+ if (!ALLOWED_EVENT_TYPES.includes(eventType)) {
|
|
|
+ // console.log(`[Ingestor Service] Skipping event with unsupported event_type: ${eventType}`);
|
|
|
+ amqpChannel.ack(msg); // Acknowledge and drop unsupported events
|
|
|
+ return;
|
|
|
+ }
|
|
|
// 增加对 eventLog.duration 的校验
|
|
|
if (eventData.duration > 100000 || eventData.duration < 0) {
|
|
|
console.warn(`[Ingestor Service] Skipping event with invalid duration: ${eventData.duration}. Event: ${JSON.stringify(eventData)}`);
|
|
|
@@ -278,18 +316,6 @@ async function processMessage(msg) {
|
|
|
amqpChannel.ack(msg); // Acknowledge and drop unsupported events
|
|
|
return;
|
|
|
}
|
|
|
- // Determine event type field name based on project_id and event source
|
|
|
- // Assuming 'type' for Android-like events, 'name' for iOS-like events,
|
|
|
- const eventType = eventData.type || eventData.name;
|
|
|
- // --- 1. Handle Message-Specific Events First ---
|
|
|
- if (["message_receive", "message_open"].includes(eventType)) {
|
|
|
- await handleMessageEvent(eventData, eventType); // 移除 amqpChannel.ack(msg); 和 return; // 让事件继续向下流转,以便被记录到ClickHouse和更新User表
|
|
|
- } // Filter by allowed event types
|
|
|
- if (!ALLOWED_EVENT_TYPES.includes(eventType)) {
|
|
|
- // console.log(`[Ingestor Service] Skipping event with unsupported event_type: ${eventType}`);
|
|
|
- amqpChannel.ack(msg); // Acknowledge and drop unsupported events
|
|
|
- return;
|
|
|
- }
|
|
|
// Determine UID field name based on project_id
|
|
|
const uid = eventData.uid || eventData.user_id; // uid for Android, user_id for iOS
|
|
|
if (!uid) {
|
|
|
@@ -310,6 +336,7 @@ async function processMessage(msg) {
|
|
|
lastActiveAtDateObj = new Date();
|
|
|
}
|
|
|
// --- 2. Prepare Event Data for ClickHouse Batch ---
|
|
|
+ // Note: message_receive 已经被前面的逻辑过滤掉了,这里只处理需要存入 ClickHouse 的事件
|
|
|
const clickhouseEvent = {
|
|
|
log_id: eventData._id ? eventData._id.toString() : new mongoose_1.default.Types.ObjectId().toHexString(), // Use existing _id or generate new
|
|
|
uid: uid,
|
|
|
@@ -330,11 +357,9 @@ async function processMessage(msg) {
|
|
|
};
|
|
|
clickhouseEventsBuffer.push(clickhouseEvent);
|
|
|
// --- 3. Prepare User Data for MongoDB Batch Update ---
|
|
|
- // userSetData will contain fields to be updated using $set for both new and existing documents.
|
|
|
- // 'project' is now excluded here as it will be handled by $setOnInsert only.
|
|
|
- //const userSetData: Partial<IUser> = { lastActiveAt: lastActiveAtDateObj };
|
|
|
const userSetData = {};
|
|
|
- // 👇 关键修改:仅当事件类型不是 message_receive 时,才更新 lastActiveAt
|
|
|
+ // 关键逻辑:只有非 message_receive 事件才更新 lastActiveAt
|
|
|
+ // message_receive 已在前面被 return 掉,但为了代码清晰和健壮性,保留此判断。
|
|
|
if (eventType !== "message_receive") {
|
|
|
userSetData.lastActiveAt = lastActiveAtDateObj;
|
|
|
}
|
|
|
@@ -350,7 +375,6 @@ async function processMessage(msg) {
|
|
|
derivedFirstLoginAt = (0, dayjs_1.default)(lastActiveAtDateObj).subtract(eventData.days, "day").toDate();
|
|
|
}
|
|
|
// Set firstLoginAt for $setOnInsert (for new documents)
|
|
|
- // This value will only be used if the document is actually inserted (upsert: true creates a new doc)
|
|
|
setOnInsertFields.firstLoginAt = derivedFirstLoginAt || lastActiveAtDateObj;
|
|
|
// Copy relevant fields from event to userSetData (for $set)
|
|
|
for (const field of USER_FIELDS_TO_UPDATE) {
|
|
|
@@ -398,9 +422,6 @@ async function processMessage(msg) {
|
|
|
$set: userSetData,
|
|
|
$setOnInsert: setOnInsertFields,
|
|
|
};
|
|
|
- // 👈 关键修改:移除 $min 操作符
|
|
|
- // `firstLoginAt` 将只在 `$setOnInsert` 时被设置,
|
|
|
- // 如果文档已存在,它将不会被更新,这符合您的需求。
|
|
|
mongoUserWriteOperations.push({
|
|
|
updateOne: {
|
|
|
filter: { uid: uid },
|