|
|
@@ -26,7 +26,7 @@ export class MessageRecordService {
|
|
|
filters: { [key: string]: any } = {},
|
|
|
sortField: string = "createdAt",
|
|
|
sortOrder: "asc" | "desc" = "desc"
|
|
|
- ): Promise<{ records: IMessageRecord[]; total: number }> {
|
|
|
+ ): Promise<{ records: IMessageRecord[]; total: number; page: number; limit: number; totalPages: number }> {
|
|
|
// 构建查询条件
|
|
|
const query: any = {};
|
|
|
if (filters.uid) {
|
|
|
@@ -41,17 +41,59 @@ export class MessageRecordService {
|
|
|
if (filters.status !== undefined) {
|
|
|
query.status = filters.status;
|
|
|
}
|
|
|
+ if (filters.createdAt) {
|
|
|
+ const dates = (filters.createdAt as string).split(",");
|
|
|
+ const startDate = new Date(dates[0]);
|
|
|
+ if (dates.length > 1) {
|
|
|
+ const endDate = new Date(dates[1]);
|
|
|
+ query.createdAt = { $gte: startDate, $lte: endDate };
|
|
|
+ } else {
|
|
|
+ query.createdAt = { $gte: startDate };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (filters.sentAt) {
|
|
|
+ const dates = (filters.sentAt as string).split(",");
|
|
|
+ const startDate = new Date(dates[0]);
|
|
|
+ if (dates.length > 1) {
|
|
|
+ const endDate = new Date(dates[1]);
|
|
|
+ query.sentAt = { $gte: startDate, $lte: endDate };
|
|
|
+ } else {
|
|
|
+ query.sentAt = { $gte: startDate };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (filters.displayedAt) {
|
|
|
+ const dates = (filters.displayedAt as string).split(",");
|
|
|
+ const startDate = new Date(dates[0]);
|
|
|
+ if (dates.length > 1) {
|
|
|
+ const endDate = new Date(dates[1]);
|
|
|
+ query.displayedAt = { $gte: startDate, $lte: endDate };
|
|
|
+ } else {
|
|
|
+ query.displayedAt = { $gte: startDate };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (filters.openedAt) {
|
|
|
+ const dates = (filters.openedAt as string).split(",");
|
|
|
+ const startDate = new Date(dates[0]);
|
|
|
+ if (dates.length > 1) {
|
|
|
+ const endDate = new Date(dates[1]);
|
|
|
+ query.openedAt = { $gte: startDate, $lte: endDate };
|
|
|
+ } else {
|
|
|
+ query.openedAt = { $gte: startDate };
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- const sort: any = {};
|
|
|
- sort[sortField] = sortOrder === "asc" ? 1 : -1;
|
|
|
-
|
|
|
- const skip = (page - 1) * limit;
|
|
|
-
|
|
|
- const records = await MessageRecord.find(query).sort(sort).skip(skip).limit(limit);
|
|
|
+ const sortOption: any = {};
|
|
|
+ sortOption[sortField] = sortOrder === "asc" ? 1 : -1;
|
|
|
|
|
|
const total = await MessageRecord.countDocuments(query);
|
|
|
+ const records = await MessageRecord.find(query)
|
|
|
+ .sort(sortOption)
|
|
|
+ .skip((page - 1) * limit)
|
|
|
+ .limit(limit);
|
|
|
|
|
|
- return { records, total };
|
|
|
+ const totalPages = Math.ceil(total / limit);
|
|
|
+
|
|
|
+ return { records, total, page, limit, totalPages };
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -95,39 +137,38 @@ export class MessageRecordService {
|
|
|
* 获取整体消息推送统计数据
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
*/
|
|
|
- public async getOverallStatistics(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getOverallStatistics(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {};
|
|
|
+
|
|
|
+ // 添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
} else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果存在任何筛选条件,则添加 $match 阶段
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
+ $match: matchConditions,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -337,39 +378,42 @@ export class MessageRecordService {
|
|
|
* 按策略获取消息统计数据
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称
|
|
|
*/
|
|
|
- public async getStatisticsByStrategy(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getStatisticsByStrategy(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {};
|
|
|
+
|
|
|
+ // 如果提供了日期,添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ };
|
|
|
} else if (endDate) {
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提供了 strategyName,添加策略名称筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有任何筛选条件,将 $match 阶段添加到管道
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
+ $match: matchConditions,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -463,39 +507,42 @@ export class MessageRecordService {
|
|
|
* 按模板获取消息统计数据
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称
|
|
|
*/
|
|
|
- public async getStatisticsByTemplate(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getStatisticsByTemplate(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {};
|
|
|
+
|
|
|
+ // 如果提供了日期,添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ };
|
|
|
} else if (endDate) {
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提供了 strategyName,添加策略名称筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有任何筛选条件,将 $match 阶段添加到管道
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
+ $match: matchConditions,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -589,39 +636,38 @@ export class MessageRecordService {
|
|
|
* 按时间维度的趋势分析,每日统计
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
*/
|
|
|
- public async getDailySentTrends(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getDailySentTrends(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {};
|
|
|
+
|
|
|
+ // 添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
} else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果存在任何筛选条件,则添加 $match 阶段
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
+ $match: matchConditions,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -716,85 +762,333 @@ export class MessageRecordService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 按多个维度获取消息推送统计数据。
|
|
|
- * 支持日期范围、模板、图片、国家和策略的组合查询。
|
|
|
- * @param filters 包含查询条件的过滤器对象
|
|
|
- * @returns 多维度统计结果
|
|
|
+ * 按国家代码获取消息统计数据
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
*/
|
|
|
- public async getMultiDimensionalStatistics(filters: { startDate?: Date; endDate?: Date; templateName?: string; strategyName?: string; cc?: string; image?: string }) {
|
|
|
+ public async getStatisticsByCc(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
+ const matchConditions: any = {};
|
|
|
|
|
|
- // 1. $match (筛选阶段)
|
|
|
- const matchStage: any = {};
|
|
|
- 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 (startDate && endDate) {
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
}
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ } else if (startDate) {
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
+ } else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: 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;
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
}
|
|
|
- if (Object.keys(matchStage).length > 0) {
|
|
|
- pipeline.push({ $match: matchStage });
|
|
|
+
|
|
|
+ // 如果存在任何筛选条件,则添加 $match 阶段
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
- // 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",
|
|
|
- inforeground: "$inforeground",
|
|
|
+ pipeline.push(
|
|
|
+ // 1. 根据 cc, status, strategyName 和 inforeground 进行分组
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ cc: "$cc",
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ strategyName: "$strategyName", // 增加 strategyName
|
|
|
+ },
|
|
|
+ count: { $sum: 1 },
|
|
|
},
|
|
|
- count: { $sum: 1 },
|
|
|
},
|
|
|
- });
|
|
|
-
|
|
|
- // 3. 第二次 $group (汇总阶段)
|
|
|
- pipeline.push({
|
|
|
- $group: {
|
|
|
- _id: {
|
|
|
- date: "$_id.date",
|
|
|
- templateName: "$_id.templateName",
|
|
|
- strategyName: "$_id.strategyName",
|
|
|
- cc: "$_id.cc",
|
|
|
- image: "$_id.image",
|
|
|
+ // 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,
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
},
|
|
|
- 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,
|
|
|
+ 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 MessageRecord.aggregate(pipeline);
|
|
|
+ return results;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error fetching statistics by cc:", error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按图片 URL 获取消息统计数据
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
+ */
|
|
|
+ public async getStatisticsByImage(startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
+ try {
|
|
|
+ const pipeline: any[] = [];
|
|
|
+ const matchConditions: any = {};
|
|
|
+
|
|
|
+ // 添加日期筛选条件
|
|
|
+ if (startDate && endDate) {
|
|
|
+ // 容错处理:如果 endDate <= startDate,将 endDate 设为 startDate 的后一天
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
+ }
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ } else if (startDate) {
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
+ } else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果存在任何筛选条件,则添加 $match 阶段
|
|
|
+ if (Object.keys(matchConditions).length > 0) {
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ pipeline.push(
|
|
|
+ // 1. 根据 image, status, strategyName 和 inforeground 进行分组
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ image: "$image",
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ strategyName: "$strategyName", // 增加 strategyName
|
|
|
+ },
|
|
|
+ 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,
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 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 MessageRecord.aggregate(pipeline);
|
|
|
+ return results;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error fetching statistics by image:", error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按多个维度获取消息推送统计数据。
|
|
|
+ * 支持日期范围、模板、图片、国家和策略的组合查询。
|
|
|
+ * @param filters 包含查询条件的过滤器对象
|
|
|
+ * @returns 多维度统计结果
|
|
|
+ */
|
|
|
+ public async getMultiDimensionalStatistics(filters: { startDate?: Date; endDate?: Date; templateName?: string; strategyName?: string; cc?: string; image?: string }) {
|
|
|
+ try {
|
|
|
+ const pipeline: any[] = [];
|
|
|
+
|
|
|
+ // 1. $match (筛选阶段)
|
|
|
+ const matchStage: any = {};
|
|
|
+ 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",
|
|
|
+ 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",
|
|
|
+ },
|
|
|
+ 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. $project (计算比率和格式化输出)
|
|
|
pipeline.push({
|
|
|
$project: {
|
|
|
@@ -842,62 +1136,69 @@ export class MessageRecordService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 按国家代码获取消息统计数据
|
|
|
+ * 按国家代码和时间维度获取每日统计数据
|
|
|
+ * @param cc 必须提供的国家代码
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
+ * @returns 每日统计数据列表
|
|
|
*/
|
|
|
- public async getStatisticsByCc(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getDailyTrendsByCc(cc: string, startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {
|
|
|
+ cc: cc,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
} else if (endDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
}
|
|
|
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 $match 阶段
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
+
|
|
|
pipeline.push(
|
|
|
- // 1. 根据 cc, status, 和 inforeground 进行分组
|
|
|
+ // 1. 将 createdAt 字段转换为日期,忽略时分秒
|
|
|
{
|
|
|
- $group: {
|
|
|
- _id: {
|
|
|
- cc: "$cc",
|
|
|
- status: "$status",
|
|
|
- inforeground: "$inforeground",
|
|
|
+ $project: {
|
|
|
+ _id: 0,
|
|
|
+ date: {
|
|
|
+ $dateTrunc: { date: "$createdAt", unit: "day", timezone: "America/Los_Angeles" },
|
|
|
},
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 2. 根据日期、状态和 inforeground 进行分组
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
count: { $sum: 1 },
|
|
|
},
|
|
|
},
|
|
|
- // 2. 将数据重组,以便按 cc 汇总
|
|
|
+ // 3. 将数据重组,以便按日期汇总
|
|
|
{
|
|
|
$group: {
|
|
|
- _id: "$_id.cc",
|
|
|
- cc: { $first: "$_id.cc" },
|
|
|
+ _id: "$_id.date",
|
|
|
totalRecords: { $sum: "$count" },
|
|
|
sent: {
|
|
|
$sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
@@ -924,11 +1225,11 @@ export class MessageRecordService {
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
+ // 4. 计算比率并格式化输出
|
|
|
{
|
|
|
$project: {
|
|
|
_id: 0,
|
|
|
- cc: "$cc",
|
|
|
+ date: "$_id",
|
|
|
totalRecords: "$totalRecords",
|
|
|
sent: "$sent",
|
|
|
delivered: "$delivered",
|
|
|
@@ -952,76 +1253,77 @@ export class MessageRecordService {
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
- // 4. 按 deliveredRate 降序排序
|
|
|
+ // 5. 按日期降序排序
|
|
|
{
|
|
|
- $sort: { deliveredRate: -1 },
|
|
|
+ $sort: { date: -1 },
|
|
|
}
|
|
|
);
|
|
|
const results = await MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
|
} catch (error) {
|
|
|
- console.error("Error fetching statistics by cc:", error);
|
|
|
+ console.error("Error fetching daily trends by cc:", error);
|
|
|
return [];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 按图片 URL 获取消息统计数据
|
|
|
+ * 按策略名称和时间维度获取每日统计数据
|
|
|
+ * @param strategyName 必须提供的策略名称
|
|
|
* @param startDate 可选的开始日期
|
|
|
* @param endDate 可选的结束日期
|
|
|
+ * @returns 每日统计数据列表
|
|
|
*/
|
|
|
- public async getStatisticsByImage(startDate?: Date, endDate?: Date) {
|
|
|
+ public async getDailyTrendsByStrategy(strategyName: string, startDate?: Date, endDate?: Date) {
|
|
|
try {
|
|
|
const pipeline: any[] = [];
|
|
|
- // 如果提供了日期,添加 $match 阶段
|
|
|
+ const matchConditions: any = {
|
|
|
+ strategyName: strategyName,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加日期筛选条件
|
|
|
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,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
} else if (startDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $gte: startDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
} else if (endDate) {
|
|
|
- pipeline.push({
|
|
|
- $match: {
|
|
|
- createdAt: {
|
|
|
- $lte: endDate,
|
|
|
- },
|
|
|
- },
|
|
|
- });
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
}
|
|
|
|
|
|
+ // 添加 $match 阶段
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
+
|
|
|
pipeline.push(
|
|
|
- // 1. 根据 image, status, 和 inforeground 进行分组
|
|
|
+ // 1. 将 createdAt 字段转换为日期,忽略时分秒
|
|
|
{
|
|
|
- $group: {
|
|
|
- _id: {
|
|
|
- image: "$image",
|
|
|
- status: "$status",
|
|
|
- inforeground: "$inforeground",
|
|
|
+ $project: {
|
|
|
+ _id: 0,
|
|
|
+ date: {
|
|
|
+ $dateTrunc: { date: "$createdAt", unit: "day", timezone: "America/Los_Angeles" },
|
|
|
},
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ // 2. 根据日期、状态和 inforeground 进行分组
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
count: { $sum: 1 },
|
|
|
},
|
|
|
},
|
|
|
- // 2. 将数据重组,以便按 image 汇总
|
|
|
+ // 3. 将数据重组,以便按日期汇总
|
|
|
{
|
|
|
$group: {
|
|
|
- _id: "$_id.image",
|
|
|
- image: { $first: "$_id.image" },
|
|
|
+ _id: "$_id.date",
|
|
|
totalRecords: { $sum: "$count" },
|
|
|
sent: {
|
|
|
$sum: { $cond: [{ $gte: ["$_id.status", 1] }, "$count", 0] },
|
|
|
@@ -1048,11 +1350,11 @@ export class MessageRecordService {
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
- // 3. 计算比率并格式化输出
|
|
|
+ // 4. 计算比率并格式化输出
|
|
|
{
|
|
|
$project: {
|
|
|
_id: 0,
|
|
|
- image: "$image",
|
|
|
+ date: "$_id",
|
|
|
totalRecords: "$totalRecords",
|
|
|
sent: "$sent",
|
|
|
delivered: "$delivered",
|
|
|
@@ -1076,15 +1378,207 @@ export class MessageRecordService {
|
|
|
},
|
|
|
},
|
|
|
},
|
|
|
- // 4. 按 deliveredRate 降序排序
|
|
|
+ // 5. 按日期升序排序(统一为升序,与getDailyTrendsByCc保持一致)
|
|
|
{
|
|
|
- $sort: { deliveredRate: -1 },
|
|
|
+ $sort: { date: -1 },
|
|
|
}
|
|
|
);
|
|
|
const results = await MessageRecord.aggregate(pipeline);
|
|
|
return results;
|
|
|
} catch (error) {
|
|
|
- console.error("Error fetching statistics by image:", error);
|
|
|
+ console.error("Error fetching daily trends by strategy:", error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按模板ID和时间维度获取每日统计数据
|
|
|
+ * @param templateName 必须提供的模板ID
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
+ * @returns 每日统计数据列表
|
|
|
+ */
|
|
|
+ public async getDailyTrendsByTemplate(templateName: string, startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
+ try {
|
|
|
+ const pipeline: any[] = [];
|
|
|
+ const matchConditions: any = {
|
|
|
+ templateName: templateName,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (startDate && endDate) {
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
+ }
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ } else if (startDate) {
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
+ } else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 $match 阶段
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
+
|
|
|
+ pipeline.push(
|
|
|
+ {
|
|
|
+ $project: {
|
|
|
+ _id: 0,
|
|
|
+ date: { $dateTrunc: { date: "$createdAt", unit: "day", timezone: "America/Los_Angeles" } },
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
+ count: { $sum: 1 },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $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] },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $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"] }] },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $sort: { date: -1 },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const results = await MessageRecord.aggregate(pipeline);
|
|
|
+ return results;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error fetching daily trends by template:", error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按图片URL和时间维度获取每日统计数据
|
|
|
+ * @param image 必须提供的图片URL
|
|
|
+ * @param startDate 可选的开始日期
|
|
|
+ * @param endDate 可选的结束日期
|
|
|
+ * @param strategyName 可选的策略名称筛选
|
|
|
+ * @returns 每日统计数据列表
|
|
|
+ */
|
|
|
+ public async getDailyTrendsByImage(image: string, startDate?: Date, endDate?: Date, strategyName?: string) {
|
|
|
+ try {
|
|
|
+ const pipeline: any[] = [];
|
|
|
+ const matchConditions: any = {
|
|
|
+ image: image,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (startDate && endDate) {
|
|
|
+ if (endDate.getTime() <= startDate.getTime()) {
|
|
|
+ endDate = new Date(startDate.getTime() + 24 * 60 * 60 * 1000);
|
|
|
+ }
|
|
|
+ matchConditions.createdAt = {
|
|
|
+ $gte: startDate,
|
|
|
+ $lte: endDate,
|
|
|
+ };
|
|
|
+ } else if (startDate) {
|
|
|
+ matchConditions.createdAt = { $gte: startDate };
|
|
|
+ } else if (endDate) {
|
|
|
+ matchConditions.createdAt = { $lte: endDate };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 strategyName 筛选条件
|
|
|
+ if (strategyName) {
|
|
|
+ matchConditions.strategyName = strategyName;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加 $match 阶段
|
|
|
+ pipeline.push({
|
|
|
+ $match: matchConditions,
|
|
|
+ });
|
|
|
+
|
|
|
+ pipeline.push(
|
|
|
+ {
|
|
|
+ $project: {
|
|
|
+ _id: 0,
|
|
|
+ date: { $dateTrunc: { date: "$createdAt", unit: "day", timezone: "America/Los_Angeles" } },
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { date: "$date", status: "$status", inforeground: "$inforeground" },
|
|
|
+ count: { $sum: 1 },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $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] },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $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"] }] },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $sort: { date: -1 },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const results = await MessageRecord.aggregate(pipeline);
|
|
|
+ return results;
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Error fetching daily trends by image:", error);
|
|
|
return [];
|
|
|
}
|
|
|
}
|