Browse Source

新增分布式锁模块;新增通知模块

pull/1/head
cKey 6 years ago
parent
commit
52a7047b11
  1. 8
      aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN.Abp.DistributedLock.csproj
  2. 13
      aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN/Abp/Distributed/DistributedLockException.cs
  3. 61
      aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN/Abp/Distributed/IDistributedLock.cs
  4. 11
      aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/OnlineClientHubBase.cs
  5. 2
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/IOnlineClient.cs
  6. 6
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClient.cs
  7. 5
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClientContext.cs
  8. 1
      aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClientManager.cs
  9. 2
      aspnet-core/modules/common/LINGYUN.Abp.Location.Baidu/LINGYUN/Abp/Location/Baidu/BaiduLocationHttpClient.cs
  10. 12
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN.Abp.Notifications.csproj
  11. 9
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs
  12. 11
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationPublisher.cs
  13. 12
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSender.cs
  14. 31
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationStore.cs
  15. 49
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationData.cs
  16. 21
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationInfo.cs
  17. 17
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationReadState.cs
  18. 29
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationSeverity.cs
  19. 11
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationSubscriptionInfo.cs
  20. 17
      aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationType.cs
  21. 19
      aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN.Abp.RedisLock.csproj
  22. 14
      aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/AbpRedisLockModule.cs
  23. 316
      aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/RedisDistributedLock.cs
  24. 26
      aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/RedisLockOptions.cs
  25. 4
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN.Abp.MessageService.Domain.Shared.csproj
  26. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/ReadStatus.cs
  27. 2
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/SendStatus.cs
  28. 5
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN.Abp.MessageService.Domain.csproj
  29. 11
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/INotificationStore.cs
  30. 30
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/Notification.cs
  31. 13
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationConsts.cs
  32. 47
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationDispatcher.cs
  33. 10
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationPublisher.cs
  34. 20
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/UserNotification.cs
  35. 18
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/ISubscribeRepository.cs
  36. 13
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/ISubscribeStore.cs
  37. 17
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/RoleSubscribe.cs
  38. 14
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/Subscribe.cs
  39. 9
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/SubscribeConsts.cs
  40. 41
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/SubscribeStore.cs
  41. 17
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/UserSubscribe.cs
  42. 7
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Utils/ISnowflakeIdGenerator.cs
  43. 1
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN.Abp.MessageService.SignalR.csproj
  44. 11
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/AbpMessageServiceSignalRModule.cs
  45. 10
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/ChatMessaheHub.cs
  46. 8
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/Hubs/AbpMessageHub.cs
  47. 60
      aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/Notifications/SignalRNotificationPublisher.cs
  48. 14
      vueJs/src/api/users.ts
  49. 0
      vueJs/src/components/TenantBox/index.vue
  50. 2
      vueJs/src/permission.ts
  51. 5
      vueJs/src/router/index.ts
  52. 26
      vueJs/src/views/admin/settings/components/GlobalSettingEditForm.vue
  53. 24
      vueJs/src/views/login/index.vue
  54. 375
      vueJs/src/views/register/index.vue

8
aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN.Abp.DistributedLock.csproj

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
</Project>

13
aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN/Abp/Distributed/DistributedLockException.cs

@ -0,0 +1,13 @@
using System;
namespace LINGYUN.Abp.Distributed
{
public class DistributedLockException : Exception
{
public DistributedLockException(string message)
: base(message)
{
}
}
}

61
aspnet-core/modules/common/LINGYUN.Abp.DistributedLock/LINGYUN/Abp/Distributed/IDistributedLock.cs

@ -0,0 +1,61 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Distributed
{
/// <summary>
/// 分布式锁接口
/// </summary>
public interface IDistributedLock
{
/// <summary>
/// 分布式锁
/// 需要手动释放锁
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockValue">锁定对象</param>
/// <param name="lockSecond">锁定时间(秒)</param>
/// <returns></returns>
bool Lock(string lockKey, string lockValue, int lockSecond = 30);
/// <summary>
/// 分布式锁
/// using块自动释放锁
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockSecond">锁定时间(秒)</param>
/// <returns></returns>
IDisposable Lock(string lockKey, int lockSecond = 30);
/// <summary>
/// 分布式锁
/// using块自动释放锁
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockSecond">锁定时间(秒)</param>
/// <returns></returns>
Task<IDisposable> LockAsync(string lockKey, int lockSecond = 30, CancellationToken token = default(CancellationToken));
/// <summary>
/// 分布式锁
/// 需要手动释放锁
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockValue">锁定对象</param>
/// <param name="lockSecond">锁定时间(秒)</param>
/// <returns></returns>
Task<bool> LockAsync(string lockKey, string lockValue, int lockSecond = 30, CancellationToken token = default(CancellationToken));
/// <summary>
/// 释放锁资源
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockValue">锁定对象</param>
/// <returns></returns>
bool Release(string lockKey, string lockValue);
/// <summary>
/// 释放锁资源
/// </summary>
/// <param name="lockKey">锁键名</param>
/// <param name="lockValue">锁定对象</param>
/// <returns></returns>
Task<bool> ReleaseAsync(string lockKey, string lockValue, CancellationToken token = default(CancellationToken));
}
}

11
aspnet-core/modules/common/LINGYUN.Abp.IM.SignalR/LINGYUN/Abp/IM/SignalR/OnlineClientHubBase.cs

