54 changed files with 1476 additions and 49 deletions
@ -0,0 +1,8 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Distributed |
||||
|
{ |
||||
|
public class DistributedLockException : Exception |
||||
|
{ |
||||
|
public DistributedLockException(string message) |
||||
|
: base(message) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -0,0 +1,9 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
public interface INotificationDispatcher |
||||
|
{ |
||||
|
Task DispatcheAsync(NotificationInfo notification); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 读取状态
|
||||
|
/// </summary>
|
||||
|
public enum NotificationReadState |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 已读
|
||||
|
/// </summary>
|
||||
|
Read = 0, |
||||
|
/// <summary>
|
||||
|
/// 未读
|
||||
|
/// </summary>
|
||||
|
UnRead = 1 |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
namespace LINGYUN.Abp.Notifications |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 通知类型
|
||||
|
/// </summary>
|
||||
|
public enum NotificationType |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// 应用
|
||||
|
/// </summary>
|
||||
|
Application = 0, |
||||
|
/// <summary>
|
||||
|
/// 系统
|
||||
|
/// </summary>
|
||||
|
System = 10 |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
namespace LINGYUN.Abp.MessageService.Messages |
namespace LINGYUN.Abp.MessageService |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// 读取状态
|
/// 读取状态
|
||||
@ -1,4 +1,4 @@ |
|||||
namespace LINGYUN.Abp.MessageService.Messages |
namespace LINGYUN.Abp.MessageService |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// 消息状态
|
/// 消息状态
|
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.Notifications |
||||
|
{ |
||||
|
class NotificationPublisher |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,13 +1,19 @@ |
|||||
using System; |
using System; |
||||
using Volo.Abp.Domain.Entities.Auditing; |
using Volo.Abp.Domain.Entities; |
||||
using Volo.Abp.MultiTenancy; |
using Volo.Abp.MultiTenancy; |
||||
|
|
||||
namespace LINGYUN.Abp.MessageService.Subscriptions |
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 Guid? TenantId { get; protected set; } |
||||
public virtual string EventType { get; protected set; } |
public virtual string NotificationName { get; protected set; } |
||||
public virtual string RoleName { get; protected set; } |
|
||||
|
protected Subscribe() { } |
||||
|
|
||||
|
protected Subscribe(string notificationName) |
||||
|
{ |
||||
|
NotificationName = notificationName; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,9 @@ |
|||||
|
namespace LINGYUN.Abp.MessageService.Subscriptions |
||||
|
{ |
||||
|
public class SubscribeConsts |
||||
|
{ |
||||
|
public const int MaxNotificationNameLength = 100; |
||||
|
|
||||
|
public const int MaxRoleNameLength = 64; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace LINGYUN.Abp.MessageService.Utils |
||||
|
{ |
||||
|
public interface ISnowflakeIdGenerator |
||||
|
{ |
||||
|
long NextId(); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -1,10 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using LINGYUN.Abp.AspNetCore.SignalR; |
|
||||
|
|
||||
namespace LINGYUN.Abp.MessageService.SignalR |
|
||||
{ |
|
||||
public abstract class ChatMessaheHub : OnlineClientHubBase |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,8 @@ |
|||||
|
using LINGYUN.Abp.IM.SignalR; |
||||
|
|
||||
|
namespace LINGYUN.Abp.MessageService.SignalR.Hubs |
||||
|
{ |
||||
|
public class AbpMessageHub : OnlineClientHubBase |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue