231 lines
5.9 KiB
JavaScript
231 lines
5.9 KiB
JavaScript
// 预警检查服务 - 定时任务调用
|
||
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();
|