This commit is contained in:
2025-08-07 09:16:23 +08:00
parent c97bd0ab55
commit 70bfe43a80
34 changed files with 532 additions and 135 deletions

View File

@@ -26,9 +26,13 @@ public class C2A_LoginRequestHandler : MessageRPC<C2A_LoginRequest, A2C_LoginRes
if (result.ErrorCode != ErrorCode.Successful && result.AccountId == -1)
{
if (request.Region == 0)
{
request.Region = RegionDef.CN;
}
//开始注册账号
var regErrorCode =
await AuthenticationHelper.Register(session.Scene, request.Username, request.Password, "用户注册");
await AuthenticationHelper.Register(session.Scene, request.Username, request.Region, "用户注册");
if (regErrorCode != ErrorCode.Successful)
{
result.ErrorCode = regErrorCode;
@@ -55,7 +59,7 @@ public class C2A_LoginRequestHandler : MessageRPC<C2A_LoginRequest, A2C_LoginRes
var machineConfig = MachineConfigData.Instance.Get(processConfig.MachineId);
// 颁发一个ToKen令牌给客户端
response.ToKen = AuthenticationJwtHelper.GetToken(scene, result.AccountId,
$"{machineConfig.OuterIP}:{outerPort}", gateSceneConfig.Id);
$"{machineConfig.OuterIP}:{outerPort}", gateSceneConfig.Id, result.region);
}
response.ErrorCode = result.ErrorCode;

View File

@@ -11,5 +11,6 @@ public class AccountDestroySystem : DestroySystem<Account>
self.Password = null;
self.CreateTime = 0;
self.LoginTime = 0;
self.Region = 0;
}
}

View File