@ -38,19 +38,14 @@ namespace LINGYUN.Abp.IM.SignalR
protected virtual IOnlineClient CreateClientForCurrentConnection()
{
return new OnlineClient(Context.ConnectionId, GetIpAddressOfClient(),
CurrentTenant.Id, CurrentUser.Id)
return new OnlineClient(Context.ConnectionId, GetClientIpAddress(),
CurrentTenant.Id, CurrentUser.Id, CurrentUser.Roles)
{
ConnectTime = Clock.Now
};
}
protected virtual string GetIpAddressOfClient()
{
return HttpContextAccessor.HttpContext?.Connection?.RemoteIpAddress?.ToString();
}
protected virtual string GetClientIpAddress(HttpContext httpContext)
protected virtual string GetClientIpAddress()
{
try
{

2
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/IOnlineClient.cs

@ -13,6 +13,8 @@ namespace LINGYUN.Abp.IM
Guid? UserId { get; }
string[] Roles { get; }
DateTime ConnectTime { get; }
object this[string key] { get; set; }

6
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClient.cs

@ -20,6 +20,8 @@ namespace LINGYUN.Abp.IM
public Guid? UserId { get; set; }
public string[] Roles { get; set; }
public DateTime ConnectTime { get; set; }
private Dictionary<string, object> _properties;
@ -39,16 +41,18 @@ namespace LINGYUN.Abp.IM
public OnlineClient()
{
Roles = new string[0];
ConnectTime = DateTime.Now;
}
public OnlineClient(string connectionId, string ipAddress, Guid? tenantId, Guid? userId)
public OnlineClient(string connectionId, string ipAddress, Guid? tenantId, Guid? userId, params string[] roles)
: this()
{
ConnectionId = connectionId;
IpAddress = ipAddress;
TenantId = tenantId;
UserId = userId;
Roles = roles;
Properties = new Dictionary<string, object>();
}

5
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClientContext.cs

@ -4,17 +4,14 @@ namespace LINGYUN.Abp.IM
{
public class OnlineClientContext
{
public string ConnectionId { get; }
public Guid? TenantId { get; }
public Guid UserId { get; }
public OnlineClientContext(string connectionId, Guid userId, Guid? tenantId)
public OnlineClientContext(Guid userId, Guid? tenantId)
{
UserId = userId;
TenantId = tenantId;
ConnectionId = connectionId;
}
}
}

1
aspnet-core/modules/common/LINGYUN.Abp.IM/LINGYUN/Abp/IM/OnlineClientManager.cs

@ -102,6 +102,7 @@ namespace LINGYUN.Abp.IM
return GetAllClients()
.Where(c => c.UserId == context.UserId && c.TenantId == context.TenantId)
.WhereIf(context.Roles.Length > 0, c => c.Roles.Any(role => context.Roles.Contains(role)))
.ToImmutableList();
}
}

2
aspnet-core/modules/common/LINGYUN.Abp.Location.Baidu/LINGYUN/Abp/Location/Baidu/BaiduLocationHttpClient.cs

@ -120,7 +120,7 @@ namespace LINGYUN.Abp.Location.Baidu
{
var localizerFactory = ServiceProvider.GetRequiredService<IStringLocalizerFactory>();
var localizerErrorMessage = baiduLocationResponse.GetErrorMessage().Localize(localizerFactory);
var localizerErrorDescription = baiduLocationResponse.GetErrorMessage().Localize(localizerFactory);
var localizerErrorDescription = baiduLocationResponse.GetErrorDescription().Localize(localizerFactory);
var localizer = ServiceProvider.GetRequiredService<IStringLocalizer<BaiduLocationResource>>();
localizerErrorMessage = localizer["ResolveLocationFailed", localizerErrorMessage, localizerErrorDescription];
if (Options.VisableErrorToClient)

12
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN.Abp.Notifications.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Core" Version="2.8.0" />
</ItemGroup>
</Project>

9
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationDispatcher.cs

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications
{
public interface INotificationDispatcher
{
Task DispatcheAsync(NotificationInfo notification);
}
}

11
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationPublisher.cs

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications
{
public interface INotificationPublisher
{
Task PublishAsync(NotificationData data, IEnumerable<Guid> userIds, Guid? tenantId);
}
}

12
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationSender.cs

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications
{
public interface INotificationSender
{
Task SendAsync(NotificationData data, Guid userId, Guid? tenantId);
Task SendAsync(NotificationData data, IEnumerable<Guid> userIds, Guid? tenantId);
}
}

31
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/INotificationStore.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Notifications
{
public interface INotificationStore
{
Task InsertUserSubscriptionAsync(Guid? tenantId, Guid userId, string notificationName);
Task DeleteUserSubscriptionAsync(Guid? tenantId, Guid userId, string notificationName);
Task<List<NotificationSubscriptionInfo>> GetSubscriptionsAsync(Guid? tenantId, string notificationName);
Task<List<NotificationSubscriptionInfo>> GetUserSubscriptionsAsync(Guid? tenantId, Guid userId);
Task<bool> IsSubscribedAsync(Guid? tenantId, Guid userId, string notificationName);
Task InsertNotificationAsync(NotificationInfo notification);
Task DeleteNotificationAsync(NotificationInfo notification);
Task InsertUserNotificationAsync(NotificationInfo notification, Guid userId);
Task DeleteUserNotificationAsync(Guid? tenantId, Guid userId, long notificationId);
Task<NotificationInfo> GetNotificationOrNullAsync(Guid? tenantId, long notificationId);
Task ChangeUserNotificationReadStateAsync(Guid? tenantId, Guid userId, string notificationName, NotificationReadState readState);
}
}

49
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationData.cs

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
namespace LINGYUN.Abp.Notifications
{
public class NotificationData
{
public virtual string Type => GetType().FullName;
public object this[string key]
{
get
{
if(Properties.TryGetValue(key, out object @obj))
{
return @obj;
}
return null;
}
set { Properties[key] = value; }
}
public Dictionary<string, object> Properties
{
get { return _properties; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
foreach (var keyValue in value)
{
if (!_properties.ContainsKey(keyValue.Key))
{
_properties[keyValue.Key] = keyValue.Value;
}
}
}
}
private readonly Dictionary<string, object> _properties;
public NotificationData()
{
_properties = new Dictionary<string, object>();
}
}
}

21
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationInfo.cs

@ -0,0 +1,21 @@
using System;
namespace LINGYUN.Abp.Notifications
{
public class NotificationInfo
{
public Guid? TenantId { get; set; }
public string Name { get; set; }
public NotificationData Data { get; set; }
public DateTime? ExpirationTime { get; set; }
public DateTime CreationTime { get; set; }
public NotificationType NotificationType { get; set; }
public NotificationSeverity NotificationSeverity { get; set; }
public NotificationInfo()
{
Data = new NotificationData();
NotificationType = NotificationType.Application;
NotificationSeverity = NotificationSeverity.Info;
}
}
}

17
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationReadState.cs

@ -0,0 +1,17 @@
namespace LINGYUN.Abp.Notifications
{
/// <summary>
/// 读取状态
/// </summary>
public enum NotificationReadState
{
/// <summary>
/// 已读
/// </summary>
Read = 0,
/// <summary>
/// 未读
/// </summary>
UnRead = 1
}
}

29
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationSeverity.cs

@ -0,0 +1,29 @@
namespace LINGYUN.Abp.Notifications
{
/// <summary>
/// 严重级别
/// </summary>
public enum NotificationSeverity : sbyte
{
/// <summary>
/// 成功
/// </summary>
Success = 0,
/// <summary>
/// 信息
/// </summary>
Info = 10,
/// <summary>
/// 警告
/// </summary>
Warn = 20,
/// <summary>
/// 错误
/// </summary>
Error = 30,
/// <summary>
/// 致命错误
/// </summary>
Fatal = 40
}
}

11
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationSubscriptionInfo.cs

@ -0,0 +1,11 @@
using System;
namespace LINGYUN.Abp.Notifications
{
public class NotificationSubscriptionInfo
{
public Guid? TenantId { get; set; }
public Guid UserId { get; set; }
public string NotificationName { get; set; }
}
}

17
aspnet-core/modules/common/LINGYUN.Abp.Notifications/LINGYUN/Abp/Notifications/NotificationType.cs

@ -0,0 +1,17 @@
namespace LINGYUN.Abp.Notifications
{
/// <summary>
/// 通知类型
/// </summary>
public enum NotificationType
{
/// <summary>
/// 应用
/// </summary>
Application = 0,
/// <summary>
/// 系统
/// </summary>
System = 10
}
}

19
aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN.Abp.RedisLock.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.2" />
<PackageReference Include="Polly" Version="7.2.1" />
<PackageReference Include="Volo.Abp.Core" Version="2.8.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LINGYUN.Abp.DistributedLock\LINGYUN.Abp.DistributedLock.csproj" />
</ItemGroup>
</Project>

14
aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/AbpRedisLockModule.cs

@ -0,0 +1,14 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Distributed.Redis
{
public class AbpRedisLockModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<RedisLockOptions>(configuration.GetSection("DistributedLock:Redis"));
}
}
}

