CredentialManager的_dictCredentialsRequesting字典的Key变更为string,已LoginName为主键。
CredentialKeepAliveResults枚举中增加RequestingLogin定义,用于指示已登录的客户端有其它客户端请求使用此用户名登录。
移除IAccountService实现类中多余的函数。

[UI.Client]
新增LoginRequestConfirmDialog窗体。
This commit is contained in:
SL 2023-09-14 14:17:16 +08:00
parent 9a8c73664d
commit 0f032ac783
12 changed files with 200 additions and 87 deletions

View File

@ -16,19 +16,6 @@ namespace Aitex.Core.Account
public sealed class AccountService : IAccountService
{
public string Module => "System";
public void Logout(string accountId)
{
EV.PostInfoLog(Module, $"User {accountId} logout sytem.");
try
{
}
catch
{
}
AccountManager.Instance.Logout(accountId);
}
public GetAccountInfoResult GetAccountInfo(string accountId)
{
@ -254,9 +241,9 @@ namespace Aitex.Core.Account
}
public void LogoutEx(Guid token)
public void LogoutEx(Credential cred)
{
AccountExManager.Instance.Logout(token);
AccountExManager.Instance.Logout(cred);
}
/// <summary>
@ -264,9 +251,9 @@ namespace Aitex.Core.Account
/// </summary>
/// <param name="token">客户端登录凭据令牌。</param>
/// <returns></returns>
public CredentialKeepAliveResults KeepAlive(Guid token)
public CredentialKeepAliveResults KeepAlive(Credential cred)
{
return CredentialManager.Instance.KeepAlive(token);
return CredentialManager.Instance.KeepAlive(cred);
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Aitex.Core.RT.Event;
using Aitex.Core.Util;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel;
namespace Aitex.Core.Account;
@ -23,9 +24,9 @@ public class CredentialManager : Singleton<CredentialManager>
private readonly Dictionary<Guid, Credential> _dictCredentialsLoggedIn = new ();
/// <summary>
/// 正在等在登录请求确认的凭据
/// 正在等在登录请求确认的凭据LoginName为字典Key
/// </summary>
private readonly Dictionary<Guid, Credential> _dictCredentialsRequesting = new ();
private readonly Dictionary<string, Credential> _dictCredentialsRequesting = new ();
private readonly PeriodicJob _threadMonitorCred;
private bool _isInitialized;
@ -87,32 +88,35 @@ public class CredentialManager : Singleton<CredentialManager>
{
lock (_syncRoot)
{
var removableList = new List<Guid>();
var loginRemovableList = new List<Guid>();
foreach (var kvp in _dictCredentialsLoggedIn)
{
var cred = kvp.Value;
if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > KEEP_ALIVE_TIMEOUT_SEC)
{
loginRemovableList.Add(cred.Token);
EV.PostLoginBySameUser(cred.Token, new Credential());
}
}
if (removableList.Count > 0)
if (loginRemovableList.Count > 0)
{
foreach (var token in removableList)
foreach (var token in loginRemovableList)
_dictCredentialsLoggedIn.Remove(token);
}
removableList.Clear();
var requestRemovableList = new List<string>();
foreach (var kvp in _dictCredentialsRequesting)
{
var cred = kvp.Value;
if ((DateTime.Now - cred.LastAliveTime).TotalSeconds > KEEP_ALIVE_TIMEOUT_SEC)
removableList.Add(kvp.Key);
requestRemovableList.Add(kvp.Key);
}
if (removableList.Count > 0)
if (requestRemovableList.Count > 0)
{
foreach (var token in removableList)
_dictCredentialsRequesting.Remove(token);
foreach (var loginName in requestRemovableList)
_dictCredentialsRequesting.Remove(loginName);
}
}
@ -157,22 +161,27 @@ public class CredentialManager : Singleton<CredentialManager>
return _dictCredentialsLoggedIn.ContainsKey(token);
}
}
/// <summary>
/// 报告客户端处于活动状态。
/// </summary>
/// <param name="token">客户端登录凭据令牌。</param>
/// <param name="cred">客户端登录凭据</param>
/// <returns></returns>
public CredentialKeepAliveResults KeepAlive(Guid token)
public CredentialKeepAliveResults KeepAlive(Credential cred)
{
lock (_syncRoot)
{
if (_dictCredentialsLoggedIn.TryGetValue(token, out var cred))
if (_dictCredentialsLoggedIn.TryGetValue(cred.Token, out var loginCred))
{
cred.LastAliveTime = DateTime.Now; // 刷新时间
return CredentialKeepAliveResults.Alived;
}
loginCred.LastAliveTime = DateTime.Now; // 刷新时间
// 如果当前用户名在请求登录列表中则返回CredentialKeepAliveResults.RequestingLogin通知
// 已登录的客户端,当前用户正在请求异地登录。
return _dictCredentialsRequesting.ContainsKey(cred.AccountInfo.LoginName)
? CredentialKeepAliveResults.RequestingLogin
: CredentialKeepAliveResults.Alive;
}
return CredentialKeepAliveResults.NotFound;
}
}
@ -186,10 +195,10 @@ public class CredentialManager : Singleton<CredentialManager>
{
lock (_syncRoot)
{
if (_dictCredentialsRequesting.ContainsKey(cred.Token))
if (_dictCredentialsRequesting.ContainsKey(cred.AccountInfo.LoginName))
throw new InvalidOperationException("the credential has been existed in requesting list.");
_dictCredentialsRequesting[cred.Token] = cred;
_dictCredentialsRequesting[cred.AccountInfo.LoginName] = cred;
}
}
@ -211,12 +220,12 @@ public class CredentialManager : Singleton<CredentialManager>
if (_dictCredentialsLoggedIn.Count >= _maxCredentialAllowed)
throw new InvalidOperationException("maximum number of login credentials reached");
if (!_dictCredentialsRequesting.ContainsKey(cred.Token))
if (!_dictCredentialsRequesting.ContainsKey(cred.AccountInfo.LoginName))
throw new InvalidOperationException("the credential is not found in requesting list.");
cred.State = CredentialState.Alive;
_dictCredentialsLoggedIn[cred.Token] = cred;
_dictCredentialsRequesting.Remove(cred.Token);
_dictCredentialsRequesting.Remove(cred.AccountInfo.LoginName);
}
}
@ -240,7 +249,7 @@ public class CredentialManager : Singleton<CredentialManager>
{
lock (_syncRoot)
{
_dictCredentialsRequesting.Remove(cred.Token);
_dictCredentialsRequesting.Remove(cred.AccountInfo.LoginName);
}
}
@ -279,20 +288,7 @@ public class CredentialManager : Singleton<CredentialManager>
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.Values.FirstOrDefault(x => x.AccountInfo.LoginName == userName);
}
}
/// <summary>
/// 获取指定令牌的正在等在登录请求的凭据。
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public Credential GetRequestingCredential(Guid token)
{
lock (_syncRoot)
{
return _dictCredentialsRequesting.TryGetValue(token, out var cred) ? cred : null;
return _dictCredentialsRequesting.TryGetValue(userName, out var cred) ? cred : null;
}
}

