247 lines
9.4 KiB
C#
247 lines
9.4 KiB
C#
using Fantasy;
|
||
using Fantasy.Async;
|
||
using Fantasy.Entitas;
|
||
using Fantasy.Entitas.Interface;
|
||
using Fantasy.Helper;
|
||
using Fantasy.Platform.Net;
|
||
|
||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
|
||
|
||
namespace NB.Authentication;
|
||
|
||
public sealed class AuthenticationComponentDestroySystem : DestroySystem<AuthenticationComponent>
|
||
{
|
||
protected override void Destroy(AuthenticationComponent self)
|
||
{
|
||
foreach (var (_, account) in self.Accounts.ToArray())
|
||
{
|
||
account.Dispose();
|
||
}
|
||
|
||
self.Accounts.Clear();
|
||
}
|
||
}
|
||
|
||
internal static class AuthenticationComponentSystem
|
||
{
|
||
public static void UpdatePosition(this AuthenticationComponent self)
|
||
{
|
||
// 1、通过远程接口或者本地文件来拿到鉴权组
|
||
// 2、通过配置文件来拿
|
||
var authentications = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Authentication);
|
||
// 拿到当前Scene的配置文件
|
||
var sceneConfig = SceneConfigData.Instance.Get(self.Scene.SceneConfigId);
|
||
// 获取到当前Scene在鉴权组的位置
|
||
self.Position = authentications.IndexOf(sceneConfig);
|
||
// 获得鉴权组的总数
|
||
self.AuthenticationCount = authentications.Count;
|
||
Log.Info($"鉴权服务器启动成功!Position:{self.Position} AuthenticationCount:{self.AuthenticationCount}");
|
||
}
|
||
|
||
internal static async FTask<(uint ErrorCode, long AccountId)> Login(this AuthenticationComponent self,
|
||
string userName, string password)
|
||
{
|
||
// 1、检查传递的参数是否完整
|
||
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
|
||
{
|
||
return (ErrorCode.ErrArgs, 0);
|
||
}
|
||
|
||
// 检查账号是否应该在当前鉴权服务器中处理
|
||
var position = HashCodeHelper.MurmurHash3(userName) % self.AuthenticationCount;
|
||
if (self.Position != position)
|
||
{
|
||
// 这个3代表的是当前账号不应该在这个鉴权服务器处理。
|
||
// return (3, 0);
|
||
return (ErrorCode.ErrServer, 0);
|
||
}
|
||
|
||
var scene = self.Scene;
|
||
var worldDateBase = scene.World.DataBase; //DateBase
|
||
var usernameHashCode = userName.GetHashCode();
|
||
|
||
using (var @lock =
|
||
await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationLoginLock, usernameHashCode))
|
||
{
|
||
// 如果用户频繁发生登录的请求,导致服务器会频繁请求数据库或缓存。
|
||
// 针对这个问题咱们可以利用缓存来解决这个问题。
|
||
// 1、创建一个新的字典容器在AuthenticationComponent中存储登录的信息。
|
||
// 2、key:userName + password, Value = ?
|
||
// 3、为了防止缓存暴涨、肯定需要一个定期清理的过程,Value肯定是要一个实体了。
|
||
// 4、因为这个实体下面咱们可以挂载一个组件,这个组件的作用就是定时清理这个缓存。5
|
||
|
||
// 问题
|
||
// 1、如果用户的密码改了怎么办?
|
||
// 因为缓存中有定时清除的,所以遇到改密码的情况下,最多等待这个缓存清除了,然后就可以登录了。
|
||
// 2、如果我不这样做,还有什么其他办法?
|
||
// 通过防火墙的策略来限制用户请求,比如100ms请求一次。
|
||
|
||
// 作业:
|
||
// 在这个AccountCacheInfo下创建一个组件,这个组件的功能就是定时清理这个缓存。
|
||
|
||
Account account = null;
|
||
var loginAccountsKey = userName + password;
|
||
|
||
if (self.LoginAccounts.TryGetValue(loginAccountsKey, out var accountCacheInfo))
|
||
{
|
||
account = accountCacheInfo.GetComponent<Account>();
|
||
|
||
if (account == null)
|
||
{
|
||
return (ErrorCode.ErrAccountOrPass, 0);
|
||
}
|
||
|
||
return (ErrorCode.Successful, account.Id);
|
||
}
|
||
|
||
uint result = 0;
|
||
accountCacheInfo = Entity.Create<AccountCacheInfo>(scene, true, true);
|
||
account = await worldDateBase.First<Account>(d => d.Username == userName);
|
||
|
||
if (account == null)
|
||
{
|
||
// 没有注册
|
||
return (ErrorCode.ErrAccountOrPass, -1); //返回-1,用于判断是否需要自动注册
|
||
}
|
||
|
||
if (account.Password != password)
|
||
{
|
||
//密码错误
|
||
result = ErrorCode.ErrAccountOrPass;
|
||
}
|
||
else
|
||
{
|
||
// 更新登录时间,并保存到数据库
|
||
account.LoginTime = TimeHelper.Now;
|
||
await worldDateBase.Save(account);
|
||
// 添加Account到缓存中
|
||
account.Deserialize(scene);
|
||
accountCacheInfo.AddComponent(account);
|
||
}
|
||
|
||
accountCacheInfo.AddComponent<AccountCacheInfoTimeOut>().TimeOut(loginAccountsKey, 5000);
|
||
self.LoginAccounts.Add(loginAccountsKey, accountCacheInfo);
|
||
|
||
if (result != 0)
|
||
{
|
||
return (result, 0);
|
||
}
|
||
|
||
return (ErrorCode.Successful, account.Id);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 鉴权注册接口
|
||
/// </summary>
|
||
/// <param name="self"></param>
|
||
/// <param name="username"></param>
|
||
/// <param name="password"></param>
|
||
/// <param name="source"></param>
|
||
internal static async FTask<uint> Register(this AuthenticationComponent self, string username, string password,
|
||
string source)
|
||
{
|
||
// 1、检查传递的参数是否完整
|
||
|
||
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
|
||
{
|
||
// 这个1代表的是参数不完整。
|
||
return ErrorCode.ErrArgs;
|
||
}
|
||
|
||
// 检查账号是否应该在当前鉴权服务器中处理
|
||
|
||
var position = HashCodeHelper.MurmurHash3(username) % self.AuthenticationCount;
|
||
if (self.Position != position)
|
||
{
|
||
// 这个代表的是当前账号不应该在这个鉴权服务器处理。
|
||
return ErrorCode.ErrServer;
|
||
}
|
||
|
||
var usernameHashCode = username.GetHashCode();
|
||
var scene = self.Scene;
|
||
|
||
// 利用协程锁来解决异步的原子问题
|
||
using (var @lock =
|
||
await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationRegisterLock, usernameHashCode))
|
||
{
|
||
// 利用缓存来减少频繁请求数据库或缓存的压力。
|
||
|
||
if (self.Accounts.TryGetValue(username, out var account))
|
||
{
|
||
// 这个2代表的是该用户已经存在。
|
||
return ErrorCode.ErrAccountHave;
|
||
}
|
||
|
||
// 2、数据库查询该账号是否存在
|
||
var worldDateBase = scene.World.DataBase;
|
||
var isExist = await worldDateBase.Exist<Account>(d => d.Username == username);
|
||
if (isExist)
|
||
{
|
||
// 这个2代表的是该用户已经存在。
|
||
return ErrorCode.ErrAccountHave;
|
||
}
|
||
|
||
//3、执行到这里的话,表示数据库或缓存没有该账号的注册信息,需要咱们创建一个。
|
||
account = Entity.Create<Account>(scene, true, true);
|
||
account.Username = username;
|
||
account.Password = password;
|
||
account.CreateTime = TimeHelper.Now;
|
||
// 写入这个实体到数据中
|
||
await worldDateBase.Save(account);
|
||
var accountId = account.Id;
|
||
// 把当前账号添加到缓存字典中。
|
||
self.Accounts.Add(username, account);
|
||
// 添加AccountTimeOut组件用来定时清除缓存
|
||
account.AddComponent<AccountTimeOut>().TimeOut(4000);
|
||
// 这个0代表的是操作成功
|
||
Log.Info($"Register source:{source} username:{username} accountId:{accountId}");
|
||
return ErrorCode.Successful;
|
||
}
|
||
}
|
||
|
||
internal static void RemoveLoginAccounts(this AuthenticationComponent self, string key, bool isDispose)
|
||
{
|
||
if (!self.LoginAccounts.Remove(key, out var accountCacheInfo))
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (isDispose)
|
||
{
|
||
accountCacheInfo.Dispose();
|
||
}
|
||
}
|
||
|
||
internal static void RemoveCache(this AuthenticationComponent self, string username, bool isDispose)
|
||
{
|
||
if (!self.Accounts.Remove(username, out var account))
|
||
{
|
||
return;
|
||
}
|
||
|
||
Log.Debug($"Remove cache username:{username} Count:{self.Accounts.Count}");
|
||
|
||
if (isDispose)
|
||
{
|
||
account.Dispose();
|
||
}
|
||
}
|
||
|
||
internal static async FTask<uint> Remove(this AuthenticationComponent self, long accountId, string source)
|
||
{
|
||
var scene = self.Scene;
|
||
|
||
// 其实呢,这里没必要加协程锁,这里加是为了给大家加深下这个协程锁的印象。
|
||
|
||
using (var @lock = await scene.CoroutineLockComponent.Wait((int)LockType.AuthenticationRemoveLock, accountId))
|
||
{
|
||
var worldDateBase = scene.World.DataBase;
|
||
await worldDateBase.Remove<Account>(accountId);
|
||
Log.Info($"Remove source:{source} accountId:{accountId}");
|
||
return 0;
|
||
}
|
||
}
|
||
} |