|
|
@@ -263,10 +263,21 @@ export class MessageRecordService {
|
|
|
console.log(`[MessageStatsCache] hit key=${cacheKey}`);
|
|
|
return cached;
|
|
|
}
|
|
|
+ let result: any[];
|
|
|
+ if (MessageRecordService.PREAGG_ENABLED && startDate && endDate) {
|
|
|
+ const t0 = Date.now();
|
|
|
+ result = await this.getByTemplateFromPreAgg(startDate, endDate, strategyName, page, limit);
|
|
|
+ if (result.length > 0) {
|
|
|
+ console.log(`[MessageStats] by-template preagg rows=${result.length} ms=${Date.now() - t0}`);
|
|
|
+ await this.setCache(cacheKey, result);
|
|
|
+ console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
const matchConditions = this.buildMatchConditions(startDate, endDate, strategyName);
|
|
|
// templateId 是 ObjectId,分组时增加基数但对展示无意义,移除后减少第一阶段 group key 大小
|
|
|
const groupFields = ["templateName"];
|
|
|
- const result = await this.getStatisticsByGroup(matchConditions, groupFields, { clickThroughRate: -1 }, page, limit);
|
|
|
+ result = await this.getStatisticsByGroup(matchConditions, groupFields, { clickThroughRate: -1 }, page, limit);
|
|
|
await this.setCache(cacheKey, result);
|
|
|
console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
return result;
|
|
|
@@ -294,8 +305,19 @@ export class MessageRecordService {
|
|
|
console.log(`[MessageStatsCache] hit key=${cacheKey}`);
|
|
|
return cached;
|
|
|
}
|
|
|
+ let result: any[];
|
|
|
+ if (MessageRecordService.PREAGG_ENABLED && startDate && endDate) {
|
|
|
+ const t0 = Date.now();
|
|
|
+ result = await this.getByCcFromPreAgg(startDate, endDate, strategyName, page, limit);
|
|
|
+ if (result.length > 0) {
|
|
|
+ console.log(`[MessageStats] by-cc preagg rows=${result.length} ms=${Date.now() - t0}`);
|
|
|
+ await this.setCache(cacheKey, result);
|
|
|
+ console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
const matchConditions = this.buildMatchConditions(startDate, endDate, strategyName);
|
|
|
- const result = await this.getStatisticsByGroup(matchConditions, ["cc"], { totalRecords: -1 }, page, limit);
|
|
|
+ result = await this.getStatisticsByGroup(matchConditions, ["cc"], { totalRecords: -1 }, page, limit);
|
|
|
await this.setCache(cacheKey, result);
|
|
|
console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
return result;
|
|
|
@@ -323,8 +345,19 @@ export class MessageRecordService {
|
|
|
console.log(`[MessageStatsCache] hit key=${cacheKey}`);
|
|
|
return cached;
|
|
|
}
|
|
|
+ let result: any[];
|
|
|
+ if (MessageRecordService.PREAGG_ENABLED && startDate && endDate) {
|
|
|
+ const t0 = Date.now();
|
|
|
+ result = await this.getByImageFromPreAgg(startDate, endDate, strategyName, page, limit);
|
|
|
+ if (result.length > 0) {
|
|
|
+ console.log(`[MessageStats] by-image preagg rows=${result.length} ms=${Date.now() - t0}`);
|
|
|
+ await this.setCache(cacheKey, result);
|
|
|
+ console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
const matchConditions = this.buildMatchConditions(startDate, endDate, strategyName);
|
|
|
- const result = await this.getStatisticsByGroup(matchConditions, ["image"], { clickThroughRate: -1 }, page, limit);
|
|
|
+ result = await this.getStatisticsByGroup(matchConditions, ["image"], { clickThroughRate: -1 }, page, limit);
|
|
|
await this.setCache(cacheKey, result);
|
|
|
console.log(`[MessageStatsCache] miss key=${cacheKey}`);
|
|
|
return result;
|
|
|
@@ -849,6 +882,150 @@ export class MessageRecordService {
|
|
|
return MessageStatsDailyUid.aggregate(pipeline).allowDiskUse(true);
|
|
|
}
|
|
|
|
|
|
+ private async getByTemplateFromPreAgg(startDate: Date, endDate: Date, strategyName: string | undefined, page: number, limit: number) {
|
|
|
+ const safePage = Math.max(1, Math.floor(page || MessageRecordService.DEFAULT_STATS_PAGE));
|
|
|
+ const safeLimit = Math.min(
|
|
|
+ MessageRecordService.MAX_STATS_LIMIT,
|
|
|
+ Math.max(1, Math.floor(limit || MessageRecordService.DEFAULT_STATS_LIMIT))
|
|
|
+ );
|
|
|
+
|
|
|
+ const pipeline: any[] = [
|
|
|
+ { $match: this.buildPreAggMatch(startDate, endDate, strategyName) },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ templateName: "$templateName",
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ uid: "$uid",
|
|
|
+ },
|
|
|
+ count: { $sum: "$msgCount" },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ templateName: "$_id.templateName",
|
|
|
+ status: "$_id.status",
|
|
|
+ inforeground: "$_id.inforeground",
|
|
|
+ },
|
|
|
+ count: { $sum: "$count" },
|
|
|
+ uniqueUsers: { $sum: 1 },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { templateName: "$_id.templateName" },
|
|
|
+ templateName: { $first: "$_id.templateName" },
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
+ ...this.getStatusAggregationFields(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { $project: this.getStatisticsProjectFields(["templateName"]) },
|
|
|
+ { $sort: { clickThroughRate: -1 } },
|
|
|
+ { $skip: (safePage - 1) * safeLimit },
|
|
|
+ { $limit: safeLimit },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return MessageStatsDailyUid.aggregate(pipeline).allowDiskUse(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async getByCcFromPreAgg(startDate: Date, endDate: Date, strategyName: string | undefined, page: number, limit: number) {
|
|
|
+ const safePage = Math.max(1, Math.floor(page || MessageRecordService.DEFAULT_STATS_PAGE));
|
|
|
+ const safeLimit = Math.min(
|
|
|
+ MessageRecordService.MAX_STATS_LIMIT,
|
|
|
+ Math.max(1, Math.floor(limit || MessageRecordService.DEFAULT_STATS_LIMIT))
|
|
|
+ );
|
|
|
+
|
|
|
+ const pipeline: any[] = [
|
|
|
+ { $match: this.buildPreAggMatch(startDate, endDate, strategyName) },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ cc: "$cc",
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ uid: "$uid",
|
|
|
+ },
|
|
|
+ count: { $sum: "$msgCount" },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ cc: "$_id.cc",
|
|
|
+ status: "$_id.status",
|
|
|
+ inforeground: "$_id.inforeground",
|
|
|
+ },
|
|
|
+ count: { $sum: "$count" },
|
|
|
+ uniqueUsers: { $sum: 1 },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { cc: "$_id.cc" },
|
|
|
+ cc: { $first: "$_id.cc" },
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
+ ...this.getStatusAggregationFields(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { $project: this.getStatisticsProjectFields(["cc"]) },
|
|
|
+ { $sort: { totalRecords: -1 } },
|
|
|
+ { $skip: (safePage - 1) * safeLimit },
|
|
|
+ { $limit: safeLimit },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return MessageStatsDailyUid.aggregate(pipeline).allowDiskUse(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private async getByImageFromPreAgg(startDate: Date, endDate: Date, strategyName: string | undefined, page: number, limit: number) {
|
|
|
+ const safePage = Math.max(1, Math.floor(page || MessageRecordService.DEFAULT_STATS_PAGE));
|
|
|
+ const safeLimit = Math.min(
|
|
|
+ MessageRecordService.MAX_STATS_LIMIT,
|
|
|
+ Math.max(1, Math.floor(limit || MessageRecordService.DEFAULT_STATS_LIMIT))
|
|
|
+ );
|
|
|
+
|
|
|
+ const pipeline: any[] = [
|
|
|
+ { $match: this.buildPreAggMatch(startDate, endDate, strategyName) },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ image: "$image",
|
|
|
+ status: "$status",
|
|
|
+ inforeground: "$inforeground",
|
|
|
+ uid: "$uid",
|
|
|
+ },
|
|
|
+ count: { $sum: "$msgCount" },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: {
|
|
|
+ image: "$_id.image",
|
|
|
+ status: "$_id.status",
|
|
|
+ inforeground: "$_id.inforeground",
|
|
|
+ },
|
|
|
+ count: { $sum: "$count" },
|
|
|
+ uniqueUsers: { $sum: 1 },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $group: {
|
|
|
+ _id: { image: "$_id.image" },
|
|
|
+ image: { $first: "$_id.image" },
|
|
|
+ totalRecords: { $sum: "$count" },
|
|
|
+ ...this.getStatusAggregationFields(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ { $project: this.getStatisticsProjectFields(["image"]) },
|
|
|
+ { $sort: { clickThroughRate: -1 } },
|
|
|
+ { $skip: (safePage - 1) * safeLimit },
|
|
|
+ { $limit: safeLimit },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return MessageStatsDailyUid.aggregate(pipeline).allowDiskUse(true);
|
|
|
+ }
|
|
|
+
|
|
|
private async getDailyTrendsFromPreAgg(startDate: Date, endDate: Date, strategyName?: string) {
|
|
|
const pipeline: any[] = [
|
|
|
{ $match: this.buildPreAggMatch(startDate, endDate, strategyName) },
|