316
aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/RedisDistributedLock.cs

@ -0,0 +1,316 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Polly;
using StackExchange.Redis;
using System;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.Distributed.Redis
{
[ExposeServices(typeof(IDistributedLock))]
[Dependency(ServiceLifetime.Singleton, TryRegister = true)]
public class RedisDistributedLock : IDistributedLock
{
public ILogger<RedisDistributedLock> Logger { protected get; set; }
private volatile ConnectionMultiplexer _connection;
private IDatabase _redis;
private readonly RedisLockOptions _options;
private readonly string _instance;
private readonly SemaphoreSlim _connectionLock = new SemaphoreSlim(initialCount: 1, maxCount: 1);
public RedisDistributedLock(IOptions<RedisLockOptions> optionsAccessor)
{
if (optionsAccessor == null)
{
throw new ArgumentNullException(nameof(optionsAccessor));
}
_options = optionsAccessor.Value;
_instance = _options.InstanceName ?? string.Empty;
Logger = NullLogger<RedisDistributedLock>.Instance;
}
private void RegistenConnectionEvent(ConnectionMultiplexer connection)
{
if (connection != null)
{
connection.ConnectionFailed += OnConnectionFailed;
connection.ConnectionRestored += OnConnectionRestored;
connection.ErrorMessage += OnErrorMessage;
connection.ConfigurationChanged += OnConfigurationChanged;
connection.HashSlotMoved += OnHashSlotMoved;
connection.InternalError += OnInternalError;
connection.ConfigurationChangedBroadcast += OnConfigurationChangedBroadcast;
}
}
public bool Lock(string lockKey, string lockValue, int lockSecond = 30)
{
Connect();
return LockTakeSync(lockKey, lockValue, TimeSpan.FromSeconds(lockSecond));
}
public async Task<bool> LockAsync(string lockKey, string lockValue, int lockSecond = 30, CancellationToken token = default)
{
await ConnectAsync(token);
return await LockTakeAsync(lockKey, lockValue, TimeSpan.FromSeconds(lockSecond));
}
public IDisposable Lock(string lockKey, int lockSecond = 30)
{
Connect();
var redisLockToken = Environment.MachineName;
var redisLockKey = _instance + lockKey;
var lockResult = LockTakeSync(redisLockKey, redisLockToken, TimeSpan.FromSeconds(lockSecond));
if (lockResult)
{
return new DisposeAction(() =>
{
LockReleaseSync(redisLockKey, redisLockToken);
});
}
Logger.LogWarning("Redis lock failed of key: {0}", redisLockKey);
throw new DistributedLockException(redisLockKey);
}
public async Task<IDisposable> LockAsync(string lockKey, int lockSecond = 30, CancellationToken token = default(CancellationToken))
{
await ConnectAsync(token);
var redisLockToken = Environment.MachineName;
var redisLockKey = _instance + lockKey;
var lockResult = await LockTakeAsync(redisLockKey, redisLockToken, TimeSpan.FromSeconds(lockSecond));
if (lockResult)
{
return new DisposeAction(async () =>
{
await LockReleaseAsync(redisLockKey, redisLockToken);
});
}
Logger.LogWarning("Redis lock failed of key: {0}", redisLockKey);
throw new DistributedLockException(redisLockKey);
}
public bool Release(string lockKey, string lockValue)
{
Connect();
return LockReleaseSync(lockKey, lockValue);
}
public async Task<bool> ReleaseAsync(string lockKey, string lockValue, CancellationToken token = default)
{
await ConnectAsync(token);
return await LockReleaseAsync(lockKey, lockValue);
}
/// <summary>
/// 同步加锁
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
protected virtual bool LockTakeSync(RedisKey key, RedisValue value, TimeSpan expiry)
{
// 定义重试策略
var policy = Policy
.HandleResult<bool>(result => !result)
.WaitAndRetry(
retryCount: _options.FailedRetryCount,
sleepDurationProvider: sleep => TimeSpan.FromMilliseconds(_options.FailedRetryInterval),
onRetry: (result, timeSpan) =>
{
Logger.LogWarning("Redis lock take failed, retry policy timeSpan:{0}", timeSpan.ToString());
});
// 加锁
var lockResult = policy.Execute(() => _redis.LockTake(key, value, expiry));
return lockResult;
}
/// <summary>
/// 异步加锁
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
protected virtual async Task<bool> LockTakeAsync(RedisKey key, RedisValue value, TimeSpan expiry)
{
// 定义重试策略
var policy = Policy
.HandleResult<bool>(result => !result)
.WaitAndRetryAsync(
retryCount: _options.FailedRetryCount,
sleepDurationProvider: sleep => TimeSpan.FromMilliseconds(_options.FailedRetryInterval),
onRetry: (result, timeSpan) =>
{
Logger.LogWarning("Redis lock take failed, retry policy timeSpan:{0}", timeSpan.ToString());
});
// 加锁
var lockResult = await policy.ExecuteAsync(async () =>
await _redis.LockTakeAsync(key, value, expiry));
return lockResult;
}
/// <summary>
/// 同步释放锁
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
protected virtual bool LockReleaseSync(RedisKey key, RedisValue value)
{
// 定义重试策略
var policy = Policy
.HandleResult<bool>(result => !result)
.WaitAndRetry(
retryCount: _options.FailedRetryCount,
sleepDurationProvider: sleep => TimeSpan.FromMilliseconds(_options.FailedRetryInterval),
onRetry: (result, timeSpan) =>
{
Logger.LogWarning("Redis lock release failed, retry policy timeSpan:{0}", timeSpan.ToString());
});
// 释放锁
var lockReleaseResult = policy.Execute(() => _redis.LockRelease(key, value));
return lockReleaseResult;
}
/// <summary>
/// 异步释放锁
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
protected virtual async Task<bool> LockReleaseAsync(RedisKey key, RedisValue value)
{
// 定义重试策略
var policy = Policy
.HandleResult<bool>(result => !result)
.WaitAndRetryAsync(
retryCount: _options.FailedRetryCount,
sleepDurationProvider: sleep => TimeSpan.FromMilliseconds(_options.FailedRetryInterval),
onRetry: (result, timeSpan) =>
{
Logger.LogWarning("Redis lock release failed, retry policy timeSpan:{0}", timeSpan.ToString());
});
// 释放锁
var lockReleaseResult = await policy.ExecuteAsync(async () =>
await _redis.LockReleaseAsync(key, value));
return lockReleaseResult;
}
private void Connect()
{
if (_redis != null)
{
return;
}
_connectionLock.Wait();
try
{
if (_redis == null)
{
if (_options.ConfigurationOptions != null)
{
_connection = ConnectionMultiplexer.Connect(_options.ConfigurationOptions);
}
else
{
_connection = ConnectionMultiplexer.Connect(_options.Configuration);
}
RegistenConnectionEvent(_connection);
_redis = _connection.GetDatabase();
}
}
finally
{
_connectionLock.Release();
}
}
private async Task ConnectAsync(CancellationToken token = default(CancellationToken))
{
token.ThrowIfCancellationRequested();
if (_redis != null)
{
return;
}
await _connectionLock.WaitAsync(token);
try
{
if (_redis == null)
{
if (_options.ConfigurationOptions != null)
{
_connection = await ConnectionMultiplexer.ConnectAsync(_options.ConfigurationOptions);
}
else
{
_connection = await ConnectionMultiplexer.ConnectAsync(_options.Configuration);
}
RegistenConnectionEvent(_connection);
_redis = _connection.GetDatabase();
}
}
finally
{
_connectionLock.Release();
}
}
private void OnConfigurationChangedBroadcast(object sender, EndPointEventArgs e)
{
Logger.LogInformation("Redis lock server master/slave changes");
}
private void OnInternalError(object sender, InternalErrorEventArgs e)
{
Logger.LogError("Redis lock internal error, origin:{0}, connectionType:{1}",
e.Origin, e.ConnectionType);
Logger.LogError(e.Exception, "Redis lock internal error");
}
private void OnHashSlotMoved(object sender, HashSlotMovedEventArgs e)
{
Logger.LogInformation("Redis lock configuration changed");
}
private void OnConfigurationChanged(object sender, EndPointEventArgs e)
{
Logger.LogInformation("Redis lock configuration changed");
}
private void OnErrorMessage(object sender, RedisErrorEventArgs e)
{
Logger.LogWarning("Redis lock error, message:{0}", e.Message);
}
private void OnConnectionRestored(object sender, ConnectionFailedEventArgs e)
{
Logger.LogWarning("Redis lock connection restored, failureType:{0}, connectionType:{1}",
e.FailureType, e.ConnectionType);
}
private void OnConnectionFailed(object sender, ConnectionFailedEventArgs e)
{
Logger.LogError("Redis lock connection failed, failureType:{0}, connectionType:{1}",
e.FailureType, e.ConnectionType);
Logger.LogError(e.Exception, "Redis lock connection failed");
}
}
}