@@ -40,13 +40,13 @@ internal static class AuthenticationComponentSystem
Log.Info($"鉴权服务器启动成功Position:{self.Position} AuthenticationCount:{self.AuthenticationCount}");
}
internal static async FTask<(uint ErrorCode, long AccountId)> Login(this AuthenticationComponent self,
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);
return (ErrorCode.ErrArgs, 0, 0);
}
// 检查账号是否应该在当前鉴权服务器中处理
@@ -55,7 +55,7 @@ internal static class AuthenticationComponentSystem
{
// 这个3代表的是当前账号不应该在这个鉴权服务器处理。
// return (3, 0);
return (ErrorCode.ErrServer, 0);
return (ErrorCode.ErrServer, 0, 0);
}
var scene = self.Scene;
@@ -90,10 +90,10 @@ internal static class AuthenticationComponentSystem
if (account == null)
{
return (ErrorCode.ErrAccountOrPass, 0);
return (ErrorCode.ErrAccountOrPass, 0, 0);
}
return (ErrorCode.Successful, account.Id);
return (ErrorCode.Successful, account.Id, account.Region);
}
uint result = 0;
@@ -103,7 +103,7 @@ internal static class AuthenticationComponentSystem
if (account == null)
{
// 没有注册
return (ErrorCode.ErrAccountOrPass, -1); //返回-1用于判断是否需要自动注册
return (ErrorCode.ErrAccountOrPass, -1, 0); //返回-1用于判断是否需要自动注册
}
if (account.Password != password)
@@ -126,10 +126,10 @@ internal static class AuthenticationComponentSystem
if (result != 0)
{
return (result, 0);
return (result, 0, 0);
}
return (ErrorCode.Successful, account.Id);
return (ErrorCode.Successful, account.Id, account.Region);
}
}
@@ -138,14 +138,14 @@ internal static class AuthenticationComponentSystem
/// </summary>
/// <param name="self"></param>
/// <param name="username"></param>
/// <param name="password"></param>
/// <param name="region"></param>
/// <param name="source"></param>
internal static async FTask<uint> Register(this AuthenticationComponent self, string username, string password,
internal static async FTask<uint> Register(this AuthenticationComponent self, string username, int region,
string source)
{
// 1、检查传递的参数是否完整
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
if (string.IsNullOrEmpty(username))
{
// 这个1代表的是参数不完整。
return ErrorCode.ErrArgs;
@@ -187,7 +187,8 @@ internal static class AuthenticationComponentSystem
//3、执行到这里的话表示数据库或缓存没有该账号的注册信息需要咱们创建一个。
account = Entity.Create<Account>(scene, true, true);
account.Username = username;
account.Password = password;
account.Password = username;
account.Region = region;
account.CreateTime = TimeHelper.Now;
// 写入这个实体到数据中
await worldDateBase.Save(account);

View File

@@ -12,7 +12,8 @@ public static class AuthenticationHelper
/// <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)
public static async FTask<(uint ErrorCode, long AccountId, int region)> Login(Scene scene, string userName,
string password)
{
return await scene.GetComponent<AuthenticationComponent>().Login(userName, password);
}
@@ -22,12 +23,12 @@ public static class AuthenticationHelper
/// </summary>
/// <param name="scene"></param>
/// <param name="username">用户名</param>
/// <param name="password">用户密码</param>
/// <param name="region">注册地区</param>
/// <param name="source">注册的来源/原因</param>
/// <returns></returns>
public static async FTask<uint> Register(Scene scene, string username, string password, string source)
public static async FTask<uint> Register(Scene scene, string username, int region, string source)
{
return await scene.GetComponent<AuthenticationComponent>().Register(username, password, source);
return await scene.GetComponent<AuthenticationComponent>().Register(username, region, source);
}
/// <summary>

View File

@@ -29,24 +29,25 @@ public static class AuthenticationJwtComponentSystem
ValidateIssuer = true, // 验证发行者
ValidateAudience = true, // 验证受众
ValidateIssuerSigningKey = true, // 验证签名密钥
ValidIssuer = "Fantasy", // 有效的发行者
ValidAudience = "Fantasy", // 有效的受众
ValidIssuer = "NoBug", // 有效的发行者
ValidAudience = "NoBug", // 有效的受众
IssuerSigningKey = new RsaSecurityKey(rsa) // RSA公钥作为签名密钥
};
}
public static string GetToken(this AuthenticationJwtComponent self, long aId, string address, uint sceneId)
public static string GetToken(this AuthenticationJwtComponent self, long aId, string address, uint sceneId, int region)
{
var jwtPayload = new JwtPayload()
{
{ "aId", aId },
{ "Address", address },
{ "SceneId", sceneId }
{ "SceneId", sceneId },
{ "Region", region },
};
var jwtSecurityToken = new JwtSecurityToken(
issuer: "Fantasy",
audience: "Fantasy",
issuer: "NoBug",
audience: "NoBug",
claims: jwtPayload.Claims,
expires: DateTime.UtcNow.AddMilliseconds(3000),
signingCredentials: self.SigningCredentials);

View File

@@ -11,9 +11,10 @@ public static class AuthenticationJwtHelper
/// <param name="aId">AccountId</param>
/// <param name="address">目标服务器的地址</param>
/// <param name="sceneId">分配的Scene的Id</param>
/// <param name="region">账号所属地区</param>
/// <returns></returns>
public static string GetToken(Scene scene, long aId, string address, uint sceneId)
public static string GetToken(Scene scene, long aId, string address, uint sceneId, int region)
{
return scene.GetComponent<AuthenticationJwtComponent>().GetToken(aId, address, sceneId);
return scene.GetComponent<AuthenticationJwtComponent>().GetToken(aId, address, sceneId, region);
}
}

View File

@@ -0,0 +1,15 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network.Interface;
namespace NB.Chat;
public sealed class
C2Chat_SendMessageRequestHandler : RouteRPC<ChatUnit, C2Chat_SendMessageRequest, Caht2C_SendMessageResponse>
{
protected override async FTask Run(ChatUnit chatUnit, C2Chat_SendMessageRequest request,
Caht2C_SendMessageResponse response, Action reply)
{
ChatSceneHelper.Broadcast(chatUnit.Scene, request.Message);
}
}

View File

@@ -0,0 +1,29 @@
using Fantasy;
using Fantasy.Platform.Net;
namespace NB.Chat;
public static class ChatSceneHelper
{
/// <summary>
/// 广播消息
/// </summary>
/// <param name="scene"></param>
/// <param name="message"></param>
public static void Broadcast(Scene scene, string message)
{
//发送给所有Gate服务器让Gate转发给其他客户端
var gateConfigs = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Gate);
var sendMessage = new Chat2C_Message()
{
Message = message
};
var networkMessagingComponent = scene.NetworkMessagingComponent;
foreach (var config in gateConfigs)
{
//发送给Gate服务器转发消息
networkMessagingComponent.SendInnerRoute(config.RouteId, sendMessage);
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Net;
using Fantasy;
using Fantasy.Network;
using Fantasy.Platform.Net;
namespace NB.Game;
public static class GameSceneHelper
{
public static SceneConfig GetSceneConfig(Session session)
{
var gameSceneConfigs = SceneConfigData.Instance.GetSceneBySceneType(SceneType.Game);
return gameSceneConfigs.First();
}
}

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

@@ -3,6 +3,7 @@ using Fantasy.Async;
using Fantasy.Network;
using Fantasy.Network.Interface;
using Fantasy.Platform.Net;
using NB.Game;
namespace NB.Gate.Handler;
@@ -13,14 +14,11 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
{
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))
{
// 如果失败,表示肯定是恶意攻击、所以毫不犹疑,直接断开当前会话。
@@ -28,51 +26,51 @@ public sealed class C2G_LoginRequestHandler : MessageRPC<C2G_LoginRequest, G2C_L
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)
// 在缓存中检查该账号是否存在
var gateUnitManageComponent = scene.GetComponent<GateUnitManageComponent>();
if (!gateUnitManageComponent.TryGet(accountId, out var gateUnit))
{
// 如果ErrorCode不是0表示请求的协议发生错误应该提示给客户端。
// 这里就不做这个了。
response.ErrorCode = gameResponse.ErrorCode;
return;
gateUnit = gateUnitManageComponent.Add(session, accountId);
}
// 要实现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了。
if (gateUnit == null)
{
Log.Error("创建GateUnit失败");
session.Dispose();
return;
}
// 给当前Session添加一个组件当Session销毁的时候会销毁这个组件。
var accountFlagComponent = session.AddComponent<SessionPlayerComponent>();
accountFlagComponent.AccountID = accountId;
// account.SessionRunTimeId = session.RuntimeId;
// response.GameAccountInfo = account.GetGameAccountInfo();
response.ErrorCode = await GateLoginHelper.Online(gateUnit);
Log.Debug($"当前的Gate服务器:{session.Scene.SceneConfigId} accountId:{accountId}");
Log.Debug($"网关内网连接到游戏服成功 routerId={gameResponse.RoleRouteId}");
// var gameSceneConfig = GameSceneHelper.GetSceneConfig(session);
//
// // 通过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>();
// routeComponent.AddAddress(RouteType.GameRoute, gameResponse.RoleRouteId);
//
// // 给当前Session添加一个组件当Session销毁的时候会销毁这个组件。
// var accountFlagComponent = session.AddComponent<SessionPlayerComponent>();
// accountFlagComponent.AccountID = accountId;
}
}

