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