View File

@ -41,10 +41,25 @@ public enum CredentialState
Reject
}
/// <summary>
/// 凭据激活结果
/// </summary>
public enum CredentialKeepAliveResults
{
/// <summary>
/// 未找到凭据
/// </summary>
NotFound,
Alived
/// <summary>
/// 凭据被激活
/// </summary>
Alive,
/// <summary>
/// 其它客户端正在请求登录
/// </summary>
RequestingLogin
}
public class Misc

View File

@ -284,28 +284,28 @@ namespace MECF.Framework.Common.Account
public void ConfirmedLoginRequest(Credential requestingCred)
{
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.Token);
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.AccountInfo.LoginName);
if (cred != null)
cred.State = CredentialState.Confirmed;
}
public void RejectLoginRequest(Credential requestingCred)
{
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.Token);
var cred = CredentialManager.Instance.GetRequestingCredential(requestingCred.AccountInfo.LoginName);
if (cred != null)
cred.State = CredentialState.Reject;
}
internal void Logout(Guid token)
internal void Logout(Credential cred)
{
var cred = CredentialManager.Instance.GetCredential(token);
if (cred != null)
var loginCred = CredentialManager.Instance.GetCredential(cred.Token);
if (loginCred != null)
{
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedOff,
cred.AccountInfo.LoginName);
loginCred.AccountInfo.LoginName);
}
CredentialManager.Instance.Remove(token);
CredentialManager.Instance.Remove(cred.Token);
}
#endregion