View File

@@ -0,0 +1,75 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Network;
using NB.Game;
namespace NB.Gate;
public static class GateLoginHelper
{
/// <summary>
/// 网关通知其他服务器上线
/// </summary>
/// <param name="self"></param>
/// <param name="gateUnit"></param>
public static async FTask<uint> Online(GateUnit gateUnit)
{
if (gateUnit == null)
{
return ErrorCode.ErrArgs;
}
Session session = gateUnit.Session;
if (session == null)
{
return ErrorCode.ErrArgs;
}
var gateUnitSessionComponent = session.GetComponent<GateUnitSessionComponent>();
if (gateUnitSessionComponent == null)
{
gateUnitSessionComponent = session.AddComponent<GateUnitSessionComponent>();
}
var routeComponent = session.GetComponent<RouteComponent>();
if (routeComponent == null)
{
routeComponent = session.AddComponent<RouteComponent>();
}
gateUnitSessionComponent.AccountID = gateUnit.AccountID;
//安排服务器,并通知进入
var gameSceneConfig = GameSceneHelper.GetSceneConfig(session);
var gameRouteId = gameSceneConfig.RouteId;
//连接到游戏中心服
var gameResponse = (Game2G_EnterResponse)await session.Scene.NetworkMessagingComponent.CallInnerRoute(
gameRouteId, new G2Game_EnterRequest()
{
AccountId = gateUnit.AccountID,
GateRouteId = session.RuntimeId
});
if (gameResponse.ErrorCode != 0)
{
return ErrorCode.OnlineSceneFailed;
}
routeComponent.AddAddress(RouteType.GameRoute, gameResponse.RoleRouteId);
gateUnit.GameSceneRouteId = gameRouteId;
return ErrorCode.Successful;
}
/// <summary>
/// 网关通知其他服务器下线
/// </summary>
/// <param name="self"></param>
/// <param name="gateUnit"></param>
public static async FTask<uint> Offline(GateUnit gateUnit)
{
//通知服务器下线
return ErrorCode.Successful;
}
}

