|
@@ -3,27 +3,45 @@ using System.Diagnostics;
|
|
|
namespace Admin.NET.Plugin.ApprovalFlow.Service;
|
|
namespace Admin.NET.Plugin.ApprovalFlow.Service;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
-/// 流程通知服务(P4-16 重构)— 聚合所有 INotifyPusher,按配置 + 渠道启用状态并行调度,
|
|
|
|
|
-/// 每个渠道的发送结果写入 ApprovalFlowNotifyLog 便于运维追溯与降级审计。
|
|
|
|
|
|
|
+/// 流程通知服务(P4-16 重构 + 模板化/DB 配置)
|
|
|
|
|
+/// - 聚合所有 INotifyPusher,按渠道启用状态串行调度
|
|
|
|
|
+/// - 标题/正文优先走 FlowNotifyTemplateService 模板渲染(支持 {变量} + BizType 覆盖),未命中回退兜底文案
|
|
|
|
|
+/// - 渠道开关/凭据优先从 DB(ApprovalFlowNotifyConfig)读取,无记录回退 ApprovalFlow.json
|
|
|
|
|
+/// - 每次分发写 ApprovalFlowNotifyLog 便于运维追溯与降级审计
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public class FlowNotifyService : ITransient
|
|
public class FlowNotifyService : ITransient
|
|
|
{
|
|
{
|
|
|
private readonly IEnumerable<INotifyPusher> _pushers;
|
|
private readonly IEnumerable<INotifyPusher> _pushers;
|
|
|
private readonly SqlSugarRepository<ApprovalFlowNotifyLog> _logRep;
|
|
private readonly SqlSugarRepository<ApprovalFlowNotifyLog> _logRep;
|
|
|
|
|
+ private readonly FlowNotifyTemplateService _tplService;
|
|
|
|
|
+ private readonly FlowNotifyConfigService _cfgService;
|
|
|
|
|
|
|
|
public FlowNotifyService(
|
|
public FlowNotifyService(
|
|
|
IEnumerable<INotifyPusher> pushers,
|
|
IEnumerable<INotifyPusher> pushers,
|
|
|
- SqlSugarRepository<ApprovalFlowNotifyLog> logRep)
|
|
|
|
|
|
|
+ SqlSugarRepository<ApprovalFlowNotifyLog> logRep,
|
|
|
|
|
+ FlowNotifyTemplateService tplService,
|
|
|
|
|
+ FlowNotifyConfigService cfgService)
|
|
|
{
|
|
{
|
|
|
_pushers = pushers;
|
|
_pushers = pushers;
|
|
|
_logRep = logRep;
|
|
_logRep = logRep;
|
|
|
|
|
+ _tplService = tplService;
|
|
|
|
|
+ _cfgService = cfgService;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public async Task NotifyUsers(List<long> userIds, FlowNotification notification)
|
|
public async Task NotifyUsers(List<long> userIds, FlowNotification notification)
|
|
|
{
|
|
{
|
|
|
if (userIds.Count == 0) return;
|
|
if (userIds.Count == 0) return;
|
|
|
|
|
|
|
|
- var cfg = App.GetConfig<NotifyChannelConfig>("ApprovalFlow:Notify") ?? new NotifyChannelConfig();
|
|
|
|
|
|
|
+ // 模板渲染(未命中则用传入的 Title/Content 兜底)
|
|
|
|
|
+ var ctx = notification.Context ?? new Dictionary<string, string?>();
|
|
|
|
|
+ var rendered = await _tplService.RenderAsync(notification.Type, notification.BizType, ctx);
|
|
|
|
|
+ if (rendered != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ notification.Title = rendered.Value.title;
|
|
|
|
|
+ notification.Content = rendered.Value.content;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var cfg = await _cfgService.GetEffectiveAsync();
|
|
|
|
|
|
|
|
foreach (var pusher in _pushers)
|
|
foreach (var pusher in _pushers)
|
|
|
{
|
|
{
|
|
@@ -45,122 +63,80 @@ public class FlowNotifyService : ITransient
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public async Task NotifyUrge(List<long> userIds, long instanceId, string title)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(userIds, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.Urge,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【催办】{title}",
|
|
|
|
|
- Content = "流程发起人催促您尽快审批,请及时处理。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public async Task NotifyNewTask(List<long> userIds, long instanceId, string title, string? nodeName)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(userIds, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.NewTask,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【待审批】{title}",
|
|
|
|
|
- Content = $"您有一条新的审批任务({nodeName}),请及时处理。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public async Task NotifyFlowCompleted(long initiatorId, long instanceId, string title, FlowInstanceStatusEnum finalStatus)
|
|
|
|
|
- {
|
|
|
|
|
- var statusText = finalStatus == FlowInstanceStatusEnum.Approved ? "已通过" : "已拒绝";
|
|
|
|
|
- await NotifyUsers(new List<long> { initiatorId }, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.FlowCompleted,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【审批{statusText}】{title}",
|
|
|
|
|
- Content = $"您发起的审批流程已{statusText}。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 转办通知 — 通知被转办人
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyTransferred(long targetUserId, long instanceId, string title, string? fromName)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(new List<long> { targetUserId }, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.Transferred,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【转办】{title}",
|
|
|
|
|
- Content = $"{fromName} 将一条审批任务转交给您,请及时处理。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 退回通知 — 通知被退回节点的审批人
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyReturned(List<long> userIds, long instanceId, string title, string? returnedBy)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(userIds, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.Returned,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【退回】{title}",
|
|
|
|
|
- Content = $"{returnedBy} 已退回该审批,请重新审核。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // ═══════════════════════════════════════════
|
|
|
|
|
+ // 事件专用便捷方法(FlowEngineService 调用)
|
|
|
|
|
+ // 每个方法收 bizType 作为最后一个参数;构造 FlowNotification 时带上变量上下文
|
|
|
|
|
+ // ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 加签通知 — 通知被加签人
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyAddSign(long targetUserId, long instanceId, string title, string? fromName)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(new List<long> { targetUserId }, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.AddSign,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【加签】{title}",
|
|
|
|
|
- Content = $"{fromName} 邀请您参与审批,请及时处理。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public Task NotifyUrge(List<long> userIds, long instanceId, string title, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(userIds, Build(FlowNotificationTypeEnum.Urge, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【催办】{title}",
|
|
|
|
|
+ fallbackContent: "流程发起人催促您尽快审批,请及时处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title }));
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 升级通知 — 通知升级目标人
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyEscalated(List<long> targetUserIds, long instanceId, string title, string? fromName, string? nodeName)
|
|
|
|
|
- {
|
|
|
|
|
- await NotifyUsers(targetUserIds, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.Escalated,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【升级】{title}",
|
|
|
|
|
- Content = $"{fromName} 将审批任务({nodeName})升级给您,请及时处理。",
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public Task NotifyNewTask(List<long> userIds, long instanceId, string title, string? nodeName, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(userIds, Build(FlowNotificationTypeEnum.NewTask, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【待审批】{title}",
|
|
|
|
|
+ fallbackContent: $"您有一条新的审批任务({nodeName}),请及时处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["nodeName"] = nodeName }));
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 超时提醒通知
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyTimeout(List<long> userIds, long instanceId, string title)
|
|
|
|
|
|
|
+ public Task NotifyFlowCompleted(long initiatorId, long instanceId, string title, FlowInstanceStatusEnum finalStatus, string bizType = "")
|
|
|
{
|
|
{
|
|
|
- await NotifyUsers(userIds, new FlowNotification
|
|
|
|
|
- {
|
|
|
|
|
- Type = FlowNotificationTypeEnum.Timeout,
|
|
|
|
|
- InstanceId = instanceId,
|
|
|
|
|
- Title = $"【超时提醒】{title}",
|
|
|
|
|
- Content = "您有一条审批任务已超时,请尽快处理。",
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ var statusText = finalStatus == FlowInstanceStatusEnum.Approved ? "已通过" : "已拒绝";
|
|
|
|
|
+ return NotifyUsers(new List<long> { initiatorId }, Build(FlowNotificationTypeEnum.FlowCompleted, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【审批{statusText}】{title}",
|
|
|
|
|
+ fallbackContent: $"您发起的审批流程已{statusText}。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["statusText"] = statusText }));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /// <summary>
|
|
|
|
|
- /// 撤回通知 — 通知被取消的审批人
|
|
|
|
|
- /// </summary>
|
|
|
|
|
- public async Task NotifyWithdrawn(List<long> userIds, long instanceId, string title, string? initiatorName)
|
|
|
|
|
|
|
+ public Task NotifyTransferred(long targetUserId, long instanceId, string title, string? fromName, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(new List<long> { targetUserId }, Build(FlowNotificationTypeEnum.Transferred, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【转办】{title}",
|
|
|
|
|
+ fallbackContent: $"{fromName} 将一条审批任务转交给您,请及时处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["fromName"] = fromName }));
|
|
|
|
|
+
|
|
|
|
|
+ public Task NotifyReturned(List<long> userIds, long instanceId, string title, string? returnedBy, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(userIds, Build(FlowNotificationTypeEnum.Returned, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【退回】{title}",
|
|
|
|
|
+ fallbackContent: $"{returnedBy} 已退回该审批,请重新审核。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["fromName"] = returnedBy }));
|
|
|
|
|
+
|
|
|
|
|
+ public Task NotifyAddSign(long targetUserId, long instanceId, string title, string? fromName, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(new List<long> { targetUserId }, Build(FlowNotificationTypeEnum.AddSign, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【加签】{title}",
|
|
|
|
|
+ fallbackContent: $"{fromName} 邀请您参与审批,请及时处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["fromName"] = fromName }));
|
|
|
|
|
+
|
|
|
|
|
+ public Task NotifyEscalated(List<long> targetUserIds, long instanceId, string title, string? fromName, string? nodeName, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(targetUserIds, Build(FlowNotificationTypeEnum.Escalated, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【升级】{title}",
|
|
|
|
|
+ fallbackContent: $"{fromName} 将审批任务({nodeName})升级给您,请及时处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["fromName"] = fromName, ["nodeName"] = nodeName }));
|
|
|
|
|
+
|
|
|
|
|
+ public Task NotifyTimeout(List<long> userIds, long instanceId, string title, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(userIds, Build(FlowNotificationTypeEnum.Timeout, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【超时提醒】{title}",
|
|
|
|
|
+ fallbackContent: "您有一条审批任务已超时,请尽快处理。",
|
|
|
|
|
+ ctx: new() { ["title"] = title }));
|
|
|
|
|
+
|
|
|
|
|
+ public Task NotifyWithdrawn(List<long> userIds, long instanceId, string title, string? initiatorName, string bizType = "")
|
|
|
|
|
+ => NotifyUsers(userIds, Build(FlowNotificationTypeEnum.Withdrawn, instanceId, bizType,
|
|
|
|
|
+ fallbackTitle: $"【已撤回】{title}",
|
|
|
|
|
+ fallbackContent: $"{initiatorName} 已撤回该审批流程。",
|
|
|
|
|
+ ctx: new() { ["title"] = title, ["initiatorName"] = initiatorName }));
|
|
|
|
|
+
|
|
|
|
|
+ private static FlowNotification Build(FlowNotificationTypeEnum type, long instanceId, string bizType,
|
|
|
|
|
+ string fallbackTitle, string fallbackContent, Dictionary<string, string?> ctx)
|
|
|
{
|
|
{
|
|
|
- await NotifyUsers(userIds, new FlowNotification
|
|
|
|
|
|
|
+ return new FlowNotification
|
|
|
{
|
|
{
|
|
|
- Type = FlowNotificationTypeEnum.Withdrawn,
|
|
|
|
|
|
|
+ Type = type,
|
|
|
InstanceId = instanceId,
|
|
InstanceId = instanceId,
|
|
|
- Title = $"【已撤回】{title}",
|
|
|
|
|
- Content = $"{initiatorName} 已撤回该审批流程。",
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ BizType = bizType ?? "",
|
|
|
|
|
+ Title = fallbackTitle,
|
|
|
|
|
+ Content = fallbackContent,
|
|
|
|
|
+ Context = ctx,
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
// ═══════════════════════════════════════════
|
|
@@ -201,8 +177,12 @@ public class FlowNotification
|
|
|
{
|
|
{
|
|
|
public FlowNotificationTypeEnum Type { get; set; }
|
|
public FlowNotificationTypeEnum Type { get; set; }
|
|
|
public long InstanceId { get; set; }
|
|
public long InstanceId { get; set; }
|
|
|
|
|
+ /// <summary>业务类型,用于模板 BizType 级覆盖查找</summary>
|
|
|
|
|
+ public string BizType { get; set; } = "";
|
|
|
public string Title { get; set; } = "";
|
|
public string Title { get; set; } = "";
|
|
|
public string Content { get; set; } = "";
|
|
public string Content { get; set; } = "";
|
|
|
|
|
+ /// <summary>模板渲染上下文(变量表)</summary>
|
|
|
|
|
+ public Dictionary<string, string?>? Context { get; set; }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -222,7 +202,7 @@ public enum FlowNotificationTypeEnum
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
-/// 通知渠道配置(来自 ApprovalFlow:Notify JSON)
|
|
|
|
|
|
|
+/// 通知渠道配置(来自 ApprovalFlow:Notify JSON 或 ApprovalFlowNotifyConfig 表的聚合结果)
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public class NotifyChannelConfig
|
|
public class NotifyChannelConfig
|
|
|
{
|
|
{
|