26
aspnet-core/modules/common/LINGYUN.Abp.RedisLock/LINGYUN/Abp/Distributed/Redis/RedisLockOptions.cs

@ -0,0 +1,26 @@
using Microsoft.Extensions.Options;
using StackExchange.Redis;
namespace LINGYUN.Abp.Distributed.Redis
{
public class RedisLockOptions : IOptions<RedisLockOptions>
{
public string Configuration { get; set; }
public ConfigurationOptions ConfigurationOptions { get; set; }
public string InstanceName { get; set; }
/// <summary>
/// 失败重试次数
/// default: 3
/// </summary>
public int FailedRetryCount { get; set; } = 3;
/// <summary>
/// 失败重试间隔 ms
/// default: 1000
/// </summary>
public int FailedRetryInterval { get; set; } = 1000;
RedisLockOptions IOptions<RedisLockOptions>.Value
{
get { return this; }
}
}
}

4
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN.Abp.MessageService.Domain.Shared.csproj

@ -5,4 +5,8 @@
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<Folder Include="LINGYUN\Abp\MessageService\Notifications\" />
</ItemGroup>
</Project>

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Messages/ReadStatus.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/ReadStatus.cs

@ -1,4 +1,4 @@
namespace LINGYUN.Abp.MessageService.Messages
namespace LINGYUN.Abp.MessageService
{
/// <summary>
/// 读取状态

2
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/Messages/SendStatus.cs → aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain.Shared/LINGYUN/Abp/MessageService/SendStatus.cs

@ -1,4 +1,4 @@
namespace LINGYUN.Abp.MessageService.Messages
namespace LINGYUN.Abp.MessageService
{
/// <summary>
/// 消息状态

5
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN.Abp.MessageService.Domain.csproj

@ -10,10 +10,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="LINGYUN\Abp\MessageService\Notifications\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.Notifications\LINGYUN.Abp.Notifications.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.MessageService.Domain.Shared\LINGYUN.Abp.MessageService.Domain.Shared.csproj" />
</ItemGroup>

11
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/INotificationStore.cs

@ -0,0 +1,11 @@
using LINGYUN.Abp.Notifications;
using System;
using System.Threading.Tasks;
namespace LINGYUN.Abp.MessageService.Notifications
{
public interface INotificationStore
{
Task InsertUserNotificationAsync(NotificationInfo notification, Guid userId);
}
}

30
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/Notification.cs

@ -0,0 +1,30 @@
using LINGYUN.Abp.Notifications;
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Notifications
{
public class Notification : Entity<long>, IMultiTenant, IHasCreationTime
{
public virtual Guid? TenantId { get; protected set; }
public virtual NotificationSeverity Severity { get; protected set; }
public virtual long NotificationId { get; protected set; }
public virtual string NotificationName { get; protected set; }
public virtual string NotificationData { get; protected set; }
public virtual string NotificationTypeName { get; protected set; }
public virtual DateTime? ExpirationTime { get; set; }
public virtual DateTime CreationTime { get; set; }
protected Notification(){}
public Notification(long id, string name, string dataType, string data, NotificationSeverity severity = NotificationSeverity.Info)
{
NotificationId = id;
Severity = severity;
NotificationName = name;
NotificationData = data;
NotificationTypeName = dataType;
CreationTime = DateTime.Now;
}
}
}

13
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationConsts.cs

@ -0,0 +1,13 @@
namespace LINGYUN.Abp.MessageService.Notifications
{
public class NotificationConsts
{
public const int MaxNameLength = 100;
public const int MaxDataLength = 1024 * 1024;
public const int MaxTypeNameLength = 256;
public const int MaxAssemblyQualifiedNameLength = 512;
}
}

47
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationDispatcher.cs

@ -0,0 +1,47 @@
using LINGYUN.Abp.MessageService.Subscriptions;
using LINGYUN.Abp.MessageService.Utils;
using LINGYUN.Abp.Notifications;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Json;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
namespace LINGYUN.Abp.MessageService.Notifications
{
public class NotificationDispatcher : INotificationDispatcher, ITransientDependency
{
protected IJsonSerializer JsonSerializer { get; }
protected ICurrentTenant CurrentTenant { get; }
protected ISubscribeStore SubscribeStore { get; }
protected INotificationStore NotificationStore { get; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager.Current;
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected INotificationPublisher NotificationPublisher { get; }
protected ISnowflakeIdGenerator SnowflakeIdGenerator { get; }
[UnitOfWork]
public virtual async Task DispatcheAsync(NotificationInfo notification)
{
using (CurrentTenant.Change(notification.TenantId))
{
var subscribeUsers = await SubscribeStore.GetUserSubscribesAsync(notification.Name);
foreach(var userId in subscribeUsers)
{
await NotificationStore.InsertUserNotificationAsync(notification, userId);
}
await CurrentUnitOfWork.SaveChangesAsync();
await NotifyAsync(notification.Data, notification.TenantId, subscribeUsers);
}
}
protected virtual async Task NotifyAsync(NotificationData data, Guid? tenantId, IEnumerable<Guid> userIds)
{
await NotificationPublisher.PublishAsync(data, userIds, tenantId);
}
}
}

10
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/NotificationPublisher.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LINGYUN.Abp.MessageService.Notifications
{
class NotificationPublisher
{
}
}

20
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Notifications/UserNotification.cs

@ -0,0 +1,20 @@
using System;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Notifications
{
public class UserNotification : Entity<long>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual Guid UserId { get; protected set; }
public virtual long NotificationId { get; protected set; }
public virtual ReadStatus ReadStatus { get; protected set; }
protected UserNotification() { }
public UserNotification(long notificationId, Guid userId)
{
UserId = userId;
NotificationId = notificationId;
}
}
}

18
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/ISubscribeRepository.cs

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public interface ISubscribeRepository : IBasicRepository<Subscribe, long>
{
Task<bool> UserSubscribeExistsAysnc(string notificationName, Guid userId);
Task<UserSubscribe> GetUserSubscribeAsync(string notificationName, Guid userId);
Task<List<string>> GetUserSubscribesAsync(Guid userId);
Task<List<Guid>> GetUserSubscribesAsync(string notificationName);
}
}

13
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/ISubscribeStore.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public interface ISubscribeStore
{
Task<List<Guid>> GetUserSubscribesAsync(string notificationName);
Task UserSubscribeAsync(string notificationName, Guid userId);
Task UserUnSubscribeAsync(string notificationName, Guid userId);
}
}