View File

@ -13,14 +13,6 @@ namespace MECF.Framework.UI.Core.Accounts
public AccountServiceClient()
{
}
public void Logout(string accountId)
{
WCFProxy.Using(delegate(IAccountService svc)
{
svc.Logout(accountId);
});
}
public GetAccountInfoResult GetAccountInfo(string accountId)
{
@ -314,20 +306,20 @@ namespace MECF.Framework.UI.Core.Accounts
});
}
public void LogoutEx(Guid token)
public void LogoutEx(Credential cred)
{
WCFProxy.Using(delegate(IAccountService svc)
{
svc.LogoutEx(token);
svc.LogoutEx(cred);
});
}
public CredentialKeepAliveResults KeepAlive(Guid token)
public CredentialKeepAliveResults KeepAlive(Credential cred)
{
var result = CredentialKeepAliveResults.NotFound;
WCFProxy.Using(delegate(IAccountService svc)
{
result = svc.KeepAlive(token);
result = svc.KeepAlive(cred);
});
return result;
}

View File

@ -0,0 +1,47 @@
<Window x:Class="MECF.Framework.UI.Client.ClientBase.Dialog.LoginRequestConfirmationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Height="220" Width="400"
d:Background="White"
Title="Login Request Confirmation"
ResizeMode="CanResize"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
Closing="Window_Closing">
<WindowChrome.WindowChrome>
<WindowChrome ResizeBorderThickness="0" CaptionHeight="0"/>
</WindowChrome.WindowChrome>
<Window.Effect>
<DropShadowEffect Color="Gray" BlurRadius="10" Direction="-90" RenderingBias="Quality" ShadowDepth="4"/>
</Window.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition />
</Grid.RowDefinitions>
<Border Background="{StaticResource Color_BG_TopStrip}"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition />
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<TextBlock Text="Login Request" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<StackPanel Grid.Row="1" Margin="20 0" HorizontalAlignment="Center" >
<TextBlock
Text="The current user is requesting to login from somewhere, do you agree to it's request and logout from the system?"
TextWrapping="Wrap"
HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="14"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="BtnAccept" Content="Accept" VerticalAlignment="Center" Height="30" Width="120" Margin="0,0,5,0" Click="BtnAccept_Click" />
<Button x:Name="BtnRejected" Content="Reject" VerticalAlignment="Center" Height="30" Width="120" Margin="5,0,0,0" Click="BtnRejected_Click" IsCancel="True" IsDefault="True" />
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@ -0,0 +1,76 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace MECF.Framework.UI.Client.ClientBase.Dialog
{
/// <summary>
/// Interaction logic for LoginRequestConfirmationDialog.xaml
/// </summary>
public partial class LoginRequestConfirmationDialog : Window
{
#region Variables
private readonly CancellationTokenSource _ctsCountDownTask;
private readonly IProgress<double> _progCountDown;
#endregion
public LoginRequestConfirmationDialog()
{
InitializeComponent();
_ctsCountDownTask = new CancellationTokenSource ();
_progCountDown = new Progress<double>(countDown =>
{
if(countDown > 0)
BtnRejected.Content = $"Reject ({countDown:F0}s)";
else
DialogResult = false;
});
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var closeTime = DateTime.Now.Add(TimeSpan.FromSeconds(30));
Task.Run(() =>
{
while (true)
{
var timeRemained = (closeTime - DateTime.Now).TotalSeconds;
if (timeRemained <= 0)
{
// force to reject
_progCountDown.Report(0);
break;
}
_progCountDown.Report(timeRemained);
Thread.Sleep(500);
if (_ctsCountDownTask.Token.IsCancellationRequested)
break;
}
});
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_ctsCountDownTask?.Cancel();
}
private void BtnRejected_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
private void BtnAccept_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
}
}

View File

@ -243,6 +243,9 @@
<Compile Include="CenterViews\Modules\PM\ProcessMonitorView.xaml.cs" />
<Compile Include="CenterViews\Modules\PM\ProcessMonitorViewModel.cs" />
<Compile Include="CenterViews\Modules\PM\ShowCloseMonitorWinEvent.cs" />
<Compile Include="ClientBase\Dialog\LoginRequestConfirmationDialog.xaml.cs">
<DependentUpon>LoginRequestConfirmationDialog.xaml</DependentUpon>
</Compile>
<Compile Include="Ctrlib\Controls\CoolingWaterDataPresenter.xaml.cs">
<DependentUpon>CoolingWaterDataPresenter.xaml</DependentUpon>
</Compile>
@ -890,6 +893,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="ClientBase\Dialog\LoginRequestConfirmationDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Ctrlib\Controls\CoolingWaterDataPresenter.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>

View File

@ -26,6 +26,7 @@
<GradientStop Offset="0" Color="#FF1B85D8" />
<GradientStop Offset="0.988" Color="#FF004D89" />
</LinearGradientBrush>
<SolidColorBrush x:Key="Color_BG_TopStrip" Color="#FF1A5E94" />
<SolidColorBrush x:Key="Color_BD_LoginTitle" Color="#FF6BBEFF" />
<SolidColorBrush x:Key="Color_BD_LoginTitle_Shadow" Color="#FF0067AB" />
<SolidColorBrush x:Key="Color_BD_LoginTitle_Highlight" Color="#FF6BBEFF" />

View File

@ -234,15 +234,5 @@ namespace MECF.Framework.UI.Core.Applications
OnWindowsLoaded();
}
public void Logoff()
{
if (Current.EnableAccountModule)
{
AccountClient.Instance.Service.Logout(AccountClient.Instance.CurrentUser.AccountId);
}
_views.Logoff();
}
}
}

View File

@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
@ -29,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWithoutCopyFiles|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
@ -36,7 +38,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion>
<LangVersion>latest</LangVersion>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>

View File

@ -33,7 +33,7 @@ namespace UserLoginTester
Console.WriteLine(@"Press any key to exit...");
Console.ReadKey();
AccountClient.Instance.Service.LogoutEx(retLogin.Credential.Token);
AccountClient.Instance.Service.LogoutEx(retLogin.Credential);
}
private static void Instance_OnDisconnectedWithRT()
@ -55,7 +55,7 @@ namespace UserLoginTester
case EventType.LoginBySameUser_Notify:
if (Guid.TryParse(evt.Description, out var token) && evt.Tag is Credential requestingCred)
{
if (token == _loginCred.Token)
if (_loginCred != null && token == _loginCred.Token)
{
Console.WriteLine(
@"Some users is requesting to login the system, you will be forced to switch to read-only mode, do you agree?");
@ -67,7 +67,7 @@ namespace UserLoginTester
{
case "Y":
AccountClient.Instance.Service.ConfirmLoginRequest(requestingCred);
AccountClient.Instance.Service.LogoutEx(_loginCred.Token);
AccountClient.Instance.Service.LogoutEx(_loginCred);
break;
case "N":
AccountClient.Instance.Service.RejectLoginRequest(requestingCred);