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 { 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(); if (account == null) { return (ErrorCode.ErrAccountOrPass, 0, 0); } return (ErrorCode.Successful, account.Id, account.Region); } uint result = 0; accountCacheInfo = Entity.Create(scene, true, true); account = await worldDateBase.First(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().TimeOut(loginAccountsKey, 5000); self.LoginAccounts.Add(loginAccountsKey, accountCacheInfo); if (result != 0) { return (result, 0, 0); } return (ErrorCode.Successful, account.Id, account.Region); } } /// /// 鉴权注册接口 /// /// /// /// /// internal static async FTask 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(d => d.Username == username); if (isExist) { // 这个2代表的是该用户已经存在。 return ErrorCode.ErrAccountHave; } //3、执行到这里的话,表示数据库或缓存没有该账号的注册信息,需要咱们创建一个。 account = Entity.Create(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().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 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(accountId); Log.Info($"Remove source:{source} accountId:{accountId}"); return 0; } } }