新增角色相关信息

This commit is contained in:
2025-07-28 23:45:46 +08:00
parent be33e12b35
commit 09bbae66d2
68 changed files with 452 additions and 662 deletions

View File

@@ -0,0 +1,75 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Helper;
using Fantasy.Network;
using Fantasy.Network.Interface;
using NB;
using NB.Gate;
namespace NB.Game;
public class G2Game_EnterRequestHandler : RouteRPC<Scene, G2Game_EnterRequest, Game2G_EnterResponse>
{
protected override async FTask Run(Scene scene, G2Game_EnterRequest request, Game2G_EnterResponse response,
Action reply)
{
Log.Debug("收到 G2Game_EnterRequestHandler");
var accountId = request.AccountId;
// 在缓存中检查该账号是否存在
var gameAccountManageComponent = scene.GetComponent<PlayerManageComponent>();
Log.Debug("检查账号是否在缓存中");
if (!gameAccountManageComponent.TryGet(accountId, out var account))
{
// 首先要先到数据库中查询是否有这个账号
account = await PlayerHelper.LoadDataBase(scene, accountId);
// 如果有的话,就直接加入在缓存中就可以了
if (account == null)
{
Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中");
// 如果没有,就要创建一个新的并且保存到数据库。
// 如果不存在,表示这是一个新的账号,需要创建一下这个账号。
account = await PlayerFactory.Create(scene, accountId);
account.Basic.Level = 99;
account.Basic.NickName = "王麻子";
account.Basic.Country = "cn";
account.Basic.Exp = 999;
account.Basic.Head = "xxx.png";
account.NeedSave = true;
}
else
{
Log.Debug("检查到账号在数据库中");
}
Log.Debug("把当前账号添加到缓存中");
// 把创建完成的Account放入到缓存中
gameAccountManageComponent.Add(account);
}
else
{
Log.Debug("检测到当前账号已经在缓存中了");
// 如果有延迟下线的计划任务,那就先取消一下。
account.CancelTimeout();
// 如果在Gate的缓存中已经存在了该账号那只能以下几种可能:
// 1、同一客户端发送了重复登录的请求数据。
// 2、客户端经历的断线然后又重新连接到这个服务器上了断线重连
// 3、多个客户端同时登录了这个账号顶号
if (request.GateRouteId == account.SessionRunTimeId)
{
// 如果执行到这里,说明是客户端发送了多次登录的请求,这样的情况下,直接返回就可以了,不需要做任何操作。
return;
}
}
account.LoginTime = TimeHelper.Now;
response.RoleRouteId = account.RuntimeId;
await FTask.CompletedTask;
}
}

View File

@@ -1,49 +0,0 @@
using Fantasy.Entitas.Interface;
namespace NB;
public sealed class RoleManagerComponentDestroySystem : DestroySystem<RoleManagerComponent>
{
protected override void Destroy(RoleManagerComponent self)
{
foreach (var (_, role) in self.Roles)
{
role.Dispose();
}
self.Roles.Clear();
}
}
public static class RoleManagerComponentSystem
{
public static void Add(this RoleManagerComponent self, Role account)
{
self.Roles.Add(account.Id, account);
}
public static Role Get(this RoleManagerComponent self, long accountId)
{
return self.Roles.GetValueOrDefault(accountId);
}
public static bool TryGet(this RoleManagerComponent self, long accountId, out Role account)
{
return self.Roles.TryGetValue(accountId, out account);
}
public static void Remove(this RoleManagerComponent self, long accountId, bool isDispose = true)
{
if (!self.Roles.Remove(accountId, out var account))
{
return;
}
if (!isDispose)
{
return;
}
account.Dispose();
}
}

View File

@@ -0,0 +1,6 @@
namespace NB.Game;
public class PlayerAutoSaveComponentSystem
{
}

View File

