using Admin.NET.Core;
using Admin.NET.Plugin.AiDOP.Const.S8;
using Admin.NET.Plugin.AiDOP.Entity.S8;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
using Microsoft.Extensions.Logging;
namespace Admin.NET.Plugin.AiDOP.Service.S8;
/// 配置页的一行:一个事件 + 它当前配置的收件人类型。
public sealed class S8NotifyEventRowDto
{
public string EventCode { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
public string TriggerHint { get; set; } = string.Empty;
public int OrderNo { get; set; }
public List RecipientTypes { get; set; } = new();
/// SPECIFIC_USER 选中的账号。
public List SpecificUserIds { get; set; } = new();
/// 本事件尚未配置任何收件人 —— 会回落 LEGACY 分层,页面必须说明。
public bool NotConfigured => RecipientTypes.Count == 0;
}
///
/// S8-NOTIFY-RECIPIENT-1:收件人配置的读写。
///
/// 写入侧只认目录内的事件与类型,并对 SPECIFIC_USER 逐个校验账号
/// (同租户 + 启用,复用 )——
/// 让「配得进去、发的时候又发不出」这种状态在写入那一刻就不可能产生。
///
public class S8NotificationRecipientConfigService : ITransient
{
private readonly SqlSugarRepository _rep;
private readonly IS8UserScopeValidator _userScope;
private readonly UserManager _userManager;
private readonly ILogger _logger;
public S8NotificationRecipientConfigService(
SqlSugarRepository rep,
IS8UserScopeValidator userScope,
UserManager userManager,
ILogger logger)
{
_rep = rep;
_userScope = userScope;
_userManager = userManager;
_logger = logger;
}
/// 某条规则(或租户默认)的事件矩阵。目录里每个事件都出一行,未配置的也要出现。
public async Task> GetMatrixAsync(long tenantId, string ruleCode)
{
var rows = await _rep.AsQueryable().ClearFilter()
.Where(x => x.TenantId == tenantId && x.RuleCode == ruleCode)
.ToListAsync();
var byEvent = rows.GroupBy(x => x.EventCode, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
return S8NotificationCatalog.Events.OrderBy(e => e.OrderNo).Select(e =>
{
var cfg = byEvent.GetValueOrDefault(e.Code) ?? new List();
var specific = cfg.FirstOrDefault(c => c.RecipientType == S8RecipientType.SpecificUser);
return new S8NotifyEventRowDto
{
EventCode = e.Code,
DisplayName = e.DisplayName,
TriggerHint = e.TriggerHint,
OrderNo = e.OrderNo,
RecipientTypes = cfg.Select(c => c.RecipientType).Distinct().ToList(),
SpecificUserIds = S8RoleResolver.SplitTokens(specific?.RecipientUserIds)
.Select(t => long.TryParse(t, out var v) ? v : 0).Where(v => v > 0).ToList()
};
}).ToList();
}
/// 全量替换某事件的收件人配置。
public async Task SetAsync(S8TrustedScope scope, string ruleCode, string eventCode,
IEnumerable? recipientTypes, IEnumerable? specificUserIds)
{
if (string.IsNullOrWhiteSpace(ruleCode)) throw new S8BizException("规则编码不能为空");
if (!S8NotificationCatalog.IsKnownEvent(eventCode))
throw new S8BizException($"未知事件码:{eventCode}");
var types = (recipientTypes ?? Enumerable.Empty())
.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
var unknown = types.Where(t => !S8NotificationCatalog.IsKnownRecipientType(t)).ToList();
if (unknown.Count > 0)
throw new S8BizException($"未知收件人类型:{string.Join('、', unknown)}");
var users = (specificUserIds ?? Enumerable.Empty()).Where(x => x > 0).Distinct().ToList();
if (types.Contains(S8RecipientType.SpecificUser, StringComparer.OrdinalIgnoreCase))
{
if (users.Count == 0)
throw new S8BizException("已选择「指定账号」,请至少选择一个账号");
var valid = (await _userScope.ValidateUsersAsync(scope.TenantId, users)).Select(u => u.UserId).ToHashSet();
if (users.Any(id => !valid.Contains(id)))
// 与人员校验同口径:跨租户与不存在共用同一文案,不泄漏存在性。
throw new S8BizException("所选账号不存在或已停用");
}
else
{
// 没勾「指定账号」就不该留下账号列表 —— 留着会在下次勾选时悄悄复活一批旧人选。
users.Clear();
}
await _rep.AsDeleteable()
.Where(x => x.TenantId == scope.TenantId && x.RuleCode == ruleCode && x.EventCode == eventCode)
.ExecuteCommandAsync();
if (types.Count > 0)
{
var now = DateTime.Now;
await _rep.AsInsertable(types.Select(t => new AdoS8NotificationRecipient
{
TenantId = scope.TenantId,
RuleCode = ruleCode,
EventCode = eventCode,
RecipientType = t,
RecipientUserIds = t.Equals(S8RecipientType.SpecificUser, StringComparison.OrdinalIgnoreCase)
? string.Join(',', users)
: null,
CreatedAt = now,
CreatedBy = _userManager.Account
}).ToList()).ExecuteCommandAsync();
}
_logger.LogInformation(
"s8_notify_recipient_updated tenant={Tenant} rule={Rule} event={Event} types={Types} users={Users}",
scope.TenantId, ruleCode, eventCode, string.Join(',', types), users.Count);
}
}