using Admin.NET.Core.Service;
using System.Text.RegularExpressions;
namespace Admin.NET.Plugin.ApprovalFlow.Service;
///
/// 审批流通知模板服务(P4-16 延伸)
/// - 对外:CRUD(管理页使用) + EnsureSystemTemplates(启动种子)
/// - 对内:RenderAsync(type, bizType, ctx) → (title, content),供 FlowNotifyService 调用
///
[ApiDescriptionSettings(ApprovalFlowConst.GroupName, Order = 58)]
public class FlowNotifyTemplateService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository _rep;
private readonly SysCacheService _cache;
private const string CacheKey = "ApprovalFlow:NotifyTemplate:All";
private static readonly Regex VarRegex = new(@"\{(\w+)\}", RegexOptions.Compiled);
public FlowNotifyTemplateService(SqlSugarRepository rep, SysCacheService cache)
{
_rep = rep;
_cache = cache;
}
///
/// 获取全部通知模板(含系统预置与业务覆盖)
///
[HttpGet]
[ApiDescriptionSettings(Name = "List")]
[DisplayName("获取通知模板列表")]
public async Task> 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();
}
///
/// 新增或更新通知模板(按 NotifyType + BizType 唯一)
///
[HttpPost]
[ApiDescriptionSettings(Name = "Save")]
[DisplayName("保存通知模板")]
public async Task 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;
}
///
/// 删除通知模板(系统预置不可删)
///
[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();
}
///
/// 可用变量说明(管理页展示)
///
[HttpGet]
[ApiDescriptionSettings(Name = "Variables")]
[DisplayName("获取可用变量列表")]
public List 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 调用)
// ═══════════════════════════════════════════
///
/// 按 (notifyType, bizType) 查找启用模板并渲染;未命中则返回 null,调用方走兜底文案。
/// 优先级:BizType 级覆盖 > 全局默认(BizType="")
///
[NonAction]
public async Task<(string title, string content)?> RenderAsync(
FlowNotificationTypeEnum type, string? bizType, Dictionary 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 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> GetAllCachedAsync()
{
var cached = _cache.Get>(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; } = "";
}