guoziyun il y a 9 mois
Parent
commit
c2078e357e

+ 117 - 40
oms/dist/src/controllers/messageRecordController.js

@@ -1,5 +1,4 @@
 "use strict";
-// oms/src/controllers/messageRecordController.ts
 Object.defineProperty(exports, "__esModule", { value: true });
 const mongoose_1 = require("mongoose");
 const messageRecordModel_1 = require("../models/messageRecordModel");
@@ -10,6 +9,7 @@ class MessageRecordController {
          * @route POST /api/message-record
          * @desc Creates a new message record
          * @access Private
+         * @returns A JSON response with the newly created record or an error message.
          */
         this.createRecord = async (req, res) => {
             try {
@@ -24,14 +24,17 @@ class MessageRecordController {
         };
         /**
          * @route GET /api/message-records
-         * @desc Retrieves all message records with pagination and optional filters
+         * @desc Retrieves all message records with pagination and optional filters.
+         * Filters can be applied by uid, activityName, strategyName, templateName, status, and various date fields.
+         * Date fields can be filtered by a single date or a range (e.g., "?createdAt=2023-01-01,2023-01-31").
          * @access Private
+         * @returns A JSON response with paginated records and pagination metadata.
          */
         this.getPaginatedRecords = async (req, res) => {
-            const { page = 1, limit = 30, uid, activityName, templateName, strategyName, status, startDate, endDate } = req.query;
+            const { page = 1, limit = 30, uid, activityName, templateName, strategyName, status } = req.query;
             const pageNum = parseInt(page, 10);
             const limitNum = parseInt(limit, 10);
-            // 动态构建查询过滤器
+            // Build the query filters dynamically based on request parameters
             const filters = {};
             if (uid) {
                 filters.uid = uid;
@@ -51,49 +54,33 @@ class MessageRecordController {
             if (templateName) {
                 filters.templateName = templateName;
             }
-            // 定义所有可查询的日期字段
+            // List of all date fields that can be filtered
             const dateQueryKeys = ["plannedSendAt", "actualSendAt", "deliveredAt", "openedAt", "createdAt", "updatedAt"];
-            // 遍历所有日期字段,处理单个日期或日期范围
+            // Iterate through date fields to handle single dates or date ranges
             dateQueryKeys.forEach((key) => {
                 const queryValue = req.query[key];
                 if (queryValue) {
                     const dates = queryValue.split(",");
-                    if (dates.length === 2) {
-                        const startDate = new Date(dates[0]);
-                        const endDate = new Date(dates[1]);
-                        // 确保日期有效
-                        if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {
+                    const startDate = new Date(dates[0]);
+                    const endDate = dates.length > 1 ? new Date(dates[1]) : null;
+                    if (!isNaN(startDate.getTime())) {
+                        if (endDate && !isNaN(endDate.getTime())) {
+                            // Filter by date range (e.g., plannedSendAt=2023-01-01,2023-01-31)
                             filters[key] = {
                                 $gte: startDate,
                                 $lte: endDate,
                             };
                         }
                         else {
-                            console.warn(`[API] Invalid date range format for ${key}: ${queryValue}. Skipping.`);
+                            // Filter by a single date
+                            filters[key] = startDate;
                         }
                     }
                     else {
-                        // 如果不是范围,则按单个日期精确匹配
-                        const singleDate = new Date(queryValue);
-                        if (!isNaN(singleDate.getTime())) {
-                            filters[key] = singleDate;
-                        }
+                        console.warn(`[API] Invalid date format for ${key}: ${queryValue}. Skipping.`);
                     }
                 }
             });
-            // 保留对原有startDate/endDate参数的兼容性,并将其应用于createdAt
-            if (startDate || endDate) {
-                // 确保 createdAt 过滤器不存在冲突,如果已通过范围查询设置,则跳过
-                if (!filters.createdAt) {
-                    filters.createdAt = {};
-                }
-                if (startDate) {
-                    filters.createdAt.$gte = new Date(startDate);
-                }
-                if (endDate) {
-                    filters.createdAt.$lte = new Date(endDate);
-                }
-            }
             try {
                 const records = await messageRecordModel_1.MessageRecord.find(filters)
                     .sort({ createdAt: -1 })
@@ -120,6 +107,7 @@ class MessageRecordController {
          * @route GET /api/message-records/user/:uid
          * @desc Retrieves message records by user UID
          * @access Private
+         * @returns A JSON response with the records for a specific user.
          */
         this.getRecordsByUid = async (req, res) => {
             try {
@@ -138,12 +126,13 @@ class MessageRecordController {
          * @route GET /api/message-record/:id
          * @desc Retrieves a single message record by ID
          * @access Private
+         * @returns A JSON response with the single record found or a "not found" message.
          */
         this.getRecordById = async (req, res) => {
             try {
-                // 检查 id 是否是有效的 ObjectId 格式
+                // Validate that the provided ID is a valid MongoDB ObjectId
                 if (!(0, mongoose_1.isObjectIdOrHexString)(req.params.id)) {
-                    return res.status(400).json({ success: false, message: "Invalid record ID" });
+                    return res.status(400).json({ success: false, message: "Invalid record ID format" });
                 }
                 const record = await messageRecordModel_1.MessageRecord.findById(req.params.id);
                 if (!record) {
@@ -160,12 +149,12 @@ class MessageRecordController {
          * @route PUT /api/message-record/:id
          * @desc Updates the status of a message record
          * @access Private
+         * @returns A JSON response with the updated record.
          */
         this.updateRecord = async (req, res) => {
             try {
-                // 检查 id 是否是有效的 ObjectId 格式
                 if (!(0, mongoose_1.isObjectIdOrHexString)(req.params.id)) {
-                    return res.status(400).json({ success: false, message: "Invalid record ID" });
+                    return res.status(400).json({ success: false, message: "Invalid record ID format" });
                 }
                 const updatedRecord = await messageRecordModel_1.MessageRecord.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
                 if (!updatedRecord) {
@@ -182,10 +171,14 @@ class MessageRecordController {
          * @route GET /api/message/statistics/overall
          * @desc Retrieves overall message push statistics
          * @access Private
+         * @returns A JSON response with overall statistics.
          */
         this.getOverallStatistics = async (req, res) => {
             try {
-                const stats = await this.messageRecordService.getOverallStatistics();
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getOverallStatistics(start, end);
                 return res.status(200).json({ success: true, data: stats });
             }
             catch (error) {
@@ -197,10 +190,14 @@ class MessageRecordController {
          * @route GET /api/message/statistics/by-activity
          * @desc Retrieves message push statistics grouped by activity
          * @access Private
+         * @returns A JSON response with activity-based statistics.
          */
         this.getStatisticsByActivity = async (req, res) => {
             try {
-                const stats = await this.messageRecordService.getStatisticsByActivity();
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getStatisticsByActivity(start, end);
                 return res.status(200).json({ success: true, data: stats });
             }
             catch (error) {
@@ -212,10 +209,14 @@ class MessageRecordController {
          * @route GET /api/message/statistics/by-strategy
          * @desc Retrieves message push statistics grouped by strategy
          * @access Private
+         * @returns A JSON response with strategy-based statistics.
          */
         this.getStatisticsByStrategy = async (req, res) => {
             try {
-                const stats = await this.messageRecordService.getStatisticsByStrategy();
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getStatisticsByStrategy(start, end);
                 return res.status(200).json({ success: true, data: stats });
             }
             catch (error) {
@@ -227,14 +228,56 @@ class MessageRecordController {
          * @route GET /api/message/statistics/by-template
          * @desc Retrieves message push statistics grouped by template
          * @access Private
+         * @returns A JSON response with template-based statistics.
          */
         this.getStatisticsByTemplate = async (req, res) => {
             try {
-                const stats = await this.messageRecordService.getStatisticsByTemplate();
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getStatisticsByTemplate(start, end);
                 return res.status(200).json({ success: true, data: stats });
             }
             catch (error) {
-                console.error("Error fetching statistics by strategy:", error);
+                console.error("Error fetching statistics by template:", error);
+                return res.status(500).json({ success: false, message: "Server error", error: error.message });
+            }
+        };
+        /**
+         * @route GET /api/message/statistics/by-cc
+         * @desc Retrieves message push statistics grouped by cc
+         * @access Private
+         * @returns A JSON response with cc-based statistics.
+         */
+        this.getStatisticsByCc = async (req, res) => {
+            try {
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getStatisticsByCc(start, end);
+                return res.status(200).json({ success: true, data: stats });
+            }
+            catch (error) {
+                console.error("Error fetching statistics by cc:", error);
+                return res.status(500).json({ success: false, message: "Server error", error: error.message });
+            }
+        };
+        /**
+         * @route GET /api/message/statistics/by-image
+         * @desc Retrieves message push statistics grouped by image
+         * @access Private
+         * @returns A JSON response with image-based statistics.
+         */
+        this.getStatisticsByImage = async (req, res) => {
+            try {
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getStatisticsByImage(start, end);
+                return res.status(200).json({ success: true, data: stats });
+            }
+            catch (error) {
+                console.error("Error fetching statistics by image:", error);
                 return res.status(500).json({ success: false, message: "Server error", error: error.message });
             }
         };
@@ -242,10 +285,14 @@ class MessageRecordController {
          * @route GET /api/message/statistics/daily-trends
          * @desc Retrieves daily sent trend statistics
          * @access Private
+         * @returns A JSON response with daily trend statistics.
          */
         this.getDailySentTrends = async (req, res) => {
             try {
-                const stats = await this.messageRecordService.getDailySentTrends();
+                const { startDate, endDate } = req.query;
+                const start = startDate ? new Date(startDate) : undefined;
+                const end = endDate ? new Date(endDate) : undefined;
+                const stats = await this.messageRecordService.getDailySentTrends(start, end);
                 return res.status(200).json({ success: true, data: stats });
             }
             catch (error) {
@@ -253,6 +300,36 @@ class MessageRecordController {
                 return res.status(500).json({ success: false, message: "Server error", error: error.message });
             }
         };
+        /**
+         * @route GET /api/message/statistics/multi-dimensional
+         * @desc Retrieves multi-dimensional message push statistics
+         * @access Private
+         * @returns A JSON response with multi-dimensional statistics.
+         */
+        this.getMultiDimensionalStatistics = async (req, res) => {
+            try {
+                const { startDate, endDate, templateName, strategyName, cc, image } = req.query;
+                const filters = {};
+                if (startDate)
+                    filters.startDate = new Date(startDate);
+                if (endDate)
+                    filters.endDate = new Date(endDate);
+                if (templateName)
+                    filters.templateName = templateName;
+                if (strategyName)
+                    filters.strategyName = strategyName;
+                if (cc)
+                    filters.cc = cc;
+                if (image)
+                    filters.image = image;
+                const stats = await this.messageRecordService.getMultiDimensionalStatistics(filters);
+                return res.status(200).json({ success: true, data: stats });
+            }
+            catch (error) {
+                console.error("Error fetching multi-dimensional statistics:", error);
+                return res.status(500).json({ success: false, message: "Server error", error: error.message });
+            }
+        };
         this.messageRecordService = new messageRecordService_1.MessageRecordService();
     }
 }

+ 3 - 0
oms/dist/src/routes/apiRoutes.js

@@ -24,7 +24,10 @@ router.get("/message/statistics/overall", messageRecordController_1.default.getO
 router.get("/message/statistics/by-activity", messageRecordController_1.default.getStatisticsByActivity);
 router.get("/message/statistics/by-strategy", messageRecordController_1.default.getStatisticsByStrategy);
 router.get("/message/statistics/by-template", messageRecordController_1.default.getStatisticsByTemplate);
+router.get("/message/statistics/by-cc", messageRecordController_1.default.getStatisticsByCc);
+router.get("/message/statistics/by-image", messageRecordController_1.default.getStatisticsByImage);
 router.get("/message/statistics/daily-trends", messageRecordController_1.default.getDailySentTrends);
+router.get("/message/statistics/multi-dimensional", messageRecordController_1.default.getMultiDimensionalStatistics); // 新增:多维度统计路由
 // 应用认证中间件,保护所有下面的路由
 router.use(authMiddleware_1.authMiddleware);
 // User routes

+ 879 - 374
oms/dist/src/services/messageRecordService.js

@@ -78,84 +78,111 @@ class MessageRecordService {
     }
     /**
      * 获取整体消息推送统计数据
+     * @param startDate 可选的开始日期
+     * @param endDate 可选的结束日期
      */
-    async getOverallStatistics() {
+    async getOverallStatistics(startDate, endDate) {
         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];
         }
         catch (error) {
@@ -165,96 +192,123 @@ class MessageRecordService {
     }
     /**
      * 按活动获取消息统计数据
+     * @param startDate 可选的开始日期
+     * @param endDate 可选的结束日期
      */
-    async getStatisticsByActivity() {
+    async getStatisticsByActivity(startDate, endDate) {
         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",
-                        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;
         }
         catch (error) {
@@ -264,96 +318,123 @@ class MessageRecordService {
     }
     /**
      * 按策略获取消息统计数据
+     * @param startDate 可选的开始日期
+     * @param endDate 可选的结束日期
      */
-    async getStatisticsByStrategy() {
+    async getStatisticsByStrategy(startDate, endDate) {
         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",
-                        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;
         }
         catch (error) {
@@ -363,201 +444,625 @@ class MessageRecordService {
     }
     /**
      * 按模板获取消息统计数据
+     * @param startDate 可选的开始日期
+     * @param endDate 可选的结束日期
      */
-    async getStatisticsByTemplate() {
+    async getStatisticsByTemplate(startDate, endDate) {
         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",
-                        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;
         }
         catch (error) {
-            console.error("Error fetching statistics by template:", error);
+            console.error("Error fetching daily sent trends:", error);
             return [];
         }
     }
-    // 按时间维度的趋势分析,每日统计
-    async getDailySentTrends() {
+    /**
+     * 按多个维度获取消息推送统计数据。
+     * 支持日期范围、模板、图片、国家和策略的组合查询。
+     * @param filters 包含查询条件的过滤器对象
+     * @returns 多维度统计结果
+     */
+    async getMultiDimensionalStatistics(filters) {
         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",
                         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;
         }
         catch (error) {
-            console.error("Error fetching daily sent trends:", error);
+            console.error("Error fetching statistics by image:", error);
             return [];
         }
     }

+ 43 - 0
oms/public/app/3rdpartylicenses.txt

@@ -1,4 +1,47 @@
 
+--------------------------------------------------------------------------------
+Package: i18n-iso-countries
+License: "MIT"
+
+The MIT License (MIT)
+
+Copyright (c) 2016 widdix GmbH
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+--------------------------------------------------------------------------------
+Package: diacritics
+License: "MIT"
+
+The MIT License (Expat)
+
+Copyright (c) 2014 Andrew Kelley
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
 --------------------------------------------------------------------------------
 Package: @angular/core
 License: "MIT"

+ 1 - 1
oms/public/app/index.html

@@ -9,5 +9,5 @@
   <style>body,html{width:100%;height:100%}*,:after,:before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}body{margin:0;color:#000000d9;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-variant:tabular-nums;line-height:1.5715;background-color:#fff;font-feature-settings:"tnum"}html{--antd-wave-shadow-color:#1890ff;--scroll-bar:0}</style><link rel="stylesheet" href="styles-LXBSU6DF.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-LXBSU6DF.css"></noscript></head>
   <body>
     <app-root></app-root>
-  <script src="polyfills-B6TNHZQ6.js" type="module"></script><script src="main-MTEOIJPW.js" type="module"></script></body>
+  <script src="polyfills-B6TNHZQ6.js" type="module"></script><script src="main-6WK7WHOW.js" type="module"></script></body>
 </html>

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
oms/public/app/main-6WK7WHOW.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
oms/public/app/main-MTEOIJPW.js


+ 114 - 42
oms/src/controllers/messageRecordController.ts

@@ -1,5 +1,3 @@
-// oms/src/controllers/messageRecordController.ts
-
 import { Request, Response } from "express";
 import { isObjectIdOrHexString } from "mongoose";
 import { MessageRecord, IMessageRecord } from "../models/messageRecordModel";
@@ -16,6 +14,7 @@ class MessageRecordController {
    * @route POST /api/message-record
    * @desc Creates a new message record
    * @access Private
+   * @returns A JSON response with the newly created record or an error message.
    */
   public createRecord = async (req: Request, res: Response): Promise<Response> => {
     try {
@@ -30,17 +29,21 @@ class MessageRecordController {
 
   /**
    * @route GET /api/message-records
-   * @desc Retrieves all message records with pagination and optional filters
+   * @desc Retrieves all message records with pagination and optional filters.
+   * Filters can be applied by uid, activityName, strategyName, templateName, status, and various date fields.
+   * Date fields can be filtered by a single date or a range (e.g., "?createdAt=2023-01-01,2023-01-31").
    * @access Private
+   * @returns A JSON response with paginated records and pagination metadata.
    */
   public getPaginatedRecords = async (req: Request, res: Response): Promise<Response> => {
-    const { page = 1, limit = 30, uid, activityName, templateName, strategyName, status, startDate, endDate } = req.query;
+    const { page = 1, limit = 30, uid, activityName, templateName, strategyName, status } = req.query;
 
     const pageNum = parseInt(page as string, 10);
     const limitNum = parseInt(limit as string, 10);
 
-    // 动态构建查询过滤器
+    // Build the query filters dynamically based on request parameters
     const filters: any = {};
+
     if (uid) {
       filters.uid = uid;
     }
@@ -64,51 +67,34 @@ class MessageRecordController {
       filters.templateName = templateName;
     }
 
-    // 定义所有可查询的日期字段
+    // List of all date fields that can be filtered
     const dateQueryKeys: (keyof IMessageRecord)[] = ["plannedSendAt", "actualSendAt", "deliveredAt", "openedAt", "createdAt", "updatedAt"];
 
-    // 遍历所有日期字段,处理单个日期或日期范围
+    // Iterate through date fields to handle single dates or date ranges
     dateQueryKeys.forEach((key) => {
       const queryValue = req.query[key as string] as string;
       if (queryValue) {
         const dates = queryValue.split(",");
-        if (dates.length === 2) {
-          const startDate = new Date(dates[0]);
-          const endDate = new Date(dates[1]);
+        const startDate = new Date(dates[0]);
+        const endDate = dates.length > 1 ? new Date(dates[1]) : null;
 
-          // 确保日期有效
-          if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {
+        if (!isNaN(startDate.getTime())) {
+          if (endDate && !isNaN(endDate.getTime())) {
+            // Filter by date range (e.g., plannedSendAt=2023-01-01,2023-01-31)
             filters[key] = {
               $gte: startDate,
               $lte: endDate,
             };
           } else {
-            console.warn(`[API] Invalid date range format for ${key}: ${queryValue}. Skipping.`);
+            // Filter by a single date
+            filters[key] = startDate;
           }
         } else {
-          // 如果不是范围,则按单个日期精确匹配
-          const singleDate = new Date(queryValue);
-          if (!isNaN(singleDate.getTime())) {
-            filters[key] = singleDate;
-          }
+          console.warn(`[API] Invalid date format for ${key}: ${queryValue}. Skipping.`);
         }
       }
     });
 
-    // 保留对原有startDate/endDate参数的兼容性,并将其应用于createdAt
-    if (startDate || endDate) {
-      // 确保 createdAt 过滤器不存在冲突,如果已通过范围查询设置,则跳过
-      if (!filters.createdAt) {
-        filters.createdAt = {};
-      }
-      if (startDate) {
-        filters.createdAt.$gte = new Date(startDate as string);
-      }
-      if (endDate) {
-        filters.createdAt.$lte = new Date(endDate as string);
-      }
-    }
-
     try {
       const records = await MessageRecord.find(filters)
         .sort({ createdAt: -1 })
@@ -137,6 +123,7 @@ class MessageRecordController {
    * @route GET /api/message-records/user/:uid
    * @desc Retrieves message records by user UID
    * @access Private
+   * @returns A JSON response with the records for a specific user.
    */
   public getRecordsByUid = async (req: Request, res: Response): Promise<Response> => {
     try {
@@ -155,12 +142,13 @@ class MessageRecordController {
    * @route GET /api/message-record/:id
    * @desc Retrieves a single message record by ID
    * @access Private
+   * @returns A JSON response with the single record found or a "not found" message.
    */
   public getRecordById = async (req: Request, res: Response): Promise<Response> => {
     try {
-      // 检查 id 是否是有效的 ObjectId 格式
+      // Validate that the provided ID is a valid MongoDB ObjectId
       if (!isObjectIdOrHexString(req.params.id)) {
-        return res.status(400).json({ success: false, message: "Invalid record ID" });
+        return res.status(400).json({ success: false, message: "Invalid record ID format" });
       }
       const record = await MessageRecord.findById(req.params.id);
       if (!record) {
@@ -177,12 +165,12 @@ class MessageRecordController {
    * @route PUT /api/message-record/:id
    * @desc Updates the status of a message record
    * @access Private
+   * @returns A JSON response with the updated record.
    */
   public updateRecord = async (req: Request, res: Response): Promise<Response> => {
     try {
-      // 检查 id 是否是有效的 ObjectId 格式
       if (!isObjectIdOrHexString(req.params.id)) {
-        return res.status(400).json({ success: false, message: "Invalid record ID" });
+        return res.status(400).json({ success: false, message: "Invalid record ID format" });
       }
       const updatedRecord = await MessageRecord.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
       if (!updatedRecord) {
@@ -199,10 +187,14 @@ class MessageRecordController {
    * @route GET /api/message/statistics/overall
    * @desc Retrieves overall message push statistics
    * @access Private
+   * @returns A JSON response with overall statistics.
    */
   public getOverallStatistics = async (req: Request, res: Response): Promise<Response> => {
     try {
-      const stats = await this.messageRecordService.getOverallStatistics();
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getOverallStatistics(start, end);
       return res.status(200).json({ success: true, data: stats });
     } catch (error: any) {
       console.error("Error fetching overall statistics:", error);
@@ -214,10 +206,14 @@ class MessageRecordController {
    * @route GET /api/message/statistics/by-activity
    * @desc Retrieves message push statistics grouped by activity
    * @access Private
+   * @returns A JSON response with activity-based statistics.
    */
   public getStatisticsByActivity = async (req: Request, res: Response): Promise<Response> => {
     try {
-      const stats = await this.messageRecordService.getStatisticsByActivity();
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getStatisticsByActivity(start, end);
       return res.status(200).json({ success: true, data: stats });
     } catch (error: any) {
       console.error("Error fetching statistics by activity:", error);
@@ -229,10 +225,14 @@ class MessageRecordController {
    * @route GET /api/message/statistics/by-strategy
    * @desc Retrieves message push statistics grouped by strategy
    * @access Private
+   * @returns A JSON response with strategy-based statistics.
    */
   public getStatisticsByStrategy = async (req: Request, res: Response): Promise<Response> => {
     try {
-      const stats = await this.messageRecordService.getStatisticsByStrategy();
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getStatisticsByStrategy(start, end);
       return res.status(200).json({ success: true, data: stats });
     } catch (error: any) {
       console.error("Error fetching statistics by strategy:", error);
@@ -244,13 +244,55 @@ class MessageRecordController {
    * @route GET /api/message/statistics/by-template
    * @desc Retrieves message push statistics grouped by template
    * @access Private
+   * @returns A JSON response with template-based statistics.
    */
   public getStatisticsByTemplate = async (req: Request, res: Response): Promise<Response> => {
     try {
-      const stats = await this.messageRecordService.getStatisticsByTemplate();
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getStatisticsByTemplate(start, end);
       return res.status(200).json({ success: true, data: stats });
     } catch (error: any) {
-      console.error("Error fetching statistics by strategy:", error);
+      console.error("Error fetching statistics by template:", error);
+      return res.status(500).json({ success: false, message: "Server error", error: error.message });
+    }
+  };
+
+  /**
+   * @route GET /api/message/statistics/by-cc
+   * @desc Retrieves message push statistics grouped by cc
+   * @access Private
+   * @returns A JSON response with cc-based statistics.
+   */
+  public getStatisticsByCc = async (req: Request, res: Response): Promise<Response> => {
+    try {
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getStatisticsByCc(start, end);
+      return res.status(200).json({ success: true, data: stats });
+    } catch (error: any) {
+      console.error("Error fetching statistics by cc:", error);
+      return res.status(500).json({ success: false, message: "Server error", error: error.message });
+    }
+  };
+
+  /**
+   * @route GET /api/message/statistics/by-image
+   * @desc Retrieves message push statistics grouped by image
+   * @access Private
+   * @returns A JSON response with image-based statistics.
+   */
+  public getStatisticsByImage = async (req: Request, res: Response): Promise<Response> => {
+    try {
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getStatisticsByImage(start, end);
+      return res.status(200).json({ success: true, data: stats });
+    } catch (error: any) {
+      console.error("Error fetching statistics by image:", error);
       return res.status(500).json({ success: false, message: "Server error", error: error.message });
     }
   };
@@ -259,16 +301,46 @@ class MessageRecordController {
    * @route GET /api/message/statistics/daily-trends
    * @desc Retrieves daily sent trend statistics
    * @access Private
+   * @returns A JSON response with daily trend statistics.
    */
   public getDailySentTrends = async (req: Request, res: Response): Promise<Response> => {
     try {
-      const stats = await this.messageRecordService.getDailySentTrends();
+      const { startDate, endDate } = req.query;
+      const start = startDate ? new Date(startDate as string) : undefined;
+      const end = endDate ? new Date(endDate as string) : undefined;
+      const stats = await this.messageRecordService.getDailySentTrends(start, end);
       return res.status(200).json({ success: true, data: stats });
     } catch (error: any) {
       console.error("Error fetching daily sent trends:", error);
       return res.status(500).json({ success: false, message: "Server error", error: error.message });
     }
   };
+
+  /**
+   * @route GET /api/message/statistics/multi-dimensional
+   * @desc Retrieves multi-dimensional message push statistics
+   * @access Private
+   * @returns A JSON response with multi-dimensional statistics.
+   */
+  public getMultiDimensionalStatistics = async (req: Request, res: Response): Promise<Response> => {
+    try {
+      const { startDate, endDate, templateName, strategyName, cc, image } = req.query;
+
+      const filters: { [key: string]: any } = {};
+      if (startDate) filters.startDate = new Date(startDate as string);
+      if (endDate) filters.endDate = new Date(endDate as string);
+      if (templateName) filters.templateName = templateName;
+      if (strategyName) filters.strategyName = strategyName;
+      if (cc) filters.cc = cc;
+      if (image) filters.image = image;
+
+      const stats = await this.messageRecordService.getMultiDimensionalStatistics(filters);
+      return res.status(200).json({ success: true, data: stats });
+    } catch (error: any) {
+      console.error("Error fetching multi-dimensional statistics:", error);
+      return res.status(500).json({ success: false, message: "Server error", error: error.message });
+    }
+  };
 }
 
 export default new MessageRecordController();

+ 3 - 0
oms/src/routes/apiRoutes.ts

@@ -23,7 +23,10 @@ router.get("/message/statistics/overall", messageRecordController.getOverallStat
 router.get("/message/statistics/by-activity", messageRecordController.getStatisticsByActivity);
 router.get("/message/statistics/by-strategy", messageRecordController.getStatisticsByStrategy);
 router.get("/message/statistics/by-template", messageRecordController.getStatisticsByTemplate);
+router.get("/message/statistics/by-cc", messageRecordController.getStatisticsByCc);
+router.get("/message/statistics/by-image", messageRecordController.getStatisticsByImage);
 router.get("/message/statistics/daily-trends", messageRecordController.getDailySentTrends);
+router.get("/message/statistics/multi-dimensional", messageRecordController.getMultiDimensionalStatistics); // 新增:多维度统计路由
 
 // 应用认证中间件,保护所有下面的路由
 router.use(authMiddleware);

+ 580 - 69
oms/src/services/messageRecordService.ts

@@ -93,10 +93,45 @@ export class MessageRecordService {
 
   /**
    * 获取整体消息推送统计数据
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
    */
-  public async getOverallStatistics() {
+  public async getOverallStatistics(startDate?: Date, endDate?: Date) {
     try {
-      const result = await MessageRecord.aggregate([
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      pipeline.push(
         // 1. 按状态和inforeground字段分组,计算每个类别的记录数
         {
           $group: {
@@ -109,22 +144,18 @@ export class MessageRecordService {
           $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] },
             },
-            // 打开数:status === 3
             opened: {
               $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
             },
             failed: {
               $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
             },
-            // 展示数:status >= 2 并且 inforeground = false
             displayCount: {
               $sum: {
                 $cond: [
@@ -148,30 +179,26 @@ export class MessageRecordService {
             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"] }],
             },
           },
-        },
-      ]);
+        }
+      );
 
+      const result = await MessageRecord.aggregate(pipeline);
       return result[0];
     } catch (error) {
       console.error("Error fetching overall statistics:", error);
@@ -181,10 +208,45 @@ export class MessageRecordService {
 
   /**
    * 按活动获取消息统计数据
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
    */
-  public async getStatisticsByActivity() {
+  public async getStatisticsByActivity(startDate?: Date, endDate?: Date) {
     try {
-      const results = await MessageRecord.aggregate([
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      pipeline.push(
         // 1. 根据 activityId, status, 和 inforeground 进行分组
         {
           $group: {
@@ -203,22 +265,18 @@ export class MessageRecordService {
             _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] },
             },
             failed: {
               $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
             },
-            // 展示数:status >= 2 并且 inforeground = false
             displayCount: {
               $sum: {
                 $cond: [
@@ -244,34 +302,30 @@ export class MessageRecordService {
             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"] }],
             },
           },
         },
-        // 4. (可选) 按 deliveredRate 降序排序
+        // 4. 按 deliveredRate 降序排序
         {
           $sort: { deliveredRate: -1 },
-        },
-      ]);
+        }
+      );
 
+      const results = await MessageRecord.aggregate(pipeline);
       return results;
     } catch (error) {
       console.error("Error fetching statistics by activity:", error);
@@ -281,10 +335,45 @@ export class MessageRecordService {
 
   /**
    * 按策略获取消息统计数据
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
    */
-  public async getStatisticsByStrategy() {
+  public async getStatisticsByStrategy(startDate?: Date, endDate?: Date) {
     try {
-      const results = await MessageRecord.aggregate([
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      pipeline.push(
         // 1. 根据 strategyId, status, 和 inforeground 进行分组
         {
           $group: {
@@ -303,22 +392,18 @@ export class MessageRecordService {
             _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] },
             },
             failed: {
               $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
             },
-            // 展示数:status >= 2 并且 inforeground = false
             displayCount: {
               $sum: {
                 $cond: [
@@ -344,33 +429,29 @@ export class MessageRecordService {
             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"] }],
             },
           },
         },
-        // 4. (可选) 按 deliveredRate 降序排序
+        // 4. 按 deliveredRate 降序排序
         {
           $sort: { deliveredRate: -1 },
-        },
-      ]);
+        }
+      );
+      const results = await MessageRecord.aggregate(pipeline);
       return results;
     } catch (error) {
       console.error("Error fetching statistics by strategy:", error);
@@ -380,10 +461,45 @@ export class MessageRecordService {
 
   /**
    * 按模板获取消息统计数据
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
    */
-  public async getStatisticsByTemplate() {
+  public async getStatisticsByTemplate(startDate?: Date, endDate?: Date) {
     try {
-      const results = await MessageRecord.aggregate([
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      pipeline.push(
         // 1. 根据 templateId, status, 和 inforeground 进行分组
         {
           $group: {
@@ -402,22 +518,18 @@ export class MessageRecordService {
             _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] },
             },
             failed: {
               $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
             },
-            // 展示数:status >= 2 并且 inforeground = false
             displayCount: {
               $sum: {
                 $cond: [
@@ -443,33 +555,29 @@ export class MessageRecordService {
             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"] }],
             },
           },
         },
-        // 4. (可选) 按 deliveredRate 降序排序
+        // 4. 按 deliveredRate 降序排序
         {
           $sort: { deliveredRate: -1 },
-        },
-      ]);
+        }
+      );
+      const results = await MessageRecord.aggregate(pipeline);
       return results;
     } catch (error) {
       console.error("Error fetching statistics by template:", error);
@@ -477,10 +585,47 @@ export class MessageRecordService {
     }
   }
 
-  // 按时间维度的趋势分析,每日统计
-  public async getDailySentTrends() {
+  /**
+   * 按时间维度的趋势分析,每日统计
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
+   */
+  public async getDailySentTrends(startDate?: Date, endDate?: Date) {
     try {
-      const results = await MessageRecord.aggregate([
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            actualSendAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            actualSendAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      pipeline.push(
         // 1. 将 actualSendAt 字段转换为日期,忽略时分秒
         {
           $project: {
@@ -504,22 +649,18 @@ export class MessageRecordService {
           $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] },
             },
-            // 打开数:status === 3
             opened: {
               $sum: { $cond: [{ $eq: ["$_id.status", 3] }, "$count", 0] },
             },
             failed: {
               $sum: { $cond: [{ $eq: ["$_id.status", -1] }, "$count", 0] },
             },
-            // 展示数:status >= 2 并且 inforeground = false
             displayCount: {
               $sum: {
                 $cond: [
@@ -544,23 +685,18 @@ export class MessageRecordService {
             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 invalidation rate
             tokenInvalidationRate: {
               $cond: [{ $eq: ["$totalRecords", 0] }, 0, { $divide: ["$failed", "$totalRecords"] }],
             },
@@ -569,12 +705,387 @@ export class MessageRecordService {
         // 5. 按日期升序排序
         {
           $sort: { date: 1 },
-        },
-      ]);
+        }
+      );
+      const results = await MessageRecord.aggregate(pipeline);
       return results;
     } catch (error) {
       console.error("Error fetching daily sent trends:", 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: {
+          _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 MessageRecord.aggregate(pipeline);
+      return results;
+    } catch (error) {
+      console.error("Error fetching multi-dimensional statistics:", error);
+      return [];
+    }
+  }
+
+  /**
+   * 按国家代码获取消息统计数据
+   * @param startDate 可选的开始日期
+   * @param endDate 可选的结束日期
+   */
+  public async getStatisticsByCc(startDate?: Date, endDate?: Date) {
+    try {
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } 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 },
+          },
+        },
+        // 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,
+                ],
+              },
+            },
+          },
+        },
+        // 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 可选的结束日期
+   */
+  public async getStatisticsByImage(startDate?: Date, endDate?: Date) {
+    try {
+      const pipeline: any[] = [];
+      // 如果提供了日期,添加 $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,
+            },
+          },
+        });
+      } else if (startDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $gte: startDate,
+            },
+          },
+        });
+      } else if (endDate) {
+        pipeline.push({
+          $match: {
+            createdAt: {
+              $lte: endDate,
+            },
+          },
+        });
+      }
+
+      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,
+                ],
+              },
+            },
+          },
+        },
+        // 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 [];
+    }
+  }
 }

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff