cangku/backend/src/services/alertChecker.js

231 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 预警检查服务 - 定时任务调用
const { v4: uuidv4 } = require('uuid');
const notificationService = require('./notification');
// 模拟数据库
const db = {
alertRules: [
// 示例规则,实际从数据库读取
{
id: 'rule-1',
name: '日销售额低于警戒线',
cubeName: 'Orders',
measureName: 'Orders.totalAmount',
dimension: 'Orders.orderDate',
condition: 'lt',
threshold: 100000,
timeGranularity: 'day',
timeRange: 'Today',
notifyChannels: ['wechat', 'dingtalk'],
severity: 'high',
status: 'active',
},
{
id: 'rule-2',
name: '订单取消率超标',
cubeName: 'Orders',
measureName: 'Orders.cancelRate',
dimension: 'Orders.orderDate',
condition: 'gt',
threshold: 0.05,
timeGranularity: 'day',
timeRange: 'Last 7 days',
notifyChannels: ['dingtalk'],
severity: 'critical',
status: 'active',
},
],
alertRecords: [],
notificationChannels: [],
};
class AlertChecker {
constructor() {
this.cubejsApi = null;
}
// 设置 Cube.js API 实例
setCubeJsApi(api) {
this.cubejsApi = api;
}
// 获取所有启用的规则
async getActiveRules() {
return db.alertRules.filter(r => r.status === 'active');
}
// 检查所有规则
async checkAllRules() {
const rules = await this.getActiveRules();
const results = [];
for (const rule of rules) {
const result = await this.checkRule(rule);
results.push(result);
}
console.log(`🔔 预警检查完成: ${results.filter(r => r.triggered).length} 条触发`);
return results;
}
// 检查单条规则
async checkRule(rule) {
try {
const query = this.buildQuery(rule);
const currentValue = await this.executeQuery(query);
const isTriggered = this.evaluateCondition(currentValue, rule.condition, rule.threshold);
if (isTriggered) {
await this.triggerAlert(rule, currentValue);
}
return {
ruleId: rule.id,
ruleName: rule.name,
triggered: isTriggered,
value: currentValue,
threshold: rule.threshold,
checkedAt: new Date().toISOString(),
};
} catch (error) {
console.error(`⚠️ 规则检查失败 [${rule.name}]:`, error.message);
return {
ruleId: rule.id,
ruleName: rule.name,
error: error.message,
checkedAt: new Date().toISOString(),
};
}
}
// 构建查询
buildQuery(rule) {
return {
measures: [rule.measureName],
timeDimensions: [
{
dimension: rule.dimension,
granularity: rule.timeGranularity,
dateRange: rule.timeRange,
},
],
filters: rule.dimensionFilter || [],
limit: 1,
};
}
// 执行查询(模拟,实际调用 Cube.js API)
async executeQuery(query) {
// 如果没有设置 Cube.js API,返回模拟数据用于测试
if (!this.cubejsApi) {
// 模拟查询结果
return Math.random() * 200000; // 随机生成一个值用于测试
}
try {
const result = await this.cubejsApi.load(query);
const data = result.data || [];
if (data.length > 0) {
const firstRow = data[0];
// 获取 measures 中的第一个指标值
const measureKey = Object.keys(firstRow).find(k => k.startsWith(query.measures[0].split('.')[0]));
return firstRow[measureKey] || 0;
}
return 0;
} catch (error) {
throw new Error(`查询失败: ${error.message}`);
}
}
// 条件判断
evaluateCondition(value, condition, threshold) {
switch (condition) {
case 'gt':
return value > threshold;
case 'lt':
return value < threshold;
case 'eq':
return value === threshold;
case 'gte':
return value >= threshold;
case 'lte':
return value <= threshold;
default:
return false;
}
}
// 触发预警
async triggerAlert(rule, currentValue) {
// 检查是否已触发(避免重复通知)
const recentRecord = db.alertRecords.find(r => {
return (
r.ruleId === rule.id &&
r.status === 'triggered' &&
new Date() - new Date(r.triggeredAt) < 30 * 60 * 1000 // 30分钟内不重复
);
});
if (recentRecord) {
console.log(`⏭️ 跳过重复预警 [${rule.name}]`);
return;
}
// 获取订阅用户
const subscribers = await this.getSubscribers(rule.id);
// 发送通知
const message = notificationService.formatAlertMessage(rule, currentValue, rule.threshold);
const notificationResults = await notificationService.sendToUsers(
subscribers.map(s => s.userId),
message,
rule.notifyChannels
);
// 记录预警
const record = {
id: uuidv4(),
ruleId: rule.id,
triggeredAt: new Date().toISOString(),
actualValue: currentValue,
threshold: rule.threshold,
status: 'triggered',
notifiedChannels: rule.notifyChannels,
notificationResults,
};
db.alertRecords.push(record);
console.log(`🚨 预警已触发 [${rule.name}]: 值=${currentValue}, 阈值=${rule.threshold}`);
console.log(` 通知渠道: ${rule.notifyChannels.join(', ')}`);
return record;
}
// 获取订阅用户
async getSubscribers(ruleId) {
// 实际项目中应从数据库查询
// 这里返回管理员用户
return [
{ userId: 'admin-1', role: 'admin' },
];
}
// 获取预警记录
getAlertRecords(filters = {}) {
let records = [...db.alertRecords];
if (filters.ruleId) {
records = records.filter(r => r.ruleId === filters.ruleId);
}
if (filters.status) {
records = records.filter(r => r.status === filters.status);
}
return records.sort((a, b) => new Date(b.triggeredAt) - new Date(a.triggeredAt));
}
}
module.exports = new AlertChecker();