Skip to content

Commit 6d8b716

Browse files
committed
feat: 全局异常告警 - 流程失败自动发送Webhook/邮件告警通知(需求12)
1 parent 7e6de5b commit 6d8b716

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

Juggle.Application/Services/Flow/FlowExecutionService.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,107 @@ public async Task<FlowResult> RunAsync(
202202
if (result.Context != null)
203203
await FlushStaticVariablesAsync(result.Context);
204204

205+
// 全局告警:流程失败时检查告警配置并通知
206+
if (!result.Success && triggerType != "debug")
207+
{
208+
try { await SendAlertAsync(definition, result, logId); } catch { /* 告警失败不影响主流程 */ }
209+
}
210+
205211
return result;
206212
}
207213

214+
// ────────────────────────────────────────────────────────────────
215+
// 全局告警
216+
// ────────────────────────────────────────────────────────────────
217+
218+
private async Task SendAlertAsync(FlowDefinitionEntity definition, FlowResult result, long logId)
219+
{
220+
// 读取告警配置
221+
var configKeys = new[] { "alert.enabled", "alert.webhook.url", "alert.webhook.secret",
222+
"alert.email.to", "alert.on.fail.enabled" };
223+
var configs = await _db.SystemConfigs
224+
.Where(c => c.Deleted == 0 && configKeys.Contains(c.ConfigKey))
225+
.ToListAsync();
226+
var cfgMap = configs.ToDictionary(c => c.ConfigKey, c => c.ConfigValue ?? "");
227+
228+
if (!cfgMap.TryGetValue("alert.enabled", out var enabled) || enabled != "true") return;
229+
if (!cfgMap.TryGetValue("alert.on.fail.enabled", out var failAlert) || failAlert != "true") return;
230+
231+
var webhookUrl = cfgMap.GetValueOrDefault("alert.webhook.url", "");
232+
var emailTo = cfgMap.GetValueOrDefault("alert.email.to", "");
233+
234+
var alertBody = new
235+
{
236+
eventType = "FLOW_FAILED",
237+
flowKey = definition.FlowKey,
238+
flowName = definition.FlowName,
239+
logId,
240+
errorMessage = result.ErrorMessage,
241+
time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
242+
};
243+
244+
// Webhook 告警
245+
if (!string.IsNullOrEmpty(webhookUrl))
246+
{
247+
try
248+
{
249+
var client = _httpClientFactory.CreateClient();
250+
var json = System.Text.Json.JsonSerializer.Serialize(alertBody);
251+
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
252+
await client.PostAsync(webhookUrl, content);
253+
}
254+
catch { /* webhook 发送失败不中断 */ }
255+
}
256+
257+
// Email 告警(如果配置了 SMTP 和收件人)
258+
if (!string.IsNullOrEmpty(emailTo))
259+
{
260+
try { await SendEmailAlertAsync(definition, result, logId, emailTo); } catch { }
261+
}
262+
}
263+
264+
private async Task SendEmailAlertAsync(FlowDefinitionEntity definition, FlowResult result, long logId, string emailTo)
265+
{
266+
var configKeys = new[] { "email.smtp.host", "email.smtp.port", "email.smtp.ssl",
267+
"email.smtp.username", "email.smtp.password",
268+
"email.from.address", "email.from.name" };
269+
var configs = await _db.SystemConfigs
270+
.Where(c => c.Deleted == 0 && configKeys.Contains(c.ConfigKey))
271+
.ToListAsync();
272+
var cfgMap = configs.ToDictionary(c => c.ConfigKey, c => c.ConfigValue ?? "");
273+
274+
var smtpHost = cfgMap.GetValueOrDefault("email.smtp.host", "");
275+
if (string.IsNullOrEmpty(smtpHost)) return;
276+
277+
int.TryParse(cfgMap.GetValueOrDefault("email.smtp.port", "465"), out int smtpPort);
278+
bool.TryParse(cfgMap.GetValueOrDefault("email.smtp.ssl", "true"), out bool useSsl);
279+
var smtpUser = cfgMap.GetValueOrDefault("email.smtp.username", "");
280+
var smtpPwd = cfgMap.GetValueOrDefault("email.smtp.password", "");
281+
var fromAddr = cfgMap.GetValueOrDefault("email.from.address", smtpUser);
282+
var fromName = cfgMap.GetValueOrDefault("email.from.name", "Juggle告警");
283+
284+
// 使用 MailKit 发送(若不可用则跳过)
285+
// 注:此处使用 System.Net.Mail 作为备用,实际项目可替换为 MailKit
286+
using var client = new System.Net.Mail.SmtpClient(smtpHost, smtpPort)
287+
{
288+
EnableSsl = useSsl,
289+
Credentials = new System.Net.NetworkCredential(smtpUser, smtpPwd),
290+
DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network,
291+
UseDefaultCredentials = false
292+
};
293+
var subject = $"[Juggle告警] 流程执行失败: {definition.FlowName}";
294+
var body = $"流程Key: {definition.FlowKey}\n流程名: {definition.FlowName}\n" +
295+
$"LogId: {logId}\n错误信息: {result.ErrorMessage}\n时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
296+
var msg = new System.Net.Mail.MailMessage(
297+
new System.Net.Mail.MailAddress(fromAddr, fromName),
298+
new System.Net.Mail.MailAddress(emailTo))
299+
{
300+
Subject = subject,
301+
Body = body
302+
};
303+
await client.SendMailAsync(msg);
304+
}
305+
208306
// ────────────────────────────────────────────────────────────────
209307
// 异步执行(预写 RUNNING 日志 + 后台执行后更新)
210308
// ────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)