S8NotificationRecipientConfigService.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. using Admin.NET.Core;
  2. using Admin.NET.Plugin.AiDOP.Const.S8;
  3. using Admin.NET.Plugin.AiDOP.Entity.S8;
  4. using Admin.NET.Plugin.AiDOP.Infrastructure;
  5. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  6. using Microsoft.Extensions.Logging;
  7. namespace Admin.NET.Plugin.AiDOP.Service.S8;
  8. /// <summary>配置页的一行:一个事件 + 它当前配置的收件人类型。</summary>
  9. public sealed class S8NotifyEventRowDto
  10. {
  11. public string EventCode { get; set; } = string.Empty;
  12. public string DisplayName { get; set; } = string.Empty;
  13. public string TriggerHint { get; set; } = string.Empty;
  14. public int OrderNo { get; set; }
  15. public List<string> RecipientTypes { get; set; } = new();
  16. /// <summary>SPECIFIC_USER 选中的账号。</summary>
  17. public List<long> SpecificUserIds { get; set; } = new();
  18. /// <summary>本事件尚未配置任何收件人 —— 会回落 LEGACY 分层,页面必须说明。</summary>
  19. public bool NotConfigured => RecipientTypes.Count == 0;
  20. }
  21. /// <summary>
  22. /// S8-NOTIFY-RECIPIENT-1:收件人配置的读写。
  23. ///
  24. /// <para><b>写入侧只认目录内的事件与类型</b>,并对 SPECIFIC_USER 逐个校验账号
  25. /// (同租户 + 启用,复用 <see cref="IS8UserScopeValidator"/>)——
  26. /// 让「配得进去、发的时候又发不出」这种状态在写入那一刻就不可能产生。</para>
  27. /// </summary>
  28. public class S8NotificationRecipientConfigService : ITransient
  29. {
  30. private readonly SqlSugarRepository<AdoS8NotificationRecipient> _rep;
  31. private readonly IS8UserScopeValidator _userScope;
  32. private readonly UserManager _userManager;
  33. private readonly ILogger<S8NotificationRecipientConfigService> _logger;
  34. public S8NotificationRecipientConfigService(
  35. SqlSugarRepository<AdoS8NotificationRecipient> rep,
  36. IS8UserScopeValidator userScope,
  37. UserManager userManager,
  38. ILogger<S8NotificationRecipientConfigService> logger)
  39. {
  40. _rep = rep;
  41. _userScope = userScope;
  42. _userManager = userManager;
  43. _logger = logger;
  44. }
  45. /// <summary>某条规则(或租户默认)的事件矩阵。目录里每个事件都出一行,未配置的也要出现。</summary>
  46. public async Task<List<S8NotifyEventRowDto>> GetMatrixAsync(long tenantId, string ruleCode)
  47. {
  48. var rows = await _rep.AsQueryable().ClearFilter()
  49. .Where(x => x.TenantId == tenantId && x.RuleCode == ruleCode)
  50. .ToListAsync();
  51. var byEvent = rows.GroupBy(x => x.EventCode, StringComparer.OrdinalIgnoreCase)
  52. .ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
  53. return S8NotificationCatalog.Events.OrderBy(e => e.OrderNo).Select(e =>
  54. {
  55. var cfg = byEvent.GetValueOrDefault(e.Code) ?? new List<AdoS8NotificationRecipient>();
  56. var specific = cfg.FirstOrDefault(c => c.RecipientType == S8RecipientType.SpecificUser);
  57. return new S8NotifyEventRowDto
  58. {
  59. EventCode = e.Code,
  60. DisplayName = e.DisplayName,
  61. TriggerHint = e.TriggerHint,
  62. OrderNo = e.OrderNo,
  63. RecipientTypes = cfg.Select(c => c.RecipientType).Distinct().ToList(),
  64. SpecificUserIds = S8RoleResolver.SplitTokens(specific?.RecipientUserIds)
  65. .Select(t => long.TryParse(t, out var v) ? v : 0).Where(v => v > 0).ToList()
  66. };
  67. }).ToList();
  68. }
  69. /// <summary>全量替换某事件的收件人配置。</summary>
  70. public async Task SetAsync(S8TrustedScope scope, string ruleCode, string eventCode,
  71. IEnumerable<string>? recipientTypes, IEnumerable<long>? specificUserIds)
  72. {
  73. if (string.IsNullOrWhiteSpace(ruleCode)) throw new S8BizException("规则编码不能为空");
  74. if (!S8NotificationCatalog.IsKnownEvent(eventCode))
  75. throw new S8BizException($"未知事件码:{eventCode}");
  76. var types = (recipientTypes ?? Enumerable.Empty<string>())
  77. .Where(t => !string.IsNullOrWhiteSpace(t)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
  78. var unknown = types.Where(t => !S8NotificationCatalog.IsKnownRecipientType(t)).ToList();
  79. if (unknown.Count > 0)
  80. throw new S8BizException($"未知收件人类型:{string.Join('、', unknown)}");
  81. var users = (specificUserIds ?? Enumerable.Empty<long>()).Where(x => x > 0).Distinct().ToList();
  82. if (types.Contains(S8RecipientType.SpecificUser, StringComparer.OrdinalIgnoreCase))
  83. {
  84. if (users.Count == 0)
  85. throw new S8BizException("已选择「指定账号」,请至少选择一个账号");
  86. var valid = (await _userScope.ValidateUsersAsync(scope.TenantId, users)).Select(u => u.UserId).ToHashSet();
  87. if (users.Any(id => !valid.Contains(id)))
  88. // 与人员校验同口径:跨租户与不存在共用同一文案,不泄漏存在性。
  89. throw new S8BizException("所选账号不存在或已停用");
  90. }
  91. else
  92. {
  93. // 没勾「指定账号」就不该留下账号列表 —— 留着会在下次勾选时悄悄复活一批旧人选。
  94. users.Clear();
  95. }
  96. await _rep.AsDeleteable()
  97. .Where(x => x.TenantId == scope.TenantId && x.RuleCode == ruleCode && x.EventCode == eventCode)
  98. .ExecuteCommandAsync();
  99. if (types.Count > 0)
  100. {
  101. var now = DateTime.Now;
  102. await _rep.AsInsertable(types.Select(t => new AdoS8NotificationRecipient
  103. {
  104. TenantId = scope.TenantId,
  105. RuleCode = ruleCode,
  106. EventCode = eventCode,
  107. RecipientType = t,
  108. RecipientUserIds = t.Equals(S8RecipientType.SpecificUser, StringComparison.OrdinalIgnoreCase)
  109. ? string.Join(',', users)
  110. : null,
  111. CreatedAt = now,
  112. CreatedBy = _userManager.Account
  113. }).ToList()).ExecuteCommandAsync();
  114. }
  115. _logger.LogInformation(
  116. "s8_notify_recipient_updated tenant={Tenant} rule={Rule} event={Event} types={Types} users={Users}",
  117. scope.TenantId, ruleCode, eventCode, string.Join(',', types), users.Count);
  118. }
  119. }