Sic.Framework/MECF.Framework.Common/MECF/Framework/Common/Account/AccountExManager.cs

365 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.Threading.Tasks;
using Aitex.Common.Util;
using Aitex.Core.Account;
using Aitex.Core.RT.Event;
using Aitex.Core.RT.Log;
using Aitex.Core.RT.SCCore;
using Aitex.Core.Util;
using Aitex.Core.WCF;
using MECF.Framework.Common.Account.Extends;
using MECF.Framework.Common.Equipment;
namespace MECF.Framework.Common.Account
{
/// <summary>
/// 账号管理器。
/// </summary>
public class AccountExManager : Singleton<AccountExManager>
{
#region Variables
private readonly string _scAccountTemplateFile = PathManager.GetCfgDir() + "Account//Account.xml";
private readonly string _scAccountLocalFile = PathManager.GetCfgDir() + "Account//_AccountEx.xml";
// 旧的_Account.xml文件路径为兼容已有的账户配置。
private readonly string _oldAccountXmlFile = PathManager.GetCfgDir() + "Account//_Account.xml";
private readonly SemaphoreSlim _syncRoot = new SemaphoreSlim(1, 1);
/// <summary>
/// 是否启用单会话登录模式。
/// </summary>
private bool _enableSingleSessionMode;
#endregion
#region Constructors
public AccountExManager()
{
}
#endregion
#region Properties
/// <summary>
/// 返回角色加载器。
/// </summary>
public RoleAccountLoader RoleAccountLoader { get; private set; }
#endregion
#region Methods
/// <summary>
/// 初始化当前对象。
/// </summary>
/// <param name="enableService"></param>
/// <exception cref="ApplicationException"></exception>
public void Initialize(bool enableService)
{
_enableSingleSessionMode = SC.SafeGetValue("System.SingleSessionLoginMode", false);
if (!File.Exists(_scAccountLocalFile))
{
if (File.Exists(_oldAccountXmlFile))
{
// 如果已存在_Account.xml则从_Account.xml创建_AccountEx.xml文件。
File.Copy(_oldAccountXmlFile, _scAccountLocalFile);
Thread.Sleep(10);
}
else if (File.Exists(_scAccountTemplateFile))
{
File.Copy(_scAccountTemplateFile, _scAccountLocalFile);
Thread.Sleep(10);
}
else
{
// 模板文件不存在
throw new ApplicationException("Can not initialize account configuration file, " +
_scAccountTemplateFile);
}
}
// 默认不支持多用户登录。
CredentialManager.Instance.Initialize(!_enableSingleSessionMode);
RoleAccountLoader = new RoleAccountLoader(_scAccountLocalFile);
RoleAccountLoader.Load();
if (enableService)
{
Singleton<WcfServiceManager>.Instance.Initialize(new Type[1] { typeof(AccountService) });
}
}
/// <summary>
/// 获取WCF客户端IP信息。
/// </summary>
/// <returns></returns>
private static RemoteEndpointMessageProperty GetCurrentWCFClientEndPoint()
{
var context = OperationContext.Current;
var prop = context.IncomingMessageProperties;
var endpoint =
prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
return endpoint;
}
/// <summary>
/// 等待已登录的用户确认是否允许新的登录请求。
/// </summary>
/// <param name="userLoggedIn">已登录用户的凭据。</param>
/// <param name="userRequesting">新用户的登录凭据。</param>
/// <returns></returns>
private static async Task<LoginRequestResults> RequestAndWaitLoginConfirmation(Credential userLoggedIn,
Credential userRequesting)
{
// 等待已登录用户响应,返回结果;
// 1. Rejected拒绝新的登录请求
// 2. Logged Out主动注销允许新的登录请求
// 3. Timeout超时没有响应保持原用户的登录凭据
return await Task.Run(async () =>
{
// 通知已登录的用户,其它客户端使用该用户名登录,是否允许登录
var ct = userRequesting.LoginRequestCancellationTokenSource.Token;
// 等待已登录的凭据被移除(客户端注销)
var sw = new Stopwatch();
sw.Start();
while (true)
{
switch (userRequesting.State)
{
case CredentialState.Confirmed:
// 客户端已注销,可以放行新的凭据
CredentialManager.Instance.Grant(userRequesting);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn,
userLoggedIn.AccountInfo.LoginName);
return await Task.FromResult(LoginRequestResults.Confirmed);
case CredentialState.Reject or CredentialState.RequestCanceled:
if(userRequesting.State == CredentialState.Reject)
// 已登录用户拒绝了登录请求
EV.PostInfoLog(ModuleName.System.ToString(),
$"User {userRequesting.AccountInfo.LoginName}'s login request from {userRequesting.LoginIP}:{userRequesting.LoginPort} is rejected");
return await Task.FromResult(LoginRequestResults.Rejected);
case CredentialState.Requesting:
case CredentialState.Alive:
break;
}
Thread.Sleep(200);
if (ct.IsCancellationRequested)
break;
// 等待指定的时间,然后向申请端发送超时
if (sw.Elapsed.TotalSeconds > CredentialManager.REQ_LOGIN_CRED_LIFT_TIME_SEC)
break;
}
// 等待确认超时
return LoginRequestResults.Timeout;
});
}
/// <summary>
/// 登录。
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <param name="roleID">角色ID</param>
/// <param name="clientInfo">发起登录请求的客户端信息</param>
/// <returns><see cref="LoginRequestResults"/></returns>
/// <exception cref="TimeoutException"></exception>
internal async Task<LoginResult> Login(string userName, string password, string roleID,
LoginClientInfo clientInfo)
{
var endPoint = GetCurrentWCFClientEndPoint();
try
{
var accountList = RoleAccountLoader.GetAccounts();
var matchedAccount = accountList.FirstOrDefault(x => x.LoginName == userName);
if (matchedAccount == null)
// 用户不存在
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchUser));
if (matchedAccount.Password != password)
// 密码错误
return await Task.FromResult(new LoginResult(LoginRequestResults.WrongPwd));
// 查找当前用户的登录角色是否存在
var matchedRole = matchedAccount.RoleIDs.FirstOrDefault(x => x == roleID);
if (matchedRole == null)
// 找不到角色
return await Task.FromResult(new LoginResult(LoginRequestResults.NoMatchRole));
// 创建新凭据
var requestingCred = new Credential(CredentialManager.GenerateToken(), matchedAccount)
{
LoginIP = endPoint.Address,
LoginPort = endPoint.Port,
ClientInfo = clientInfo,
RoleID = roleID
};
// 如果禁用了单会话登录模式,则直接放行凭据。
if (!_enableSingleSessionMode)
{
CredentialManager.Instance.ForceGrant(requestingCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, $"{userName}@{requestingCred.LoginIP}:{requestingCred.LoginPort}");
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, requestingCred));
}
// 如果还没有任何用户登录到RT则直接放行
if (CredentialManager.Instance.LoggedInCount == 0 && CredentialManager.Instance.LoginRequestingCount == 0)
{
// 添加凭据到登录请求列表Grant时需要请求列表中存在此凭据。
CredentialManager.Instance.AddRequestingList(requestingCred);
// 没有用户登录,直接发放凭据
CredentialManager.Instance.Grant(requestingCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, $"{userName}@{requestingCred.LoginIP}:{requestingCred.LoginPort}");
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, requestingCred));
}
if (CredentialManager.Instance.IsSupportMultiUserLogin)
{
// 支持多用户同时登录
// 如果当前用户名正在请求登录,则拒绝发起新的请求。
if (CredentialManager.Instance.GetRequestingCredential(userName) != null)
return await Task.FromResult(new LoginResult(LoginRequestResults.RequstingLogin));
// 添加凭据到登录请求列表Grant时需要请求列表中存在此凭据。
CredentialManager.Instance.AddRequestingList(requestingCred);
// 查找当前请求的用户是否已经登录
var loggedCred = CredentialManager.Instance.GetCredential(userName);
if (loggedCred == null)
{
// 用户未登录,直接放行凭据
CredentialManager.Instance.Grant(requestingCred);
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedIn, $"{userName}@{requestingCred.LoginIP}:{requestingCred.LoginPort}");
return await Task.FromResult(new LoginResult(LoginRequestResults.Confirmed, requestingCred));
}
// 否则等待已登录的Session确认
var ret = await RequestAndWaitLoginConfirmation(loggedCred, requestingCred);
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, requestingCred) : new LoginResult(ret);
}
else
{
// 仅支持单一用户登录
if (CredentialManager.Instance.LoggedInCount > 1)
{
EV.PostWarningLog(ModuleName.System.ToString(),
$"{nameof(CredentialManager)} does not support multiple users login but more than one credentials found.");
}
// 如果有正在请求登录,则拒绝发起新的请求。
if (CredentialManager.Instance.LoginRequestingCount > 0)
return await Task.FromResult(new LoginResult(LoginRequestResults.RequstingLogin));
// 添加凭据到登录请求列表Grant时需要请求列表中存在此凭据。
CredentialManager.Instance.AddRequestingList(requestingCred);
// 已登录的用户凭据
var loggedCred = CredentialManager.Instance.GetCredential();
var ret = await RequestAndWaitLoginConfirmation(loggedCred, requestingCred);
return ret == LoginRequestResults.Confirmed ? new LoginResult(ret, requestingCred) : new LoginResult(ret);
}
}
catch (Exception ex)
{
LOG.Error(ex.Message, ex);
return await Task.FromResult(new LoginResult(LoginRequestResults.Error));
}
finally
{
}
}
/// <summary>
/// 登录请求发起端取消登录请求。
/// </summary>
/// <param name="userName"></param>
internal void CancelLoginRequest(string userName)
{
CredentialManager.Instance.Cancel(userName);
}
/// <summary>
/// 确认登录请求。
/// </summary>
/// <param name="userName"></param>
internal void ConfirmedLoginRequest(string userName)
{
CredentialManager.Instance.Accept(userName);
}
/// <summary>
/// 拒绝登录请求。
/// </summary>
/// <param name="userName"></param>
internal void RejectLoginRequest(string userName)
{
CredentialManager.Instance.Reject(userName);
}
internal void Logout(Guid myToken)
{
var loginCred = CredentialManager.Instance.GetCredential(myToken);
if (loginCred != null)
{
EV.PostMessage(ModuleName.System.ToString(), EventEnum.UserLoggedOff,
$"{loginCred.AccountInfo.LoginName}@{loginCred.LoginIP}:{loginCred.LoginPort}");
}
CredentialManager.Instance.Remove(myToken);
}
/// <summary>
/// 校验指定账户密码是否正确。
/// </summary>
/// <param name="accountId">账号ID</param>
/// <param name="passwordMD5">MD5加密后的密码</param>
/// <returns></returns>
internal bool CheckPassword(string accountId, string passwordMD5)
{
var account = RoleAccountLoader.GetAccounts().FirstOrDefault(x => x.UserId == accountId);
if (account == null)
{
LOG.Error($"CheckPassword Unable to find the account with ID {accountId}");
return false;
}
return account.Password == passwordMD5;
}
#endregion
}
}