从零开始搭建游戏服务器 第四节 MongoDB引入并实现注册登录

这里写目录标题

  • 前言
  • 正文
    • 添加依赖
    • 安装MongoDB
    • 添加MongoDB相关配置
    • 创建MongoContext类
    • 尝试初始化DB连接
    • 实现注册功能
    • 测试注册功能
    • 实现登录逻辑
    • 测试登录流程
  • 结语
  • 下节预告

前言

游戏服务器中, 很重要的一点就是如何保存玩家的游戏数据.
当一个服务端架构趋于稳定且功能全面, 开发者会发现服务端的业务开发基本就围绕着CRUD来展开,
即业务数据的创建 \ 查找 \ 更新 \ 删除.
本节内容我们就将MongoDB作为持久化数据库引入项目中.

正文

添加依赖

在build.gradle中添加依赖

implementation 'org.springframework.data:spring-data-mongodb:4.2.4'
implementation 'org.mongodb:mongodb-driver-sync:5.0.0'

安装MongoDB

MongoDB的安装步骤我就不一一解释了,
读者可以选择使用docker快速创建一个MongoDB容器,
也可以使用MongoDB官方提供的免费云数据库进行练习 https://www.mongodb.com/cloud/atlas/register
也可以在本地运行一个MongoDB进程

添加MongoDB相关配置

在common模块下添加common.conf配置文件以及CommonConfig类.

mongodb.host=mongodb://%s:%s@localhost:27017/%s?retryWrites=true&w=majority
mongodb.user=root
mongodb.password=123456
# 登录服数据库名
mongodb.login.db=login
@Getter
@Component
@PropertySource("classpath:common.conf")
public class CommonConfig {@Value("${mongodb.host}")String mongoHost;@Value("${mongodb.user}")String mongoUser;@Value("${mongodb.password}")String mongoPassword;@Value("${mongodb.login.db}")String loginDbName;
}

创建MongoContext类

为了管理MongoDB连接, 创建一个MongoContext类用于初始化mongodb连接与管理.

package org.common.mongo;
import ...
/*** Mongo上下文*/
@Slf4j
@Component
public class MongoContext {private MongoClient mongoClient;private MongoTemplate mongoTemplate;public void initMongoContext(String mongoUrl, String user, String password, String dbName) {String url = String.format(mongoUrl, user, password, dbName);log.info(url);MongoClientSettings.Builder settings = MongoClientSettings.builder();settings.applyConnectionString(new ConnectionString(url));MongoClient mongoClient = MongoClients.create(settings.build());mongoTemplate = new MongoTemplate(mongoClient, dbName);log.info("mongo server ok!");}}

其中MongoClient 是 mongodb-driver-sync库的核心, 用于直接连接和操作 MongoDB 数据库。
而MongoTemplate是spring-data-mongodb库的核心, 它基于MongoClient进行了接口封装, 提供了比 MongoClient 更丰富的功能,包括更简洁的查询构建、更强大的映射支持。

在MongoContext外层封装一层MongoService用来实现Mongo增删改查相关接口。

@Slf4j
@Component
public class MongoService {private MongoContext mongoContext;public void initMongoService(String mongoUrl, String user, String password, String dbName) {MongoContext mongoContext = new MongoContext();mongoContext.initMongoContext(mongoUrl, user, password, dbName);this.mongoContext = mongoContext;}public MongoContext getMongoContext() {return mongoContext;}/*** 插入数据*/public <T extends BaseCollection> boolean insert(T obj) {mongoContext.getMongoTemplate().insert(obj);return true;}/*** 查询数据*/public <T extends BaseCollection> BaseCollection findById(Object id, Class<T> clz) {T object = mongoContext.getMongoTemplate().findById(id, clz);return object;}public <T extends BaseCollection> T findOneByQuery(Criteria criteria, Class<T> clz) {Query query = Query.query(criteria);T object = mongoContext.getMongoTemplate().findOne(query, clz);return object;}//TODO 删//TODO 改

先实现了增查,以便我们后面实现账号注册登录功能来举例。

尝试初始化DB连接

在LoginServer的initServer下面增加MongoContext的初始化代码. 然后运行.

