Files
Fishing2Server/Hotfix/Authentication/System/AuthenticationComponentSystem.cs
2026-01-18 16:37:46 +08:00

249 lines
9.4 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.Linq;
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, int region)> Login(this AuthenticationComponent self,
string userName, string password)
{
// 1、检查传递的参数是否完整
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
{
return (ErrorCode.ErrArgs, 0, 0);
}
// 检查账号是否应该在当前鉴权服务器中处理
var position = HashCodeHelper.MurmurHash3(userName) % self.AuthenticationCount;
if (self.Position != position)
{
// 这个3代表的是当前账号不应该在这个鉴权服务器处理。
// return (3, 0);
return (ErrorCode.ErrServer, 0, 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, 0);
}
return (ErrorCode.Successful, account.Id, account.Region);
}
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, 0); //返回-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, 0);
}
return (ErrorCode.Successful, account.Id, account.Region);
}
}
/// <summary>
/// 鉴权注册接口
/// </summary>
/// <param name="self"></param>
/// <param name="username"></param>
/// <param name="region"></param>
/// <param name="source"></param>
internal static async FTask<uint> Register(this AuthenticationComponent self, string username, int region,
string source)
{
// 1、检查传递的参数是否完整
if (string.IsNullOrEmpty(username))
{
// 这个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 = username;
account.Region = region;
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;
}
}
}