27 changed files with 553 additions and 85 deletions
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"ExpandedNodes": [ |
||||
|
"" |
||||
|
], |
||||
|
"SelectedNode": "\\D:\\Projects\\MicroService\\CRM\\Vue\\vue-abp", |
||||
|
"PreviewInSolutionExplorer": false |
||||
|
} |
||||
Binary file not shown.
@ -0,0 +1,17 @@ |
|||||
|
# LINGYUN.Abp.EventBus.CAP |
||||
|
|
||||
|
分布式事件总线 CAP 集成 |
||||
|
|
||||
|
#### 注意 |
||||
|
|
||||
|
* 由于 CAP 官方模块中, MySqlConnector 为高版本,与 Volo.Abp.EntityFrameworkCore.MySQL 依赖版本不兼容 |
||||
|
当 Abp 框架升级到 4.0 版本之后,此模块升级为 CAP 最新版本 |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpCAPEventBusModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
# LINGYUN.Abp.Notifications |
||||
|
|
||||
|
实时通知基础模块 |
||||
|
|
||||
|
|
||||
|
#### 注意 |
||||
|
|
||||
|
* 当前的通知数据模型 NotificationData 设计极度不合理 |
||||
|
|
||||
|
将在框架升级为4.0版本之后变更,将与现有通知数据不兼容(这可能就是敏捷开发的避免吧 ;) ) |
||||
|
|
||||
|
## 配置使用 |
||||
|
|
||||
|
```csharp |
||||
|
[DependsOn(typeof(AbpNotificationModule))] |
||||
|
public class YouProjectModule : AbpModule |
||||
|
{ |
||||
|
// other |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using Microsoft.Extensions.Options; |
||||
|
using System; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.Utils |
||||
|
{ |
||||
|
// reference: https://github.com/dotnetcore/CAP
|
||||
|
// reference: https://blog.csdn.net/lq18050010830/article/details/89845790
|
||||
|
public class SnowflakeIdGenerator : ISnowflakeIdGenerator, ISingletonDependency |
||||
|
{ |
||||
|
public const long Twepoch = 1288834974657L; |
||||
|
|
||||
|
private static readonly object _lock = new object(); |
||||
|
private long _lastTimestamp = -1L; |
||||
|
|
||||
|
protected long MaxWorkerId { get; set; } |
||||
|
protected long MaxDatacenterId { get; set; } |
||||
|
|
||||
|
protected int WorkerIdShift { get; } |
||||
|
protected int DatacenterIdShift { get; } |
||||
|
protected int TimestampLeftShift { get; } |
||||
|
protected long SequenceMask { get; } |
||||
|
|
||||
|
protected SnowflakeIdOptions Options { get; } |
||||
|
|
||||
|
public SnowflakeIdGenerator(IOptions<SnowflakeIdOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
|
||||
|
WorkerIdShift = Options.SequenceBits; |
||||
|
DatacenterIdShift = Options.SequenceBits + Options.WorkerIdBits; |
||||
|
TimestampLeftShift = Options.SequenceBits + Options.WorkerIdBits + Options.DatacenterIdBits; |
||||
|
SequenceMask = -1L ^ (-1L << Options.SequenceBits); |
||||
|
} |
||||
|
|
||||
|
internal void Initialize(long sequence = 0L) |
||||
|
{ |
||||
|
Sequence = sequence; |
||||
|
MaxWorkerId = -1L ^ (-1L << Options.WorkerIdBits); |
||||
|
MaxDatacenterId = -1L ^ (-1L << Options.DatacenterIdBits); |
||||
|
|
||||
|
if (!int.TryParse(Environment.GetEnvironmentVariable("WORKERID", EnvironmentVariableTarget.Machine), out var workerId)) |
||||
|
{ |
||||
|
workerId = RandomHelper.GetRandom((int)MaxWorkerId); |
||||
|
} |
||||
|
|
||||
|
if (!int.TryParse(Environment.GetEnvironmentVariable("DATACENTERID", EnvironmentVariableTarget.Machine), out var datacenterId)) |
||||
|
{ |
||||
|
datacenterId = RandomHelper.GetRandom((int)MaxDatacenterId); |
||||
|
} |
||||
|
|
||||
|
if (workerId > MaxWorkerId || workerId < 0) |
||||
|
throw new ArgumentException($"worker Id can't be greater than {MaxWorkerId} or less than 0"); |
||||
|
|
||||
|
if (datacenterId > MaxDatacenterId || datacenterId < 0) |
||||
|
throw new ArgumentException($"datacenter Id can't be greater than {MaxDatacenterId} or less than 0"); |
||||
|
|
||||
|
WorkerId = workerId; |
||||
|
DatacenterId = datacenterId; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public long WorkerId { get; protected set; } |
||||
|
public long DatacenterId { get; protected set; } |
||||
|
public long Sequence { get; protected set; } |
||||
|
|
||||
|
public virtual long Create() |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
var timestamp = TimeGen(); |
||||
|
|
||||
|
if (timestamp < _lastTimestamp) |
||||
|
throw new Exception( |
||||
|
$"InvalidSystemClock: Clock moved backwards, Refusing to generate id for {_lastTimestamp - timestamp} milliseconds"); |
||||
|
|
||||
|
if (_lastTimestamp == timestamp) |
||||
|
{ |
||||
|
Sequence = (Sequence + 1) & SequenceMask; |
||||
|
if (Sequence == 0) timestamp = TilNextMillis(_lastTimestamp); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Sequence = 0; |
||||
|
} |
||||
|
|
||||
|
_lastTimestamp = timestamp; |
||||
|
var id = ((timestamp - Twepoch) << TimestampLeftShift) | |
||||
|
(DatacenterId << DatacenterIdShift) | |
||||
|
(WorkerId << WorkerIdShift) | Sequence; |
||||
|
|
||||
|
return id; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual long TilNextMillis(long lastTimestamp) |
||||
|
{ |
||||
|
var timestamp = TimeGen(); |
||||
|
while (timestamp <= lastTimestamp) timestamp = TimeGen(); |
||||
|
return timestamp; |
||||
|
} |
||||
|
|
||||
|
protected virtual long TimeGen() |
||||
|
{ |
||||
|
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
namespace LINGYUN.Abp.MessageService.Utils |
||||
|
{ |
||||
|
public class SnowflakeIdOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 机器Id长度
|
||||
|
/// </summary>
|
||||
|
public int WorkerIdBits { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 机房Id长度
|
||||
|
/// </summary>
|
||||
|
public int DatacenterIdBits { get; set; } |
||||
|
/// <summary>
|
||||
|
/// 每秒生成Id的数量
|
||||
|
/// </summary>
|
||||
|
public int SequenceBits { get; set; } |
||||
|
public SnowflakeIdOptions() |
||||
|
{ |
||||
|
WorkerIdBits = 5; |
||||
|
DatacenterIdBits = 5; |
||||
|
SequenceBits = 12; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,16 +0,0 @@ |
|||||
using DotNetCore.CAP.Internal; |
|
||||
using Microsoft.Extensions.DependencyInjection; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
|
|
||||
namespace LINGYUN.Abp.MessageService.Utils |
|
||||
{ |
|
||||
[Dependency(ServiceLifetime.Singleton, TryRegister = true)] |
|
||||
[ExposeServices(typeof(ISnowflakeIdGenerator))] |
|
||||
public class SnowflakeIdGenerator : ISnowflakeIdGenerator |
|
||||
{ |
|
||||
public long Create() |
|
||||
{ |
|
||||
return SnowflakeId.Default().NextId(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,23 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.1" /> |
||||
|
<PackageReference Include="Shouldly" Version="3.0.2" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\message\LINGYUN.Abp.MessageService.Domain\LINGYUN.Abp.MessageService.Domain.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.Notifications.Tests\LINGYUN.Abp.Notifications.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,36 @@ |
|||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Data; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpMessageServiceDomainModule), |
||||
|
typeof(AbpNotificationsTestsModule) |
||||
|
)] |
||||
|
public class AbpMessageServiceDomainTestModule : AbpModule |
||||
|
{ |
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
SeedTestData(context); |
||||
|
} |
||||
|
|
||||
|
private static void SeedTestData(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
using (var scope = context.ServiceProvider.CreateScope()) |
||||
|
{ |
||||
|
var dataSeeder = scope.ServiceProvider.GetRequiredService<IDataSeeder>(); |
||||
|
AsyncHelper.RunSync(async () => |
||||
|
{ |
||||
|
await dataSeeder.SeedAsync(); |
||||
|
await scope.ServiceProvider |
||||
|
.GetRequiredService<AbpNotificationsTestDataBuilder>() |
||||
|
.BuildAsync(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
using LINGYUN.Abp.MessageService.Notifications; |
||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService |
||||
|
{ |
||||
|
public class AbpNotificationsTestDataBuilder : ITransientDependency |
||||
|
{ |
||||
|
private readonly INotificationStore _notificationStore; |
||||
|
private readonly IUserNotificationRepository _userNotificationRepository; |
||||
|
public AbpNotificationsTestDataBuilder( |
||||
|
INotificationStore notificationStore, |
||||
|
IUserNotificationRepository userNotificationRepository) |
||||
|
{ |
||||
|
_notificationStore = notificationStore; |
||||
|
_userNotificationRepository = userNotificationRepository; |
||||
|
} |
||||
|
|
||||
|
public async Task BuildAsync() |
||||
|
{ |
||||
|
await AddNotificationsAsync(); |
||||
|
} |
||||
|
|
||||
|
private async Task AddUserNotificationsAsync(NotificationInfo notificationInfo, IEnumerable<Guid> userIds) |
||||
|
{ |
||||
|
await _notificationStore.InsertUserNotificationsAsync(notificationInfo, userIds); |
||||
|
} |
||||
|
|
||||
|
private async Task AddNotificationsAsync() |
||||
|
{ |
||||
|
var notification1 = new NotificationInfo |
||||
|
{ |
||||
|
Name = NotificationsTestsNames.Test1, |
||||
|
Severity = NotificationSeverity.Success, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Lifetime = NotificationLifetime.OnlyOne, |
||||
|
Type = NotificationType.Application |
||||
|
}; |
||||
|
|
||||
|
var notification2 = new NotificationInfo |
||||
|
{ |
||||
|
Name = NotificationsTestsNames.Test2, |
||||
|
Severity = NotificationSeverity.Success, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Lifetime = NotificationLifetime.Persistent, |
||||
|
Type = NotificationType.Application |
||||
|
}; |
||||
|
|
||||
|
var notification3 = new NotificationInfo |
||||
|
{ |
||||
|
Name = NotificationsTestsNames.Test3, |
||||
|
Severity = NotificationSeverity.Success, |
||||
|
CreationTime = DateTime.Now, |
||||
|
Lifetime = NotificationLifetime.OnlyOne, |
||||
|
Type = NotificationType.User |
||||
|
}; |
||||
|
|
||||
|
await _notificationStore.InsertNotificationAsync(notification1); |
||||
|
await _notificationStore.InsertNotificationAsync(notification2); |
||||
|
await _notificationStore.InsertNotificationAsync(notification3); |
||||
|
|
||||
|
NotificationsTestConsts.NotificationId1 = notification1.GetId(); |
||||
|
NotificationsTestConsts.NotificationId2 = notification2.GetId(); |
||||
|
NotificationsTestConsts.NotificationId3 = notification3.GetId(); |
||||
|
|
||||
|
await AddUserNotificationsAsync(notification2, new Guid[] { NotificationsTestConsts.User1Id, NotificationsTestConsts.User2Id }); |
||||
|
await AddUserNotificationsAsync(notification3, new Guid[] { NotificationsTestConsts.User1Id }); |
||||
|
|
||||
|
await _notificationStore.ChangeUserNotificationReadStateAsync(null, NotificationsTestConsts.User1Id, NotificationsTestConsts.NotificationId3, NotificationReadState.Read); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp3.1</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
<IsPackable>false</IsPackable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> |
||||
|
<PackageReference Include="NSubstitute" Version="4.2.1" /> |
||||
|
<PackageReference Include="Shouldly" Version="3.0.2" /> |
||||
|
<PackageReference Include="xunit" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.extensibility.execution" Version="2.4.1" /> |
||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\modules\message\LINGYUN.Abp.MessageService.EntityFrameworkCore\LINGYUN.Abp.MessageService.EntityFrameworkCore.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.EntityFrameworkCore.Tests\LINGYUN.Abp.EntityFrameworkCore.Tests.csproj" /> |
||||
|
<ProjectReference Include="..\LINGYUN.Abp.MessageService.Domain.Tests\LINGYUN.Abp.MessageService.Domain.Tests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.Tests; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.EntityFrameworkCore |
||||
|
{ |
||||
|
public abstract class AbpMessageServiceEntityFrameworkCoreTestBase : AbpTestsBase<AbpMessageServiceEntityFrameworkCoreTestModule> |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using LINGYUN.Abp.EntityFrameworkCore.Tests; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.EntityFrameworkCore |
||||
|
{ |
||||
|
[DependsOn( |
||||
|
typeof(AbpMessageServiceEntityFrameworkCoreModule), |
||||
|
typeof(AbpMessageServiceDomainTestModule), |
||||
|
typeof(AbpEntityFrameworkCoreTestModule) |
||||
|
)] |
||||
|
public class AbpMessageServiceEntityFrameworkCoreTestModule : AbpModule |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,81 @@ |
|||||
|
using LINGYUN.Abp.MessageService.EntityFrameworkCore; |
||||
|
using LINGYUN.Abp.Notifications; |
||||
|
using Shouldly; |
||||
|
using System.Threading.Tasks; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.Notifications |
||||
|
{ |
||||
|
public class UserNotificationRepositoryTests : AbpMessageServiceEntityFrameworkCoreTestBase |
||||
|
{ |
||||
|
private readonly IUserNotificationRepository _repository; |
||||
|
public UserNotificationRepositoryTests() |
||||
|
{ |
||||
|
_repository = GetRequiredService<IUserNotificationRepository>(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task AnyAsync_Test() |
||||
|
{ |
||||
|
(await _repository.AnyAsync(NotificationsTestConsts.User1Id, NotificationsTestConsts.NotificationId1)).ShouldBeFalse(); |
||||
|
(await _repository.AnyAsync(NotificationsTestConsts.User1Id, NotificationsTestConsts.NotificationId3)).ShouldBeTrue(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetByIdAsync_Test() |
||||
|
{ |
||||
|
(await _repository.GetByIdAsync(NotificationsTestConsts.User1Id, NotificationsTestConsts.NotificationId2)).ShouldNotBeNull(); |
||||
|
(await _repository.GetByIdAsync(NotificationsTestConsts.User1Id, NotificationsTestConsts.NotificationId1)).ShouldBeNull(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetNotificationsAsync_Test() |
||||
|
{ |
||||
|
(await _repository.GetNotificationsAsync( |
||||
|
NotificationsTestConsts.User1Id, |
||||
|
NotificationReadState.Read)) |
||||
|
.Count |
||||
|
.ShouldBe(1); |
||||
|
(await _repository.GetNotificationsAsync( |
||||
|
NotificationsTestConsts.User1Id, |
||||
|
NotificationReadState.UnRead)) |
||||
|
.Count |
||||
|
.ShouldBe(1); |
||||
|
(await _repository.GetNotificationsAsync( |
||||
|
NotificationsTestConsts.User1Id)) |
||||
|
.Count |
||||
|
.ShouldBe(2); |
||||
|
(await _repository.GetNotificationsAsync( |
||||
|
NotificationsTestConsts.User2Id, |
||||
|
NotificationReadState.UnRead)) |
||||
|
.Count |
||||
|
.ShouldBe(1); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetCountAsync_Test() |
||||
|
{ |
||||
|
(await _repository.GetCountAsync( |
||||
|
NotificationsTestConsts.User1Id, |
||||
|
readState: NotificationReadState.Read)) |
||||
|
.ShouldBe(1); |
||||
|
(await _repository.GetCountAsync( |
||||
|
NotificationsTestConsts.User1Id)) |
||||
|
.ShouldBe(2); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public async Task GetListAsync_Test() |
||||
|
{ |
||||
|
(await _repository.GetListAsync( |
||||
|
NotificationsTestConsts.User1Id, |
||||
|
readState: NotificationReadState.Read)) |
||||
|
.Count |
||||
|
.ShouldBe(1); |
||||
|
(await _repository.GetListAsync( |
||||
|
NotificationsTestConsts.User1Id)) |
||||
|
.Count |
||||
|
.ShouldBe(2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public static class NotificationsTestConsts |
||||
|
{ |
||||
|
public static Guid User1Id { get; } = Guid.NewGuid(); |
||||
|
|
||||
|
public static Guid User2Id { get; } = Guid.NewGuid(); |
||||
|
|
||||
|
public static long NotificationId1 { get; set; } |
||||
|
public static long NotificationId2 { get; set; } |
||||
|
public static long NotificationId3 { get; set; } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue