FlowNotifyTemplateService.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. using Admin.NET.Core.Service;
  2. using System.Text.RegularExpressions;
  3. namespace Admin.NET.Plugin.ApprovalFlow.Service;
  4. /// <summary>
  5. /// 审批流通知模板服务(P4-16 延伸)
  6. /// - 对外:CRUD(管理页使用) + EnsureSystemTemplates(启动种子)
  7. /// - 对内:RenderAsync(type, bizType, ctx) → (title, content),供 FlowNotifyService 调用
  8. /// </summary>
  9. [ApiDescriptionSettings(ApprovalFlowConst.GroupName, Order = 58)]
  10. public class FlowNotifyTemplateService : IDynamicApiController, ITransient
  11. {
  12. private readonly SqlSugarRepository<ApprovalFlowNotifyTemplate> _rep;
  13. private readonly SysCacheService _cache;
  14. private const string CacheKey = "ApprovalFlow:NotifyTemplate:All";
  15. private static readonly Regex VarRegex = new(@"\{(\w+)\}", RegexOptions.Compiled);
  16. public FlowNotifyTemplateService(SqlSugarRepository<ApprovalFlowNotifyTemplate> rep, SysCacheService cache)
  17. {
  18. _rep = rep;
  19. _cache = cache;
  20. }
  21. /// <summary>
  22. /// 获取全部通知模板(含系统预置与业务覆盖)
  23. /// </summary>
  24. [HttpGet]
  25. [ApiDescriptionSettings(Name = "List")]
  26. [DisplayName("获取通知模板列表")]
  27. public async Task<List<ApprovalFlowNotifyTemplate>> List([FromQuery] NotifyTemplateQueryInput? input = null)
  28. {
  29. return await _rep.AsQueryable()
  30. .WhereIF(!string.IsNullOrEmpty(input?.NotifyType), t => t.NotifyType == input!.NotifyType)
  31. .WhereIF(input?.BizType != null, t => t.BizType == input!.BizType)
  32. .OrderBy(t => t.BizType)
  33. .OrderBy(t => t.NotifyType)
  34. .ToListAsync();
  35. }
  36. /// <summary>
  37. /// 新增或更新通知模板(按 NotifyType + BizType 唯一)
  38. /// </summary>
  39. [HttpPost]
  40. [ApiDescriptionSettings(Name = "Save")]
  41. [DisplayName("保存通知模板")]
  42. public async Task<long> Save(NotifyTemplateSaveInput input)
  43. {
  44. if (string.IsNullOrWhiteSpace(input.NotifyType)) throw Oops.Oh("NotifyType 必填");
  45. if (string.IsNullOrWhiteSpace(input.Title)) throw Oops.Oh("标题模板必填");
  46. if (string.IsNullOrWhiteSpace(input.Content)) throw Oops.Oh("正文模板必填");
  47. var bizType = input.BizType ?? "";
  48. var entity = await _rep.AsQueryable()
  49. .FirstAsync(t => t.NotifyType == input.NotifyType && t.BizType == bizType);
  50. if (entity == null)
  51. {
  52. entity = new ApprovalFlowNotifyTemplate
  53. {
  54. NotifyType = input.NotifyType,
  55. BizType = bizType,
  56. Title = input.Title,
  57. Content = input.Content,
  58. IsEnabled = input.IsEnabled ?? true,
  59. IsSystem = false,
  60. Remark = input.Remark,
  61. };
  62. await _rep.InsertAsync(entity);
  63. }
  64. else
  65. {
  66. entity.Title = input.Title;
  67. entity.Content = input.Content;
  68. entity.IsEnabled = input.IsEnabled ?? entity.IsEnabled;
  69. entity.Remark = input.Remark;
  70. await _rep.UpdateAsync(entity);
  71. }
  72. InvalidateCache();
  73. return entity.Id;
  74. }
  75. /// <summary>
  76. /// 删除通知模板(系统预置不可删)
  77. /// </summary>
  78. [HttpPost]
  79. [ApiDescriptionSettings(Name = "Delete")]
  80. [DisplayName("删除通知模板")]
  81. public async Task Delete(BaseIdInput input)
  82. {
  83. var entity = await _rep.GetByIdAsync(input.Id) ?? throw Oops.Oh("模板不存在");
  84. if (entity.IsSystem) throw Oops.Oh("系统预置模板不可删除(可改为停用)");
  85. await _rep.DeleteByIdAsync(input.Id);
  86. InvalidateCache();
  87. }
  88. /// <summary>
  89. /// 可用变量说明(管理页展示)
  90. /// </summary>
  91. [HttpGet]
  92. [ApiDescriptionSettings(Name = "Variables")]
  93. [DisplayName("获取可用变量列表")]
  94. public List<NotifyTemplateVariableOutput> Variables()
  95. {
  96. return new()
  97. {
  98. new() { Key = "title", Description = "流程标题(业务单据主标题)", AppliedTo = "全部" },
  99. new() { Key = "nodeName", Description = "当前/相关节点名", AppliedTo = "NewTask / Urge / Escalated / Timeout" },
  100. new() { Key = "initiatorName", Description = "流程发起人", AppliedTo = "Withdrawn / FlowCompleted" },
  101. new() { Key = "fromName", Description = "操作人(转办 / 退回 / 升级 / 加签发起者)", AppliedTo = "Transferred / Returned / AddSign / Escalated" },
  102. new() { Key = "statusText", Description = "\"已通过\" 或 \"已拒绝\"", AppliedTo = "FlowCompleted" },
  103. };
  104. }
  105. // ═══════════════════════════════════════════
  106. // 渲染(供 FlowNotifyService 调用)
  107. // ═══════════════════════════════════════════
  108. /// <summary>
  109. /// 按 (notifyType, bizType) 查找启用模板并渲染;未命中则返回 null,调用方走兜底文案。
  110. /// 优先级:BizType 级覆盖 &gt; 全局默认(BizType="")
  111. /// </summary>
  112. [NonAction]
  113. public async Task<(string title, string content)?> RenderAsync(
  114. FlowNotificationTypeEnum type, string? bizType, Dictionary<string, string?> ctx)
  115. {
  116. var typeStr = type.ToString();
  117. var all = await GetAllCachedAsync();
  118. ApprovalFlowNotifyTemplate? tpl = null;
  119. if (!string.IsNullOrEmpty(bizType))
  120. tpl = all.FirstOrDefault(t => t.IsEnabled && t.NotifyType == typeStr && t.BizType == bizType);
  121. tpl ??= all.FirstOrDefault(t => t.IsEnabled && t.NotifyType == typeStr && t.BizType == "");
  122. if (tpl == null) return null;
  123. return (Interpolate(tpl.Title, ctx), Interpolate(tpl.Content, ctx));
  124. }
  125. private static string Interpolate(string template, Dictionary<string, string?> ctx)
  126. {
  127. if (string.IsNullOrEmpty(template)) return template;
  128. return VarRegex.Replace(template, m =>
  129. {
  130. var key = m.Groups[1].Value;
  131. return ctx.TryGetValue(key, out var v) ? (v ?? "") : "";
  132. });
  133. }
  134. private async Task<List<ApprovalFlowNotifyTemplate>> GetAllCachedAsync()
  135. {
  136. var cached = _cache.Get<List<ApprovalFlowNotifyTemplate>>(CacheKey);
  137. if (cached != null) return cached;
  138. var list = await _rep.AsQueryable().ToListAsync();
  139. _cache.Set(CacheKey, list, TimeSpan.FromMinutes(10));
  140. return list;
  141. }
  142. [NonAction]
  143. public void InvalidateCache() => _cache.Remove(CacheKey);
  144. // ═══════════════════════════════════════════
  145. // 种子
  146. // ═══════════════════════════════════════════
  147. [NonAction]
  148. public async Task EnsureSystemTemplates()
  149. {
  150. var exists = await _rep.AsQueryable().AnyAsync(t => t.IsSystem);
  151. if (exists) return;
  152. var defaults = new (string type, string title, string content, string remark)[]
  153. {
  154. ("NewTask", "【待审批】{title}", "您有一条新的审批任务({nodeName}),请及时处理。", "新任务创建"),
  155. ("Urge", "【催办】{title}", "流程发起人催促您尽快审批,请及时处理。", "催办"),
  156. ("FlowCompleted", "【审批{statusText}】{title}", "您发起的审批流程已{statusText}。", "流程完成"),
  157. ("Transferred", "【转办】{title}", "{fromName} 将一条审批任务转交给您,请及时处理。", "转办"),
  158. ("Returned", "【退回】{title}", "{fromName} 已退回该审批,请重新审核。", "退回"),
  159. ("AddSign", "【加签】{title}", "{fromName} 邀请您参与审批,请及时处理。", "加签"),
  160. ("Withdrawn", "【已撤回】{title}", "{initiatorName} 已撤回该审批流程。", "撤回"),
  161. ("Escalated", "【升级】{title}", "{fromName} 将审批任务({nodeName})升级给您,请及时处理。", "升级"),
  162. ("Timeout", "【超时提醒】{title}", "您有一条审批任务已超时,请尽快处理。", "超时"),
  163. };
  164. foreach (var (type, title, content, remark) in defaults)
  165. {
  166. await _rep.InsertAsync(new ApprovalFlowNotifyTemplate
  167. {
  168. NotifyType = type,
  169. BizType = "",
  170. Title = title,
  171. Content = content,
  172. IsEnabled = true,
  173. IsSystem = true,
  174. Remark = remark,
  175. });
  176. }
  177. InvalidateCache();
  178. }
  179. }
  180. public class NotifyTemplateQueryInput
  181. {
  182. public string? NotifyType { get; set; }
  183. public string? BizType { get; set; }
  184. }
  185. public class NotifyTemplateSaveInput
  186. {
  187. [Required, MaxLength(32)]
  188. public string NotifyType { get; set; } = "";
  189. [MaxLength(32)]
  190. public string? BizType { get; set; }
  191. [Required, MaxLength(256)]
  192. public string Title { get; set; } = "";
  193. [Required, MaxLength(1024)]
  194. public string Content { get; set; } = "";
  195. public bool? IsEnabled { get; set; }
  196. [MaxLength(256)]
  197. public string? Remark { get; set; }
  198. }
  199. public class NotifyTemplateVariableOutput
  200. {
  201. public string Key { get; set; } = "";
  202. public string Description { get; set; } = "";
  203. public string AppliedTo { get; set; } = "";
  204. }