|
@@ -1,7 +1,45 @@
|
|
|
|
|
+// oms/src/services/messageActivityService.ts
|
|
|
|
|
+
|
|
|
import { MessageActivity, IMessageActivity } from "../models/messageActivityModel";
|
|
import { MessageActivity, IMessageActivity } from "../models/messageActivityModel";
|
|
|
-import { MessageTemplate } from "../models/messageTemplateModel";
|
|
|
|
|
|
|
+import { IMessageTemplate, MessageTemplate } from "../models/messageTemplateModel";
|
|
|
|
|
+import { MessageRecord, IMessageRecord } from "../models/messageRecordModel";
|
|
|
|
|
+import { UserTargetingService } from "./userTargetingService";
|
|
|
|
|
+import rabbitmqService from "./rabbitmqService";
|
|
|
|
|
+import { addHours, isPast, differenceInHours } from "date-fns";
|
|
|
|
|
+
|
|
|
|
|
+// 语言映射,用于根据国家代码确定语言
|
|
|
|
|
+const ccToLangMap: { [key: string]: string } = {
|
|
|
|
|
+ US: "en",
|
|
|
|
|
+ GB: "en",
|
|
|
|
|
+ CA: "en",
|
|
|
|
|
+ AU: "en",
|
|
|
|
|
+ CN: "zh-cn",
|
|
|
|
|
+ TW: "zh-tw",
|
|
|
|
|
+ JP: "ja",
|
|
|
|
|
+ KR: "ko",
|
|
|
|
|
+ FR: "fr",
|
|
|
|
|
+ DE: "de",
|
|
|
|
|
+ ES: "es",
|
|
|
|
|
+ PT: "pt",
|
|
|
|
|
+ RU: "ru",
|
|
|
|
|
+ IT: "it",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 检查传入的语言是否在消息模板中定义
|
|
|
|
|
+const getLocalizedText = (template: IMessageTemplate, lang: string, key: "messageTitle" | "messageContent"): string => {
|
|
|
|
|
+ if (template[key][lang]) {
|
|
|
|
|
+ return template[key][lang];
|
|
|
|
|
+ }
|
|
|
|
|
+ return template[key]["en"] || ""; // 如果找不到语言,则默认使用 'en'
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
export class MessageActivityService {
|
|
export class MessageActivityService {
|
|
|
|
|
+ private userTargetingService: UserTargetingService;
|
|
|
|
|
+
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ this.userTargetingService = new UserTargetingService();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 创建一个新的消息活动
|
|
* 创建一个新的消息活动
|
|
|
* @param activityData 消息活动数据
|
|
* @param activityData 消息活动数据
|
|
@@ -68,4 +106,133 @@ export class MessageActivityService {
|
|
|
public async deleteMessageActivity(activityId: string): Promise<IMessageActivity | null> {
|
|
public async deleteMessageActivity(activityId: string): Promise<IMessageActivity | null> {
|
|
|
return await MessageActivity.findByIdAndDelete(activityId);
|
|
return await MessageActivity.findByIdAndDelete(activityId);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据消息活动生成消息记录。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param activityId 要生成记录的消息活动ID
|
|
|
|
|
+ * @returns 成功生成的记录数量
|
|
|
|
|
+ */
|
|
|
|
|
+ public async generateRecordsForActivity(activityId: string): Promise<number> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取消息活动和模板
|
|
|
|
|
+ const activity = await MessageActivity.findById(activityId);
|
|
|
|
|
+ if (!activity || !activity.templateId) {
|
|
|
|
|
+ console.error(`Message activity or template not found for id: ${activityId}`);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const template = await MessageTemplate.findById(activity.templateId);
|
|
|
|
|
+ if (!template) {
|
|
|
|
|
+ console.error(`Message template not found for id: ${activity.templateId}`);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查找所有目标用户
|
|
|
|
|
+ const targetUsers = await this.userTargetingService.findTargetUsers(activity.filter || []);
|
|
|
|
|
+ if (targetUsers.length === 0) {
|
|
|
|
|
+ console.log(`No users found for activity: ${activity.name}`);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 找出最近3天内(以 plannedSendAt 计)已收到过消息的用户
|
|
|
|
|
+ const threeDaysAgo = addHours(new Date(), -72);
|
|
|
|
|
+ const recentMessages = await MessageRecord.aggregate([
|
|
|
|
|
+ { $match: { uid: { $in: targetUsers.map((u) => u.uid) } } },
|
|
|
|
|
+ { $sort: { plannedSendAt: -1 } },
|
|
|
|
|
+ { $group: { _id: "$uid", lastPlannedSendAt: { $first: "$plannedSendAt" } } },
|
|
|
|
|
+ ]);
|
|
|
|
|
+ const lastPlannedSendMap = new Map<string, Date>();
|
|
|
|
|
+ recentMessages.forEach((item) => {
|
|
|
|
|
+ if (item.lastPlannedSendAt >= threeDaysAgo) {
|
|
|
|
|
+ lastPlannedSendMap.set(item._id, item.lastPlannedSendAt);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const recordsToInsert = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 遍历目标用户,生成消息记录
|
|
|
|
|
+ for (const user of targetUsers) {
|
|
|
|
|
+ // 检查用户是否在最近3天内收到过消息
|
|
|
|
|
+ if (lastPlannedSendMap.has(user.uid)) {
|
|
|
|
|
+ console.log(`User ${user.uid} has a recent message scheduled. Skipping.`);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 确定消息语言
|
|
|
|
|
+ let userLang = "en"; // 默认语言
|
|
|
|
|
+ if (user.lang) {
|
|
|
|
|
+ userLang = user.lang;
|
|
|
|
|
+ } else if (user.cc) {
|
|
|
|
|
+ userLang = ccToLangMap[user.cc] || "en";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 从模板中获取本地化文本
|
|
|
|
|
+ const messageTitle = getLocalizedText(template, userLang, "messageTitle");
|
|
|
|
|
+ const messageContent = getLocalizedText(template, userLang, "messageContent");
|
|
|
|
|
+
|
|
|
|
|
+ if (!messageTitle || !messageContent) {
|
|
|
|
|
+ console.error(`No message title or content found for user ${user.uid} with lang/cc: ${userLang}/${user.cc}. Skipping.`);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 计算 plannedSendAt
|
|
|
|
|
+ let baseDate = activity.scheduleAt || new Date();
|
|
|
|
|
+ // 如果活动计划时间已过期,则使用当前日期作为基础
|
|
|
|
|
+ if (isPast(baseDate) && !activity.everyday) {
|
|
|
|
|
+ baseDate = new Date();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let plannedSendAt = baseDate;
|
|
|
|
|
+ if (user.lastActiveAt) {
|
|
|
|
|
+ // 获取用户上次活跃时间的小时和分钟
|
|
|
|
|
+ const lastActiveHour = user.lastActiveAt.getHours();
|
|
|
|
|
+ const lastActiveMinute = user.lastActiveAt.getMinutes();
|
|
|
|
|
+
|
|
|
|
|
+ // 将基础日期的时间调整为用户上次活跃时间的前一小时
|
|
|
|
|
+ plannedSendAt = new Date(baseDate.getFullYear(), baseDate.getMonth(), baseDate.getDate(), lastActiveHour - 1, lastActiveMinute, 0, 0);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果调整后的时间在过去,则将日期推到第二天
|
|
|
|
|
+ if (isPast(plannedSendAt) && differenceInHours(new Date(), plannedSendAt) > 2) {
|
|
|
|
|
+ plannedSendAt = addHours(plannedSendAt, 24);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 创建消息记录对象
|
|
|
|
|
+ recordsToInsert.push({
|
|
|
|
|
+ uid: user.uid,
|
|
|
|
|
+ activityId: activity._id,
|
|
|
|
|
+ templateId: template._id,
|
|
|
|
|
+ title: messageTitle,
|
|
|
|
|
+ content: messageContent,
|
|
|
|
|
+ image: activity.image,
|
|
|
|
|
+ bigger: activity.bigger,
|
|
|
|
|
+ action: activity.action,
|
|
|
|
|
+ param: activity.param,
|
|
|
|
|
+ extend: activity.extend,
|
|
|
|
|
+ status: 0, // 0: 未发送
|
|
|
|
|
+ plannedSendAt: plannedSendAt,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 批量插入消息记录
|
|
|
|
|
+ let count = 0;
|
|
|
|
|
+ if (recordsToInsert.length > 0) {
|
|
|
|
|
+ const result = await MessageRecord.insertMany(recordsToInsert);
|
|
|
|
|
+ console.log(`Successfully generated ${result.length} message records.`);
|
|
|
|
|
+ count = result.length;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log("No new message records to generate.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 10. 更新activity的lastPubDate字段
|
|
|
|
|
+ activity.lastPubDate = new Date();
|
|
|
|
|
+ await activity.save();
|
|
|
|
|
+
|
|
|
|
|
+ return count;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("Error generating message records for activity:", error);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|