@@ -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