17
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/RoleSubscribe.cs

@ -0,0 +1,17 @@
using System;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public class RoleSubscribe : Subscribe, IHasCreationTime
{
public virtual string RoleName { get; set; }
public virtual DateTime CreationTime { get; set; }
protected RoleSubscribe() { }
public RoleSubscribe(string notificationName, string roleName) : base(notificationName)
{
RoleName = roleName;
CreationTime = DateTime.Now;
}
}
}

14
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/Subscribe.cs

@ -1,13 +1,19 @@
using System;
using Volo.Abp.Domain.Entities.Auditing;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public class Subscribe : CreationAuditedEntity<long>, IMultiTenant
public abstract class Subscribe : Entity<long>, IMultiTenant
{
public virtual Guid? TenantId { get; protected set; }
public virtual string EventType { get; protected set; }
public virtual string RoleName { get; protected set; }
public virtual string NotificationName { get; protected set; }
protected Subscribe() { }
protected Subscribe(string notificationName)
{
NotificationName = notificationName;
}
}
}

9
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/SubscribeConsts.cs

@ -0,0 +1,9 @@
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public class SubscribeConsts
{
public const int MaxNotificationNameLength = 100;
public const int MaxRoleNameLength = 64;
}
}

41
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/SubscribeStore.cs

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public class SubscribeStore : ISubscribeStore, ITransientDependency
{
protected ISubscribeRepository SubscribeRepository { get; }
public SubscribeStore(ISubscribeRepository subscribeRepository)
{
SubscribeRepository = subscribeRepository;
}
public virtual async Task<List<Guid>> GetUserSubscribesAsync(string notificationName)
{
return await SubscribeRepository.GetUserSubscribesAsync(notificationName);
}
public virtual async Task UserSubscribeAsync(string notificationName, Guid userId)
{
var userSubscribeExists = await SubscribeRepository.UserSubscribeExistsAysnc(notificationName, userId);
if (!userSubscribeExists)
{
var userSbuscribe = new UserSubscribe(notificationName, userId);
await SubscribeRepository.InsertAsync(userSbuscribe);
}
}
public virtual async Task UserUnSubscribeAsync(string notificationName, Guid userId)
{
var userSubscribe = await SubscribeRepository.GetUserSubscribeAsync(notificationName, userId);
if (userSubscribe != null)
{
await SubscribeRepository.DeleteAsync(userSubscribe);
}
}
}
}

