@@ -201,6 +201,18 @@ interface ResendConfig {
201201 to : string ;
202202}
203203
204+ // 飞书配置接口
205+ interface FeishuConfig {
206+ webhookUrl : string ;
207+ }
208+
209+ // 企业微信配置接口
210+ interface WeComConfig {
211+ webhookUrl : string ;
212+ }
213+
214+
215+
204216/**
205217 * 解析通知渠道配置
206218 */
@@ -258,9 +270,11 @@ function parseChannelConfig<T>(channel: models.NotificationChannel): T {
258270 }
259271}
260272
261- /**
262- * 通过Resend API发送邮件通知
263- */
273+
274+ // =================================================================
275+ // Section: 各渠道发送器实现 (Sender Implementations)
276+ // =================================================================
277+
264278async function sendResendNotification (
265279 channel : models . NotificationChannel ,
266280 subject : string ,
@@ -427,8 +441,51 @@ async function sendTelegramNotification(
427441 } ;
428442 }
429443}
444+
445+ // =================================================================
446+ // Section: 新的通知发送器抽象层 (Refactored Sender Abstraction)
447+ // =================================================================
448+
449+ /**
450+ * 定义了通知发送器的统一接口。
451+ * 每种通知渠道(如邮件、Telegram)都必须实现这个接口。
452+ * "Good code is all about making the data structures, so the code is obvious."
453+ * 这个接口就是我们新的数据结构。
454+ */
455+ interface NotificationSender {
456+ (
457+ channel : models . NotificationChannel ,
458+ subject : string ,
459+ content : string
460+ ) : Promise < { success : boolean ; error ?: string } > ;
461+ }
462+
463+ /**
464+ * 发送器注册表。
465+ * 这是一个从渠道类型字符串到其发送器实现的映射。
466+ * "Talk is cheap. Show me the code."
467+ * 这段代码取代了原来愚蠢的 if-else 链。
468+ */
469+ const senderRegistry : Record < string , NotificationSender > = { } ;
470+
471+ /**
472+ * 注册一个新的通知发送器。
473+ * @param type 渠道类型 (e.g., 'resend', 'telegram')
474+ * @param sender 实现了 NotificationSender 接口的函数
475+ */
476+ function registerSender ( type : string , sender : NotificationSender ) {
477+ if ( senderRegistry [ type ] ) {
478+ console . warn ( `[通知注册] 覆盖已存在的发送器: ${ type } ` ) ;
479+ }
480+ senderRegistry [ type ] = sender ;
481+ console . log ( `[通知注册] 成功注册发送器: ${ type } ` ) ;
482+ }
483+
484+
430485/**
431- * 根据渠道类型发送通知
486+ * 根据渠道类型发送通知 (重构后)
487+ * 这个函数现在只负责查找和调用,不再关心具体实现。
488+ * "The point of interfaces is that you don't have to care."
432489 */
433490async function sendNotificationByChannel (
434491 channel : models . NotificationChannel ,
@@ -444,23 +501,150 @@ async function sendNotificationByChannel(
444501 return { success : false , error : "通知渠道已禁用" } ;
445502 }
446503
447- console . log ( `[渠道分发] 渠道ID=${ channel . id } 的类型为${ channel . type } ` ) ;
448-
449- if ( channel . type === "resend" ) {
450- console . log ( `[渠道分发] 使用Resend邮件服务发送通知` ) ;
451- return await sendResendNotification ( channel , subject , content ) ;
452- } else if ( channel . type === "telegram" ) {
453- console . log ( `[渠道分发] 使用Telegram发送通知` ) ;
454- return await sendTelegramNotification ( channel , subject , content ) ;
504+ const sender = senderRegistry [ channel . type ] ;
505+ if ( sender ) {
506+ console . log ( `[渠道分发] 找到类型为 ${ channel . type } 的发送器,开始执行` ) ;
507+ return await sender ( channel , subject , content ) ;
455508 } else {
456509 console . error ( `[渠道分发] 不支持的通知渠道类型: ${ channel . type } ` ) ;
457510 return { success : false , error : `不支持的通知渠道类型: ${ channel . type } ` } ;
458511 }
459512}
460513
514+
515+
461516/**
462- * 发送通知
517+ * 发送飞书通知
463518 */
519+ async function sendFeishuNotification (
520+ channel : models . NotificationChannel ,
521+ subject : string ,
522+ content : string
523+ ) : Promise < { success : boolean ; error ?: string } > {
524+ try {
525+ const config = parseChannelConfig < FeishuConfig > ( channel ) ;
526+ const webhookUrl = config . webhookUrl ;
527+
528+ if ( ! webhookUrl ) {
529+ console . error ( "[飞书通知] Webhook URL 不能为空" ) ;
530+ return { success : false , error : "飞书 Webhook URL 不能为空" } ;
531+ }
532+
533+ const message = {
534+ msg_type : "interactive" ,
535+ card : {
536+ header : {
537+ title : {
538+ content : subject ,
539+ tag : "plain_text" ,
540+ } ,
541+ } ,
542+ elements : [
543+ {
544+ tag : "div" ,
545+ text : {
546+ content : content ,
547+ tag : "lark_md" ,
548+ } ,
549+ } ,
550+ ] ,
551+ } ,
552+ } ;
553+
554+ console . log ( "[飞书通知] 准备发送通知到:" , webhookUrl ) ;
555+ const response = await fetch ( webhookUrl , {
556+ method : "POST" ,
557+ headers : {
558+ "Content-Type" : "application/json" ,
559+ } ,
560+ body : JSON . stringify ( message ) ,
561+ } ) ;
562+
563+ const responseData = await response . json ( ) ;
564+
565+ if ( responseData . StatusCode === 0 || responseData . code === 0 ) {
566+ console . log ( "[飞书通知] 发送成功" ) ;
567+ return { success : true } ;
568+ } else {
569+ console . error ( "[飞书通知] 发送失败:" , responseData ) ;
570+ return {
571+ success : false ,
572+ error : responseData . StatusMessage || responseData . msg || "发送失败" ,
573+ } ;
574+ }
575+ } catch ( error ) {
576+ console . error ( "发送飞书通知异常:" , error ) ;
577+ return {
578+ success : false ,
579+ error : error instanceof Error ? error . message : String ( error ) ,
580+ } ;
581+ }
582+ }
583+
584+ // 注册已有的发送器
585+ registerSender ( "resend" , sendResendNotification ) ;
586+ registerSender ( "telegram" , sendTelegramNotification ) ;
587+ registerSender ( "feishu" , sendFeishuNotification ) ;
588+
589+ /**
590+ * 发送企业微信通知
591+ */
592+ async function sendWeComNotification (
593+ channel : models . NotificationChannel ,
594+ subject : string ,
595+ content : string
596+ ) : Promise < { success : boolean ; error ?: string } > {
597+ try {
598+ const config = parseChannelConfig < WeComConfig > ( channel ) ;
599+ const webhookUrl = config . webhookUrl ;
600+
601+ if ( ! webhookUrl ) {
602+ console . error ( "[企业微信通知] Webhook URL 不能为空" ) ;
603+ return { success : false , error : "企业微信 Webhook URL 不能为空" } ;
604+ }
605+
606+ // 企业微信的 Markdown 格式要求主题是加粗标题
607+ const markdownContent = `**${ subject } **\n\n${ content } ` ;
608+
609+ const message = {
610+ msgtype : "markdown" ,
611+ markdown : {
612+ content : markdownContent ,
613+ } ,
614+ } ;
615+
616+ console . log ( "[企业微信通知] 准备发送通知到:" , webhookUrl ) ;
617+ const response = await fetch ( webhookUrl , {
618+ method : "POST" ,
619+ headers : {
620+ "Content-Type" : "application/json" ,
621+ } ,
622+ body : JSON . stringify ( message ) ,
623+ } ) ;
624+
625+ const responseData = await response . json ( ) ;
626+
627+ if ( responseData . errcode === 0 ) {
628+ console . log ( "[企业微信通知] 发送成功" ) ;
629+ return { success : true } ;
630+ } else {
631+ console . error ( "[企业微信通知] 发送失败:" , responseData ) ;
632+ return {
633+ success : false ,
634+ error : `错误码: ${ responseData . errcode } , 错误信息: ${ responseData . errmsg } ` ,
635+ } ;
636+ }
637+ } catch ( error ) {
638+ console . error ( "发送企业微信通知异常:" , error ) ;
639+ return {
640+ success : false ,
641+ error : error instanceof Error ? error . message : String ( error ) ,
642+ } ;
643+ }
644+ }
645+
646+ registerSender ( "wecom" , sendWeComNotification ) ;
647+
464648export async function sendNotification (
465649 type : "monitor" | "agent" | "system" ,
466650 targetId : number | null ,
0 commit comments