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

View File

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

View File

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

View File

@ -13,14 +13,6 @@ namespace MECF.Framework.UI.Core.Accounts
public AccountServiceClient() public AccountServiceClient()
{ {
} }
public void Logout(string accountId)
{
WCFProxy.Using(delegate(IAccountService svc)
{
svc.Logout(accountId);
});
}
public GetAccountInfoResult GetAccountInfo(string 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) 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; var result = CredentialKeepAliveResults.NotFound;
WCFProxy.Using(delegate(IAccountService svc) WCFProxy.Using(delegate(IAccountService svc)
{ {
result = svc.KeepAlive(token); result = svc.KeepAlive(cred);
}); });
return result; 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\ProcessMonitorView.xaml.cs" />
<Compile Include="CenterViews\Modules\PM\ProcessMonitorViewModel.cs" /> <Compile Include="CenterViews\Modules\PM\ProcessMonitorViewModel.cs" />
<Compile Include="CenterViews\Modules\PM\ShowCloseMonitorWinEvent.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"> <Compile Include="Ctrlib\Controls\CoolingWaterDataPresenter.xaml.cs">
<DependentUpon>CoolingWaterDataPresenter.xaml</DependentUpon> <DependentUpon>CoolingWaterDataPresenter.xaml</DependentUpon>
</Compile> </Compile>
@ -890,6 +893,10 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Page> </Page>
<Page Include="ClientBase\Dialog\LoginRequestConfirmationDialog.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Ctrlib\Controls\CoolingWaterDataPresenter.xaml"> <Page Include="Ctrlib\Controls\CoolingWaterDataPresenter.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

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

View File

@ -234,15 +234,5 @@ namespace MECF.Framework.UI.Core.Applications
OnWindowsLoaded(); 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> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
@ -29,6 +30,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWithoutCopyFiles|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'DebugWithoutCopyFiles|AnyCPU'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@ -36,7 +38,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>7.3</LangVersion> <LangVersion>latest</LangVersion>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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