@@ -1,6 +1,6 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate;
namespace NB.Game;
public sealed class PlayerDestroySystem : DestroySystem<Player>
{

View File

@@ -2,8 +2,9 @@ using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Helper;
using NB.Game;
namespace NB.Gate;
namespace NB.Game;
public static class PlayerFactory
{

View File

@@ -1,9 +1,8 @@
using NB.Gate.System;
using Fantasy;
using Fantasy.Async;
using Fantasy.Network;
namespace NB.Gate;
namespace NB.Game;
public static class PlayerHelper
{

View File

@@ -1,6 +1,6 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate.System;
namespace NB.Game;
public sealed class PlayerManageComponentDestroySystem : DestroySystem<PlayerManageComponent>
{
@@ -27,7 +27,7 @@ public static class PlayerManageComponentSystem
return self.Players.GetValueOrDefault(accountId);
}
public static bool TryGet(this PlayerManageComponent self, long accountId, out Player? account)
public static bool TryGet(this PlayerManageComponent self, long accountId, out Player account)
{
return self.Players.TryGetValue(accountId, out account);
}

View File

@@ -0,0 +1,32 @@
// using Fantasy;
// using Fantasy.Async;
// using Fantasy.Network;
// using Fantasy.Network.Interface;
//
// namespace NB.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<PlayerFlagComponent>();
//
// if (gameAccountFlagComponent == null)
// {
// // 表示不应该访问这个接口,要先访问登录的接口。
// // response.ErrorCode = 1;
// session.Dispose();
// return;
// }
//
// Player account = gameAccountFlagComponent.Player;
//
// if (account == null)
// {
// // 表示这个Account已经被销毁过了。不是咱们想要的了
// }
//
// response.GameAccountInfo = account.GetGameAccountInfo();
// await FTask.CompletedTask;
// }
// }

View File

@@ -0,0 +1,78 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.Platform.Net;
namespace NB.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;
// Log.Info($"网关服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId} {scene.RouteId} {session.RouteId}");
if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId))
{
// 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。
session.Dispose();
return;
}
// 首先需要找到一个需要建立Route的Scene的SceneConfig。
// 例子演示的连接的ChatScene,所以这里我通过SceneConfigData拿到这个SceneConfig。
// 如果是其他Scene用法跟这个没有任何区别。
var gameSceneConfig = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Game)[0];
// 通过chatSceneConfig拿到这个Scene的RouteId
var gameRouteId = gameSceneConfig.RouteId;
//连接到游戏中心服
var gameResponse = (Game2G_EnterResponse)await session.Scene.NetworkMessagingComponent.CallInnerRoute(
gameRouteId, new G2Game_EnterRequest()
{
AccountId = accountId,
GateRouteId = session.RuntimeId
});
if (gameResponse.ErrorCode != 0)
{
// 如果ErrorCode不是0表示请求的协议发生错误应该提示给客户端。
// 这里就不做这个了。
response.ErrorCode = gameResponse.ErrorCode;
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(RouteType.GameRoute, gameResponse.RoleRouteId);
// 这些操作完成后就完成了Route消息的建立。
// 后面可以直接发送Route消息通过Gate自动中转给Chat了。
// 给当前Session添加一个组件当Session销毁的时候会销毁这个组件。
var accountFlagComponent = session.AddComponent<SessionPlayerComponent>();
accountFlagComponent.AccountID = accountId;
// account.SessionRunTimeId = session.RuntimeId;
// response.GameAccountInfo = account.GetGameAccountInfo();
Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}");
Log.Debug($"网关内网连接到游戏服成功 routerId={gameResponse.RoleRouteId}");
}
}

View File

@@ -1,32 +0,0 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network;
using Fantasy.Network.Interface;
namespace NB.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<PlayerFlagComponent>();
if (gameAccountFlagComponent == null)
{
// 表示不应该访问这个接口,要先访问登录的接口。
// response.ErrorCode = 1;
session.Dispose();
return;
}
Player account = gameAccountFlagComponent.Player;
if (account == null)
{
// 表示这个Account已经被销毁过了。不是咱们想要的了
}
response.GameAccountInfo = account.GetGameAccountInfo();
await FTask.CompletedTask;
}
}

