任务14:后端-注册登录处理Handler与消息

7,8,9,10知识量还是很多的,新手要删除代码多做几次。另外,思路上是有主干的。

第一个:主要就是请求,消息指令,消息类型,消息体,接收消息返回handler

第二个:构建需要的ECS对象(Entity,Component,System)

第三个:顺顺Map,Gate,Realm服务。

有了这个骨干思维,就是在各方法体里实现需要的功能了。

第一个小山头最高点胜利就在眼前了哦!加油!有了前面打好的基础,现在就轻松了。

前面服务端创建的组件,在Program.cs添加

E:\ETCoreLandlords\Landlords_Server02_03\Server\App\Program.cs

//数据库组件
Game.Scene.AddComponent<DBComponent>();
Game.Scene.AddComponent<DBProxyComponent>();

Game.Scene.AddComponent<UserComponent>();
Game.Scene.AddComponent<SessionKeyComponent>();
Game.Scene.AddComponent<SessionUserComponent>();

在服务端添加两个Helper类 GateHelper.cs,RealmHelper.cs 看其它对象中怎么用的就明白,不多讲。\Server\Hotfix\Helper\

using ETModel;
using System.Net;

namespace ETHotfix
{
    public static class GateHelper
    {
        /// <summary>
        /// 验证Session是否绑定了玩家
        /// </summary>
        /// <param name="session"></param>
        /// <returns></returns>
        public static bool SignSession(Session session)
        {
            SessionUserComponent sessionUser = session.GetComponent<SessionUserComponent>();
            if (sessionUser == null || Game.Scene.GetComponent<UserComponent>().Get(sessionUser.User.UserID) == null)
            {
                return false;
            }
            return true;
        } 
    }
}
using ETModel;
using System.Net;
using System.Threading.Tasks;

namespace ETHotfix
{
    public static class RealmHelper
    {
        private static int value;
        /// <summary>
        /// 随机生成区号 1~
        /// </summary>
        public static long GenerateId()
        {
            //随机获得GateId 1~2
            int randomGateAppId = RandomHelper.RandomNumber(0, StartConfigComponent.Instance.GateConfigs.Count) + 1;
            long time = TimeHelper.ClientNowSeconds();
            //1540 2822 75   时间为10位数
            //区号取第11位数
            return (randomGateAppId *100000000000 + time + ++value);
        }

        /// <summary>
        /// 生成指定大区的账号 参数为1以上的整数
        /// </summary>
        /// <param name="GateAppId"></param>
        /// <returns></returns>
        public static long GenerateId(int GateAppId)
        {
            long time = TimeHelper.ClientNowSeconds();
            //1540 2822 75   时间为10位数
            //区号取第11位数
            return (GateAppId * 100000000000 + time + ++value);
        }

        /// <summary>
        /// 查询账号所在大区 参数为1以上的整数
        /// </summary>
        public static int GetGateAppIdFromUserId(long userID)
        {
            return (int)(userID/100000000000);
        }
    }
}

在服务端添加与前端对应的消息指令,消息类型,消息体

前后端的消息指令、消息类型、消息体就是一样的。(上节你应该已经复制一份到服务端了吧)

如果服务端收到请求后出现 0001 消息没有处理:xxx数字编号 的报错,通常是前端请求后端接收请求指令编号不一致。

添加定义错误顾类型ErrorCode

\Server\ET.Core\Module\Message\ErrorCode.cs

//自定义错误
        public const int ERR_AccountAlreadyRegisted = 300001;
        public const int ERR_RepeatedAccountExist = 300002;
        public const int ERR_UserNotOnline = 300003;
        public const int ERR_CreateNewCharacter = 300007;
        public const int ERR_Success = 0;

在服务端添加与前端对应的请求处理Handler

对应前端请求A0001_RegisterHandler

using System;
using System.Net;
using ETModel;
using System.Collections.Generic;
using MongoDB.Bson;

namespace ETHotfix
{
    [MessageHandler(AppType.Realm)]
    public class A0001_RegisterHandler : AMRpcHandler<A0001_Register_C2R, A0001_Register_R2C>
    {
        protected override async ETTask Run(Session session, A0001_Register_C2R request,A0001_Register_R2C response, Action reply)
        {
            try
            {
                DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>();

                //验证假定的账号和密码
                List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}'}}");
                if (result.Count == 1)
                {

                    response.Error = ErrorCode.ERR_AccountAlreadyRegisted;
                    reply();
                    return;
                }
                else if (result.Count > 1)
                {
                    response.Error = ErrorCode.ERR_RepeatedAccountExist;
                    Log.Error("出现重复账号:" + request.Account);
                    reply();
                    return;
                }

                //生成玩家帐号 这里随机生成区号
                AccountInfo newAccount = ComponentFactory.CreateWithId<AccountInfo>(RealmHelper.GenerateId());
                newAccount.Account = request.Account;
                newAccount.Password = request.Password;
                await dbProxy.Save(newAccount);

                //生成玩家的用户信息 用户名在消息中提供
                UserInfo newUser = ComponentFactory.CreateWithId<UserInfo, string>(newAccount.Id, request.Account);
                await dbProxy.Save(newUser);

                reply();

                await ETTask.CompletedTask;
            }
            catch (Exception e)
            {
                ReplyError(response, e, reply);
            }
        }
    }
}

介绍下MongoDB组件的基础使用

框架的DBProxyComponent 提供了方法,Query<AccountInfo>(),Save()

可以找些MongoDB C#相关的资料多练习一下理解熟悉,这里只是简单介绍。

我们前面创建的每个数据实体类对应一个Mongo数据Collection,通过Studio 3T,可以直观的看到。

Query<查询目标>(查询条件)

Save(保存目标Collection对应的实例)

List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}'}}");

//生成玩家帐号 这里随机生成区号
AccountInfo newAccount = ComponentFactory.CreateWithId<AccountInfo>(RealmHelper.GenerateId());
newAccount.Account = request.Account;
newAccount.Password = request.Password;
await dbProxy.Save(newAccount);

//生成玩家的用户信息 用户名在消息中提供
UserInfo newUser = ComponentFactory.CreateWithId<UserInfo, string>(newAccount.Id, request.Account);
await dbProxy.Save(newUser);

大家经过观察可以发现,其实数据实体类的属性,对应了数据表中的字段!是不是开始有点明白了呢?

对应前端请求A0002_Login_C2R

A0002_LoginHandler.cs \Server\Hotfix\Landlords\Handler\Realm\A0002_LoginHandler.cs

查询数据验证账号,再向Gate请求A0006_GetLoginKey_R2G获得GateLoginKey

using System;
using System.Net;
using ETModel;
using System.Collections.Generic;
using MongoDB.Bson;

namespace ETHotfix
{
    [MessageHandler(AppType.Realm)]
    public class A0002_LoginHandler : AMRpcHandler<A0002_Login_C2R, A0002_Login_R2C>
    {
        protected override async ETTask Run(Session session, A0002_Login_C2R request,A0002_Login_R2C response,Action reply)
        {
            try
            {
                DBProxyComponent dbProxy = Game.Scene.GetComponent<DBProxyComponent>();
                //验证提交来的的账号和密码
                List<ComponentWithId> result = await dbProxy.Query<AccountInfo>($"{{Account:'{request.Account}',Password:'{request.Password}'}}");

                if (result.Count != 1)
                {
                    response.Error = ErrorCode.ERR_AccountOrPasswordError;
                    reply();
                    return;
                }

                AccountInfo account = (AccountInfo)result[0];

                int GateAppId;
                StartConfig config;
                //获取账号所在区服的AppId 索取登陆Key
                if (StartConfigComponent.Instance.GateConfigs.Count ==1)
                { //只有一个Gate服务器时当作AllServer配置处理
                    config = StartConfigComponent.Instance.StartConfig;
                }
                else
                { //有多个Gate服务器时当作分布式配置处理
                    GateAppId = RealmHelper.GetGateAppIdFromUserId(account.Id);
                    config = StartConfigComponent.Instance.GateConfigs[GateAppId - 1];
                }
                IPEndPoint innerAddress = config.GetComponent<InnerConfig>().IPEndPoint;
                Session gateSession = Game.Scene.GetComponent<NetInnerComponent>().Get(innerAddress);
                string outerAddress = config.GetComponent<OuterConfig>().Address2;

                A0006_GetLoginKey_G2R g2RGetLoginKey = (A0006_GetLoginKey_G2R)await gateSession.Call(new A0006_GetLoginKey_R2G() { UserID = account.Id });

                response.GateAddress = outerAddress;
                response.GateLoginKey = g2RGetLoginKey.GateLoginKey;
                reply();
            }
            catch (Exception e)
            {
                ReplyError(response, e, reply);
            }
        }
    }
}

对应前端请求A0003_LoginGate_C2G

A0003_LoginGateHanler.cs \Server\Hotfix\Landlords\Handler\Gate\A0003_LoginGateHanler.cs

完成登录验证,创建User对象,将新上线的User添加到容器中

session挂SessionUserComponent,体会下是不是就将此User绑定到了session上

session与user挂MailBoxComponent,就可以通过MailBoxComponent进行actor通信

using System;
using ETModel;
using System.Net;