View File

@@ -0,0 +1,55 @@
using Fantasy;
using Fantasy.Async;
using Fantasy.Entitas;
using Fantasy.Network;
using Fantasy.Platform.Net;
using NB.Game;
namespace NB.Gate;
public static class GateUnitManageComponentSystem
{
public static GateUnit Add(this GateUnitManageComponent self, Session session, long accountId)
{
if (self.Units.TryGetValue(accountId, out var unit))
{
unit.Session = session;
return unit;
}
unit = Entity.Create<GateUnit>(self.Scene, accountId, true, true);
unit.AccountID = accountId;
unit.Session = session;
self.Units.Add(accountId, unit);
return unit;
}
public static GateUnit? Get(this GateUnitManageComponent self, long accountId)
{
return self.Units.GetValueOrDefault(accountId);
}
public static bool TryGet(this GateUnitManageComponent self, long accountId, out GateUnit? unit)
{
return self.Units.TryGetValue(accountId, out unit);
}
public static async FTask Remove(this GateUnitManageComponent self, long accountId, bool isDispose = true)
{
if (!self.Units.TryGetValue(accountId, out var unit)) return;
//通知其他服务器下线
var result = await GateLoginHelper.Offline(unit);
if (result != 0)
{
Log.Error($"通知其他服务器下线失败,错误码:{result}");
return;
}
Log.Info($"accountId:{accountId} 下线成功");
self.Units.Remove(accountId);
if (isDispose)
{
unit.Dispose();
}
}
}

View File

@@ -0,0 +1,17 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate;
public class GateUnitSessionComponentSystem : DestroySystem<GateUnitSessionComponent>
{
protected override void Destroy(GateUnitSessionComponent self)
{
var gateUnitManageComponent = self.Scene.GetComponent<GateUnitManageComponent>();
if (gateUnitManageComponent != null)
{
_ = gateUnitManageComponent.Remove(self.AccountID);
}
self.AccountID = 0;
}
}

View File

@@ -0,0 +1,16 @@
using Fantasy.Entitas.Interface;
namespace NB.Gate;
public class GateUnitDestroySystem : DestroySystem<GateUnit>
{
protected override void Destroy(GateUnit self)
{
self.AccountID = 0;
self.Kick = false;
}
}
public class GateUnitSystem
{
}

View File

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

View File

@@ -25,6 +25,8 @@ public class OnSceneCreate_Init : AsyncEventSystem<OnCreateScene>
}
case SceneType.Gate:
{
// 用于管理网关所有连接的组件
scene.AddComponent<GateUnitManageComponent>();
// 用于验证JWT是否合法的组件
scene.AddComponent<GateJWTComponent>();
break;