View File

@@ -1,101 +0,0 @@
using NB.Gate.System;
using Fantasy;
using Fantasy.Async;
using Fantasy.Network;
using Fantasy.Network.Interface;
namespace NB.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;
// Log.Info($"网关服场景 {scene.Id} {scene.RouteId} {scene.SceneConfigId} {scene.RouteId} {session.RouteId}");
if (!GateJWTHelper.ValidateToken(scene, request.ToKen, out var accountId))
{
// 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。
session.Dispose();
return;
}
// 在缓存中检查该账号是否存在
var gameAccountManageComponent = scene.GetComponent<PlayerManageComponent>();
Log.Debug("检查账号是否在缓存中");
if (!gameAccountManageComponent.TryGet(accountId, out var account))
{
// 首先要先到数据库中查询是否有这个账号
account = await PlayerHelper.LoadDataBase(scene, accountId);
// 如果有的话,就直接加入在缓存中就可以了
if (account == null)
{
Log.Debug("检查到账号没有在数据库中,需要创建一个新的账号并且保存到数据库中");
// 如果没有,就要创建一个新的并且保存到数据库。
// 如果不存在,表示这是一个新的账号,需要创建一下这个账号。
account = await PlayerFactory.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<PlayerFlagComponent>();
gameAccountFlagComponent.AccountID = 0;
gameAccountFlagComponent.Player = null;
// 给客户端发送一个重复登录的消息,如果当前客户端是自己上次登录的,发送也不会收到。
oldSession.Send(new G2C_RepeatLogin());
// 给当前Session做一个定时销毁的任务因为不做这个定时销毁直接销毁的话有可能消息还没有发送过去就销毁了
oldSession.SetTimeout(3000);
}
}
// 给当前Session添加一个组件当Session销毁的时候会销毁这个组件。
var accountFlagComponent = session.AddComponent<PlayerFlagComponent>();
accountFlagComponent.AccountID = accountId;
accountFlagComponent.Player = account;
account.SessionRunTimeId = session.RuntimeId;
response.GameAccountInfo = account.GetGameAccountInfo();
Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}");
//连接到游戏中心服
}
}

View File

@@ -2,18 +2,18 @@ using Fantasy.Entitas.Interface;
namespace NB.Gate;
public sealed class PlayerFlagComponentDestroySystem : DestroySystem<PlayerFlagComponent>
public sealed class SessionPlayerComponentDestroySystem : DestroySystem<SessionPlayerComponent>
{
protected override void Destroy(PlayerFlagComponent self)
protected override void Destroy(SessionPlayerComponent self)
{
if (self.AccountID != 0)
{
// 执行下线过程、并且要求在5分钟后完成缓存清理。也就是5分钟后会保存数据到数据库。
// 由于5分钟太长了、咱们测试的时候不方便测试也可以把这个时间改短一些比如10秒。
PlayerHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine();
self.AccountID = 0;
// PlayerHelper.Disconnect(self.Scene, self.AccountID, 1000 * 60 * 5).Coroutine();
// self.AccountID = 0;
}
self.Player = null;
// self.Player = null;
}
}

View File

@@ -2,6 +2,7 @@
using Fantasy.Async;
using Fantasy.Event;
using NB.Authentication;
using NB.Game;
using NB.Gate;
namespace NB;
@@ -26,14 +27,12 @@ public class OnSceneCreate_Init : AsyncEventSystem<OnCreateScene>
{
// 用于验证JWT是否合法的组件
scene.AddComponent<GateJWTComponent>();
// 用于管理玩家的组件
scene.AddComponent<PlayerManageComponent>();
break;
}
case SceneType.Game:
{
//游戏服用于管理用户的组件
scene.AddComponent<RoleManagerComponent>();
//用于管理玩家的组件
scene.AddComponent<PlayerManageComponent>();
break;
}
}