18 changed files with 337 additions and 75 deletions
@ -0,0 +1,8 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpEmailExceptionHandlingOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 默认异常收件人
|
||||
|
/// </summary>
|
||||
|
public string DefaultReceiveEmail { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 异常类型指定收件人处理映射列表
|
||||
|
/// </summary>
|
||||
|
public IDictionary<Exception, string> Handlers { get; set; } |
||||
|
public AbpEmailExceptionHandlingOptions() |
||||
|
{ |
||||
|
Handlers = new Dictionary<Exception, string>(); |
||||
|
} |
||||
|
/// <summary>
|
||||
|
/// 把需要接受异常通知的用户加进处理列表
|
||||
|
/// </summary>
|
||||
|
/// <param name="ex">处理的异常类型</param>
|
||||
|
/// <param name="receivedEmails">接收邮件的用户类别,群发用,符号分隔</param>
|
||||
|
public void HandReceivedException(Exception ex, string receivedEmails) |
||||
|
{ |
||||
|
if (Handlers.ContainsKey(ex)) |
||||
|
{ |
||||
|
Handlers[ex] += receivedEmails; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Handlers.Add(ex, receivedEmails); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string GetReceivedEmailOrDefault(Exception ex) |
||||
|
{ |
||||
|
if (Handlers.TryGetValue(ex, out string receivedUsers)) |
||||
|
{ |
||||
|
return receivedUsers; |
||||
|
} |
||||
|
return DefaultReceiveEmail; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling.Emailing |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpExceptionHandlingModule))] |
||||
|
public class AbpEmailingExceptionHandlingModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Emailing; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpEmailingExceptionSubscriber : AbpExceptionSubscriberBase |
||||
|
{ |
||||
|
protected IEmailSender EmailSender { get; } |
||||
|
protected AbpEmailExceptionHandlingOptions EmailOptions { get; } |
||||
|
public AbpEmailingExceptionSubscriber( |
||||
|
IEmailSender emailSender, |
||||
|
IServiceScopeFactory serviceScopeFactory, |
||||
|
IOptions<AbpExceptionHandlingOptions> options, |
||||
|
IOptions<AbpEmailExceptionHandlingOptions> emailOptions) |
||||
|
: base(serviceScopeFactory, options) |
||||
|
{ |
||||
|
EmailSender = emailSender; |
||||
|
EmailOptions = emailOptions.Value; |
||||
|
} |
||||
|
|
||||
|
protected override async Task SendErrorNotifierAsync(ExceptionSendNotifierContext context) |
||||
|
{ |
||||
|
var receivedUsers = EmailOptions.GetReceivedEmailOrDefault(context.Exception); |
||||
|
|
||||
|
if (!receivedUsers.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
// TODO: 使用 Template 格式化推送
|
||||
|
await EmailSender.SendAsync(receivedUsers, |
||||
|
context.Exception.GetType().FullName, |
||||
|
context.Exception.Message); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.ExceptionHandling\LINGYUN.Abp.ExceptionHandling.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using LINGYUN.Abp.Notifications; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpExceptionHandlingNotificationDefinitionProvider : NotificationDefinitionProvider |
||||
|
{ |
||||
|
public override void Define(INotificationDefinitionContext context) |
||||
|
{ |
||||
|
context.Add(new NotificationDefinition(AbpExceptionHandlingNotificationNames.NotificationName)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpExceptionHandlingNotificationNames |
||||
|
{ |
||||
|
public const string NotificationName = "Abp.ExceptionHandling.Notifier"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpExceptionHandlingModule))] |
||||
|
public class AbpNotificationsExceptionHandlingModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpNotificationsExceptionSubscriber : AbpExceptionSubscriberBase |
||||
|
{ |
||||
|
protected ICurrentTenant CurrentTenant { get; } |
||||
|
public AbpNotificationsExceptionSubscriber( |
||||
|
ICurrentTenant currentTenant, |
||||
|
IServiceScopeFactory serviceScopeFactory, |
||||
|
IOptions<AbpExceptionHandlingOptions> options) |
||||
|
: base(serviceScopeFactory, options) |
||||
|
{ |
||||
|
CurrentTenant = currentTenant; |
||||
|
} |
||||
|
|
||||
|
protected override async Task SendErrorNotifierAsync(ExceptionSendNotifierContext context) |
||||
|
{ |
||||
|
var notificationDispatcher = context.ServiceProvider.GetRequiredService<INotificationDispatcher>(); |
||||
|
var notificationName = NotificationNameNormalizer |
||||
|
.NormalizerName(AbpExceptionHandlingNotificationNames.NotificationName); |
||||
|
var notificationData = new NotificationData(); |
||||
|
// 写入通知数据
|
||||
|
//TODO:集成TextTemplate完成格式化的推送
|
||||
|
notificationData.WriteStandardData( |
||||
|
context.Exception.GetType().FullName, context.Exception.Message, |
||||
|
DateTime.Now, "System"); |
||||
|
|
||||
|
await notificationDispatcher.DispatchAsync(notificationName, notificationData, |
||||
|
CurrentTenant.Id, NotificationSeverity.Error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Volo.Abp.Core" Version="2.9.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpExceptionHandlingModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Volo.Abp.Collections; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class AbpExceptionHandlingOptions |
||||
|
{ |
||||
|
public ITypeList<Exception> Handlers { get; } |
||||
|
public AbpExceptionHandlingOptions() |
||||
|
{ |
||||
|
Handlers = new TypeList<Exception>(); |
||||
|
} |
||||
|
|
||||
|
public bool HasNotifierError(Exception ex) |
||||
|
{ |
||||
|
if (typeof(IHasNotifierErrorMessage).IsAssignableFrom(ex.GetType())) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
return Handlers.Any(x => x.IsAssignableFrom(ex.GetType())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Logging.Abstractions; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.ExceptionHandling; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public abstract class AbpExceptionSubscriberBase : ExceptionSubscriber |
||||
|
{ |
||||
|
protected IServiceScopeFactory ServiceScopeFactory { get; } |
||||
|
protected AbpExceptionHandlingOptions Options { get; } |
||||
|
|
||||
|
public IServiceProvider ServiceProvider { get; set; } |
||||
|
protected readonly object ServiceProviderLock = new object(); |
||||
|
|
||||
|
protected TService LazyGetRequiredService<TService>(ref TService reference) |
||||
|
=> LazyGetRequiredService(typeof(TService), ref reference); |
||||
|
|
||||
|
protected TRef LazyGetRequiredService<TRef>(Type serviceType, ref TRef reference) |
||||
|
{ |
||||
|
if (reference == null) |
||||
|
{ |
||||
|
lock (ServiceProviderLock) |
||||
|
{ |
||||
|
if (reference == null) |
||||
|
{ |
||||
|
reference = (TRef)ServiceProvider.GetRequiredService(serviceType); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return reference; |
||||
|
} |
||||
|
|
||||
|
protected ILoggerFactory LoggerFactory => LazyGetRequiredService(ref _loggerFactory); |
||||
|
private ILoggerFactory _loggerFactory; |
||||
|
|
||||
|
protected ILogger Logger => _lazyLogger.Value; |
||||
|
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); |
||||
|
|
||||
|
|
||||
|
protected AbpExceptionSubscriberBase( |
||||
|
IServiceScopeFactory serviceScopeFactory, |
||||
|
IOptions<AbpExceptionHandlingOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
ServiceScopeFactory = serviceScopeFactory; |
||||
|
} |
||||
|
|
||||
|
public override async Task HandleAsync(ExceptionNotificationContext context) |
||||
|
{ |
||||
|
if (context.Handled && |
||||
|
Options.HasNotifierError(context.Exception)) |
||||
|
{ |
||||
|
using (var scope = ServiceScopeFactory.CreateScope()) |
||||
|
{ |
||||
|
await SendErrorNotifierAsync( |
||||
|
new ExceptionSendNotifierContext(scope.ServiceProvider, context.Exception, context.LogLevel)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract Task SendErrorNotifierAsync(ExceptionSendNotifierContext context); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
public class ExceptionSendNotifierContext |
||||
|
{ |
||||
|
[NotNull] |
||||
|
public Exception Exception { get; } |
||||
|
|
||||
|
[NotNull] |
||||
|
public IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public LogLevel LogLevel { get; } |
||||
|
internal ExceptionSendNotifierContext( |
||||
|
[NotNull] IServiceProvider serviceProvider, |
||||
|
[NotNull] Exception exception, |
||||
|
LogLevel? logLevel = null) |
||||
|
{ |
||||
|
ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider)); |
||||
|
Exception = Check.NotNull(exception, nameof(exception)); |
||||
|
LogLevel = logLevel ?? exception.GetLogLevel(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.ExceptionHandling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 需要发送异常通知的自定义异常需要实现此接口
|
||||
|
/// </summary>
|
||||
|
public interface IHasNotifierErrorMessage |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,75 +0,0 @@ |
|||||
using LINGYUN.Abp.Notifications; |
|
||||
using Microsoft.AspNetCore.Mvc; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Threading.Tasks; |
|
||||
using Volo.Abp.AspNetCore.Mvc; |
|
||||
|
|
||||
namespace LINGYUN.Abp.MessageService.Controllers |
|
||||
{ |
|
||||
[Route("api/app/notifications")] |
|
||||
public class NotificationController : AbpController |
|
||||
{ |
|
||||
private readonly INotificationDispatcher _notificationDispatcher; |
|
||||
public NotificationController( |
|
||||
INotificationDispatcher notificationDispatcher) |
|
||||
{ |
|
||||
_notificationDispatcher = notificationDispatcher; |
|
||||
} |
|
||||
|
|
||||
[HttpGet] |
|
||||
[Route("Test")] |
|
||||
public async Task<Dictionary<string, object>> Test() |
|
||||
{ |
|
||||
await Task.CompletedTask; |
|
||||
|
|
||||
return new Dictionary<string, object>() |
|
||||
{ |
|
||||
{"thing2", "测试标题" }, |
|
||||
{"name3", "测试人员" }, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
[HttpPost] |
|
||||
[Route("Send")] |
|
||||
public async Task SendNofitication([FromBody] SendNotification notification) |
|
||||
{ |
|
||||
var notificationData = new NotificationData(); |
|
||||
notificationData.Properties["title"] = notification.Title; |
|
||||
notificationData.Properties["message"] = notification.Message; |
|
||||
notificationData.Properties["datetime"] = Clock.Now; |
|
||||
notificationData.Properties["severity"] = notification.Severity; |
|
||||
|
|
||||
notificationData.Properties.AddIfNotContains(notification.Data); |
|
||||
|
|
||||
var notificationName = NotificationNameNormalizer.NormalizerName("TestApplicationNotofication"); |
|
||||
|
|
||||
await _notificationDispatcher.DispatchAsync(notificationName, notificationData, |
|
||||
notificationSeverity: notification.Severity); |
|
||||
|
|
||||
// await _notificationDispatcher.DispatcheAsync(notificationInfo);
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class SendNotification |
|
||||
{ |
|
||||
public Guid UserId { get; set; } |
|
||||
public string Title { get; set; } |
|
||||
public string Message { get; set; } |
|
||||
public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>(); |
|
||||
public NotificationSeverity Severity { get; set; } = NotificationSeverity.Success; |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public class TestApplicationNotificationData : NotificationData |
|
||||
{ |
|
||||
public object Message |
|
||||
{ |
|
||||
get { return this[nameof(Message)]; } |
|
||||
set |
|
||||
{ |
|
||||
Properties[nameof(Message)] = value; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue