|
@@ -78,84 +78,111 @@ class MessageRecordService {
|
|
|
}
|
|
}
|
|
|
/**
|
|
/**
|
|
|
* 获取整体消息推送统计数据
|
|
* 获取整体消息推送统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
*/
|
|
*/
|
|
|
- async getOverallStatistics() {
|
|
|
|
|
|
|
+ async getOverallStatistics(startDate, endDate) {
|
|
|
try {
|
|
try {
|
|
|
- const result = await messageRecordModel_1.MessageRecord.aggregate([
|
|
|
|
|
- // 1. 按状态和inforeground字段分组,计算每个类别的记录数
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: { status: "$status", inforeground: "$inforeground" },
|
|
|
|
|
- count: { $sum: 1 },
|
|
|
|
|
- },
|
|
|
|
|
- },
|
|
|
|
|
- // 2. 将数据重组,计算总数、发送成功数、送达数、打开数、展示数
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: null,
|
|
|
|
|
- totalRecords: { $sum: "$count" },
|
|
|
|
|
- // 发送成功数:status >= 1
|
|
|
|
|
- sent: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 送达数:status >= 2
|
|
|
|
|
- delivered: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- // 打开数:status === 3
|
|
|
|
|
- opened: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- failed: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- // 展示数:status >= 2 并且 inforeground = false
|
|
|
|
|
- displayCount: {
|
|
|
|
|
- $sum: {
|
|
|
|
|
- $cond: [
|
|
|
|
|
- {
|
|
|
|
|
- $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
- },
|
|
|
|
|
- "$count",
|
|
|
|
|
- 0,
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 按状态和inforeground字段分组,计算每个类别的记录数
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { status: "$status", inforeground: "$inforeground" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
},
|
|
},
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- totalRecords: "$totalRecords",
|
|
|
|
|
- sent: "$sent",
|
|
|
|
|
- delivered: "$delivered",
|
|
|
|
|
- opened: "$opened",
|
|
|
|
|
- failed: "$failed",
|
|
|
|
|
- displayCount: "$displayCount",
|
|
|
|
|
- // 发送成功率 = sent / totalRecords
|
|
|
|
|
- sentSuccessRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 送达率 = delivered / sent
|
|
|
|
|
- deliveredRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 展示率 = displayCount / delivered
|
|
|
|
|
- displayRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 点击率 = opened / displayCount
|
|
|
|
|
- clickThroughRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // Token 失效率 = failed / totalRecords
|
|
|
|
|
- tokenInvalidationRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,计算总数、发送成功数、送达数、打开数、展示数
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: null,
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- ]);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ const result = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
return result[0];
|
|
return result[0];
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
@@ -165,96 +192,123 @@ class MessageRecordService {
|
|
|
}
|
|
}
|
|
|
/**
|
|
/**
|
|
|
* 按活动获取消息统计数据
|
|
* 按活动获取消息统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
*/
|
|
*/
|
|
|
- async getStatisticsByActivity() {
|
|
|
|
|
|
|
+ async getStatisticsByActivity(startDate, endDate) {
|
|
|
try {
|
|
try {
|
|
|
- const results = await messageRecordModel_1.MessageRecord.aggregate([
|
|
|
|
|
- // 1. 根据 activityId, status, 和 inforeground 进行分组
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: {
|
|
|
|
|
- activityId: "$activityId",
|
|
|
|
|
- activityName: "$activityName",
|
|
|
|
|
- status: "$status",
|
|
|
|
|
- inforeground: "$inforeground",
|
|
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- count: { $sum: 1 },
|
|
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 2. 将数据重组,以便按 activityId 汇总
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: "$_id.activityId",
|
|
|
|
|
- activityName: { $first: "$_id.activityName" },
|
|
|
|
|
- totalRecords: { $sum: "$count" },
|
|
|
|
|
- // 发送成功数:status >= 1
|
|
|
|
|
- sent: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 送达数:status >= 2
|
|
|
|
|
- delivered: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 打开数:status === 3
|
|
|
|
|
- opened: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- failed: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 展示数:status >= 2 并且 inforeground = false
|
|
|
|
|
- displayCount: {
|
|
|
|
|
- $sum: {
|
|
|
|
|
- $cond: [
|
|
|
|
|
- {
|
|
|
|
|
- $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
- },
|
|
|
|
|
- "$count",
|
|
|
|
|
- 0,
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- activityId: "$_id",
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 根据 activityId, status, 和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ activityId: "$activityId",
|
|
|
activityName: "$activityName",
|
|
activityName: "$activityName",
|
|
|
- totalRecords: "$totalRecords",
|
|
|
|
|
- sent: "$sent",
|
|
|
|
|
- delivered: "$delivered",
|
|
|
|
|
- opened: "$opened",
|
|
|
|
|
- failed: "$failed",
|
|
|
|
|
- displayCount: "$displayCount",
|
|
|
|
|
- // 发送成功率 = sent / totalRecords
|
|
|
|
|
- sentSuccessRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 送达率 = delivered / sent
|
|
|
|
|
- deliveredRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 展示率 = displayCount / delivered
|
|
|
|
|
- displayRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 点击率 = opened / displayCount
|
|
|
|
|
- clickThroughRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // Token 失效率 = failed / totalRecords
|
|
|
|
|
- tokenInvalidationRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,以便按 activityId 汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.activityId",
|
|
|
|
|
+ activityName: { $first: "$_id.activityName" },
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- // 4. (可选) 按 deliveredRate 降序排序
|
|
|
|
|
- {
|
|
|
|
|
- $sort: { deliveredRate: -1 },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ activityId: "$_id",
|
|
|
|
|
+ activityName: "$activityName",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
- ]);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 按 deliveredRate 降序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
return results;
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
@@ -264,96 +318,123 @@ class MessageRecordService {
|
|
|
}
|
|
}
|
|
|
/**
|
|
/**
|
|
|
* 按策略获取消息统计数据
|
|
* 按策略获取消息统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
*/
|
|
*/
|
|
|
- async getStatisticsByStrategy() {
|
|
|
|
|
|
|
+ async getStatisticsByStrategy(startDate, endDate) {
|
|
|
try {
|
|
try {
|
|
|
- const results = await messageRecordModel_1.MessageRecord.aggregate([
|
|
|
|
|
- // 1. 根据 strategyId, status, 和 inforeground 进行分组
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: {
|
|
|
|
|
- strategyId: "$strategyId",
|
|
|
|
|
- strategyName: "$strategyName",
|
|
|
|
|
- status: "$status",
|
|
|
|
|
- inforeground: "$inforeground",
|
|
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- count: { $sum: 1 },
|
|
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 2. 将数据重组,以便按 strategyId 汇总
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: "$_id.strategyId",
|
|
|
|
|
- strategyName: { $first: "$_id.strategyName" },
|
|
|
|
|
- totalRecords: { $sum: "$count" },
|
|
|
|
|
- // 发送成功数:status >= 1
|
|
|
|
|
- sent: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 送达数:status >= 2
|
|
|
|
|
- delivered: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 打开数:status === 3
|
|
|
|
|
- opened: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- failed: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 展示数:status >= 2 并且 inforeground = false
|
|
|
|
|
- displayCount: {
|
|
|
|
|
- $sum: {
|
|
|
|
|
- $cond: [
|
|
|
|
|
- {
|
|
|
|
|
- $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
- },
|
|
|
|
|
- "$count",
|
|
|
|
|
- 0,
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- strategyId: "$_id",
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 根据 strategyId, status, 和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ strategyId: "$strategyId",
|
|
|
strategyName: "$strategyName",
|
|
strategyName: "$strategyName",
|
|
|
- totalRecords: "$totalRecords",
|
|
|
|
|
- sent: "$sent",
|
|
|
|
|
- delivered: "$delivered",
|
|
|
|
|
- opened: "$opened",
|
|
|
|
|
- failed: "$failed",
|
|
|
|
|
- displayCount: "$displayCount",
|
|
|
|
|
- // 发送成功率 = sent / totalRecords
|
|
|
|
|
- sentSuccessRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 送达率 = delivered / sent
|
|
|
|
|
- deliveredRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 展示率 = displayCount / delivered
|
|
|
|
|
- displayRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // 点击率 = opened / displayCount
|
|
|
|
|
- clickThroughRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
- },
|
|
|
|
|
- // Token 失效率 = failed / totalRecords
|
|
|
|
|
- tokenInvalidationRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,以便按 strategyId 汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.strategyId",
|
|
|
|
|
+ strategyName: { $first: "$_id.strategyName" },
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- // 4. (可选) 按 deliveredRate 降序排序
|
|
|
|
|
- {
|
|
|
|
|
- $sort: { deliveredRate: -1 },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ strategyId: "$_id",
|
|
|
|
|
+ strategyName: "$strategyName",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
- ]);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 按 deliveredRate 降序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
return results;
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
@@ -363,201 +444,625 @@ class MessageRecordService {
|
|
|
}
|
|
}
|
|
|
/**
|
|
/**
|
|
|
* 按模板获取消息统计数据
|
|
* 按模板获取消息统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
*/
|
|
*/
|
|
|
- async getStatisticsByTemplate() {
|
|
|
|
|
|
|
+ async getStatisticsByTemplate(startDate, endDate) {
|
|
|
try {
|
|
try {
|
|
|
- const results = await messageRecordModel_1.MessageRecord.aggregate([
|
|
|
|
|
- // 1. 根据 templateId, status, 和 inforeground 进行分组
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: {
|
|
|
|
|
- templateId: "$templateId",
|
|
|
|
|
- templateName: "$templateName",
|
|
|
|
|
- status: "$status",
|
|
|
|
|
- inforeground: "$inforeground",
|
|
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- count: { $sum: 1 },
|
|
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 2. 将数据重组,以便按 templateId 汇总
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: "$_id.templateId",
|
|
|
|
|
- templateName: { $first: "$_id.templateName" },
|
|
|
|
|
- totalRecords: { $sum: "$count" },
|
|
|
|
|
- // 发送成功数:status >= 1
|
|
|
|
|
- sent: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 送达数:status >= 2
|
|
|
|
|
- delivered: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 打开数:status === 3
|
|
|
|
|
- opened: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- failed: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 展示数:status >= 2 并且 inforeground = false
|
|
|
|
|
- displayCount: {
|
|
|
|
|
- $sum: {
|
|
|
|
|
- $cond: [
|
|
|
|
|
- {
|
|
|
|
|
- $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
- },
|
|
|
|
|
- "$count",
|
|
|
|
|
- 0,
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- templateId: "$_id",
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 根据 templateId, status, 和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ templateId: "$templateId",
|
|
|
templateName: "$templateName",
|
|
templateName: "$templateName",
|
|
|
- totalRecords: "$totalRecords",
|
|
|
|
|
- sent: "$sent",
|
|
|
|
|
- delivered: "$delivered",
|
|
|
|
|
- opened: "$opened",
|
|
|
|
|
- failed: "$failed",
|
|
|
|
|
- displayCount: "$displayCount",
|
|
|
|
|
- // 发送成功率 = sent / totalRecords
|
|
|
|
|
- sentSuccessRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,以便按 templateId 汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.templateId",
|
|
|
|
|
+ templateName: { $first: "$_id.templateName" },
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
- // 送达率 = delivered / sent
|
|
|
|
|
- deliveredRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ templateId: "$_id",
|
|
|
|
|
+ templateName: "$templateName",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 按 deliveredRate 降序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
|
|
+ return results;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error("Error fetching statistics by template:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按时间维度的趋势分析,每日统计
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
|
|
+ */
|
|
|
|
|
+ async getDailySentTrends(startDate, endDate) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ actualSendAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- // 展示率 = displayCount / delivered
|
|
|
|
|
- displayRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ actualSendAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- // 点击率 = opened / displayCount
|
|
|
|
|
- clickThroughRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ actualSendAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- // Token 失效率 = failed / totalRecords
|
|
|
|
|
- tokenInvalidationRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 将 actualSendAt 字段转换为日期,忽略时分秒
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ date: {
|
|
|
|
|
+ $dateTrunc: { date: "$actualSendAt", unit: "day", timezone: "America/Los_Angeles" },
|
|
|
|
|
+ },
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 根据日期、状态和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 将数据重组,以便按日期汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.date",
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- // 4. (可选) 按 deliveredRate 降序排序
|
|
|
|
|
- {
|
|
|
|
|
- $sort: { deliveredRate: -1 },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ date: "$_id",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
- ]);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 5. 按日期升序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { date: 1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
return results;
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
|
- console.error("Error fetching statistics by template:", error);
|
|
|
|
|
|
|
+ console.error("Error fetching daily sent trends:", error);
|
|
|
return [];
|
|
return [];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // 按时间维度的趋势分析,每日统计
|
|
|
|
|
- async getDailySentTrends() {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按多个维度获取消息推送统计数据。
|
|
|
|
|
+ * 支持日期范围、模板、图片、国家和策略的组合查询。
|
|
|
|
|
+ * @param filters 包含查询条件的过滤器对象
|
|
|
|
|
+ * @returns 多维度统计结果
|
|
|
|
|
+ */
|
|
|
|
|
+ async getMultiDimensionalStatistics(filters) {
|
|
|
try {
|
|
try {
|
|
|
- const results = await messageRecordModel_1.MessageRecord.aggregate([
|
|
|
|
|
- // 1. 将 actualSendAt 字段转换为日期,忽略时分秒
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- date: {
|
|
|
|
|
- $dateTrunc: { date: "$actualSendAt", unit: "day", timezone: "America/Los_Angeles" },
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 1. $match (筛选阶段)
|
|
|
|
|
+ const matchStage = {};
|
|
|
|
|
+ if (filters.startDate || filters.endDate) {
|
|
|
|
|
+ matchStage.createdAt = {};
|
|
|
|
|
+ if (filters.startDate) {
|
|
|
|
|
+ matchStage.createdAt.$gte = filters.startDate;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (filters.startDate && filters.endDate.getTime() <= filters.startDate.getTime()) {
|
|
|
|
|
+ filters.endDate = new Date(filters.startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ matchStage.createdAt.$lte = filters.endDate;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.templateName) {
|
|
|
|
|
+ matchStage.templateName = filters.templateName;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.strategyName) {
|
|
|
|
|
+ matchStage.strategyName = filters.strategyName;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.cc) {
|
|
|
|
|
+ matchStage.cc = filters.cc;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filters.image) {
|
|
|
|
|
+ matchStage.image = filters.image;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (Object.keys(matchStage).length > 0) {
|
|
|
|
|
+ pipeline.push({ $match: matchStage });
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2. $group (分组和聚合阶段)
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ date: { $dateTrunc: { date: "$createdAt", unit: "day", timezone: "America/Los_Angeles" } },
|
|
|
|
|
+ templateName: "$templateName",
|
|
|
|
|
+ strategyName: "$strategyName",
|
|
|
|
|
+ cc: "$cc",
|
|
|
|
|
+ image: "$image",
|
|
|
status: "$status",
|
|
status: "$status",
|
|
|
inforeground: "$inforeground",
|
|
inforeground: "$inforeground",
|
|
|
},
|
|
},
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
},
|
|
},
|
|
|
- // 2. 根据日期、状态和 inforeground 进行分组
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
|
|
- count: { $sum: 1 },
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ // 3. 第二次 $group (汇总阶段)
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ date: "$_id.date",
|
|
|
|
|
+ templateName: "$_id.templateName",
|
|
|
|
|
+ strategyName: "$_id.strategyName",
|
|
|
|
|
+ cc: "$_id.cc",
|
|
|
|
|
+ image: "$_id.image",
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
- // 3. 将数据重组,以便按日期汇总
|
|
|
|
|
- {
|
|
|
|
|
- $group: {
|
|
|
|
|
- _id: "$_id.date",
|
|
|
|
|
- totalRecords: { $sum: "$count" },
|
|
|
|
|
- // 发送成功数:status >= 1
|
|
|
|
|
- sent: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
- },
|
|
|
|
|
- // 送达数:status >= 2
|
|
|
|
|
- delivered: {
|
|
|
|
|
- $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: { $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] } },
|
|
|
|
|
+ delivered: { $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] } },
|
|
|
|
|
+ opened: { $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] } },
|
|
|
|
|
+ failed: { $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] } },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [{ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }] }, "$count", 0],
|
|
|
},
|
|
},
|
|
|
- // 打开数:status === 3
|
|
|
|
|
- opened: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ // 4. $project (计算比率和格式化输出)
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ date: "$_id.date",
|
|
|
|
|
+ templateName: "$_id.templateName",
|
|
|
|
|
+ strategyName: "$_id.strategyName",
|
|
|
|
|
+ cc: "$_id.cc",
|
|
|
|
|
+ image: "$_id.image",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ // 5. $sort (排序)
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $sort: { date: 1, templateName: 1, cc: 1, image: 1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
|
|
+ return results;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error("Error fetching multi-dimensional statistics:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按国家代码获取消息统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
|
|
+ */
|
|
|
|
|
+ async getStatisticsByCc(startDate, endDate) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- failed: {
|
|
|
|
|
- $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- // 展示数:status >= 2 并且 inforeground = false
|
|
|
|
|
- displayCount: {
|
|
|
|
|
- $sum: {
|
|
|
|
|
- $cond: [
|
|
|
|
|
- {
|
|
|
|
|
- $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
- },
|
|
|
|
|
- "$count",
|
|
|
|
|
- 0,
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 根据 cc, status, 和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ cc: "$cc",
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
},
|
|
},
|
|
|
- // 4. 计算比率并格式化输出
|
|
|
|
|
- {
|
|
|
|
|
- $project: {
|
|
|
|
|
- _id: 0,
|
|
|
|
|
- date: "$_id",
|
|
|
|
|
- totalRecords: "$totalRecords",
|
|
|
|
|
- sent: "$sent",
|
|
|
|
|
- delivered: "$delivered",
|
|
|
|
|
- opened: "$opened",
|
|
|
|
|
- failed: "$failed",
|
|
|
|
|
- displayCount: "$displayCount",
|
|
|
|
|
- // 发送成功率 = sent / totalRecords
|
|
|
|
|
- sentSuccessRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,以便按 cc 汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.cc",
|
|
|
|
|
+ cc: { $first: "$_id.cc" },
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
- // 送达率 = delivered / sent
|
|
|
|
|
- deliveredRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ cc: "$cc",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 按 deliveredRate 降序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
|
|
+ return results;
|
|
|
|
|
+ }
|
|
|
|
|
+ catch (error) {
|
|
|
|
|
+ console.error("Error fetching statistics by cc:", error);
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按图片 URL 获取消息统计数据
|
|
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
|
|
+ */
|
|
|
|
|
+ async getStatisticsByImage(startDate, endDate) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const pipeline = [];
|
|
|
|
|
+ // 如果提供了日期,添加 $match 阶段
|
|
|
|
|
+ if (startDate && endDate) {
|
|
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- // 展示率 = displayCount / delivered
|
|
|
|
|
- displayRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (startDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $gte: startDate,
|
|
|
},
|
|
},
|
|
|
- // 点击率 = opened / displayCount
|
|
|
|
|
- clickThroughRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (endDate) {
|
|
|
|
|
+ pipeline.push({
|
|
|
|
|
+ $match: {
|
|
|
|
|
+ createdAt: {
|
|
|
|
|
+ $lte: endDate,
|
|
|
},
|
|
},
|
|
|
- // Token invalidation rate
|
|
|
|
|
- tokenInvalidationRate: {
|
|
|
|
|
- $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ pipeline.push(
|
|
|
|
|
+ // 1. 根据 image, status, 和 inforeground 进行分组
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: {
|
|
|
|
|
+ image: "$image",
|
|
|
|
|
+ status: "$status",
|
|
|
|
|
+ inforeground: "$inforeground",
|
|
|
|
|
+ },
|
|
|
|
|
+ count: { $sum: 1 },
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+ // 2. 将数据重组,以便按 image 汇总
|
|
|
|
|
+ {
|
|
|
|
|
+ $group: {
|
|
|
|
|
+ _id: "$_id.image",
|
|
|
|
|
+ image: { $first: "$_id.image" },
|
|
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
|
|
+ sent: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ delivered: {
|
|
|
|
|
+ $sum: { $cond: [{ $gte: ["$_id.status", 2] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ opened: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ failed: {
|
|
|
|
|
+ $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
|
|
|
|
|
+ },
|
|
|
|
|
+ displayCount: {
|
|
|
|
|
+ $sum: {
|
|
|
|
|
+ $cond: [
|
|
|
|
|
+ {
|
|
|
|
|
+ $and: [{ $gte: ["$_id.status", 2] }, { $eq: ["$_id.inforeground", false] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ "$count",
|
|
|
|
|
+ 0,
|
|
|
|
|
+ ],
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
},
|
|
},
|
|
|
- // 5. 按日期升序排序
|
|
|
|
|
- {
|
|
|
|
|
- $sort: { date: 1 },
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 3. 计算比率并格式化输出
|
|
|
|
|
+ {
|
|
|
|
|
+ $project: {
|
|
|
|
|
+ _id: 0,
|
|
|
|
|
+ image: "$image",
|
|
|
|
|
+ totalRecords: "$totalRecords",
|
|
|
|
|
+ sent: "$sent",
|
|
|
|
|
+ delivered: "$delivered",
|
|
|
|
|
+ opened: "$opened",
|
|
|
|
|
+ failed: "$failed",
|
|
|
|
|
+ displayCount: "$displayCount",
|
|
|
|
|
+ sentSuccessRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$sent", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ deliveredRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$sent", 0] }, 0, { $divide: ["$delivered", "$sent"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ displayRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$delivered", 0] }, 0, { $divide: ["$displayCount", "$delivered"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ clickThroughRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$displayCount", 0] }, 0, { $divide: ["$opened", "$displayCount"] }],
|
|
|
|
|
+ },
|
|
|
|
|
+ tokenInvalidationRate: {
|
|
|
|
|
+ $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
- ]);
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ // 4. 按 deliveredRate 降序排序
|
|
|
|
|
+ {
|
|
|
|
|
+ $sort: { deliveredRate: -1 },
|
|
|
|
|
+ });
|
|
|
|
|
+ const results = await messageRecordModel_1.MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
return results;
|
|
|
}
|
|
}
|
|
|
catch (error) {
|
|
catch (error) {
|
|
|
- console.error("Error fetching daily sent trends:", error);
|
|
|
|
|
|
|
+ console.error("Error fetching statistics by image:", error);
|
|
|
return [];
|
|
return [];
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|