水电费
This commit is contained in:
@@ -2,12 +2,14 @@ using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
// 这个是一个自定义系统类型,用于决定系统类型。
|
||||
// 也可以用枚举,看个人怎么使用了。
|
||||
public static class CustomSystemType
|
||||
{
|
||||
public const int RunSystem = 1;
|
||||
}
|
||||
|
||||
// 这个是一个自定义系统,用于处理自定义事件。
|
||||
public abstract class RunSystem<T> : CustomSystem<T> where T : Entity
|
||||
{
|
||||
@@ -15,23 +17,26 @@ public abstract class RunSystem<T> : CustomSystem<T> where T : Entity
|
||||
/// 自定义事件类型,用于决定事件的类型。
|
||||
/// </summary>
|
||||
public override int CustomEventType => CustomSystemType.RunSystem;
|
||||
|
||||
/// <summary>
|
||||
/// 不知道为什么这样定义的,就照搬就可以了。
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
protected abstract override void Custom(T self);
|
||||
|
||||
/// <summary>
|
||||
/// 不知道为什么这样定义的,就照搬就可以了。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override Type EntitiesType() => typeof(T);
|
||||
}
|
||||
|
||||
// 下面是一个测试自定义系统。
|
||||
// 首先定义一个组件用来测试自定义系统。
|
||||
public class TestCustomSystemComponent : Entity
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// 现在给TestCustomSystemComponent组件添加一个自定义系统。
|
||||
// 现在添加的就是上面定义的RunSystem自定义系统。
|
||||
public class TestCustomSystemComponentRunSystem : RunSystem<TestCustomSystemComponent>
|
||||
@@ -47,4 +52,4 @@ public class TestCustomSystemComponentRunSystem : RunSystem<TestCustomSystemComp
|
||||
// 第一个参数是你要执行自定义系统的实体实例
|
||||
// 第二个参数是自定义系统类型
|
||||
// scene.EntityComponent.CustomSystem(testCustomSystemComponent, CustomSystemType.RunSystem);
|
||||
// 你可以在OnCreateSceneEvent.cs的Handler方法里找到执行这个的例子。
|
||||
// 你可以在OnCreateSceneEvent.cs的Handler方法里找到执行这个的例子。
|
||||
47
Hotfix/EntityHelper.cs
Normal file
47
Hotfix/EntityHelper.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Authentication;
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Network;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace System;
|
||||
|
||||
public static class EntityHelper
|
||||
{
|
||||
public static bool CheckInterval(this Entity entity, int interval)
|
||||
{
|
||||
var sessionTimeOutComponent = entity.GetComponent<EntityTimeOutComponent>();
|
||||
|
||||
if (sessionTimeOutComponent == null)
|
||||
{
|
||||
sessionTimeOutComponent = entity.AddComponent<EntityTimeOutComponent>();
|
||||
sessionTimeOutComponent.SetInterval(interval);
|
||||
return true;
|
||||
}
|
||||
|
||||
return sessionTimeOutComponent.CheckInterval();
|
||||
}
|
||||
|
||||
public static void SetTimeout(this Entity entity, int timeout = 3000, Func<FTask>? task = null)
|
||||
{
|
||||
var sessionTimeOutComponent = entity.GetComponent<EntityTimeOutComponent>();
|
||||
|
||||
if (sessionTimeOutComponent == null)
|
||||
{
|
||||
sessionTimeOutComponent = entity.AddComponent<EntityTimeOutComponent>();
|
||||
}
|
||||
|
||||
sessionTimeOutComponent.TimeOut(timeout, task);
|
||||
}
|
||||
|
||||
public static bool IsTimeOutComponent(this Entity entity)
|
||||
{
|
||||
return entity.GetComponent<EntityTimeOutComponent>() != null;
|
||||
}
|
||||
|
||||
public static void CancelTimeout(this Entity entity)
|
||||
{
|
||||
entity.RemoveComponent<EntityTimeOutComponent>();
|
||||
}
|
||||
}
|
||||
78
Hotfix/EntityTimeOutComponentSystem.cs
Normal file
78
Hotfix/EntityTimeOutComponentSystem.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Helper;
|
||||
|
||||
namespace System.Authentication;
|
||||
|
||||
public sealed class EntityTimeOutComponentDestroySystem : DestroySystem<EntityTimeOutComponent>
|
||||
{
|
||||
protected override void Destroy(EntityTimeOutComponent self)
|
||||
{
|
||||
if (self.TimerId != 0)
|
||||
{
|
||||
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
|
||||
}
|
||||
|
||||
self.NextTime = 0;
|
||||
self.Interval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EntityTimeOutComponentSystem
|
||||
{
|
||||
public static void SetInterval(this EntityTimeOutComponent self, int interval)
|
||||
{
|
||||
if (interval <= 0)
|
||||
{
|
||||
throw new ArgumentException("interval must be greater than 0", nameof(interval));
|
||||
}
|
||||
|
||||
self.Interval = interval;
|
||||
self.NextTime = TimeHelper.Now + interval;
|
||||
}
|
||||
|
||||
public static bool CheckInterval(this EntityTimeOutComponent self)
|
||||
{
|
||||
if (self.NextTime > TimeHelper.Now)
|
||||
{
|
||||
Log.Warning("当前连接请求的间隔过小");
|
||||
return false;
|
||||
}
|
||||
|
||||
self.NextTime = TimeHelper.Now + self.Interval;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void TimeOut(this EntityTimeOutComponent self, int timeout, Func<FTask>? task = null)
|
||||
{
|
||||
var scene = self.Scene;
|
||||
var parentRunTimeId = self.Parent.RuntimeId;
|
||||
|
||||
if (self.TimerId != 0)
|
||||
{
|
||||
self.Scene.TimerComponent.Net.Remove(ref self.TimerId);
|
||||
}
|
||||
|
||||
self.TimerId =
|
||||
scene.TimerComponent.Net.OnceTimer(timeout, () => { self.Handler(parentRunTimeId, task).Coroutine(); });
|
||||
}
|
||||
|
||||
private static async FTask Handler(this EntityTimeOutComponent self, long parentRunTimeId, Func<FTask>? task = null)
|
||||
{
|
||||
var selfParent = self.Parent;
|
||||
|
||||
if (selfParent == null || parentRunTimeId != selfParent.RuntimeId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (task != null)
|
||||
{
|
||||
await task();
|
||||
}
|
||||
|
||||
self.TimerId = 0;
|
||||
selfParent.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Outer\Demo\" />
|
||||
<Folder Include="Outer\Gate\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
38
Hotfix/OnSceneCreate_Init.cs
Normal file
38
Hotfix/OnSceneCreate_Init.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Authentication;
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Authentication;
|
||||
using Fantasy.Event;
|
||||
using Fantasy.Gate;
|
||||
|
||||
namespace System;
|
||||
|
||||
public class OnSceneCreate_Init : AsyncEventSystem<OnCreateScene>
|
||||
{
|
||||
protected override async FTask Handler(OnCreateScene self)
|
||||
{
|
||||
var scene = self.Scene;
|
||||
|
||||
switch (scene.SceneType)
|
||||
{
|
||||
case SceneType.Authentication:
|
||||
{
|
||||
// 用于鉴权服务器注册和登录相关逻辑的组件
|
||||
scene.AddComponent<AuthenticationComponent>().UpdatePosition();
|
||||
// 用于颁发ToKen证书相关的逻辑。
|
||||
scene.AddComponent<AuthenticationJwtComponent>();
|
||||
break;
|
||||
}
|
||||
case SceneType.Gate:
|
||||
{
|
||||
// 用于验证JWT是否合法的组件
|
||||
scene.AddComponent<GateJWTComponent>();
|
||||
// 用于管理GameAccount的组件
|
||||
scene.AddComponent<GameAccountManageComponent>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Authentication.Jwt;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy.Authentication.Handler;
|
||||
|
||||
public class C2A_LoginRequestHandler : MessageRPC<C2A_LoginRequest, A2C_LoginResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2A_LoginRequest request, A2C_LoginResponse response,
|
||||
Action reply)
|
||||
{
|
||||
var scene = session.Scene;
|
||||
var result = await AuthenticationHelper.Login(scene, request.Username, request.Password);
|
||||
|
||||
if (result.ErrorCode == 0)
|
||||
{
|
||||
// 通过配置表或其他方式拿到Gate服务器组的信息
|
||||
var gates = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Gate);
|
||||
// 通过当前账号的ID拿到要分配Gate服务器
|
||||
var gatePosition = result.AccountId % gates.Count;
|
||||
// 通过计算出来的位置下标拿到Gate服务器的配置
|
||||
var gateSceneConfig = gates[(int)gatePosition];
|
||||
// 通过Gate的SceneConfig文件拿到外网的ID地址和端口
|
||||
var outerPort = gateSceneConfig.OuterPort;
|
||||
var processConfig = ProcessConfigData.Instance.Get(gateSceneConfig.ProcessConfigId);
|
||||
var machineConfig = MachineConfigData.Instance.Get(processConfig.MachineId);
|
||||
// 颁发一个ToKen令牌给客户端
|
||||
response.ToKen = AuthenticationJwtHelper.GetToken(scene, result.AccountId,
|
||||
$"{machineConfig.OuterIP}:{outerPort}", gateSceneConfig.Id);
|
||||
}
|
||||
|
||||
response.ErrorCode = result.ErrorCode;
|
||||
Log.Debug($"Login 当前的服务器是:{scene.SceneConfigId}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy.Authentication.Handler;
|
||||
|
||||
public sealed class C2A_RegisterRequestHandler : MessageRPC<C2A_RegisterRequest, A2C_RegisterResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2A_RegisterRequest request, A2C_RegisterResponse response,
|
||||
Action reply)
|
||||
{
|
||||
if (!session.CheckInterval(2000))
|
||||
{
|
||||
// 返回这个3代表操作过于频繁。
|
||||
response.ErrorCode = 3;
|
||||
return;
|
||||
}
|
||||
|
||||
session.SetTimeout(3000);
|
||||
response.ErrorCode =
|
||||
await AuthenticationHelper.Register(session.Scene, request.Username, request.Password, "用户注册");
|
||||
Log.Debug($"Register 当前的服务器是:{session.Scene.SceneConfigId}");
|
||||
}
|
||||
}
|
||||
15
Hotfix/Outer/Authentication/System/AccountSystem.cs
Normal file
15
Hotfix/Outer/Authentication/System/AccountSystem.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
namespace Fantasy.Authentication;
|
||||
|
||||
public class AccountDestroySystem : DestroySystem<Account>
|
||||
{
|
||||
protected override void Destroy(Account self)
|
||||
{
|
||||
self.Username = null;
|
||||
self.Password = null;
|
||||
self.CreateTime = 0;
|
||||
self.LoginTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
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 Fantasy.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))
|
||||
{
|
||||
// 这个1代表的是参数不完整。
|
||||
return (1, 0);
|
||||
}
|
||||
|
||||
// 检查账号是否应该在当前鉴权服务器中处理
|
||||
|
||||
var position = HashCodeHelper.MurmurHash3(userName) % self.AuthenticationCount;
|
||||
if (self.Position != position)
|
||||
{
|
||||
// 这个3代表的是当前账号不应该在这个鉴权服务器处理。
|
||||
return (3, 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、因为这个实体下面咱们可以挂载一个组件,这个组件的作用就是定时清理这个缓存。
|
||||
|
||||
// 问题
|
||||
// 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 (2, 0);
|
||||
}
|
||||
|
||||
return (0, account.Id);
|
||||
}
|
||||
|
||||
uint result = 0;
|
||||
accountCacheInfo = Entity.Create<AccountCacheInfo>(scene, true, true);
|
||||
account = await worldDateBase.First<Account>(d => d.Username == userName && d.Password == password);
|
||||
|
||||
if (account == null)
|
||||
{
|
||||
// 这个2代表的是该用户没有注册或者用户或密码错误
|
||||
result = 2;
|
||||
}
|
||||
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 (0, 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 1;
|
||||
}
|
||||
|
||||
// 检查账号是否应该在当前鉴权服务器中处理
|
||||
|
||||
var position = HashCodeHelper.MurmurHash3(username) % self.AuthenticationCount;
|
||||
if (self.Position != position)
|
||||
{
|
||||
// 这个3代表的是当前账号不应该在这个鉴权服务器处理。
|
||||
return 3;
|
||||
}
|
||||
|
||||
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 2;
|
||||
}
|
||||
|
||||
// 2、数据库查询该账号是否存在
|
||||
var worldDateBase = scene.World.DataBase;
|
||||
var isExist = await worldDateBase.Exist<Account>(d => d.Username == username);
|
||||
if (isExist)
|
||||
{
|
||||
// 这个2代表的是该用户已经存在。
|
||||
return 2;
|
||||
}
|
||||
|
||||
//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 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Hotfix/Outer/Authentication/System/AuthenticationHelper.cs
Normal file
66
Hotfix/Outer/Authentication/System/AuthenticationHelper.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
|
||||
namespace Fantasy.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using Fantasy;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Fantasy.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Fantasy;
|
||||
|
||||
namespace Fantasy.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Entitas.Interface;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Fantasy.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Entitas.Interface;
|
||||
|
||||
namespace Fantasy.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class
|
||||
C2G_CreateAddressableRequestHandler : MessageRPC<C2G_CreateAddressableRequest, G2C_CreateAddressableResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_CreateAddressableRequest request,
|
||||
G2C_CreateAddressableResponse response, Action reply)
|
||||
{
|
||||
var scene = session.Scene;
|
||||
// 1、首先要通过SceneConfig配置文件拿到进行注册Addressable协议的服务器
|
||||
// 实际开发的时候,可能会根据一些规则来选择不同的Map服务器。
|
||||
// 演示的例子里只有一个MapScene,所以我就拿第一个Map服务器进行通讯了。
|
||||
// 我这里仅是演示功能,不是一定要这样拿Map
|
||||
var sceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[0];
|
||||
// 2、使用Scene.NetworkMessagingComponent.CallInnerRoute方法跟Gate服务器进行通讯。
|
||||
// b,这个Id在sceneConfig.RouteId可以获取到。
|
||||
// 第二个参数是需要发送网络协议,这个协议在Fantasy/Examples/Config/ProtoBuf里的InnerBson或Inner文件定义。
|
||||
var responseAddressableId =
|
||||
(M2G_ResponseAddressableId)await scene.NetworkMessagingComponent.CallInnerRoute(sceneConfig.RouteId,
|
||||
new G2M_RequestAddressableId());
|
||||
// 3、给session添加一个AddressableRouteComponent组件,这个组件很重要、能否转发Addressable协议主要是通过这个。
|
||||
var addressableRouteComponent = session.AddComponent<AddressableRouteComponent>();
|
||||
// 4、拿到MapScene返回的AddressableId赋值给addressableRouteComponent.AddressableId。
|
||||
addressableRouteComponent.AddressableId = responseAddressableId.AddressableId;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2G_SendAddressableToMapHandler : Message<C2G_SendAddressableToMap>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_SendAddressableToMap message)
|
||||
{
|
||||
var addressableRouteComponent = session.GetComponent<AddressableRouteComponent>();
|
||||
|
||||
if (addressableRouteComponent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Gate发送一个Addressable消息给MAP
|
||||
|
||||
await session.Scene.NetworkMessagingComponent.SendAddressable(addressableRouteComponent.AddressableId,
|
||||
new G2M_SendAddressableMessage()
|
||||
{
|
||||
Tag = message.Tag
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
using Fantasy.Platform.Net;
|
||||
using Fantasy.Serialize;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2M_MoveToMapRequestHandler : AddressableRPC<Unit, C2M_MoveToMapRequest, M2C_MoveToMapResponse>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, C2M_MoveToMapRequest request, M2C_MoveToMapResponse response, Action reply)
|
||||
{
|
||||
// 1、首先要通过SceneConfig配置文件拿到MapScene的配置文件。
|
||||
// 这里Map[1]就是要发送的服务器,因为Map[0]是当前的Scene。
|
||||
var scene = unit.Scene;
|
||||
var mapSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[1];
|
||||
// 2、锁定Addressable防止在转移期间有消息发送过来。
|
||||
// LockAndRelease方法是先锁定Addressable消息后再销毁这个组件。
|
||||
// 注意:只有这个方法的销毁组件不会去Addressable里删除自己的位置信息。
|
||||
// 其他的任何销毁这个AddressableMessageComponent方法都会去Addressable里删除自己的位置信息。
|
||||
await unit.GetComponent<AddressableMessageComponent>().LockAndRelease();
|
||||
// 3、通过NetworkMessagingComponent发送内部消息给Map的Scene。
|
||||
var sendResponse = await scene.NetworkMessagingComponent.CallInnerRoute(mapSceneConfig.RouteId,
|
||||
new M2M_SendUnitRequest()
|
||||
{
|
||||
Unit = unit
|
||||
});
|
||||
if (sendResponse.ErrorCode != 0)
|
||||
{
|
||||
Log.Error($"转移Unit到目标Map服务器失败 ErrorCode={sendResponse.ErrorCode}");
|
||||
return;
|
||||
}
|
||||
// 这个Unit已经转移到另外的Map服务器了,所以就不需要这个组件了。
|
||||
unit.Dispose();
|
||||
Log.Debug("转移Unit到目标Map服务器成功");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2M_TestMessageHandler : Addressable<Unit, C2M_TestMessage>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, C2M_TestMessage message)
|
||||
{
|
||||
Log.Debug($"C2M_TestMessageHandler = {message.Tag} Scene:{unit.Scene.Scene.SceneConfigId}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2M_TestRequestHandler : AddressableRPC<Unit, C2M_TestRequest, M2C_TestResponse>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, C2M_TestRequest request, M2C_TestResponse response, Action reply)
|
||||
{
|
||||
Log.Debug($"Receive C2M_TestRequest Tag = {request.Tag}");
|
||||
response.Tag = "Hello M2C_TestResponse";
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class G2M_SendAddressableMessageHandler : Addressable<Unit, G2M_SendAddressableMessage>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, G2M_SendAddressableMessage message)
|
||||
{
|
||||
Log.Debug($"收到Gate发送来的Addressable消息 message:{message.Tag}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class M2M_SendUnitRequestHandler : RouteRPC<Scene, M2M_SendUnitRequest, M2M_SendUnitResponse>
|
||||
{
|
||||
protected override async FTask Run(Scene scene, M2M_SendUnitRequest request, M2M_SendUnitResponse response, Action reply)
|
||||
{
|
||||
var requestUnit = request.Unit;
|
||||
// 反序列化Unit,把Unit注册到框架中
|
||||
requestUnit.Deserialize(scene);
|
||||
// 解锁这个Unit的Addressable消息,解锁后,Gate上缓存的消息会发送到这里。
|
||||
// 由于AddressableMessageComponent不支持存数据库,所以在发送Unit的时候,会自动把这个给忽略掉。
|
||||
// 所以需要再次手动的添加下才可以。
|
||||
await requestUnit.AddComponent<AddressableMessageComponent>().UnLock("M2M_SendUnitRequestHandler");
|
||||
Log.Debug($"传送完成 {scene.SceneConfigId}");
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy.Gate;
|
||||
|
||||
public sealed class C2G_TestMessageHandler : Message<C2G_TestMessage>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_TestMessage message)
|
||||
{
|
||||
Log.Debug($"Receive C2G_TestMessage Tag={message.Tag}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy.Gate;
|
||||
|
||||
public sealed class C2G_TestRequestHandler : MessageRPC<C2G_TestRequest, G2C_TestResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_TestRequest request, G2C_TestResponse response, Action reply)
|
||||
{
|
||||
Log.Debug($"Receive C2G_TestRequest Tag = {request.Tag}");
|
||||
response.Tag = "Hello G2C_TestResponse";
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy.Gate;
|
||||
|
||||
public sealed class C2G_TestRequestPushMessageHandler : Message<C2G_TestRequestPushMessage>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_TestRequestPushMessage message)
|
||||
{
|
||||
// 因为没有服务器的相关的逻辑,所以制作了一个协议来触发服务器发送消息给客户端的环境。
|
||||
// 使用当前会话的Session.Send发送消息给客户端。
|
||||
// 如果需要群发,你可以用一个容器保存起来,发送的时候遍历这个容器调用Send方法就可以了。
|
||||
session.Send(new G2C_PushMessage()
|
||||
{
|
||||
Tag = "Hi G2C_PushMessage"
|
||||
});
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2Chat_TestRPCRoamingRequestHandler : RoamingRPC<Terminus, C2Chat_TestRPCRoamingRequest, Chat2C_TestRPCRoamingResponse>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Chat_TestRPCRoamingRequest request, Chat2C_TestRPCRoamingResponse response, Action reply)
|
||||
{
|
||||
Log.Debug($"C2Chat_TestRPCRoamingRequestHandler message:{request.Tag} SceneType:{terminus.Scene.SceneType} SceneId:{terminus.Scene.RuntimeId}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Chat_TestRoamingMessageHandler : Roaming<Terminus, C2Chat_TestRoamingMessage>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Chat_TestRoamingMessage message)
|
||||
{
|
||||
Log.Debug($"C2Chat_TestRoamingMessageHandler message:{message.Tag} SceneType:{terminus.Scene.SceneType} SceneId:{terminus.Scene.RuntimeId}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Chat_TestSendMapMessageHandler : Roaming<Terminus, C2Chat_TestSendMapMessage>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Chat_TestSendMapMessage message)
|
||||
{
|
||||
terminus.Send(RoamingType.MapRoamingType, new Chat2M_TestMessage()
|
||||
{
|
||||
Tag = "Hi Inner Roaming Message!"
|
||||
});
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Platform.Net;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2G_ConnectRoamingRequestHandler : MessageRPC<C2G_ConnectRoamingRequest, G2C_ConnectRoamingResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_ConnectRoamingRequest request, G2C_ConnectRoamingResponse response, Action reply)
|
||||
{
|
||||
// 给session创建一个漫游功能。
|
||||
// 这个功能很重要,这个组件是整个Roaming系统最核心的组件,这个组件会处理Roaming协议
|
||||
// 这个功能会处理Roaming协议,所以创建这个是必须的。
|
||||
// CreateRoaming需要支持三个参数:
|
||||
// roamingId:这个参数是RoamingId,RoamingId是Roaming的唯一标识,不能重复。
|
||||
// 指定了这个RoamingId后,服务器其他漫游终端的Id会是你设置的RoamingId。
|
||||
// 这样操作方便统一管理漫游协议。
|
||||
// 一般这个RoamingId是一个角色的Id,这样方便管理。
|
||||
// isAutoDispose:是否在Session断开的时候自动断开漫游功能。
|
||||
// delayRemove:如果开启了自定断开漫游功能需要设置一个延迟多久执行断开。
|
||||
// 这里没有角色的Id,所以这里使用1来代替。
|
||||
// isAutoDispose我选择自动断开,这个断开的时机是Session断开后执行。
|
||||
// delayRemove断开漫游功能后,Session会自动断开,所以这里设置延迟1000毫秒执行断开。
|
||||
// 这里创建的漫游功能会自动处理Roaming协议,所以不需要手动处理Roaming协议。
|
||||
var roaming = session.CreateRoaming(1,true,1000);
|
||||
// 通过SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[0]拿到Map场景的配置信息
|
||||
// 如果需要协议漫游其他Scene可以在配置中查找要漫游的服务器。
|
||||
// 可以同时漫游多个Scene,但每个Scene的漫游都有一个固定的类型,不能重复。
|
||||
var mapConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[0];
|
||||
// 通过RoamingComponent.Link(session, mapConfig, 1, 1)链接Map场景
|
||||
// 第一个参数是Session,第二个参数是Map场景的配置信息,第三个参数是Map场景的RouteId,第四个参数是Map场景的RoamingType。
|
||||
// 这个RoamingType是通过RoamingType.Config文件中定义的。
|
||||
// RouteType.Config文件位置在你定义的网络文件协议文件夹下。如果找不到RoamingType.Config文件,可以运行下导出协议工具导出一个协议后会自动创建。
|
||||
// 该示例工程下文件位置在Config/NetworkProtocol/RoamingType.Config
|
||||
// 执行完后漫游会自动把Session绑定到Map场景上。
|
||||
// 后面发送该类型的消息到Session上会自动转发给Map场景。
|
||||
var linkResponse = await roaming.Link(session, mapConfig, RoamingType.MapRoamingType);
|
||||
if (linkResponse != 0)
|
||||
{
|
||||
response.ErrorCode = linkResponse;
|
||||
return;
|
||||
}
|
||||
// 同样,你可以创建多个漫游的场景,但每个场景的RouteId和RoamingType不能重复。
|
||||
// 这里创建Chat场景的漫游。
|
||||
var chatConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Chat)[0];
|
||||
linkResponse = await roaming.Link(session, chatConfig, RoamingType.ChatRoamingType);
|
||||
if (linkResponse != 0)
|
||||
{
|
||||
response.ErrorCode = linkResponse;
|
||||
return;
|
||||
}
|
||||
// 如果你觉的每次创建一个场景的漫游都麻烦,你可以利用RoamingType.RoamingTypes遍历创建。
|
||||
// 但这样的会把你在RoamingType.Config定义的都创建出来
|
||||
foreach (var roamingType in RoamingType.RoamingTypes)
|
||||
{
|
||||
// 这里添加roaming.Link的方法进行创建。
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Map_PushMessageToClientHandler : Roaming<Terminus, C2Map_PushMessageToClient>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Map_PushMessageToClient message)
|
||||
{
|
||||
terminus.Send(new Map2C_PushMessageToClient()
|
||||
{
|
||||
Tag = message.Tag
|
||||
});
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2Map_TestRoamingMessageHandler : Roaming<Terminus, C2Map_TestRoamingMessage>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Map_TestRoamingMessage message)
|
||||
{
|
||||
Log.Debug($"C2Map_TestRoamingMessageHandler message:{message.Tag} SceneType:{terminus.Scene.SceneType} SceneId:{terminus.Scene.RuntimeId}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Platform.Net;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2Map_TestTransferRequestHandler : RoamingRPC<Terminus, C2Map_TestTransferRequest, Map2C_TestTransferResponse>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, C2Map_TestTransferRequest request, Map2C_TestTransferResponse response, Action reply)
|
||||
{
|
||||
Log.Debug($"C2Map_TestTransferRequestHandler1 terminus:{terminus.RuntimeId}");
|
||||
var mapConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[1];
|
||||
response.ErrorCode = await terminus.StartTransfer(mapConfig.RouteId);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Roaming;
|
||||
using Fantasy.Roaming;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class Chat2M_TestMessageHandler : Roaming<Terminus, Chat2M_TestMessage>
|
||||
{
|
||||
protected override async FTask Run(Terminus terminus, Chat2M_TestMessage message)
|
||||
{
|
||||
Log.Debug($"Chat2M_TestMessageHandler message:{message.Tag} SceneType:{terminus.Scene.SceneType} SceneId:{terminus.Scene.RuntimeId}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Chat_TestMessageHandler : Route<ChatUnit, C2Chat_TestMessage>
|
||||
{
|
||||
protected override async FTask Run(ChatUnit entity, C2Chat_TestMessage message)
|
||||
{
|
||||
Log.Debug($"C2Chat_TestMessageHandler.c2Chat_TestMessage: {message}");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Chat_TestMessageRequestHandler : RouteRPC<ChatUnit, C2Chat_TestMessageRequest, Chat2C_TestMessageResponse>
|
||||
{
|
||||
protected override async FTask Run(ChatUnit entity, C2Chat_TestMessageRequest request, Chat2C_TestMessageResponse response, Action reply)
|
||||
{
|
||||
Log.Debug($"C2Chat_TestMessageRequestHandler request = {request}");
|
||||
response.Tag = "Hello RouteRPC";
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2Chat_TestRequestPushMessageHandler : Route<ChatUnit, C2Chat_TestRequestPushMessage>
|
||||
{
|
||||
protected override async FTask Run(ChatUnit chatUnit, C2Chat_TestRequestPushMessage message)
|
||||
{
|
||||
// 因为没有服务器的相关的逻辑,所以制作了一个协议来触发服务器发送消息给客户端的环境。
|
||||
// 使用当前Scene.NetworkMessagingComponent.SendInnerRoute发送消息给客户端。
|
||||
// 只需要把消息发送给创建链接的Gate上就会自动转发消息到客户端上。
|
||||
// 因为chatUnit.GateRouteId是在G2Chat_CreateRouteRequestHandler方法里记录的所以直接使用这个就可以了。
|
||||
|
||||
chatUnit.Scene.NetworkMessagingComponent.SendInnerRoute(chatUnit.GateRouteId, new Chat2C_PushMessage()
|
||||
{
|
||||
Tag = "Hi Route Chat2C_PushMessage"
|
||||
});
|
||||
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2G_CreateChatRouteRequestHandler : MessageRPC<C2G_CreateChatRouteRequest, G2C_CreateChatRouteResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_CreateChatRouteRequest request, G2C_CreateChatRouteResponse response, Action reply)
|
||||
{
|
||||
// 首先需要找到一个需要建立Route的Scene的SceneConfig。
|
||||
// 例子演示的连接的ChatScene,所以这里我通过SceneConfigData拿到这个SceneConfig。
|
||||
// 如果是其他Scene,用法跟这个没有任何区别。
|
||||
var chatSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Chat)[0];
|
||||
// 通过chatSceneConfig拿到这个Scene的RouteId
|
||||
var chatRouteId = chatSceneConfig.RouteId;
|
||||
// 通过Scene拿到当前Scene的NetworkMessagingComponent。
|
||||
// NetworkMessagingComponent是服务器之间通讯的唯一手段。
|
||||
var networkMessagingComponent = session.Scene.NetworkMessagingComponent;
|
||||
// 通过CallInnerRoute方法发送一个RPC消息给ChatScene上。
|
||||
// 任何一个实体的RunTimeId都可以做为RouteId使用。
|
||||
// 所以这个传递了一个session.RunTimeId,是方便Chat发送消息回Gate上。
|
||||
var routeResponse = (Chat2G_CreateRouteResponse)await networkMessagingComponent.CallInnerRoute(chatRouteId,
|
||||
new G2Chat_CreateRouteRequest()
|
||||
{
|
||||
GateRouteId = session.RouteId
|
||||
});
|
||||
if (routeResponse.ErrorCode != 0)
|
||||
{
|
||||
// 如果ErrorCode不是0表示请求的协议发生错误,应该提示给客户端。
|
||||
// 这里就不做这个了。
|
||||
return;
|
||||
}
|
||||
// 要实现Route协议的转发,需要给Session添加一个RouteComponent,这个非常重要。
|
||||
var routeComponent = session.AddComponent<RouteComponent>();
|
||||
// 需要再Examples/Config/NetworkProtocol/RouteType.Config里添加一个ChatRoute
|
||||
// 然后点击导表工具,会自动生成一个RouteType.cs文件。
|
||||
// 使用你定义的ChatRoute当routeType的参数传递进去。
|
||||
// routeResponse会返回一个ChatRouteId,这个就是Chat的RouteId。
|
||||
routeComponent.AddAddress((int)RouteType.ChatRoute,routeResponse.ChatRouteId);
|
||||
// 这些操作完成后,就完成了Route消息的建立。
|
||||
// 后面可以直接发送Route消息通过Gate自动中转给Chat了。
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Network.Route;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2G_CreateSubSceneAddressableRequestHandler : MessageRPC<C2G_CreateSubSceneAddressableRequest, G2C_CreateSubSceneAddressableResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_CreateSubSceneAddressableRequest request, G2C_CreateSubSceneAddressableResponse response, Action reply)
|
||||
{
|
||||
var scene = session.Scene;
|
||||
var subSceneRouteId = session.GetComponent<GateSubSceneFlagComponent>().SubSceneRouteId;
|
||||
// 1、向SubScene请求AddressableId
|
||||
var responseAddressableId = (SubScene2G_AddressableIdResponse)await scene.NetworkMessagingComponent.CallInnerRoute(subSceneRouteId, new G2SubScene_AddressableIdRequest());
|
||||
// 2、给session添加一个AddressableRouteComponent组件,这个组件很重要、能否转发Addressable协议主要是通过这个。
|
||||
var addressableRouteComponent = session.AddComponent<AddressableRouteComponent>();
|
||||
// 3、拿到SubScene返回的AddressableId赋值给addressableRouteComponent.AddressableId。
|
||||
addressableRouteComponent.AddressableId = responseAddressableId.AddressableId;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
using Fantasy.Platform.Net;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public sealed class C2G_CreateSubSceneRequestHandler : MessageRPC<C2G_CreateSubSceneRequest, G2C_CreateSubSceneResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_CreateSubSceneRequest request, G2C_CreateSubSceneResponse response, Action reply)
|
||||
{
|
||||
var scene = session.Scene;
|
||||
var sceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Map)[0];
|
||||
var createSubSceneResponse = (M2G_CreateSubSceneResponse)await scene.NetworkMessagingComponent.CallInnerRoute(sceneConfig.RouteId, new G2M_CreateSubSceneRequest());
|
||||
|
||||
if (createSubSceneResponse.ErrorCode != 0)
|
||||
{
|
||||
// 创建SubScene失败。
|
||||
response.ErrorCode = createSubSceneResponse.ErrorCode;
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录下这个RouteId,以便后续的消息转发。
|
||||
session.AddComponent<GateSubSceneFlagComponent>().SubSceneRouteId = createSubSceneResponse.SubSceneRouteId;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2G_SendToSubSceneMessageHandler : Message<C2G_SendToSubSceneMessage>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_SendToSubSceneMessage message)
|
||||
{
|
||||
var subSceneRouteId = session.GetComponent<GateSubSceneFlagComponent>().SubSceneRouteId;
|
||||
session.Scene.NetworkMessagingComponent.SendInnerRoute(subSceneRouteId, new G2SubScene_SentMessage()
|
||||
{
|
||||
Tag = "Hi SubScene",
|
||||
});
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2SubScene_TestDisposeMessageHandler : Addressable<Unit, C2SubScene_TestDisposeMessage>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, C2SubScene_TestDisposeMessage message)
|
||||
{
|
||||
var unitScene = unit.Scene;
|
||||
var unitSceneSceneType = unitScene.SceneType;
|
||||
unitScene.Dispose();
|
||||
Log.Debug($"{unitSceneSceneType} {unitScene.RuntimeId} is Dispose!");
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace Fantasy;
|
||||
|
||||
public class C2SubScene_TestMessageHandler : Addressable<Unit, C2SubScene_TestMessage>
|
||||
{
|
||||
protected override async FTask Run(Unit unit, C2SubScene_TestMessage message)
|
||||
{
|
||||
Log.Debug($"C2M_TestMessageHandler = {message.Tag} SceneType:{unit.Scene.SceneType} {unit.Scene.GetComponent<SubSceneTestComponent>() == null}");
|
||||
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// using Fantasy.Async;
|
||||
// using Fantasy.Network;
|
||||
// using Fantasy.Network.Interface;
|
||||
//
|
||||
// namespace Fantasy.Gate;
|
||||
//
|
||||
// public class C2G_LoginMessageHandler : Message<C2G_TestMessage>
|
||||
// {
|
||||
// protected override async FTask Run(Session session, C2G_TestMessage message)
|
||||
// {
|
||||
// Log.Debug($"Receive C2G_TestMessage Tag={message.Tag}");
|
||||
// await FTask.CompletedTask;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,33 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Gate;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public sealed class C2G_GetAccountInfoRequestHandler : MessageRPC<C2G_GetAccountInfoRequest, G2C_GetAccountInfoResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_GetAccountInfoRequest request, G2C_GetAccountInfoResponse response, Action reply)
|
||||
{
|
||||
var gameAccountFlagComponent = session.GetComponent<GameAccountFlagComponent>();
|
||||
|
||||
if (gameAccountFlagComponent == null)
|
||||
{
|
||||
// 表示不应该访问这个接口,要先访问登录的接口。
|
||||
// response.ErrorCode = 1;
|
||||
session.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
GameAccount account = gameAccountFlagComponent.Account;
|
||||
|
||||
if (account == null)
|
||||
{
|
||||
// 表示这个Account已经被销毁过了。不是咱们想要的了
|
||||
}
|
||||
|
||||
response.GameAccountInfo = account.GetGameAccountInfo();
|
||||
await FTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
102
Hotfix/Outer/Gate/Handler/Outer/C2G_LoginRequestHandler.cs
Normal file
102
Hotfix/Outer/Gate/Handler/Outer/C2G_LoginRequestHandler.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Gate.System;
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Gate;
|
||||
using Fantasy.Network;
|
||||
using Fantasy.Network.Interface;
|
||||
|
||||
#pragma warning disable CS8604 // Possible null reference argument.
|
||||
|
||||
namespace System.Gate.Handler;
|
||||
|
||||
public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_LoginResponse>
|
||||
{
|
||||
protected override async FTask Run(Session session, C2G_LoginRequest request, G2C_LoginResponse response,
|
||||
Action reply)
|
||||
{
|
||||
if (string.IsNullOrEmpty(request.ToKen))
|
||||
{
|
||||
// 1、客户端漏传了 response.ErrorCode = 1;
|
||||
// 2、恶意攻击导致的 session.Dispose();
|
||||
session.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var scene = session.Scene;
|
||||
|
||||
if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId))
|
||||
{
|
||||
// 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。
|
||||
session.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
// 在缓存中检查该账号是否存在
|
||||
var gameAccountManageComponent = scene.GetComponent<GameAccountManageComponent>();
|
||||
Log.Debug("检查账号是否在缓存中");
|
||||
if (!gameAccountManageComponent.TryGet(accountId, out var account))
|
||||
{
|
||||
// 首先要先到数据库中查询是否有这个账号
|
||||
account = await GameAccountHelper.LoadDataBase(scene, accountId);
|
||||
// 如果有的话,就直接加入在缓存中就可以了
|
||||
if (account == null)
|
||||
{
|
||||
Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中");
|
||||
// 如果没有,就要创建一个新的并且保存到数据库。
|
||||
// 如果不存在,表示这是一个新的账号,需要创建一下这个账号。
|
||||
account = await GameAccountFactory.Create(scene, accountId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("检查到账号在数据库中");
|
||||
}
|
||||
|
||||
Log.Debug("把当前账号添加到缓存中");
|
||||
// 把创建完成的Account放入到缓存中
|
||||
gameAccountManageComponent.Add(account);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("检测到当前账号已经在缓存中了");
|
||||
// 如果有延迟下线的计划任务,那就先取消一下。
|
||||
account.CancelTimeout();
|
||||
// 如果在Gate的缓存中已经存在了该账号那只能以下几种可能:
|
||||
// 1、同一客户端发送了重复登录的请求数据。
|
||||
// 2、客户端经历的断线然后又重新连接到这个服务器上了(断线重连)。
|
||||
// 3、多个客户端同时登录了这个账号(顶号)。
|
||||
|
||||
if (session.RuntimeId == account.SessionRunTimeId)
|
||||
{
|
||||
// 如果执行到这里,说明是客户端发送了多次登录的请求,这样的情况下,直接返回就可以了,不需要做任何操作。
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug("检测到当前账号的Session不是同一个");
|
||||
if (scene.TryGetEntity<Session>(account.SessionRunTimeId, out var oldSession))
|
||||
{
|
||||
Log.Debug("当前账号的Session在当前的系统中,所以需要发送一个重复登录的命令,并且要断开这个Session");
|
||||
// 如果这个Session在当前框架中可以查询到。
|
||||
// 那表示就是当前的会话还是在存在的,有如下几个可能:
|
||||
// 1、客户端断线重连,要给这个Session发送一个消息,通知它有人登录了。
|
||||
// 2、其他的客户端登录了这个账号,要给这个Session发送一个消息,通知它有人登录了。
|
||||
|
||||
var gameAccountFlagComponent = oldSession.GetComponent<GameAccountFlagComponent>();
|
||||
gameAccountFlagComponent.AccountID = 0;
|
||||
gameAccountFlagComponent.Account = null;
|
||||
// 给客户端发送一个重复登录的消息,如果当前客户端是自己上次登录的,发送也不会收到。
|
||||
oldSession.Send(new G2C_RepeatLogin());
|
||||
// 给当前Session做一个定时销毁的任务,因为不做这个定时销毁,直接销毁的话,有可能消息还没有发送过去就销毁了
|
||||
oldSession.SetTimeout(3000);
|
||||
}
|
||||
}
|
||||
|
||||
// 给当前Session添加一个组件,当Session销毁的时候会销毁这个组件。
|
||||
var accountFlagComponent = session.AddComponent<GameAccountFlagComponent>();
|
||||
accountFlagComponent.AccountID = accountId;
|
||||
accountFlagComponent.Account = account;
|
||||
|
||||
account.SessionRunTimeId = session.RuntimeId;
|
||||
response.GameAccountInfo = account.GetGameAccountInfo();
|
||||
Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}");
|
||||
}
|
||||
}
|
||||
30
Hotfix/Outer/Gate/System/GameAccount/GameAccountFactory.cs
Normal file
30
Hotfix/Outer/Gate/System/GameAccount/GameAccountFactory.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Entitas;
|
||||
using Fantasy.Gate;
|
||||
using Fantasy.Helper;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public static class GameAccountFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建一个新的GameAccount
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="aId">ToKen令牌传递过来的aId</param>
|
||||
/// <param name="isSaveDataBase">是否在创建的过程中保存到数据库</param>
|
||||
/// <returns></returns>
|
||||
public static async FTask<GameAccount> Create(Scene scene, long aId, bool isSaveDataBase = true)
|
||||
{
|
||||
var gameAccount = Entity.Create<GameAccount>(scene, aId, false, false);
|
||||
gameAccount.LoginTime = gameAccount.CreateTime = TimeHelper.Now;
|
||||
|
||||
if (isSaveDataBase)
|
||||
{
|
||||
await gameAccount.SaveDataBase();
|
||||
}
|
||||
|
||||
return gameAccount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Gate;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public sealed class GameAccountFlagComponentDestroySystem : DestroySystem<GameAccountFlagComponent>
|
||||
{
|
||||
protected override void Destroy(GameAccountFlagComponent self)
|
||||
{
|
||||
if (self.AccountID != 0)
|
||||
{
|
||||
// 执行下线过程、并且要求在5分钟后完成缓存清理。也就是5分钟后会保存数据到数据库。
|
||||
// 由于5分钟太长了、咱们测试的时候,不方便测试,也可以把这个时间改短一些,比如10秒。
|
||||
GameAccountHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine();
|
||||
self.AccountID = 0;
|
||||
}
|
||||
|
||||
self.Account = null;
|
||||
}
|
||||
}
|
||||
114
Hotfix/Outer/Gate/System/GameAccount/GameAccountHelper.cs
Normal file
114
Hotfix/Outer/Gate/System/GameAccount/GameAccountHelper.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Gate.System;
|
||||
using Fantasy;
|
||||
using Fantasy.Async;
|
||||
using Fantasy.Gate;
|
||||
using Fantasy.Network;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public static class GameAccountHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 从数据库中读取GameAccount
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="accountId">账号Id</param>
|
||||
/// <returns></returns>
|
||||
public static async FTask<GameAccount?> LoadDataBase(Scene scene, long accountId)
|
||||
{
|
||||
var account = await scene.World.DataBase.First<GameAccount>(d => d.Id == accountId);
|
||||
if (account == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
account.Deserialize(scene);
|
||||
return account;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存账号到数据库中
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
public static async FTask SaveDataBase(this GameAccount self)
|
||||
{
|
||||
await self.Scene.World.DataBase.Save(self);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行该账号的断开逻辑,不要非必要不要使用这个接口,这个接口是内部使用。
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
public static async FTask Disconnect(this GameAccount self)
|
||||
{
|
||||
// 保存该账号信息到数据库中。
|
||||
await SaveDataBase(self);
|
||||
// 在缓存中移除自己,并且执行自己的Dispose方法。
|
||||
self.Scene.GetComponent<GameAccountManageComponent>().Remove(self.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 账号完整的断开逻辑,执行了这个接口后,该账号会完全下线。
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="accountId"></param>
|
||||
/// <param name="timeOut"></param>
|
||||
public static async FTask Disconnect(Scene scene, long accountId, int timeOut = 1000 * 60 * 3)
|
||||
{
|
||||
// 调用该方法有如下几种情况:
|
||||
// 1、客户端主动断开,如:退出游戏、切换账号、等。 客户端会主动发送一个协议给服务器通知服务器断开。
|
||||
// 2、客户端断断线 客户端不会主动发送一个协议给服务器,是由服务器的心跳来检测是否断开了。
|
||||
// 如果是心跳检测断开的Session,我怎么能拿到当前的这个账号来进行下线处理呢?
|
||||
// 通过给当前的Session挂载一个组件,当销毁这个Session时候呢,也会销毁这个组件。
|
||||
// 这样的话,是不是可以在登录的时候,给这个组件把AccountId存到这个组件呢?
|
||||
|
||||
// 要检查当前缓存中是否存在该账号的数据
|
||||
var gameAccountManageComponent = scene.GetComponent<GameAccountManageComponent>();
|
||||
if (!gameAccountManageComponent.TryGet(accountId, out var account))
|
||||
{
|
||||
// 如果缓存中没有、那表示已经下线或者根本不存在该账号,应该在打印一个警告,因为正常的情况下是不会出现的。
|
||||
Log.Warning($"GameAccountHelper Disconnect accountId : {accountId} not found");
|
||||
return;
|
||||
}
|
||||
// 为了防止逻辑的错误,加一个警告来排除下
|
||||
if (!scene.TryGetEntity<Session>(account.SessionRunTimeId, out var session))
|
||||
{
|
||||
// 如果没有找到对应的Session,那只有一种可能就是当前的链接会话已经断开了,一般的情况下也不会出现的,所以咱们也要打印一个警告。
|
||||
Log.Warning($"GameAccountHelper Disconnect accountId : {accountId} SessionRunTimeId : {account.SessionRunTimeId} not found");
|
||||
return;
|
||||
}
|
||||
// 如果不存在定时任务的组件,那就添加并设置定时任务
|
||||
if (account.IsTimeOutComponent())
|
||||
{
|
||||
// 如果已经存在了,那就表示当然已经有一个延时断开的任务了,那就不需要重复添加了
|
||||
return;
|
||||
}
|
||||
// 立即下线处理
|
||||
if (timeOut <= 0)
|
||||
{
|
||||
// 如果timeOut销毁或等会0的情况下,执行立即下线。
|
||||
await account.Disconnect();
|
||||
return;
|
||||
}
|
||||
// 设置延迟下线
|
||||
account.SetTimeout(timeOut, account.Disconnect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得GameAccountInfo
|
||||
/// </summary>
|
||||
/// <param name="self"></param>
|
||||
/// <returns></returns>
|
||||
public static GameAccountInfo GetGameAccountInfo(this GameAccount self)
|
||||
{
|
||||
// 其实可以不用每次都NEW一个新的GameAccountInfo
|
||||
// 可以在当前账号下创建一个GameAccountInfo,每次变动会提前通知这个GameAccountInfo
|
||||
// 又或者每次调用该方法的时候,把值重新赋值一下。
|
||||
|
||||
return new GameAccountInfo()
|
||||
{
|
||||
CreateTime = self.CreateTime,
|
||||
LoginTime = self.LoginTime
|
||||
};
|
||||
}
|
||||
}
|
||||
14
Hotfix/Outer/Gate/System/GameAccount/GameAccountSystem.cs
Normal file
14
Hotfix/Outer/Gate/System/GameAccount/GameAccountSystem.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Gate;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public sealed class GameAccountDestroySystem : DestroySystem<GameAccount>
|
||||
{
|
||||
protected override void Destroy(GameAccount self)
|
||||
{
|
||||
self.CreateTime = 0;
|
||||
self.LoginTime = 0;
|
||||
self.SessionRunTimeId = 0;
|
||||
}
|
||||
}
|
||||
50
Hotfix/Outer/Gate/System/GameAccountManageComponentSystem.cs
Normal file
50
Hotfix/Outer/Gate/System/GameAccountManageComponentSystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Gate;
|
||||
|
||||
namespace System.Gate.System;
|
||||
|
||||
public sealed class GameAccountManageComponentDestroySystem : DestroySystem<GameAccountManageComponent>
|
||||
{
|
||||
protected override void Destroy(GameAccountManageComponent self)
|
||||
{
|
||||
foreach (var (_, gameAccount) in self.Accounts)
|
||||
{
|
||||
gameAccount.Dispose();
|
||||
}
|
||||
|
||||
self.Accounts.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static class GameAccountManageComponentSystem
|
||||
{
|
||||
public static void Add(this GameAccountManageComponent self, GameAccount account)
|
||||
{
|
||||
self.Accounts.Add(account.Id, account);
|
||||
}
|
||||
|
||||
public static GameAccount? Get(this GameAccountManageComponent self, long accountId)
|
||||
{
|
||||
return self.Accounts.GetValueOrDefault(accountId);
|
||||
}
|
||||
|
||||
public static bool TryGet(this GameAccountManageComponent self, long accountId, out GameAccount? account)
|
||||
{
|
||||
return self.Accounts.TryGetValue(accountId, out account);
|
||||
}
|
||||
|
||||
public static void Remove(this GameAccountManageComponent self, long accountId, bool isDispose = true)
|
||||
{
|
||||
if (!self.Accounts.Remove(accountId, out var account))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDispose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
account.Dispose();
|
||||
}
|
||||
}
|
||||
65
Hotfix/Outer/Gate/System/JWT/GateJWTComponentSystem.cs
Normal file
65
Hotfix/Outer/Gate/System/JWT/GateJWTComponentSystem.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using Fantasy.Entitas.Interface;
|
||||
using Fantasy.Gate;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public sealed class GateJWTComponentAwakeSystem : AwakeSystem<GateJWTComponent>
|
||||
{
|
||||
protected override void Awake(GateJWTComponent self)
|
||||
{
|
||||
self.Awake();
|
||||
}
|
||||
}
|
||||
|
||||
public static class GateJWTComponentSystem
|
||||
{
|
||||
public static void Awake(this GateJWTComponent self)
|
||||
{
|
||||
var rsa = RSA.Create();
|
||||
rsa.ImportRSAPublicKey(Convert.FromBase64String(self.PublicKeyPem), 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 bool ValidateToken(this GateJWTComponent self, string token, out JwtPayload payload)
|
||||
{
|
||||
payload = null;
|
||||
|
||||
try
|
||||
{
|
||||
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||
jwtSecurityTokenHandler.ValidateToken(token, self.TokenValidationParameters, out _);
|
||||
payload = jwtSecurityTokenHandler.ReadJwtToken(token).Payload;
|
||||
return true;
|
||||
}
|
||||
catch (SecurityTokenInvalidAudienceException)
|
||||
{
|
||||
Console.WriteLine("验证受众失败!");
|
||||
return false;
|
||||
}
|
||||
catch (SecurityTokenInvalidIssuerException)
|
||||
{
|
||||
Console.WriteLine("验证发行者失败!");
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Hotfix/Outer/Gate/System/JWT/GateJWTHelper.cs
Normal file
36
Hotfix/Outer/Gate/System/JWT/GateJWTHelper.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using Fantasy;
|
||||
using Fantasy.Gate;
|
||||
|
||||
namespace System.Gate;
|
||||
|
||||
public static class GateJWTHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证令牌是否合法
|
||||
/// </summary>
|
||||
/// <param name="scene"></param>
|
||||
/// <param name="token">ToKen</param>
|
||||
/// <param name="accountId">如果验证成功会返回正常的AccountId</param>
|
||||
/// <returns>如果是True表示验证成功,False表示验证失败</returns>
|
||||
public static bool ValidateToken(Scene scene, string token, out long accountId)
|
||||
{
|
||||
if (!ValidateToken(scene, token, out JwtPayload payload))
|
||||
{
|
||||
// 如果令牌验证失败,表示当前令牌不合法、那就返回为false,让上层处理。
|
||||
accountId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果不等于当前Scene的ConfigId的话,把就表示该连接不应该连接到当前的Gate里。
|
||||
// 所以理应把当前连接关闭掉。
|
||||
var sceneId = Convert.ToInt64(payload["SceneId"]);
|
||||
accountId = Convert.ToInt64(payload["aId"]);
|
||||
return sceneId == scene.SceneConfigId;
|
||||
}
|
||||
|
||||
private static bool ValidateToken(Scene scene, string token, out JwtPayload payload)
|
||||
{
|
||||
return scene.GetComponent<GateJWTComponent>().ValidateToken(token, out payload);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user