cangku/backend/src/services/notification.js

190 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 axios = require('axios');
class NotificationService {
constructor() {
// 企业微信
this.weixinCorpId = process.env.WEIXIN_CORP_ID;
this.weixinAgentId = process.env.WEIXIN_AGENT_ID;
this.weixinSecret = process.env.WEIXIN_SECRET;
this.weixinToken = null;
this.weixinTokenExpiry = null;
// 钉钉
this.dingtalkToken = process.env.DINGTALK_TOKEN;
// 邮件
this.smtpConfig = {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
user: process.env.SMTP_USER,
password: process.env.SMTP_PASSWORD,
};
}
// 获取企业微信 Access Token
async getWeChatToken() {
if (this.weixinToken && this.weixinTokenExpiry > Date.now()) {
return this.weixinToken;
}
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
const params = {
corpid: this.weixinCorpId,
corpsecret: this.weixinSecret,
};
const response = await axios.get(url, { params });
const { access_token, expires_in } = response.data;
if (response.data.errcode !== 0) {
throw new Error(`企业微信 Token 获取失败: ${response.data.errmsg}`);
}
this.weixinToken = access_token;
this.weixinTokenExpiry = Date.now() + (expires_in - 60) * 1000;
return access_token;
}
// 发送企业微信消息
async sendWeChat(userIds, message) {
if (!this.weixinCorpId || !this.weixinAgentId) {
console.log('[通知] 企业微信未配置,跳过');
return { success: false, reason: 'not_configured' };
}
try {
const token = await this.getWeChatToken();
const url = `https://qyapi.weixin.qq.com/cgi-bin/message/send`;
const response = await axios.post(
url,
{
touser: userIds.join('|'),
msgtype: 'text',
agentid: this.weixinAgentId,
text: { content: message },
},
{ params: { access_token: token } }
);
if (response.data.errcode !== 0) {
throw new Error(response.data.errmsg);
}
return { success: true, messageId: response.data.msgid };
} catch (error) {
console.error('[通知] 企业微信发送失败:', error.message);
return { success: false, error: error.message };
}
}
// 发送钉钉消息
async sendDingTalk(userIds, message) {
if (!this.dingtalkToken) {
console.log('[通知] 钉钉未配置,跳过');
return { success: false, reason: 'not_configured' };
}
try {
const url = `https://oapi.dingtalk.com/robot/send`;
const response = await axios.post(
url,
{
msgtype: 'text',
text: { content: message },
at: {
atUserIds: userIds,
},
},
{ params: { access_token: this.dingtalkToken } }
);
if (response.data.errcode !== 0) {
throw new Error(response.data.errmsg);
}
return { success: true };
} catch (error) {
console.error('[通知] 钉钉发送失败:', error.message);
return { success: false, error: error.message };
}
}
// 发送邮件
async sendEmail(recipients, subject, htmlBody) {
if (!this.smtpConfig.host) {
console.log('[通知] 邮件 SMTP 未配置,跳过');
return { success: false, reason: 'not_configured' };
}
// 实际项目中应使用 nodemailer 或邮件服务
console.log(`[通知] 发送邮件给 ${recipients.join(', ')}: ${subject}`);
return { success: true };
}
// 发送短信(预留接口)
async sendSMS(phoneNumbers, content) {
console.log(`[通知] 发送短信给 ${phoneNumbers.join(', ')}: ${content}`);
return { success: true };
}
// 统一发送接口
async sendToUsers(userIds, message, channels) {
const results = {};
for (const channel of channels) {
const userIdsForChannel = userIds; // 可以根据用户偏好筛选
switch (channel) {
case 'wechat':
results.wechat = await this.sendWeChat(userIdsForChannel, message);
break;
case 'dingtalk':
results.dingtalk = await this.sendDingTalk(userIdsForChannel, message);
break;
case 'email':
results.email = await this.sendEmail(userIdsForChannel, '预警通知', message);
break;
case 'sms':
results.sms = await this.sendSMS(userIdsForChannel, message);
break;
default:
results[channel] = { success: false, reason: 'unknown_channel' };
}
}
return results;
}
// 格式化预警消息
formatAlertMessage(rule, value, threshold) {
const severityEmoji = {
low: 'ℹ️',
medium: '⚠️',
high: '🔴',
critical: '🚨',
};
const severityZh = {
low: '低',
medium: '中',
high: '高',
critical: '严重',
};
return `
${severityEmoji[rule.severity] || '⚠️'} 【${severityZh[rule.severity] || '中'}级预警】${rule.name}
📊 当前值: ${value}
🎯 阈值: ${rule.condition === 'gt' ? '大于' : rule.condition === 'lt' ? '小于' : rule.condition} ${threshold}
⏰ 时间: ${new Date().toLocaleString()}
请及时处理!
`.trim();
}
}
module.exports = new NotificationService();