
- 新增通知系统完整架构,包含通知设置、历史记录、任务管理等核心功能 - 实现工作任务分配服务,支持人员和设备的智能分配 - 添加人员分组管理功能,支持灵活的通知目标配置 - 完善相关枚举定义和数据传输对象 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
580 lines
22 KiB
C#
580 lines
22 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using System.Linq;
|
||
using Microsoft.Extensions.Logging;
|
||
using FreeSql;
|
||
using ZhonTai.Admin.Core.Db.Transaction;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Notification;
|
||
using NPP.SmartSchedue.Api.Contracts.Core.Repositories;
|
||
using NPP.SmartSchedue.Api.Contracts.Domain.Notification;
|
||
|
||
namespace NPP.SmartSchedue.Api.Services.Notification;
|
||
|
||
/// <summary>
|
||
/// 系统消息服务实现
|
||
/// 决策点1:基础通知方式 - 系统消息通知
|
||
/// 业务思考:系统消息是内部通知的核心方式,需要支持实时推送、状态管理、操作交互等功能
|
||
/// 设计原则:
|
||
/// 1. 消息持久化存储,确保不丢失
|
||
/// 2. 支持消息状态管理(未读/已读/已删除)
|
||
/// 3. 支持操作按钮,实现交互式消息
|
||
/// 4. 支持批量操作,提高效率
|
||
/// 5. 支持模板化消息,统一格式
|
||
/// </summary>
|
||
public class SystemMessageService : ISystemMessageService
|
||
{
|
||
private readonly ILogger<SystemMessageService> _logger;
|
||
private readonly INotificationTemplateService _templateService;
|
||
private readonly IBaseRepository<NotificationHistoryEntity> _notificationHistoryRepository;
|
||
private readonly IUnitOfWorkManager _uowManager;
|
||
|
||
public SystemMessageService(
|
||
ILogger<SystemMessageService> logger,
|
||
INotificationTemplateService templateService,
|
||
IBaseRepository<NotificationHistoryEntity> notificationHistoryRepository,
|
||
IUnitOfWorkManager uowManager)
|
||
{
|
||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||
_templateService = templateService ?? throw new ArgumentNullException(nameof(templateService));
|
||
_notificationHistoryRepository = notificationHistoryRepository ?? throw new ArgumentNullException(nameof(notificationHistoryRepository));
|
||
_uowManager = uowManager ?? throw new ArgumentNullException(nameof(uowManager));
|
||
}
|
||
|
||
#region 单个消息发送
|
||
|
||
/// <summary>
|
||
/// 发送系统消息
|
||
/// 业务场景:工作任务分配、设备状态变更、排班通知等
|
||
/// </summary>
|
||
public async Task<bool> SendSystemMessageAsync(
|
||
long recipientPersonnelId,
|
||
string title,
|
||
string content,
|
||
SystemMessageTypeEnum messageType = SystemMessageTypeEnum.Info,
|
||
string businessType = "",
|
||
long? businessId = null)
|
||
{
|
||
try
|
||
{
|
||
if (recipientPersonnelId <= 0)
|
||
{
|
||
_logger.LogWarning("收件人ID无效:{RecipientId}", recipientPersonnelId);
|
||
return false;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content))
|
||
{
|
||
_logger.LogWarning("消息标题或内容不能为空");
|
||
return false;
|
||
}
|
||
|
||
using var uow = _uowManager.Begin();
|
||
|
||
// 创建通知历史记录
|
||
var notificationHistory = new NotificationHistoryEntity
|
||
{
|
||
NotificationType = Core.Enums.NotificationTypeEnum.SystemMessage,
|
||
RecipientType = "Personnel",
|
||
RecipientId = recipientPersonnelId.ToString(),
|
||
Subject = title,
|
||
Content = content,
|
||
BusinessType = businessType,
|
||
BusinessId = businessId,
|
||
Status = Core.Enums.NotificationStatusEnum.Sent,
|
||
SentTime = DateTime.Now,
|
||
MessageType = GetNotificationMessageType(messageType),
|
||
IsRead = false,
|
||
IsDeleted = false,
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
|
||
await _notificationHistoryRepository.InsertAsync(notificationHistory);
|
||
await uow.CommitAsync();
|
||
|
||
_logger.LogInformation("系统消息发送成功:收件人 {RecipientId},标题:{Title}", recipientPersonnelId, title);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "发送系统消息失败:收件人 {RecipientId},标题:{Title}", recipientPersonnelId, title);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 发送带操作按钮的系统消息
|
||
/// 业务场景:任务确认、审批流程、操作确认等交互式场景
|
||
/// </summary>
|
||
public async Task<bool> SendSystemMessageWithActionsAsync(
|
||
long recipientPersonnelId,
|
||
string title,
|
||
string content,
|
||
List<SystemMessageAction> actions,
|
||
SystemMessageTypeEnum messageType = SystemMessageTypeEnum.Info,
|
||
string businessType = "",
|
||
long? businessId = null)
|
||
{
|
||
try
|
||
{
|
||
if (recipientPersonnelId <= 0)
|
||
{
|
||
_logger.LogWarning("收件人ID无效:{RecipientId}", recipientPersonnelId);
|
||
return false;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content))
|
||
{
|
||
_logger.LogWarning("消息标题或内容不能为空");
|
||
return false;
|
||
}
|
||
|
||
using var uow = _uowManager.Begin();
|
||
|
||
// 序列化操作按钮
|
||
var actionsJson = actions != null && actions.Any()
|
||
? System.Text.Json.JsonSerializer.Serialize(actions)
|
||
: "";
|
||
|
||
// 创建通知历史记录
|
||
var notificationHistory = new NotificationHistoryEntity
|
||
{
|
||
NotificationType = Core.Enums.NotificationTypeEnum.SystemMessage,
|
||
RecipientType = "Personnel",
|
||
RecipientId = recipientPersonnelId.ToString(),
|
||
Subject = title,
|
||
Content = content,
|
||
BusinessType = businessType,
|
||
BusinessId = businessId,
|
||
Status = Core.Enums.NotificationStatusEnum.Sent,
|
||
SentTime = DateTime.Now,
|
||
MessageType = GetNotificationMessageType(messageType),
|
||
IsRead = false,
|
||
IsDeleted = false,
|
||
ActionsData = actionsJson, // 存储操作按钮数据
|
||
CreatedTime = DateTime.Now
|
||
};
|
||
|
||
await _notificationHistoryRepository.InsertAsync(notificationHistory);
|
||
await uow.CommitAsync();
|
||
|
||
_logger.LogInformation("带操作按钮的系统消息发送成功:收件人 {RecipientId},标题:{Title},操作数量:{ActionCount}",
|
||
recipientPersonnelId, title, actions?.Count ?? 0);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "发送带操作按钮的系统消息失败:收件人 {RecipientId},标题:{Title}", recipientPersonnelId, title);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 批量消息发送
|
||
|
||
/// <summary>
|
||
/// 批量发送系统消息
|
||
/// 业务场景:排班变更通知、紧急通知、系统维护通知等需要群发的场景
|
||
/// </summary>
|
||
public async Task<Dictionary<long, bool>> BatchSendSystemMessageAsync(
|
||
List<long> recipientPersonnelIds,
|
||
string title,
|
||
string content,
|
||
SystemMessageTypeEnum messageType = SystemMessageTypeEnum.Info,
|
||
string businessType = "",
|
||
long? businessId = null)
|
||
{
|
||
var results = new Dictionary<long, bool>();
|
||
|
||
if (recipientPersonnelIds == null || !recipientPersonnelIds.Any())
|
||
{
|
||
_logger.LogWarning("批量发送系统消息:收件人列表为空");
|
||
return results;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(content))
|
||
{
|
||
_logger.LogWarning("消息标题或内容不能为空");
|
||
foreach (var recipientId in recipientPersonnelIds)
|
||
{
|
||
results[recipientId] = false;
|
||
}
|
||
return results;
|
||
}
|
||
|
||
// 去重收件人
|
||
var uniqueRecipients = recipientPersonnelIds.Distinct().Where(id => id > 0).ToList();
|
||
|
||
try
|
||
{
|
||
using var uow = _uowManager.Begin();
|
||
|
||
var notificationHistories = new List<NotificationHistoryEntity>();
|
||
var messageTypeValue = GetNotificationMessageType(messageType);
|
||
var currentTime = DateTime.Now;
|
||
|
||
// 批量创建通知记录
|
||
foreach (var recipientId in uniqueRecipients)
|
||
{
|
||
var notificationHistory = new NotificationHistoryEntity
|
||
{
|
||
NotificationType = Core.Enums.NotificationTypeEnum.SystemMessage,
|
||
RecipientType = "Personnel",
|
||
RecipientId = recipientId.ToString(),
|
||
Subject = title,
|
||
Content = content,
|
||
BusinessType = businessType,
|
||
BusinessId = businessId,
|
||
Status = Core.Enums.NotificationStatusEnum.Sent,
|
||
SentTime = currentTime,
|
||
MessageType = messageTypeValue,
|
||
IsRead = false,
|
||
IsDeleted = false,
|
||
CreatedTime = currentTime
|
||
};
|
||
|
||
notificationHistories.Add(notificationHistory);
|
||
results[recipientId] = true; // 预设为成功,如果出错会被重置
|
||
}
|
||
|
||
// 批量插入
|
||
await _notificationHistoryRepository.InsertAsync(notificationHistories);
|
||
await uow.CommitAsync();
|
||
|
||
var successCount = results.Values.Count(r => r);
|
||
_logger.LogInformation("批量发送系统消息完成:总数 {Total},成功 {Success},失败 {Failed}",
|
||
recipientPersonnelIds.Count, successCount, recipientPersonnelIds.Count - successCount);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "批量发送系统消息失败");
|
||
// 如果批量操作失败,将所有结果设为失败
|
||
foreach (var recipientId in uniqueRecipients)
|
||
{
|
||
results[recipientId] = false;
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 个性化批量发送系统消息
|
||
/// 业务场景:个性化任务分配通知、个性化提醒等需要不同内容的场景
|
||
/// </summary>
|
||
public async Task<Dictionary<long, bool>> BatchSendPersonalizedSystemMessageAsync(List<SystemMessageItem> messageItems)
|
||
{
|
||
var results = new Dictionary<long, bool>();
|
||
|
||
if (messageItems == null || !messageItems.Any())
|
||
{
|
||
_logger.LogWarning("个性化批量发送系统消息:消息项列表为空");
|
||
return results;
|
||
}
|
||
|
||
try
|
||
{
|
||
using var uow = _uowManager.Begin();
|
||
|
||
var notificationHistories = new List<NotificationHistoryEntity>();
|
||
var currentTime = DateTime.Now;
|
||
|
||
foreach (var item in messageItems)
|
||
{
|
||
try
|
||
{
|
||
if (item.RecipientPersonnelId <= 0 ||
|
||
string.IsNullOrWhiteSpace(item.Title) ||
|
||
string.IsNullOrWhiteSpace(item.Content))
|
||
{
|
||
_logger.LogWarning("消息项无效:收件人ID {RecipientId},标题:{Title}",
|
||
item.RecipientPersonnelId, item.Title);
|
||
results[item.RecipientPersonnelId] = false;
|
||
continue;
|
||
}
|
||
|
||
// 渲染模板变量(如果有)
|
||
var title = item.Title;
|
||
var content = item.Content;
|
||
|
||
if (item.Variables != null && item.Variables.Any())
|
||
{
|
||
title = await _templateService.RenderTemplateAsync(item.Title, item.Variables);
|
||
content = await _templateService.RenderTemplateAsync(item.Content, item.Variables);
|
||
}
|
||
|
||
// 序列化操作按钮
|
||
var actionsJson = item.Actions != null && item.Actions.Any()
|
||
? System.Text.Json.JsonSerializer.Serialize(item.Actions)
|
||
: "";
|
||
|
||
var notificationHistory = new NotificationHistoryEntity
|
||
{
|
||
NotificationType = Core.Enums.NotificationTypeEnum.SystemMessage,
|
||
RecipientType = "Personnel",
|
||
RecipientId = item.RecipientPersonnelId.ToString(),
|
||
Subject = title,
|
||
Content = content,
|
||
BusinessType = item.BusinessType,
|
||
BusinessId = item.BusinessId,
|
||
Status = Core.Enums.NotificationStatusEnum.Sent,
|
||
SentTime = currentTime,
|
||
MessageType = GetNotificationMessageType(item.MessageType),
|
||
IsRead = false,
|
||
IsDeleted = false,
|
||
ActionsData = actionsJson,
|
||
CreatedTime = currentTime
|
||
};
|
||
|
||
notificationHistories.Add(notificationHistory);
|
||
results[item.RecipientPersonnelId] = true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理个性化消息项失败:收件人 {RecipientId}", item.RecipientPersonnelId);
|
||
results[item.RecipientPersonnelId] = false;
|
||
}
|
||
}
|
||
|
||
// 批量插入成功的记录
|
||
if (notificationHistories.Any())
|
||
{
|
||
await _notificationHistoryRepository.InsertAsync(notificationHistories);
|
||
await uow.CommitAsync();
|
||
}
|
||
|
||
var successCount = results.Values.Count(r => r);
|
||
_logger.LogInformation("个性化批量发送系统消息完成:总数 {Total},成功 {Success},失败 {Failed}",
|
||
messageItems.Count, successCount, messageItems.Count - successCount);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "个性化批量发送系统消息失败");
|
||
// 如果批量操作失败,将所有结果设为失败
|
||
foreach (var item in messageItems)
|
||
{
|
||
results[item.RecipientPersonnelId] = false;
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 消息模板
|
||
|
||
/// <summary>
|
||
/// 使用模板发送系统消息
|
||
/// 业务场景:标准化通知,如任务分配模板、维护提醒模板等
|
||
/// </summary>
|
||
public async Task<bool> SendSystemMessageByTemplateAsync(
|
||
long recipientPersonnelId,
|
||
string titleTemplate,
|
||
string contentTemplate,
|
||
Dictionary<string, string> variables,
|
||
SystemMessageTypeEnum messageType = SystemMessageTypeEnum.Info,
|
||
string businessType = "",
|
||
long? businessId = null)
|
||
{
|
||
try
|
||
{
|
||
// 渲染模板
|
||
var title = await _templateService.RenderTemplateAsync(titleTemplate, variables);
|
||
var content = await _templateService.RenderTemplateAsync(contentTemplate, variables);
|
||
|
||
// 发送消息
|
||
return await SendSystemMessageAsync(recipientPersonnelId, title, content, messageType, businessType, businessId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "使用模板发送系统消息失败:收件人 {RecipientId}", recipientPersonnelId);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 消息管理
|
||
|
||
/// <summary>
|
||
/// 标记消息为已读
|
||
/// 业务场景:用户查看消息后更新状态,用于消息中心的已读/未读状态管理
|
||
/// </summary>
|
||
public async Task<bool> MarkMessageAsReadAsync(long messageId, long recipientPersonnelId)
|
||
{
|
||
try
|
||
{
|
||
using var uow = _uowManager.Begin();
|
||
|
||
var message = await _notificationHistoryRepository
|
||
.Where(x => x.Id == messageId &&
|
||
x.RecipientId == recipientPersonnelId.ToString() &&
|
||
!x.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (message == null)
|
||
{
|
||
_logger.LogWarning("消息不存在或已被删除:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
return false;
|
||
}
|
||
|
||
if (!message.IsRead)
|
||
{
|
||
message.IsRead = true;
|
||
message.ReadTime = DateTime.Now;
|
||
message.ModifiedTime = DateTime.Now;
|
||
|
||
await _notificationHistoryRepository.UpdateAsync(message);
|
||
await uow.CommitAsync();
|
||
|
||
_logger.LogDebug("消息标记为已读:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "标记消息为已读失败:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量标记消息为已读
|
||
/// 业务场景:用户批量处理消息,提高操作效率
|
||
/// </summary>
|
||
public async Task<int> BatchMarkMessagesAsReadAsync(List<long> messageIds, long recipientPersonnelId)
|
||
{
|
||
if (messageIds == null || !messageIds.Any())
|
||
{
|
||
_logger.LogWarning("批量标记消息为已读:消息ID列表为空");
|
||
return 0;
|
||
}
|
||
|
||
try
|
||
{
|
||
using var uow = _uowManager.Begin();
|
||
var currentTime = DateTime.Now;
|
||
|
||
// 查询未读的消息
|
||
var messages = await _notificationHistoryRepository
|
||
.Where(x => messageIds.Contains(x.Id) &&
|
||
x.RecipientId == recipientPersonnelId.ToString() &&
|
||
!x.IsDeleted &&
|
||
!x.IsRead)
|
||
.ToListAsync();
|
||
|
||
if (!messages.Any())
|
||
{
|
||
_logger.LogInformation("没有找到需要标记为已读的消息:收件人 {RecipientId}", recipientPersonnelId);
|
||
return 0;
|
||
}
|
||
|
||
// 批量更新
|
||
foreach (var message in messages)
|
||
{
|
||
message.IsRead = true;
|
||
message.ReadTime = currentTime;
|
||
message.ModifiedTime = currentTime;
|
||
}
|
||
|
||
await _notificationHistoryRepository.UpdateAsync(messages);
|
||
await uow.CommitAsync();
|
||
|
||
_logger.LogInformation("批量标记消息为已读完成:收件人 {RecipientId},更新数量 {Count}", recipientPersonnelId, messages.Count);
|
||
return messages.Count;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "批量标记消息为已读失败:收件人 {RecipientId}", recipientPersonnelId);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除消息
|
||
/// 业务场景:用户清理不需要的消息,实现软删除以保留审计记录
|
||
/// </summary>
|
||
public async Task<bool> DeleteMessageAsync(long messageId, long recipientPersonnelId)
|
||
{
|
||
try
|
||
{
|
||
using var uow = _uowManager.Begin();
|
||
|
||
var message = await _notificationHistoryRepository
|
||
.Where(x => x.Id == messageId &&
|
||
x.RecipientId == recipientPersonnelId.ToString() &&
|
||
!x.IsDeleted)
|
||
.FirstAsync();
|
||
|
||
if (message == null)
|
||
{
|
||
_logger.LogWarning("消息不存在或已被删除:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
return false;
|
||
}
|
||
|
||
// 软删除
|
||
message.IsDeleted = true;
|
||
message.ModifiedTime = DateTime.Now;
|
||
|
||
await _notificationHistoryRepository.UpdateAsync(message);
|
||
await uow.CommitAsync();
|
||
|
||
_logger.LogInformation("消息删除成功:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
return true;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "删除消息失败:消息ID {MessageId},收件人 {RecipientId}", messageId, recipientPersonnelId);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户未读消息数量
|
||
/// 业务场景:消息中心红点提示、导航栏未读消息数显示
|
||
/// </summary>
|
||
public async Task<int> GetUnreadMessageCountAsync(long recipientPersonnelId)
|
||
{
|
||
try
|
||
{
|
||
var count = await _notificationHistoryRepository
|
||
.Where(x => x.RecipientId == recipientPersonnelId.ToString() &&
|
||
x.NotificationType == Core.Enums.NotificationTypeEnum.SystemMessage &&
|
||
!x.IsRead &&
|
||
!x.IsDeleted)
|
||
.CountAsync();
|
||
|
||
return count;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "获取用户未读消息数量失败:收件人 {RecipientId}", recipientPersonnelId);
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有辅助方法
|
||
|
||
/// <summary>
|
||
/// 将SystemMessageTypeEnum转换为通知历史实体的MessageType字段
|
||
/// </summary>
|
||
private string GetNotificationMessageType(SystemMessageTypeEnum messageType)
|
||
{
|
||
return messageType switch
|
||
{
|
||
SystemMessageTypeEnum.Info => "信息",
|
||
SystemMessageTypeEnum.Success => "成功",
|
||
SystemMessageTypeEnum.Warning => "警告",
|
||
SystemMessageTypeEnum.Error => "错误",
|
||
SystemMessageTypeEnum.Urgent => "紧急",
|
||
_ => "信息"
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
} |