 @Overrideprotected void initServer() {LoginConfig config = SpringUtils.getBean(LoginConfig.class);// actor初始化AkkaContext.initActorSystem();// netty启动NettyServer nettyServer = SpringUtils.getBean(NettyServer.class);nettyServer.start(config.getPort());// mongo服务启动CommonConfig commonConfig = SpringUtils.getBean(CommonConfig.class);MongoService mongoService = SpringUtils.getBean(MongoService.class);mongoService.initMongoService(commonConfig.getMongoHost(), commonConfig.getMongoUser(), commonConfig.getMongoPassword(), commonConfig.getLoginDbName());log.info("LoginServer start!");}

启动LoginServer得到结果:
mongo连接成功

实现注册功能

上一节我们使用Protobuf创建了注册协议, 从客户端发送到了登录服进行解析.
接下来我们将注册的账号密码进行入库以便后续取出使用.

我们先构思一下一个账号应该有的数据, 创建一个AccountCollection类, 用于映射Mongo数据库中的AccountCollection表.

@Document
public class AccountCollection extends BaseCollection {@Idprivate long accountId;private String accountName;private String password;// getter & setter
}

很好理解, @Document注解表示该类是一个mongo的文档映射类, @Id表示这个字段作为该文档的主键.
BaseCollection目前就是一个空的Abstract类, 实现了Serializable接口.

public abstract class BaseCollection implements Serializable {
}

接下来修改ConnectActor中的onClientUpMsg方法, 该方法负责接收客户端上行协议并进行解包.

    private Behavior<BaseMsg> onClientUpMsg(ClientUpMsg msg) throws InvalidProtocolBufferException {Pack decode = PackCodec.decode(msg.getData());log.info("receive client up msg. cmdId = {}", decode.getCmdId());byte[] data = decode.getData();if (decode.getCmdId() == ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE) {// 注册协议LoginProtoHandler.onPlayerRegisterMsg(this, PlayerMsg.C2SPlayerRegister.parseFrom(data));}return this;}

我增加了一个LoginProtoHandler类用于处理登录相关的业务逻辑.
若是将所有的代码都写在ConnectActor, 将来这里的代码会越来越长最终变得不可控.
使用单一职责的思想, 使ConnectActor只负责进行协议的解包, 具体业务逻辑由各个功能模块自己实现, 将来游戏服我们也会这么处理, 这里只简单提一嘴.

@Slf4j
public class LoginProtoHandler {public static void onPlayerRegisterMsg(ConnectActor actor, PlayerMsg.C2SPlayerRegister up) {log.info("player register, accountName = {}, password = {}", up.getAccountName(), up.getPassword());long accountId = 1L;String accountName = up.getAccountName();String password = up.getPassword();AccountCollection accountCollection = new AccountCollection();accountCollection.setAccountId(accountId);accountCollection.setAccountName(accountName);accountCollection.setPassword(password);MongoService mongoService = SpringUtils.getBean(MongoService.class);boolean res = mongoService.insert(accountCollection);log.info("create account collection. accountId = {}, accountName = {}, res = {}", accountId, accountName, res);// 回包PlayerMsg.S2CPlayerRegister.Builder builder = PlayerMsg.S2CPlayerRegister.newBuilder();builder.setSuccess(res);byte[] down = PackCodec.encode(new Pack(ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE, builder.build().toByteArray()));actor.getCtx().writeAndFlush(down);}}

LoginProtoHandler负责处理客户端上行的关于账号登录相关的协议.
onPlayerRegisterMsg负责账号注册相关逻辑.
由于账号Id的生成规则我还没想好, 先用一个1L来进行测试, 然后我们调用MongoService的insert方法, 将accountCollection写入mongo. 并进行回包.

修改ClientMain, 使我们在输入"register"时, 发送注册协议进行账号的注册.

	@Overrideprotected void handleBackGroundCmd(String cmd) {if (cmd.equals("test")) {channel.writeAndFlush("test".getBytes());} else if (cmd.equals("register")) {PlayerMsg.C2SPlayerRegister.Builder builder = PlayerMsg.C2SPlayerRegister.newBuilder();builder.setAccountName("clintAccount");builder.setPassword("123456");Pack pack = new Pack(ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE, builder.build().toByteArray());byte[] data = PackCodec.encode(pack);channel.writeAndFlush(data);} else if (cmd.equals("login")) {PlayerMsg.C2SPlayerLogin.Builder builder = PlayerMsg.C2SPlayerLogin.newBuilder();builder.setAccountName("clintAccount");builder.setPassword("123456");Pack pack = new Pack(ProtoEnumMsg.CMD.ID.PLAYER_LOGIN_VALUE, builder.build().toByteArray());byte[] data = PackCodec.encode(pack);channel.writeAndFlush(data);}}

这里顺便把登录的协议一并附上, 没啥技术含量就不多讲.

测试注册功能

启动登录服, 启动客户端, 客户端控制台输入register.
看看登录服的打印:
注册成功
再看看mongo中数据是否写入成功:
db数据
可以看到确实写入了一条AccountCollection文档, 字段_id为每个collection的主键, 他的数值也就是我们使用@Id注解标识的字段, 另外_class是spring-data-mongo库为我们添加的类名, 用于读取数据时反序列化用. 需要注意的是如果你的AccountCollection类修改了包名或类名, 这里反序列化就会失败, 需要额外添加处理.

实现登录逻辑

登录与注册相差不大, 只是把添加数据修改为查找数据.
修改LoginProtoHandler, 添加onPlayerLoginMsg

    public static void onPlayerLoginMsg(ConnectActor actor, PlayerMsg.C2SPlayerLogin up) {String accountName = up.getAccountName();String password = up.getPassword();MongoService mongoService = SpringUtils.getBean(MongoService.class);Criteria criteria = Criteria.where("accountName").is(accountName);AccountCollection accountCollection = mongoService.findOneByQuery(criteria, AccountCollection.class);PlayerMsg.S2CPlayerLogin.Builder builder = PlayerMsg.S2CPlayerLogin.newBuilder();if (accountCollection == null) {log.warn("login without account. accountName = {}", accountName);builder.setSuccess(false);} else if( !accountCollection.getPassword().equals(password) ) {log.warn("login password error. accountName = {}", accountName);builder.setSuccess(false);} else {log.info("login success. accountName = {}, accountId = {}", accountName, accountCollection.getAccountId());builder.setSuccess(true);builder.setAccountId(accountCollection.getAccountId());}byte[] down = PackCodec.encode(new Pack(ProtoEnumMsg.CMD.ID.PLAYER_LOGIN_VALUE, builder.build().toByteArray()));actor.getCtx().writeAndFlush(down);}

这里我们使用Criteria创建一个条件, 根据accountName来查找一条mongo中的文档, 然后对比密码是否一致, 来实现登录流程.
修改ConnectActor使其对Login协议进行解包并分发到LoginProtoHandler中.

    private Behavior<BaseMsg> onClientUpMsg(ClientUpMsg msg) throws InvalidProtocolBufferException {Pack decode = PackCodec.decode(msg.getData());log.info("receive client up msg. cmdId = {}", decode.getCmdId());byte[] data = decode.getData();if (decode.getCmdId() == ProtoEnumMsg.CMD.ID.PLAYER_REGISTER_VALUE) {// 注册协议LoginProtoHandler.onPlayerRegisterMsg(this, PlayerMsg.C2SPlayerRegister.parseFrom(data));} else if (decode.getCmdId() == ProtoEnumMsg.CMD.ID.PLAYER_LOGIN_VALUE) {// 登录协议LoginProtoHandler.onPlayerLoginMsg(this, PlayerMsg.C2SPlayerLogin.parseFrom(data));}return this;}

测试登录流程

启动登录服, 启动客户端, 客户端控制台输入login.
看看登录服的打印:
登录成功

结语

本节我们将MongoDB引入到项目中作为我们的持久化数据库来使用, 并通过注册登录的两个小例子, 来展示spring-data-mongo这个库的用法.
我们只需要定好映射类, 便算是搭建好了一张表结构, 使用起来还是很简单的, 当然我们后面还会继续对其封装, 减少业务开发人员对MongoService的直接调用.
另外, 我们实现的注册登录例子十分粗糙, 其实只是做了一次mongo的读写, 对于游戏服务器来说, 注册登录功能是重中之重, 它维护着玩家的账号安全, 同时也是我们整个游戏的入口.
对于账号注册, 我们还需要做 账号id生成, 账号名重复性检测, accountId与accountName的缓存映射, 后期还有sdk接入等工作.
对于账号登录, 我们还需要做 登录状态修改, 多点登录顶号, 分配游戏服 等工作.
这些我们后面会继续优化.

下节预告

下一节笔者将会引入redis作为游戏的缓存数据库. 当游戏玩家变多, 使用缓存数据库可以大幅减小数据库读写压力. 同时redis的特性可以做很多事情, 比如我们可以用redis的incrby来做账号的递增而不怕多进程中为玩家分配到同一个id; 还可以用作为分布式锁来实现一些需要多进程同时处理的业务功能.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/756685.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt-pdf-viewer-library 编译过程记录

1.qtpdfviewerinitializer.h 中 类模板问题需要修改为下面代码: https://github.com/develtar/qt-pdf-viewer-library 下载代码&#xff1a; 编译出现错误 修改代码&#xff0c;如下: 2.无法触发onViewerLoaded 事件&#xff0c;就是界面无法显示PDF文件 修改下面代码&#…

使用JNDIExploit-1.2-SNAPSHOT.jar复现log4j2详细流程

1.进入到改工具所在的目录&#xff0c;然后cmd打开命令行 查看一下帮助信息 -l 指定开启ladp服务的端口 -p 指定开启http服务的端口 -i 指定开启服务的ip&#xff0c;也就是攻击者的ip&#xff0c;也可以是黑客的公网服务器 因为这里的靶场是部署在kali当中的&#xf…

竞争优势:大型语言模型 (LLM) 如何重新定义业务策略

人工智能在内容创作中的突破 在当今快节奏的商业环境中&#xff0c;像 GPT-4 这样的大型语言模型 (LLM) 不再只是一种技术新颖性&#xff1b; 它们已成为重新定义跨行业业务战略的基石。 从增强客户服务到推动创新&#xff0c;法学硕士提供了企业不容忽视的竞争优势。 1. 加强…

设计模式中的UML基础

设计模式中的UML基础 目录 1、UML概述 2、UML的用途 3、UML的构成 4、UML图 5、UML类图 5.1、类的构成 5.2、类与类之间的关系 6、绘制UML图的软件工具 在讲解设计模式时&#xff0c;会使用到UML建模中的类图去讲解类与类之间的关系&#xff0c;所以这里需要给大家普…

【SpringSecurity】十三、基于Session实现授权认证

文章目录 1、基于session的认证2、Demosession实现认证session实现授权 1、基于session的认证 流程&#xff1a; 用户认证成功后&#xff0c;服务端生成用户数据保存在session中服务端返回给客户端session id (sid&#xff09;&#xff0c;被客户端存到自己的cookie中客户端下…

Android Studio实现内容丰富的安卓校园二手交易平台(带聊天功能)

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 项目编号083 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看二手商品列表 3.发布二手商品 4.商品详情 5.聊天功能…

在微信小程序中或UniApp中自定义tabbar实现毛玻璃高斯模糊效果

backdrop-filter: blur(10px); 这一行代码表示将背景进行模糊处理&#xff0c;模糊程度为10像素。这会导致背景内容在这个元素后面呈现模糊效果。 background-color: rgb(255 255 255 / .32); 这一行代码表示设置元素的背景颜色为白色&#xff08;RGB值为0, 0, 0&#xff09;&a…

第四百一十二回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"给geolocator插件提交问题的结果"相关的内容&#xff0c;本章回中将介绍自定义标题栏.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我…

wireshark windows 抓包https

windows下 1.配置环境变量以生成ssl协商会话密钥日志记录 系统设置-》高级设置-》环境变量 新增环境变量 SSLKEYLOGFILE C:\Users\Public\Documents\SSLKEY\sslkey.log 打开公用共享文档创建SSLKEY文件夹用于后续系统存放协商密钥日志 2.配置Wireshark选项进行抓包 点击…

(一)Linux+Windows下安装ffmpeg

一丶前言 FFmpeg是一个开源的音视频处理工具集&#xff0c;由多个命令行工具组成。它可以在跨平台的环境中处理、转换、编辑和流媒体处理音视频文件。 FFmpeg支持多种常见的音视频格式和编解码器&#xff0c;可以对音视频文件进行编码、解码、转码、剪辑、合并等操作。它具有广…

最近火绒的explorer问题,电脑黑屏只有鼠标

由于安全限制&#xff0c;覆盖文件是行不通的&#xff0c;按照火绒官方给的方法试试&#xff0c;还是不行。主要是他最后一步写得有问题。恭喜火绒&#xff0c;成功的将我们所有客户的电脑安装的火绒卸载。 解决方案 1、CTRLSHIFTESC调出任务管理器&#xff1b; 2、左上角&am…

.net使用excel的cells对象没有value方法——学习.net的Excel工作表问题

$exception {"Public member Value on type Range not found."} System.MissingMemberException 代码准备运行问题解决1. 下载别的版本的.net框架2. 安装3. 运行 代码 Imports Excel Microsoft.office.Interop.Excel Public Class Form1Private Sub Button1_Click(…

万用表革新升级,WT588F02BP-14S语音芯片助力智能测量新体验v

万能表功能&#xff1a; 万能表是一款集多功能于一体的电子测量工具&#xff0c;能够精准测量电压、电流、电阻等参数&#xff0c;广泛应用于电气、电子、通信等领域。其操作简便、测量准确&#xff0c;是工程师们进行电路调试、故障排查的得力助手&#xff0c;为提升工作效率…

奥特曼剧透GPT-5,将在高级推理功能上实现重大进步

奥特曼&#xff1a;“GPT-5的能力提升幅度将超乎人们的想象...” 自 Claude 3 发布以来&#xff0c;外界对 GPT-5 的期待越来越强。毕竟Claude 3已经全面超越了 GPT-4&#xff0c;成为迄今为止最强大模型。 而且距离 GPT-4 发布已经过去了整整一年时间&#xff0c;2023年3月1…

Android 源码中 内置系统App(整个APP源码方式集成)

1. 如何新建一个系统 App 项目 使用 Android Studio 新建一个空项目 FirstSystemApp&#xff0c;包名设置为 com.yuandaima.firstsystemapp&#xff0c;语言选择 Java。后面为叙述方便称该项目为 as 项目。 接着在 jelly/rice14 目录下创建如下的目录和文件&#xff1a; 接着…

金江能源:助力新能源行业发展上市之路逐步迈进

在当今全球节能减排的大背景下,新能源产业成为了社会发展的热门领域。楚雄州金江能源集团有限公司作为新能源产业中的佼佼者,凭借其雄厚的技术实力和前瞻性的发展战略,已经展开了公司上市的蓄势之路。5月15日,金江能源将在港交所上市,为公司的发展注入更多资金和资源。 作为一…

vue axios 缓存 接口请求实现缓存加载

文章写的多了&#xff0c;开头就不知道怎么写了&#xff0c;硬挤一些句子总觉的卖弄。其实更多的想留下各位看官&#xff0c;多多的点赞&#xff0c;多多的关注&#xff0c;多的收藏。为将来的博客化动作做好前期数据粉丝基础。哦哦哦&#xff0c;我在想啥呢。。这大下午的。。…

xAI开发的一款巨大型语言模型(HLM)--Grok 1

在xAI发布Grok的权重和架构之后&#xff0c;很明显大型语言模型&#xff08;LLM&#xff09;的时代已经过去&#xff0c;现在是巨大型语言模型&#xff08;HLM&#xff09;的时代。这个混合专家模型发布了3140亿个参数&#xff0c;并且在Apache 2.0许可下发布。这个模型没有针对…

【项目管理后台】Vue3+Ts+Sass实战框架搭建一

项目管理后台 建立项目最好是卸载Vetur 新建.env.d.ts文件安装Eslint安装校验忽略文件添加运行脚本 安装prettier新建.prettierrc.json添加规则新建.prettierignore忽略文件 安装配置stylelint新建.stylelintrc.cjs 添加后的运行脚本配置husky配置commitlint配置husky 强制使用…

从服务器到云原生:企业IT基础设施的演进之路

随着数字经济的迅猛发展&#xff0c;企业IT数字化转型已成为推动业务创新和提升竞争力的关键。在这一转型过程中&#xff0c;基础设施的建设与升级显得尤为重要。企业需要不断优化和更新他们的基础设施&#xff0c;以适应不断变化的市场需求和技术发展。本文将探讨企业IT数字化…