如何在MongoDB查询中传入特定数据?网站访问图表查询的TS实现需求
解决方案:参数化MongoDB查询 + TypeScript集成
我来帮你把固定日期的查询改成支持前端传参的版本,同时集成到TypeScript代码里,分步骤拆解:
一、先把固定查询改成参数化版本
针对三个图表场景,我们把查询中的硬编码日期替换为动态参数,同时通过日期计算确定精准的时间范围:
1. 选定日期按小时统计访问量
需要接收目标日期(如 YYYY-MM-DD 格式字符串),查询该日期00:00到次日00:00的记录,按小时分组统计:
// 参数:targetDate: string (格式 "YYYY-MM-DD") const startOfDay = new Date(`${targetDate}T00:00:00Z`); const endOfDay = new Date(startOfDay); endOfDay.setDate(endOfDay.getDate() + 1); db.visits.aggregate([ { $match: { timestamp: { $gte: startOfDay, $lt: endOfDay } } }, { $group: { _id: { $hour: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]);
2. 选定月份按天统计访问量
需要接收目标月份(如 YYYY-MM 格式字符串),查询该月第一天00:00到次月第一天00:00的记录,按天分组:
// 参数:targetMonth: string (格式 "YYYY-MM") const [year, month] = targetMonth.split("-").map(Number); const startOfMonth = new Date(Date.UTC(year, month - 1, 1)); const endOfMonth = new Date(Date.UTC(year, month, 1)); db.visits.aggregate([ { $match: { timestamp: { $gte: startOfMonth, $lt: endOfMonth } } }, { $group: { _id: { $dayOfMonth: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]);
3. 选定年份按月统计访问量
需要接收目标年份(如 YYYY 格式字符串),查询该年1月1日00:00到次年1月1日00:00的记录,按月分组:
// 参数:targetYear: string (格式 "YYYY") const year = Number(targetYear); const startOfYear = new Date(Date.UTC(year, 0, 1)); const endOfYear = new Date(Date.UTC(year + 1, 0, 1)); db.visits.aggregate([ { $match: { timestamp: { $gte: startOfYear, $lt: endOfYear } } }, { $group: { _id: { $month: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]);
二、TypeScript代码集成
接下来把这些逻辑封装成类型安全的TS函数,同时处理前端传入的参数验证和格式转换:
1. 定义类型和工具函数
先明确参数类型,再加一个日期格式验证函数,避免无效参数导致的错误:
// 定义三种查询的参数类型 type HourlyQueryParams = { type: "hourly"; targetDate: string }; type DailyQueryParams = { type: "daily"; targetMonth: string }; type MonthlyQueryParams = { type: "monthly"; targetYear: string }; type VisitStatsQueryParams = HourlyQueryParams | DailyQueryParams | MonthlyQueryParams; // 验证日期格式的工具函数 const isValidDateString = (str: string, format: "YYYY-MM-DD" | "YYYY-MM" | "YYYY"): boolean => { switch (format) { case "YYYY-MM-DD": return /^\d{4}-\d{2}-\d{2}$/.test(str) && !isNaN(new Date(str).getTime()); case "YYYY-MM": return /^\d{4}-\d{2}$/.test(str) && !isNaN(new Date(`${str}-01`).getTime()); case "YYYY": return /^\d{4}$/.test(str) && !isNaN(new Date(`${str}-01-01`).getTime()); default: return false; } };
2. 封装MongoDB查询函数
假设你用的是mongodb官方TS驱动,封装一个统一的查询函数:
import { MongoClient, Collection } from "mongodb"; // 全局存储集合实例(实际项目中建议用依赖注入) let visitsCollection: Collection; // 初始化MongoDB连接(示例) const initMongo = async () => { const client = await MongoClient.connect("your-mongodb-uri"); visitsCollection = client.db("your-db-name").collection("visits"); }; // 获取访问统计的核心函数 export const getVisitStats = async (params: VisitStatsQueryParams) => { // 先验证参数格式 switch (params.type) { case "hourly": if (!isValidDateString(params.targetDate, "YYYY-MM-DD")) { throw new Error("无效的日期格式,请传入YYYY-MM-DD格式的字符串"); } break; case "daily": if (!isValidDateString(params.targetMonth, "YYYY-MM")) { throw new Error("无效的月份格式,请传入YYYY-MM格式的字符串"); } break; case "monthly": if (!isValidDateString(params.targetYear, "YYYY")) { throw new Error("无效的年份格式,请传入YYYY格式的字符串"); } break; } let aggregatePipeline; // 根据参数类型构建对应的聚合管道 switch (params.type) { case "hourly": { const startOfDay = new Date(`${params.targetDate}T00:00:00Z`); const endOfDay = new Date(startOfDay); endOfDay.setDate(endOfDay.getDate() + 1); aggregatePipeline = [ { $match: { timestamp: { $gte: startOfDay, $lt: endOfDay } } }, { $group: { _id: { $hour: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]; break; } case "daily": { const [year, month] = params.targetMonth.split("-").map(Number); const startOfMonth = new Date(Date.UTC(year, month - 1, 1)); const endOfMonth = new Date(Date.UTC(year, month, 1)); aggregatePipeline = [ { $match: { timestamp: { $gte: startOfMonth, $lt: endOfMonth } } }, { $group: { _id: { $dayOfMonth: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]; break; } case "monthly": { const year = Number(params.targetYear); const startOfYear = new Date(Date.UTC(year, 0, 1)); const endOfYear = new Date(Date.UTC(year + 1, 0, 1)); aggregatePipeline = [ { $match: { timestamp: { $gte: startOfYear, $lt: endOfYear } } }, { $group: { _id: { $month: "$timestamp" }, count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]; break; } } // 执行聚合查询并返回结果 return await visitsCollection.aggregate(aggregatePipeline).toArray(); };
3. 和图表端的交互
前端图表组件(比如ECharts、Chart.js)在用户选择日期/月份/年份后,将参数传给后端TS接口。这里用Express写个示例接口:
import express from "express"; const app = express(); app.use(express.json()); // 初始化Mongo连接 initMongo().then(() => console.log("MongoDB连接成功")); app.post("/api/visit-stats", async (req, res) => { try { const params = req.body as VisitStatsQueryParams; const stats = await getVisitStats(params); res.json(stats); } catch (error) { res.status(400).json({ error: (error as Error).message }); } }); app.listen(3000, () => console.log("服务运行在3000端口"));
前端调用示例(React为例):
// 用户选择日期后触发查询 const fetchHourlyStats = async (targetDate: string) => { const response = await fetch("/api/visit-stats", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type: "hourly", targetDate }) }); const stats = await response.json(); // 更新图表数据 setChartData(stats); };
三、性能优化建议
- 给
timestamp字段创建单字段索引,加速时间范围查询:db.visits.createIndex({ timestamp: 1 }); - 如果数据量极大,可以考虑预聚合(比如每天定时统计并存储结果),减少实时聚合的计算压力。
内容的提问来源于stack exchange,提问作者William.P
相关产品推荐
相关产品推荐