namespace ETHotfix
{
    [MessageHandler(AppType.Gate)]
    public class A0003_LoginGateHanler : AMRpcHandler<A0003_LoginGate_C2G, A0003_LoginGate_G2C>
    {
        protected override async ETTask Run(Session session, A0003_LoginGate_C2G request,A0003_LoginGate_G2C response,Action reply)
        {
            try
            {
                SessionKeyComponent SessionKeyComponent = Game.Scene.GetComponent<SessionKeyComponent>();
                //获取玩家的永久Id
                long gateUserID = SessionKeyComponent.Get(request.GateLoginKey);

                //验证登录Key是否正确
                if (gateUserID == 0)
                {
                    response.Error = ErrorCode.ERR_ConnectGateKeyError;
                    //客户端提示:连接网关服务器超时
                    reply();
                    return;
                }

                //Key过期
                SessionKeyComponent.Remove(request.GateLoginKey);
                
                //gateUserID传参创建User
                User user = ComponentFactory.Create<User, long>(gateUserID);
                
                //将新上线的User添加到UserComponent容器中
                Game.Scene.GetComponent<UserComponent>().Add(user);
                user.AddComponent<MailBoxComponent>();
                
                //session挂SessionUser组件,user绑定到session上
                //session挂MailBox组件可以通过MailBox进行actor通信
                session.AddComponent<SessionUserComponent>().User = user;
                session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession);
                
                StartConfigComponent config = Game.Scene.GetComponent<StartConfigComponent>();
                //构建realmSession通知Realm服务器 玩家已上线
                //...
                
                //设置User的参数
                user.GateAppID = config.StartConfig.AppId;
                user.GateSessionID = session.InstanceId;
                user.ActorID = 0;

                //回复客户端
                response.UserID = user.UserID;
                reply();

                await ETTask.CompletedTask;
            }
            catch (Exception e)
            {
                ReplyError(response, e, reply);
            }
        }
    }
}

特别补充:上面LoginGateHandler中有这样两处

  • user.AddComponent<MailBoxComponent>();
  • session.AddComponent<MailBoxComponent, string>(MailboxType.GateSession);

这是将Gate上的session,user通过挂MailBoxComponent进行actor通信,后面还会遇到给Map上的Gamer挂
MailBoxComponent进行actor通信:
newgamer.AddComponent<MailBoxComponent>().AddLocation();

这里大家特注意一下要留下深一点的印象!!!
添加 MailBoxComponent时,有带MailboxType.GateSession参数和不带两种方式的,这是有重要用意和区别的,以后我们会讲到。

在A0002_LoginHandler.cs中向Gate请求获得GateLoginKey

A0006_GetLoginKey.cs \Server\Hotfix\Landlords\Handler\Gate\A0006_GetLoginKey.cs

using ETModel;
using System;

namespace ETHotfix
{
    [MessageHandler(AppType.Gate)]
    public class A0006_GetLoginKey : AMRpcHandler<A0006_GetLoginKey_R2G, A0006_GetLoginKey_G2R>
    {
        protected override async ETTask Run(Session session, A0006_GetLoginKey_R2G request, A0006_GetLoginKey_G2R response,Action reply)
        {
            try
            {
                long key = RandomHelper.RandInt64();
                Game.Scene.GetComponent<SessionKeyComponent>().Add(key, request.UserID);
                response.GateLoginKey = key;
                reply();

                await ETTask.CompletedTask;
            }
            catch (Exception e)
            {
                ReplyError(response, e, reply);
            }
        }
    }
}

GateLoginKey请求对应的消息定义

InnerMessage.proto 中增加GetLogKey请求的消息指令与类型

\Proto\InnerMessage.proto

/// <summary>
/// 斗地主内网消息
/// </summary>
message A0006_GetLoginKey_R2G // IRequest
{
    int32 RpcId = 90;
    int64 UserID = 1;
}

message A0006_GetLoginKey_G2R // IResponse
{
    int32 RpcId = 90;
    int32 Error = 91;
    string Message = 92;
    int64 GateLoginKey = 1;
}

更新 .proto 文件后,到unity中找到菜单点Tools>Ptoto2CS工具,确认下重新生成的消息体类与指令脚本。(同样复制改变的消息指令与消息类到服务端!)

课时有调整,可能你错过了重要的这课,不知道定义和自动生成消息体,消息指令,请前往这课学习 定义消息体字段,用protobuf工具生成消息体

前后端都运行起来吧,先注册一个账号,然后登录即可看到效果。

登录成功后只是获得一个返回,加了一个空界面。

下节我们继续讲如何实现进入游戏大厅。

注册,登录成功界面上都有红字提示,注意看红字,并没有做登录注册界面的切换效果。

 

posted @ 2023-02-02 10:17  Domefy  阅读(94)  评论(0)    收藏  举报