| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- using Admin.NET.Core.Service;
- using System.Text.RegularExpressions;
- namespace Admin.NET.Plugin.ApprovalFlow.Service;
- /// <summary>
- /// 审批流通知模板服务(P4-16 延伸)
- /// - 对外:CRUD(管理页使用) + EnsureSystemTemplates(启动种子)
- /// - 对内:RenderAsync(type, bizType, ctx) → (title, content),供 FlowNotifyService 调用
- /// </summary>
- [ApiDescriptionSettings(ApprovalFlowConst.GroupName, Order = 58)]
- public class FlowNotifyTemplateService : IDynamicApiController, ITransient
- {
- private readonly SqlSugarRepository<ApprovalFlowNotifyTemplate> _rep;
- private readonly SysCacheService _cache;
- private const string CacheKey = "ApprovalFlow:NotifyTemplate:All";
- private static readonly Regex VarRegex = new(@"\{(\w+)\}", RegexOptions.Compiled);
- public FlowNotifyTemplateService(SqlSugarRepository<ApprovalFlowNotifyTemplate> rep, SysCacheService cache)
- {
- _rep = rep;
- _cache = cache;
- }
- /// <summary>
- /// 获取全部通知模板(含系统预置与业务覆盖)
- /// </summary>
- [HttpGet]
- [ApiDescriptionSettings(Name = "List")]
- [DisplayName("获取通知模板列表")]
- public async Task<List<ApprovalFlowNotifyTemplate>> List([FromQuery] NotifyTemplateQueryInput? input = null)
- {
- return await _rep.AsQueryable()
- .WhereIF(!string.IsNullOrEmpty(input?.NotifyType), t => t.NotifyType == input!.NotifyType)
- .WhereIF(input?.BizType != null, t => t.BizType == input!.BizType)
- .OrderBy(t => t.BizType)
- .OrderBy(t => t.NotifyType)
- .ToListAsync();
- }
- /// <summary>
- /// 新增或更新通知模板(按 NotifyType + BizType 唯一)
- /// </summary>
- [HttpPost]
- [ApiDescriptionSettings(Name = "Save")]
- [DisplayName("保存通知模板")]
- public async Task<long> Save(NotifyTemplateSaveInput input)
- {
- if (string.IsNullOrWhiteSpace(input.NotifyType)) throw Oops.Oh("NotifyType 必填");
- if (string.IsNullOrWhiteSpace(input.Title)) throw Oops.Oh("标题模板必填");
- if (string.IsNullOrWhiteSpace(input.Content)) throw Oops.Oh("正文模板必填");
- var bizType = input.BizType ?? "";
- var entity = await _rep.AsQueryable()
- .FirstAsync(t => t.NotifyType == input.NotifyType && t.BizType == bizType);
- if (entity == null)
- {
- entity = new ApprovalFlowNotifyTemplate
- {
- NotifyType = input.NotifyType,
- BizType = bizType,
- Title = input.Title,
- Content = input.Content,
- IsEnabled = input.IsEnabled ?? true,
- IsSystem = false,
- Remark = input.Remark,
- };
- await _rep.InsertAsync(entity);
- }
- else
- {
- entity.Title = input.Title;
- entity.Content = input.Content;
- entity.IsEnabled = input.IsEnabled ?? entity.IsEnabled;
- entity.Remark = input.Remark;
- await _rep.UpdateAsync(entity);
- }
- InvalidateCache();
- return entity.Id;
- }
- /// <summary>
- /// 删除通知模板(系统预置不可删)
- /// </summary>
- [HttpPost]
- [ApiDescriptionSettings(Name = "Delete")]
- [DisplayName("删除通知模板")]
- public async Task Delete(BaseIdInput input)
- {
- var entity = await _rep.GetByIdAsync(input.Id) ?? throw Oops.Oh("模板不存在");
- if (entity.IsSystem) throw Oops.Oh("系统预置模板不可删除(可改为停用)");
- await _rep.DeleteByIdAsync(input.Id);
- InvalidateCache();
- }
- /// <summary>
- /// 可用变量说明(管理页展示)
- /// </summary>
- [HttpGet]
- [ApiDescriptionSettings(Name = "Variables")]
- [DisplayName("获取可用变量列表")]
- public List<NotifyTemplateVariableOutput> Variables()
- {
- return new()
- {
- new() { Key = "title", Description = "流程标题(业务单据主标题)", AppliedTo = "全部" },
- new() { Key = "nodeName", Description = "当前/相关节点名", AppliedTo = "NewTask / Urge / Escalated / Timeout" },
- new() { Key = "initiatorName", Description = "流程发起人", AppliedTo = "Withdrawn / FlowCompleted" },
- new() { Key = "fromName", Description = "操作人(转办 / 退回 / 升级 / 加签发起者)", AppliedTo = "Transferred / Returned / AddSign / Escalated" },
- new() { Key = "statusText", Description = "\"已通过\" 或 \"已拒绝\"", AppliedTo = "FlowCompleted" },
- };
- }
- // ═══════════════════════════════════════════
- // 渲染(供 FlowNotifyService 调用)
- // ═══════════════════════════════════════════
- /// <summary>
- /// 按 (notifyType, bizType) 查找启用模板并渲染;未命中则返回 null,调用方走兜底文案。
- /// 优先级:BizType 级覆盖 > 全局默认(BizType="")
- /// </summary>
- [NonAction]
- public async Task<(string title, string content)?> RenderAsync(
- FlowNotificationTypeEnum type, string? bizType, Dictionary<string, string?> ctx)
- {
- var typeStr = type.ToString();
- var all = await GetAllCachedAsync();
- ApprovalFlowNotifyTemplate? tpl = null;
- if (!string.IsNullOrEmpty(bizType))
- tpl = all.FirstOrDefault(t => t.IsEnabled && t.NotifyType == typeStr && t.BizType == bizType);
- tpl ??= all.FirstOrDefault(t => t.IsEnabled && t.NotifyType == typeStr && t.BizType == "");
- if (tpl == null) return null;
- return (Interpolate(tpl.Title, ctx), Interpolate(tpl.Content, ctx));
- }
- private static string Interpolate(string template, Dictionary<string, string?> ctx)
- {
- if (string.IsNullOrEmpty(template)) return template;
- return VarRegex.Replace(template, m =>
- {
- var key = m.Groups[1].Value;
- return ctx.TryGetValue(key, out var v) ? (v ?? "") : "";
- });
- }
- private async Task<List<ApprovalFlowNotifyTemplate>> GetAllCachedAsync()
- {
- var cached = _cache.Get<List<ApprovalFlowNotifyTemplate>>(CacheKey);
- if (cached != null) return cached;
- var list = await _rep.AsQueryable().ToListAsync();
- _cache.Set(CacheKey, list, TimeSpan.FromMinutes(10));
- return list;
- }
- [NonAction]
- public void InvalidateCache() => _cache.Remove(CacheKey);
- // ═══════════════════════════════════════════
- // 种子
- // ═══════════════════════════════════════════
- [NonAction]
- public async Task EnsureSystemTemplates()
- {
- var exists = await _rep.AsQueryable().AnyAsync(t => t.IsSystem);
- if (exists) return;
- var defaults = new (string type, string title, string content, string remark)[]
- {
- ("NewTask", "【待审批】{title}", "您有一条新的审批任务({nodeName}),请及时处理。", "新任务创建"),
- ("Urge", "【催办】{title}", "流程发起人催促您尽快审批,请及时处理。", "催办"),
- ("FlowCompleted", "【审批{statusText}】{title}", "您发起的审批流程已{statusText}。", "流程完成"),
- ("Transferred", "【转办】{title}", "{fromName} 将一条审批任务转交给您,请及时处理。", "转办"),
- ("Returned", "【退回】{title}", "{fromName} 已退回该审批,请重新审核。", "退回"),
- ("AddSign", "【加签】{title}", "{fromName} 邀请您参与审批,请及时处理。", "加签"),
- ("Withdrawn", "【已撤回】{title}", "{initiatorName} 已撤回该审批流程。", "撤回"),
- ("Escalated", "【升级】{title}", "{fromName} 将审批任务({nodeName})升级给您,请及时处理。", "升级"),
- ("Timeout", "【超时提醒】{title}", "您有一条审批任务已超时,请尽快处理。", "超时"),
- };
- foreach (var (type, title, content, remark) in defaults)
- {
- await _rep.InsertAsync(new ApprovalFlowNotifyTemplate
- {
- NotifyType = type,
- BizType = "",
- Title = title,
- Content = content,
- IsEnabled = true,
- IsSystem = true,
- Remark = remark,
- });
- }
- InvalidateCache();
- }
- }
- public class NotifyTemplateQueryInput
- {
- public string? NotifyType { get; set; }
- public string? BizType { get; set; }
- }
- public class NotifyTemplateSaveInput
- {
- [Required, MaxLength(32)]
- public string NotifyType { get; set; } = "";
- [MaxLength(32)]
- public string? BizType { get; set; }
- [Required, MaxLength(256)]
- public string Title { get; set; } = "";
- [Required, MaxLength(1024)]
- public string Content { get; set; } = "";
- public bool? IsEnabled { get; set; }
- [MaxLength(256)]
- public string? Remark { get; set; }
- }
- public class NotifyTemplateVariableOutput
- {
- public string Key { get; set; } = "";
- public string Description { get; set; } = "";
- public string AppliedTo { get; set; } = "";
- }
|