17
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Subscriptions/UserSubscribe.cs

@ -0,0 +1,17 @@
using System;
using Volo.Abp.Auditing;
namespace LINGYUN.Abp.MessageService.Subscriptions
{
public class UserSubscribe : Subscribe, IHasCreationTime
{
public virtual DateTime CreationTime { get; set; }
public virtual Guid UserId { get; set; }
protected UserSubscribe() { }
public UserSubscribe(string notificationName, Guid userId) : base(notificationName)
{
CreationTime = DateTime.Now;
UserId = userId;
}
}
}

7
aspnet-core/modules/message/LINGYUN.Abp.MessageService.Domain/LINGYUN/Abp/MessageService/Utils/ISnowflakeIdGenerator.cs

@ -0,0 +1,7 @@
namespace LINGYUN.Abp.MessageService.Utils
{
public interface ISnowflakeIdGenerator
{
long NextId();
}
}

1
aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN.Abp.MessageService.SignalR.csproj

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="..\..\common\LINGYUN.Abp.IM.SignalR\LINGYUN.Abp.IM.SignalR.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.MessageService.Domain\LINGYUN.Abp.MessageService.Domain.csproj" />
</ItemGroup>
</Project>

11
aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/AbpMessageServiceSignalRModule.cs

@ -0,0 +1,11 @@
using LINGYUN.Abp.IM.SignalR;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.MessageService.SignalR
{
[DependsOn(typeof(AbpIMSignalRModule))]
public class AbpMessageServiceSignalRModule : AbpModule
{
}
}

10
aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/ChatMessaheHub.cs

@ -1,10 +0,0 @@
using System.Threading.Tasks;
using LINGYUN.Abp.AspNetCore.SignalR;
namespace LINGYUN.Abp.MessageService.SignalR
{
public abstract class ChatMessaheHub : OnlineClientHubBase
{
}
}

8
aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/Hubs/AbpMessageHub.cs

@ -0,0 +1,8 @@
using LINGYUN.Abp.IM.SignalR;
namespace LINGYUN.Abp.MessageService.SignalR.Hubs
{
public class AbpMessageHub : OnlineClientHubBase
{
}
}

60
aspnet-core/modules/message/LINGYUN.Abp.MessageService.SignalR/LINGYUN/Abp/MessageService/SignalR/Notifications/SignalRNotificationPublisher.cs

@ -0,0 +1,60 @@
using LINGYUN.Abp.IM;
using LINGYUN.Abp.MessageService.Notifications;
using LINGYUN.Abp.MessageService.SignalR.Hubs;
using LINGYUN.Abp.Notifications;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace LINGYUN.Abp.MessageService.SignalR.Notifications
{
public class SignalRNotificationPublisher : INotificationPublisher, ITransientDependency
{
public ILogger<SignalRNotificationPublisher> Logger { protected get; set; }
private readonly IOnlineClientManager _onlineClientManager;
private readonly IHubContext<AbpMessageHub> _hubContext;
public SignalRNotificationPublisher(
IOnlineClientManager onlineClientManager,
IHubContext<AbpMessageHub> hubContext)
{
_hubContext = hubContext;
_onlineClientManager = onlineClientManager;
Logger = NullLogger<SignalRNotificationPublisher>.Instance;
}
public async Task PublishAsync(NotificationData data, IEnumerable<Guid> userIds, Guid? tenantId)
{
try
{
foreach(var userId in userIds)
{
var onlineClientContext = new OnlineClientContext(userId, tenantId);
var onlineClients = _onlineClientManager.GetAllByContext(onlineClientContext);
foreach (var onlineClient in onlineClients)
{
var signalRClient = _hubContext.Clients.Client(onlineClient.ConnectionId);
if (signalRClient == null)
{
Logger.LogDebug("Can not get user " + onlineClientContext.UserId + " with connectionId " + onlineClient.ConnectionId + " from SignalR hub!");
continue;
}
await signalRClient.SendAsync("getNotification", data);
}
}
}
catch(Exception ex)
{
//Logger.LogWarning("Could not send notification to user: " + userId);
Logger.LogWarning(ex.ToString(), ex);
}
}
}
}

14
vueJs/src/api/users.ts

