using System.Linq.Expressions;
using Fake.Application;
using Fake.Rbac.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Oms.Application.Dtos.Shared;
using Oms.Application.Dtos.System;
using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Entities;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.SystemAggregate.Entities;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.System;
///
/// 公告 API 服务。
///
[ApiExplorerSettings(GroupName = "OMS-System")]
public class NoticeService : ApplicationService
{
private const string NoticeTypeNotice = "notice";
private const string NoticeTypeMessage = "message";
private const string ReceiverTypeUser = "user";
private const string ReceiverTypeCustomer = "customer";
private const string InternalUserFilter = """
(
NOT EXISTS (SELECT 1 FROM `user_role` ur0 WHERE ur0.`UserId` = u.`Id`)
OR EXISTS (
SELECT 1
FROM `user_role` ur
INNER JOIN `role` r ON r.`Id` = ur.`RoleId` AND r.`IsDeleted` = 0
WHERE ur.`UserId` = u.`Id`
AND r.`Code` <> 'Customer'
AND r.`Code` NOT LIKE 'CustomerSub\_%'
)
)
""";
private INoticeRepository NoticeRepository => LazyServiceProvider.GetRequiredService();
private INoticeReadRepository NoticeReadRepository => LazyServiceProvider.GetRequiredService();
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService();
private FakeRbacDbContext RbacDbContext => LazyServiceProvider.GetRequiredService();
///
/// 查询通知列表。
///
[HttpGet("/system/notice/list")]
public async Task> GetListAsync([FromQuery] NoticeListInput input)
{
var receiverUserId = CurrentUser.GetId();
if (receiverUserId <= 0)
{
return new OmsPageResult { Items = [], Total = 0 };
}
var expectedReceiverType = OmsSessionHelper.IsCustomerLogin(CurrentUser)
? ReceiverTypeCustomer
: ReceiverTypeUser;
var myReadNoticeIds = (await NoticeReadRepository.GetListAsync(x =>
x.ReceiverType == expectedReceiverType && x.ReceiverId == receiverUserId))
.Select(x => x.NoticeId)
.Distinct()
.ToList();
var predicate = BuildListPredicate(
input,
receiverUserId,
expectedReceiverType,
input.OnlyUnread == true ? myReadNoticeIds : null);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var notices = await NoticeRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary { [nameof(Notice.CreateTime)] = false, [nameof(Notice.Id)] = false });
var total = await NoticeRepository.CountAsync(predicate);
var noticeIds = notices.Select(x => x.Id).Distinct().ToArray();
var reads = noticeIds.Length == 0
? []
: await NoticeReadRepository.GetListAsync(x => noticeIds.Contains(x.NoticeId));
var readCountMap = reads
.GroupBy(x => x.NoticeId)
.ToDictionary(
x => x.Key,
x => (int)x.Select(item => $"{item.ReceiverType}:{item.ReceiverId}").Distinct(StringComparer.Ordinal).Count());
var myReadIdSet = myReadNoticeIds.ToHashSet();
var selectionIdMap = await BuildReceiverSelectionIdMapAsync(notices);
return new OmsPageResult
{
Items = notices.Select(x => MapNoticeOutput(
x,
readCountMap.TryGetValue(x.Id, out var readCount) ? readCount : 0,
myReadIdSet.Contains(x.Id),
selectionIdMap.TryGetValue(x.Id, out var selectionIds) ? selectionIds : [])).ToList(),
Total = total
};
}
///
/// 接收人候选项(按接收对象类型加载)。
///
[HttpGet("/system/notice/receiver-options")]
public async Task> GetReceiverOptionsAsync([FromQuery] NoticeReceiverOptionsInput input)
{
var receiverType = NormalizeReceiverType(input.ReceiverType);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var keyword = input.Keyword?.Trim();
if (receiverType == ReceiverTypeCustomer)
{
return await QueryCustomerReceiverOptionsAsync(keyword, pageIndex, pageSize);
}
return await QueryUserReceiverOptionsAsync(keyword, pageIndex, pageSize);
}
///
/// 新增通知。
///
[HttpPost("/system/notice/create")]
public async Task CreateAsync(NoticeCreateInput input)
{
var noticeType = NormalizeNoticeType(input.NoticeType);
ValidateNoticeText(noticeType, input.Title, input.Content);
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
await NoticeRepository.InsertAsync(new Notice
{
NoticeType = noticeType,
Title = NormalizeTitle(input.Title),
Content = input.Content.Trim(),
IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup),
ReceiverType = receiverType,
ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]"
});
}
///
/// 更新通知。
///
[HttpPost("/system/notice/update")]
public async Task UpdateAsync(NoticeUpdateInput input)
{
var entity = await GetNoticeOrThrowAsync(input.Id);
var noticeType = NormalizeNoticeType(input.NoticeType);
ValidateNoticeText(noticeType, input.Title, input.Content);
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
entity.NoticeType = noticeType;
entity.Title = NormalizeTitle(input.Title);
entity.Content = input.Content.Trim();
entity.IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup);
entity.ReceiverType = receiverType;
entity.ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]";
await NoticeRepository.UpdateAsync(entity);
// 全体广播不清理已读;定向接收时移除已不在名单内的已读记录
if (receiverIds.Count > 0)
{
var existsReads = await NoticeReadRepository.GetListAsync(x => x.NoticeId == entity.Id);
var receiverIdSet = receiverIds.ToHashSet();
foreach (var read in existsReads.Where(x =>
!string.Equals(x.ReceiverType, receiverType, StringComparison.OrdinalIgnoreCase) ||
!receiverIdSet.Contains(x.ReceiverId)))
{
await NoticeReadRepository.DeleteAsync(read);
}
}
}
///
/// 删除通知。
///
[HttpPost("/system/notice/delete")]
public async Task DeleteAsync(IdInput input)
{
var entity = await GetNoticeOrThrowAsync(input.Id);
await NoticeRepository.DeleteAsync(entity);
}
///
/// 确认通知已读。
///
[HttpPost("/system/notice/read-confirm")]
public async Task ReadConfirmAsync(IdInput input)
{
var notice = await GetNoticeOrThrowAsync(input.Id);
var receiverIds = OmsDocHelper.DeserializeJson>(notice.ReceiverIds) ?? [];
var currentReceiverType = OmsSessionHelper.IsCustomerLogin(CurrentUser)
? ReceiverTypeCustomer
: ReceiverTypeUser;
var currentReceiverId = CurrentUser.GetId();
if (currentReceiverId <= 0)
{
throw new BusinessException("当前登录主体无效");
}
// 空接收人 = 全体广播;否则校验当前主体在定向名单内
if (receiverIds.Count > 0 && !receiverIds.Contains(currentReceiverId))
{
throw new BusinessException("当前主体不在通知接收范围内");
}
var existed = await NoticeReadRepository.GetByNoticeReceiverAsync(notice.Id, currentReceiverType, currentReceiverId);
if (existed != null)
{
return;
}
await NoticeReadRepository.InsertAsync(new NoticeRead
{
NoticeId = notice.Id,
ReceiverType = currentReceiverType,
ReceiverId = currentReceiverId,
ReadTime = DateTime.Now
});
}
private async Task> QueryUserReceiverOptionsAsync(
string? keyword,
int pageIndex,
int pageSize)
{
var where = $"WHERE u.`IsDeleted` = 0 AND {InternalUserFilter}";
var args = new List