修改协议工具

This commit is contained in:
2025-07-27 23:46:43 +08:00
parent 743c1d2baa
commit be33e12b35
112 changed files with 1021 additions and 1119 deletions

View File

@@ -0,0 +1,15 @@
using Fantasy;
using Fantasy.Entitas.Interface;
namespace NB.Authentication;
public class AccountDestroySystem : DestroySystem<Account>
{
protected override void Destroy(Account self)
{
self.Username = null;
self.Password = null;
self.CreateTime = 0;
self.LoginTime = 0;
}
}

View File

@@ -0,0 +1,247 @@
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;
}
}
}

View File

@@ -0,0 +1,66 @@
using Fantasy;
using Fantasy.Async;
namespace NB.Authentication;
public static class AuthenticationHelper
{
/// <summary>
/// 登录账号
/// </summary>
/// <param name="scene"></param>
/// <param name="userName">用户名</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
public static async FTask<(uint ErrorCode, long AccountId)> Login(Scene scene, string userName, string password)
{
return await scene.GetComponent<AuthenticationComponent>().Login(userName, password);
}
/// <summary>
/// 注册一个新的账号
/// </summary>
/// <param name="scene"></param>
/// <param name="username">用户名</param>
/// <param name="password">用户密码</param>
/// <param name="source">注册的来源/原因</param>
/// <returns></returns>
public static async FTask<uint> Register(Scene scene, string username, string password, string source)
{
return await scene.GetComponent<AuthenticationComponent>().Register(username, password, source);
}
/// <summary>
/// 移除一个账号
/// </summary>
/// <param name="scene"></param>
/// <param name="accountId">账号ID</param>
/// <param name="source">移除的来源/原因</param>
/// <returns></returns>
public static async FTask<uint> Remove(Scene scene, long accountId, string source)
{
return await scene.GetComponent<AuthenticationComponent>().Remove(accountId, source);
}
/// <summary>
/// 移除缓存中的Account
/// </summary>
/// <param name="scene"></param>
/// <param name="username">账号名字</param>
/// <param name="isDispose">是否销毁</param>
public static void RemoveCache(Scene scene, string username, bool isDispose)
{
scene.GetComponent<AuthenticationComponent>().RemoveCache(username, isDispose);
}
/// <summary>
/// 移除LoginAccounts缓存中的数据仅供内部调用不明白原理的不要调用否则后果自负。
/// </summary>
/// <param name="scene"></param>
/// <param name="key"></param>
/// <param name="isDispose"></param>
internal static void RemoveLoginAccounts(Scene scene, string key, bool isDispose)
{
scene.GetComponent<AuthenticationComponent>().RemoveLoginAccounts(key, isDispose);
}
}

View File

@@ -0,0 +1,55 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Fantasy;
using Fantasy.Entitas.Interface;
using Microsoft.IdentityModel.Tokens;
namespace NB.Authentication.Jwt;
public sealed class AuthenticationJwtComponentAwakeSystem : AwakeSystem<AuthenticationJwtComponent>
{
protected override void Awake(AuthenticationJwtComponent self)
{
self.Awake();
}
}
public static class AuthenticationJwtComponentSystem
{
public static void Awake(this AuthenticationJwtComponent self)
{
var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(self.PublicKeyPem), out _);
rsa.ImportRSAPrivateKey(Convert.FromBase64String(self.PrivateKeyPem), out _);
self.SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256);
// 创建 TokenValidationParameters 对象,用于配置验证参数
self.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = false, // 禁止令牌验证时间是否过期
ValidateIssuer = true, // 验证发行者
ValidateAudience = true, // 验证受众
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = "Fantasy", // 有效的发行者
ValidAudience = "Fantasy", // 有效的受众
IssuerSigningKey = new RsaSecurityKey(rsa) // RSA公钥作为签名密钥
};
}
public static string GetToken(this AuthenticationJwtComponent self, long aId, string address, uint sceneId)
{
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "Address", address },
{ "SceneId", sceneId }
};
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Fantasy",
audience: "Fantasy",
claims: jwtPayload.Claims,
expires: DateTime.UtcNow.AddMilliseconds(3000),
signingCredentials: self.SigningCredentials);
return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
}
}

View File

@@ -0,0 +1,19 @@
using Fantasy;
namespace NB.Authentication.Jwt;
public static class AuthenticationJwtHelper
{
/// <summary>
/// 获取一个新的令牌
/// </summary>
/// <param name="scene"></param>
/// <param name="aId">AccountId</param>
/// <param name="address">目标服务器的地址</param>
/// <param name="sceneId">分配的Scene的Id</param>
/// <returns></returns>
public static string GetToken(Scene scene, long aId, string address, uint sceneId)
{
return scene.GetComponent<AuthenticationJwtComponent>().GetToken(aId, address, sceneId);
}
}

View File

@@ -0,0 +1,40 @@
using Fantasy;
using Fantasy.Entitas.Interface;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace NB.Authentication;
public sealed class AccountCacheInfoTimeOutDestroySystem : DestroySystem<AccountCacheInfoTimeOut>
{
protected override void Destroy(AccountCacheInfoTimeOut self)
{
if (self.TimerId != 0)
{
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
}
self.Key = null;
}
}
public static class AccountCacheInfoTimeOutSystem
{
public static void TimeOut(this AccountCacheInfoTimeOut self, string key, int timeout)
{
self.Key = key;
// 创建一个任务计时器、用在timeout时间后执行并且要清楚掉当前鉴权服务器缓存
var scene = self.Scene;
var runTimeId = self.RuntimeId;
self.TimerId = scene.TimerComponent.Net.OnceTimer(timeout, () =>
{
if (runTimeId != self.RuntimeId)
{
return;
}
self.TimerId = 0;
AuthenticationHelper.RemoveLoginAccounts(scene, self.Key,true);
});
}
}

View File

@@ -0,0 +1,37 @@
using Fantasy;
using Fantasy.Entitas.Interface;
namespace NB.Authentication;
public sealed class AccountTimeOutDestroySystem : DestroySystem<AccountTimeOut>
{
protected override void Destroy(AccountTimeOut self)
{
if (self.TimerId != 0)
{
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
}
}
}
public static class AccountTimeOutSystem
{
public static void TimeOut(this AccountTimeOut self, int timeout)
{
// 创建一个任务计时器、用在timeout时间后执行并且要清楚掉当前鉴权服务器缓存
var scene = self.Scene;
var account = (Account)self.Parent;
var accountRunTimeId = account.RuntimeId;
self.TimerId = scene.TimerComponent.Net.OnceTimer(timeout, () =>
{
if (accountRunTimeId != account.RuntimeId)
{
return;
}
self.TimerId = 0;
AuthenticationHelper.RemoveCache(scene, account.Username,true);
});
}
}