接上一篇 前端监控,这篇是后端的实现
统计数据的 schema
const mongoose = require('mongoose');
const statSchema = mongoose.Schema({
/* 访问的页面信息 */
// 域名
domain: String,
// 当前 URL 地址
url: String,
// 当前页面标题
title: String,
// 上一个访问页面 URL 地址
referrer: String,
/* 页面性能信息 */
// DNS解析时间
dnsTime: Number,
// TCP建立时间
tcpTime: Number,
// 首屏时间
firstPaintTime: Number,
// DOM渲染完成时间
domRenderTime: Number,
// 页面onload时间
loadTime: Number,
/* 客户端设备信息 */
// 屏幕高度
sh: Number,
// 屏幕宽度
sw: Number,
// 屏幕颜色深度
scd: Number,
// 客户端语言
lang: String,
// 客户端标识
userAgent: String,
// 客户端ip地址
clientIp: String,
createdAt: {
type: Date,
default: Date.now,
},
});
数据处理
接收前端传递过来的统计数据
const Stat = require('../../models/stat');
// @desc 通过 GET 接收前端传递过来的统计数据
// @route GET /v1/stat?args=
// @access Public
exports.getStat = async (req, res, next) => {
try {
const obj = {};
// 获取请求中查询参数
const params = decodeURIComponent(req.query.args).split('&');
// 将a=1&b=2 转换成 {a:1,b:2}
params.forEach((item) => {
const a = item.split('=');
// obj[a[0]] = a[1];
[, obj[a[0]]] = a;
});
// 获取客户端IP地址
obj.clientIp = req.ip;
obj.userAgent = req.headers['user-agent'];
await Stat.create(obj);
res.send('ok');
} catch (error) {
next(error);
}
};
查询统计数据
// @desc 查询统计数据
// @route GET /v1/statistics
// @access Public
exports.getStatistics = async (req, res, next) => {
// 将日期转换为时区为北京时间的日期
function dateFormat(date) {
if (date) {
return new Date(date).toLocaleDateString('zh-CN', {
timeZone: 'Asia/Shanghai',
});
}
return new Date().toLocaleDateString('zh-CN', {
timeZone: 'Asia/Shanghai',
});
}
try {
const params = {};
// 按域名筛选
if (req.query.domain) {
params.domain = req.query.domain;
}
const limit = req.query.limit || 10;
// 日期筛选
// 如果只有一个日期参数,就只筛选出该日期的当天统计数据
// 如果没有日期参数返回今天的统计数据
let dateStart;
let dateEnd;
if (req.query.dateStart && req.query.dateEnd) {
dateStart = dateFormat(req.query.dateStart);
dateEnd = dateFormat(req.query.dateEnd);
} else if (req.query.dateStart && !req.query.dateEnd) {
dateStart = dateFormat(req.query.dateStart);
dateEnd = dateFormat(req.query.dateStart);
} else if (!req.query.dateStart && req.query.dateEnd) {
dateStart = dateFormat(req.query.dateEnd);
dateEnd = dateFormat(req.query.dateEnd);
} else {
dateStart = dateFormat();
dateEnd = dateFormat();
}
params.createdAt = {
$gte: new Date(`${dateStart} 00:00:00`),
$lte: new Date(`${dateEnd} 23:59:59`),
};
// 浏览量统计
const pv = await Stat.countDocuments(params);
// ip 数统计
const ip = (
await Stat.aggregate([
{
$match: params,
},
{
$group: { _id: '$clientIp', clientIp: { $sum: 1 } },
},
])
).length;
// 当天热门浏览的文章
const hotUrl = await Stat.aggregate([
{
$match: params,
},
{
$group: { _id: '$url', views: { $sum: 1 } },
},
]).sort({ views: 'desc' }).limit(limit);
// 获取 DNS 响应时间的平均值(排除为0的值)
const dnsTime = await Stat.aggregate([
{
$match: params,
},
{
$match: { dnsTime: { $ne: 0 } },
},
{
$group: {
_id: 'dnsTime',
dnsTime: { $avg: '$dnsTime' },
},
},
]);
// 获取 TCP 响应时间的平均值 (排除为0的值)
const tcpTime = await Stat.aggregate([
{
$match: params,
},
{
$match: { tcpTime: { $ne: 0 } },
},
{
$group: {
_id: 'tcpTime',
tcpTime: { $avg: '$tcpTime' },
},
},
]);
// 其他性能数据的平均值 firstPaintTime, domRenderTime, loadTime
const performanceTime = await Stat.aggregate([
{
$match: params,
},
{
$group: {
_id: '性能信息',
firstPaintTime: { $avg: '$firstPaintTime' },
domRenderTime: { $avg: '$domRenderTime' },
loadTime: { $avg: '$loadTime' },
},
},
{
$project: {
_id: 0,
},
},
]);
res.status(200).json({
success: true,
message: '获取统计数据',
dateStart,
dateEnd,
ip,
pv,
performance: {
dnsTime: dnsTime.length !== 0 ? dnsTime[0].dnsTime : null,
tcpTime: tcpTime.length !== 0 ? tcpTime[0].tcpTime : null,
...performanceTime[0],
},
hotUrl,
});
} catch (error) {
next(error);
}
};