@ -80,9 +80,9 @@ export default class UserApiService {
}
public static userRegister(registerData: UserRegisterData) {
const _url = '/api/account/register'
const _url = '/api/account/phone/register'
return ApiService.HttpRequest<UserDataDto>({
baseURL: IdentityServerUrl,
baseURL: IdentityServiceUrl,
url: _url,
method: 'POST',
data: registerData
@ -201,10 +201,14 @@ export class UsersGetPagedDto extends PagedAndSortedResultRequestDto {
/** 用户注册对象 */
export class UserRegisterData {
/** 应用名称 */
appName!: string
/** 手机号码 */
phoneNumber!: string
/** 手机验证码 */
verifyCode!: string
/** 名称 */
name?: string
/** 用户名 */
userName!: string
userName?: string
/** 密码 */
password!: string
/** 邮件地址 */

0
vueJs/src/views/login/components/TenantSelect.vue → vueJs/src/components/TenantBox/index.vue

2
vueJs/src/permission.ts

@ -11,7 +11,7 @@ import settings from './settings'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect']
const whiteList = ['/login', '/auth-redirect', '/register']
const getPageTitle = (key: string) => {
const hasKey = i18n.te(`route.${key}`)

5
vueJs/src/router/index.ts

@ -61,6 +61,11 @@ export const constantRoutes: RouteConfig[] = [
component: () => import(/* webpackChunkName: "login" */ '@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/register',
component: () => import(/* webpackChunkName: "login" */ '@/views/register/index.vue'),
meta: { hidden: true }
},
{
path: '/auth-redirect',
component: () => import(/* webpackChunkName: "auth-redirect" */ '@/views/login/auth-redirect.vue'),

26
vueJs/src/views/admin/settings/components/GlobalSettingEditForm.vue

@ -1,7 +1,6 @@
<template>
<div v-if="globalSettingLoaded">
<el-form
v-if="globalSettingLoaded"
ref="formGlobalSetting"
v-model="globalSetting"
label-width="180px"
@ -13,11 +12,24 @@
prop="globalSetting['Abp.Localization.DefaultLanguage'].value"
:label="globalSetting['Abp.Localization.DefaultLanguage'].displayName"
>
<el-input
<el-select
v-model="globalSetting['Abp.Localization.DefaultLanguage'].value"
style="width: 100%;"
@change="(value) => handleSettingValueChanged('Abp.Localization.DefaultLanguage', value)"
>
<el-option
v-for="language in definedLanguages"
:key="language"
:label="language"
:value="language"
:disabled="language===globalSetting['Abp.Localization.DefaultLanguage'].value"
/>
</el-select>
<!-- <el-input
v-model="globalSetting['Abp.Localization.DefaultLanguage'].value"
:placeholder="globalSetting['Abp.Localization.DefaultLanguage'].description"
@input="(value) => handleSettingValueChanged('Abp.Localization.DefaultLanguage', value)"
/>
/> -->
</el-form-item>
</el-tab-pane>
<el-tab-pane
@ -201,6 +213,7 @@
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { AbpConfigurationModule } from '@/store/modules/abp'
import SettingService, { Setting, SettingUpdate, SettingsUpdate } from '@/api/settings'
const booleanStrings = ['True', 'true', 'False', 'false']
@ -213,6 +226,13 @@ export default class extends Vue {
private globalSetting: {[key: string]: Setting} = {}
private globalSettingChangeKeys = new Array<string>()
get definedLanguages() {
const languages = AbpConfigurationModule.configuration.localization.languages.map((lang: any) => {
return lang.cultureName
})
return languages
}
get allowIdentitySetting() {
if (this.globalSetting['Abp.Identity.Password.RequiredLength']) {
return true

24
vueJs/src/views/login/index.vue

@ -14,10 +14,12 @@
</h3>
<lang-select class="set-language" />
</div>
<TenantSelect
<el-form-item label-width="0px">
<tenant-box
v-if="isMultiEnabled"
v-model="loginForm.tenantName"
/>
</el-form-item>
<el-tabs
stretch
@tab-click="handleLoginTabChanged"
@ -102,6 +104,18 @@
</div>
</el-tab-pane>
</el-tabs>
<el-form-item
label-width="100px"
label="没有用户?"
>
<el-link
type="success"
@click="handleRedirectRegister"
>
注册
</el-link>
</el-form-item>
<el-form-item style="width:100%;">
<el-button
type="primary"
@ -121,7 +135,7 @@ import { Input } from 'element-ui'
import { Route } from 'vue-router'
import { UserModule } from '@/store/modules/user'
import { Dictionary } from 'vue-router/types/router'
import TenantSelect from './components/TenantSelect.vue'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType } from '@/api/users'
@ -131,7 +145,7 @@ import { AbpConfigurationModule } from '@/store/modules/abp'
name: 'Login',
components: {
LangSelect,
TenantSelect
TenantBox
}
})
export default class extends Vue {
@ -214,6 +228,10 @@ export default class extends Vue {
})
}
private handleRedirectRegister() {
this.$router.replace('register')
}
private handleUserLogin() {
const frmLogin = this.$refs.formLogin as any
frmLogin.validate(async(valid: boolean) => {

375
vueJs/src/views/register/index.vue

@ -0,0 +1,375 @@
<template>
<div class="login-container">
<el-form
ref="formLogin"
:model="loginForm"
:rules="loginFormRules"
label-position="left"
label-width="0px"
class="demo-ruleForm login-page"
>
<div class="title-container">
<h3 class="title">
{{ $t('login.title') }}
</h3>
<lang-select class="set-language" />
</div>
<el-form-item label-width="0px">
<tenant-box
v-if="isMultiEnabled"
v-model="loginForm.tenantName"
/>
</el-form-item>
<el-form-item
prop="username"
>
<el-input
v-model="loginForm.username"
prefix-icon="el-icon-user"
type="text"
auto-complete="off"
tabindex="1"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.username')})"
/>
</el-form-item>
<el-form-item
prop="password"
>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
prefix-icon="el-icon-lock"
:type="passwordType"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.password')})"
name="password"
tabindex="2"
@keyup.enter.native="handleUserLogin"
/>
<span
class="show-pwd"
@click="showPwd"
>
<svg-icon :name="passwordType === 'password' ? 'eye-off' : 'eye-on'" />
</span>
</el-form-item>
<el-form-item
prop="phoneNumber"
>
<el-input
ref="loginItemPhone"
v-model="loginForm.phoneNumber"
prefix-icon="el-icon-mobile-phone"
type="text"
maxlength="11"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneNumber')})"
/>
</el-form-item>
<el-form-item
prop="verifyCode"
>
<el-row>
<el-col :span="16">
<el-input
v-model="loginForm.verifyCode"
auto-complete="off"
:placeholder="$t('global.pleaseInputBy', {key: $t('login.phoneVerifyCode')})"
prefix-icon="el-icon-key"
style="margin:-right: 10px;"
/>
</el-col>
<el-col :span="8">
<el-button
ref="sendButton"
style="margin-left: 10px;width: 132px;"
:disabled="sending"
@click="handleSendPhoneVerifyCode"
>
{{ sendButtonName }}
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item
label-width="100px"
label="已有用户?"
>
<el-link
type="success"
@click="handleRedirectLogin"
>
登录
</el-link>
</el-form-item>
<el-form-item style="width:100%;">
<el-button
type="primary"
style="width:100%;"
:loading="logining"
@click="handleUserLogin"
>
{{ $t('login.logIn') }}
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts">
import { Input } from 'element-ui'
import { Route } from 'vue-router'
import { UserModule } from '@/store/modules/user'
import { Dictionary } from 'vue-router/types/router'
import TenantBox from '@/components/TenantBox/index.vue'
import LangSelect from '@/components/LangSelect/index.vue'
import { Component, Vue, Watch } from 'vue-property-decorator'
import UserService, { PhoneVerify, VerifyType, UserRegisterData } from '@/api/users'
import { AbpConfigurationModule } from '@/store/modules/abp'
@Component({
name: 'Register',
components: {
LangSelect,
TenantBox
}
})
export default class extends Vue {
private loginType = 'password'
private passwordType = 'password'
private redirect?: string
private sendTimer: any
private sending = false
private sendButtonName = this.l('login.sendVerifyCode')
private logining = false
private loginForm = {
tenantName: '',
username: '',
password: '',
phoneNumber: '',
verifyCode: ''
}
get isMultiEnabled() {
return AbpConfigurationModule.configuration.multiTenancy.isEnabled
}
private validatePhoneNumberValue = (rule: any, value: string, callback: any) => {
const phoneReg = /^1[34578]\d{9}$/
if (!value || !phoneReg.test(value)) {
callback(new Error(this.l('global.pleaseInputBy', { key: this.l('global.correctPhoneNumber') })))
} else {
callback()
}
}
private loginFormRules = {
username: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.username') }), trigger: 'blur'
}
],
password: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.password') }), trigger: 'blur'
}
],
phoneNumber: [
{
required: true, validator: this.validatePhoneNumberValue, trigger: 'blur'
}
],
verifyCode: [
{
required: true, message: this.l('global.pleaseInputBy', { key: this.l('login.phoneVerifyCode') }), trigger: 'blur'
}
]
}
destroyed() {
if (this.sendTimer) {
clearInterval(this.sendTimer)
}
}
@Watch('$route', { immediate: true })
private onRouteChange(route: Route) {
// TODO: remove the "as Dictionary<string>" hack after v4 release for vue-router
// See https://github.com/vuejs/vue-router/pull/2050 for details
const query = route.query as Dictionary<string>
if (query) {
this.redirect = query.redirect
}
}
private showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
(this.$refs.password as Input).focus()
})
}
private handleRedirectLogin() {
this.$router.replace('login')
}
private handleUserLogin() {
const frmLogin = this.$refs.formLogin as any
frmLogin.validate(async(valid: boolean) => {
if (valid) {
this.logining = true
try {
if (this.loginType === 'password') {
const userLogin = {
tenantName: this.loginForm.tenantName,
username: this.loginForm.username,
password: this.loginForm.password
}
await UserModule.Login(userLogin)
this.$router.push({
path: this.redirect || '/'
})
} else {
const phoneLogin = {
tenantName: this.loginForm.tenantName,
phoneNumber: this.loginForm.phoneNumber,
verifyCode: this.loginForm.verifyCode
}
await UserModule.PhoneLogin(phoneLogin)
this.$router.push({
path: this.redirect || '/'
})
}
} catch {
this.resetLoginButton()
}
}
})
}
private handleSendPhoneVerifyCode() {
const frmLogin = this.$refs.formLogin as any
frmLogin.validateField('phoneNumber', (errorMsg: string) => {
if (!errorMsg) {
this.sending = true
const phoneVerify = new PhoneVerify()
phoneVerify.phoneNumber = this.loginForm.phoneNumber
phoneVerify.verifyType = VerifyType.signin
UserService.sendPhoneVerifyCode(phoneVerify).then(() => {
let interValTime = 60
const sendingName = this.l('login.afterSendVerifyCode')
const sendedName = this.l('login.sendVerifyCode')
this.sendTimer = setInterval(() => {
this.sendButtonName = interValTime + sendingName
--interValTime
if (interValTime < 0) {
this.sendButtonName = sendedName
this.sending = false
clearInterval(this.sendTimer)
}
}, 1000)
}).catch(() => {
this.sending = false
})
}
})
}
private handleLoginTabChanged(tab: any) {
this.loginType = tab.paneName === '1' ? 'phone' : 'password'
}
private l(name: string, values?: any[] | { [key: string]: any }) {
return this.$t(name, values).toString()
}
private resetLoginButton() {
setTimeout(() => {
this.logining = false
}, 0.5 * 1000)
}
}
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100%;
overflow: hidden;
background-color: $loginBg;
.svg-container {
padding: 6px 5px 6px 15px;
color: $darkGray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
margin: 0px auto 20px auto;
text-align: center;
font-weight: bold;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.set-language {
position: absolute;
top: 3px;
font-size: 18px;
right: 0px;
cursor: pointer;
}
}
.show-pwd {
position: absolute;
right: 10px;
font-size: 16px;
color: $darkGray;
cursor: pointer;
user-select: none;
}
}
.login-page {
-webkit-border-radius: 5px;
border-radius: 5px;
margin: 130px auto;
width: 500px;
padding: 35px 35px 15px;
border: 1px solid #8c9494;
box-shadow: 0 0 25px #454646;
background-color:rgb(247, 255, 255);
.loginTab.el-tabs__item {
width: 180px;
}
}
label.el-checkbox.rememberme {
margin: 0px 0px 15px;
text-align: left;
}
</style>
Loading…
Cancel
Save