iogame服务端游戏框架整合springboot + mybatisplus

iogame服务端游戏框架整合springboot + mybatisplus

概述

​ 本次测试为了记录第一次尝试游戏开发的过程,项目使用了游戏服务端开源框架iogame iogame gitee源码,前端采用uniapp框架,Vue3+js,后端使用springboot+mybatisplus,数据库版本为mysql5.7结合的方式开发一个h5 demo。对于iogame框架不熟悉的同学可以先移步 iogame说明介绍文档 观看相关介绍文档。

文章目录

  • iogame服务端游戏框架整合springboot + mybatisplus
    • 概述
    • 一、加入相关依赖
    • 二、修改application.properties配置
    • 三、快速开始
      • 1、创建测试表
      • 2、cmd
      • 3、Action
      • 4、实体类
      • 5、Mapper
      • 6、Service
      • 7、逻辑服
      • 8、启动类
    • 四、测试
        • 用户注册页面:
        • websocket.js
        • 页面路由pages.json:
        • 服务端项目启动:
        • 前端项目启动:
    • 踩坑记录:

使用框架组件 版本 备注
iogame 17.1.38
JDK 17
springboot 2.7.0
mybatis-plus 3.5.3.1
mysql 5.7.40

一、加入相关依赖


    com.baomidou
    mybatis-plus-boot-starter
    3.5.3.1



    mysql
    mysql-connector-java
    5.1.49

二、修改application.properties配置

数据库使用了8.x以上的版本驱动为com.mysql.cj.jdbc.Driver

server.port=8001

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/my_demo?useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

三、快速开始

先来看一下整体项目目录结构:

iogame服务端游戏框架整合springboot + mybatisplus_第1张图片

下面开始上代码。

1、创建测试表

CREATE TABLE `game_users` (
 `id` varchar(64) NOT NULL,
 `user_name` varchar(50) NOT NULL,
 `password` varchar(50) NOT NULL,
 `email` varchar(50) NOT NULL,
 `score` int(11) DEFAULT '0',
 PRIMARY KEY (`id`)
)

2、cmd

public interface LoginModule {
    int cmd = SpringCmdModule.loginCmd;

    int register = 0;

    int login = 1;
}

3、Action

@Slf4j
@Component
@ActionController(LoginModule.cmd)
public class UserAction {

    @Resource
    UserService userService;

    @ActionMethod(LoginModule.register)
    public String createUser(User user) {
        return userService.createUser(user);
    }
}

4、实体类

@Data
@ToString
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class User {
    String id;
    String userName;
    String password;
    String email;
}

5、Mapper

@Mapper
public interface UserMapper extends BaseMapper {

    void createUser(User user);
}

xml:





    
        insert into game_users (id, user_name, password, email)
        values (#{id}, #{userName}, #{password}, #{email});
    

6、Service

public interface UserService extends IService {

    String createUser(User user);
}

service实现类:

@Service
public class UserServiceImpl extends ServiceImpl implements UserService {

    @Resource
    UserMapper userMapper;

    @Override
    public String createUser(User user) {
        user.setId(UUID.randomUUID().toString());
        userMapper.createUser(user);
        return user.getId();
    }
}

7、逻辑服

@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class GameLoginClient extends AbstractBrokerClientStartup {
    @Override
    public BarSkeleton createBarSkeleton() {
        // 业务框架构建器 配置
        BarSkeletonBuilderParamConfig config = MyBarSkeletonConfig.createBarSkeletonBuilderParamConfig()
                // 扫描 action 类所在包
                .scanActionPackage(UserAction.class);

        // 业务框架构建器
        BarSkeletonBuilder builder = MyBarSkeletonConfig.createBarSkeletonBuilder(config);
        // 开启 jsr380 验证
        builder.getSetting().setValidator(true);

        return builder.build();
    }

    @Override
    public BrokerClientBuilder createBrokerClientBuilder() {
        BrokerClientBuilder builder = BrokerClient.newBuilder();
        builder.appName("spring login 登录游戏逻辑服");
        return builder;
    }
}

8、启动类

启动类设置了json编解码。

@MapperScan("com.io.game.login.mapper")
@ComponentScan("com.io.game.login")
@SpringBootApplication
public class LoginApplication {

    public static void main(String[] args) {
        // 设置 json 编解码。如果不设置,默认为 jprotobuf
        IoGameGlobalSetting.me().setDataCodec(new JsonDataCodec());
        // 启动 spring boot
        SpringApplication.run(LoginApplication.class, args);

        // 启动班级逻辑服
        GameLoginClient gameLogicClient = new GameLoginClient();
        BrokerClientApplication.start(gameLogicClient);
    }

    @Bean
    public ActionFactoryBeanForSpring actionFactoryBean() {
        // 将业务框架交给 spring 管理
        return ActionFactoryBeanForSpring.me();
    }
}

注意:

​ 因为我的启动类不和项目在一个模块,需要加上@ComponentScan(“com.io.game.login”)注解,不然会报 No qualifying bean of type ‘com.io.game.login.action.UserAction’ available 错误提示。

iogame服务端游戏框架整合springboot + mybatisplus_第2张图片

13:31:34.847 ERROR [uestMessage-8-2] .game.common.kit.log.IoGameLoggerFactory.handler(ActionCommandTryHandler.java:44) : No qualifying bean of type 'com.io.game.login.action.UserAction' available

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.io.game.login.action.UserAction' available
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
	at com.iohao.game.action.skeleton.ext.spring.ActionFactoryBeanForSpring.getBean(ActionFactoryBeanForSpring.java:53)
	at com.iohao.game.action.skeleton.core.DependencyInjectionPart.getBean(DependencyInjectionPart.java:65)
	at com.iohao.game.action.skeleton.core.DefaultActionFactoryBean.getBean(DefaultActionFactoryBean.java:31)
	at com.iohao.game.action.skeleton.core.DefaultActionCommandFlowExecute.execute(DefaultActionCommandFlowExecute.java:53)
	at com.iohao.game.action.skeleton.core.ActionCommandHandler.handler(ActionCommandHandler.java:37)
	at com.iohao.game.action.skeleton.core.ActionCommandTryHandler.handler(ActionCommandTryHandler.java:42)
	at com.iohao.game.action.skeleton.core.BarSkeleton.handle(BarSkeleton.java:108)
	at com.iohao.game.bolt.broker.core.common.processor.hook.DefaultRequestMessageClientProcessorHook.lambda$processLogic$0(DefaultRequestMessageClientProcessorHook.java:74)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

以及@MapperScan(“com.io.game.login.mapper”)注解,不加会提示找不到mapper。

iogame服务端游戏框架整合springboot + mybatisplus_第3张图片

13:40:44.746 ERROR [    main] agnostics.LoggingFailureAnalysisReporter.report(LoggingFailureAnalysisReporter.java:40) : 

***************************

APPLICATION FAILED TO START

***************************

Description:

A component required a bean of type 'com.io.game.login.mapper.UserMapper' that could not be found.


Action:

Consider defining a bean of type 'com.io.game.login.mapper.UserMapper' in your configuration.


Process finished with exit code 1

四、测试

​ 前端我使用的是uniapp框架(打算做一个h5小游戏),使用Vue3+js编写的一个简陋页面(由于本人主要做后端的,前端不是很熟悉,可能会有错误的地方,欢迎大家指正)

先看下前端整体项目目录结构:

iogame服务端游戏框架整合springboot + mybatisplus_第4张图片

用户注册页面:




websocket.js

发送消息最主要的方法是createExternalMessage,因为前后端通信使用的json编解码。

import $ from 'jquery'

function formatDate(now) {
    var year = now.getFullYear();
    var month = now.getMonth() + 1;
    var date = now.getDate();
    var hour = now.getHours();
    var minute = now.getMinutes();
    var second = now.getSeconds();
    return year + "-" + (month = month < 10 ? ("0" + month) : month) + "-" + (date = date < 10 ? ("0" + date) : date) + " " + (hour = hour < 10 ? ("0" + hour) : hour) + ":" + (minute = minute < 10 ? ("0" + minute) : minute) + ":" + (second = second < 10 ? ("0" + second) : second);
}

var websocket;

function addsocket(addr) {

    var wsaddr = "ws://" + addr;
    StartWebSocket(wsaddr);

}


function StartWebSocket(wsUri) {
    websocket = new WebSocket(wsUri);
    websocket.binaryType = 'arraybuffer';

    websocket.onopen = function (evt) {
        onOpen(evt, wsUri)
    };
    websocket.onclose = function (evt) {
        onClose(evt)
    };
    websocket.onmessage = function (evt) {
        onMessage(evt)
    };
    websocket.onerror = function (evt) {
        onError(evt)
    };

}

function onOpen(evt, wsUri) {
    writeToScreen("连接成功,现在你可以发送信息进行测试了!");
    writeToScreen(wsUri);
}

function onClose(evt) {
    writeToScreen("Websocket连接已断开!");
    websocket.close();
}

function binaryData(data) {
	if (data != null) {
		return JSON.parse(new TextDecoder("utf-8").decode(new Uint8Array(data)))
	}
}

function onMessage(evt) {
    let externalMessage = binaryData(evt.data);
    let bizData = externalMessage.data;
    let bizDataJson = binaryData(bizData);
    externalMessage.data = bizDataJson;

    let json = JSON.stringify(externalMessage);
    
    writeToScreen('服务端回应 ' + formatDate(new Date()) + '
' + json + ''); } function onError(evt) { writeToScreen('发生错误: ' + evt.data); } function SendMessage(data) { var externalMessageBytes = createExternalMessage(data); websocket.send(externalMessageBytes); } function createExternalMessage(data) { if (data != null) { console.log(data.data) var message = { cmdCode: data.cmdCode, cmdMerge: mergeCmd(data.cmd, data.subCmd), data: data.data } var json = JSON.stringify(message); writeToScreen('你发送的信息 ' + formatDate(new Date()) + '
' + json); var textEncoder = new TextEncoder(); var dataArray = textEncoder.encode(JSON.stringify(data.data)); message.data = Array.from(dataArray); json = JSON.stringify(message); return textEncoder.encode(json); } } function writeToScreen(message) { var div = "
" + message + "
"; var d = $("#output"); var d = d[0]; var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight; $("#output").append(div); if (doScroll) { d.scrollTop = d.scrollHeight - d.clientHeight; } } function mergeCmd(cmd,subCmd) { return (cmd << 16) + subCmd; } export default { addsocket, SendMessage, websocket };
页面路由pages.json:
{
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "uni-app"
			}
		},
		{
            "path" : "pages/user/login",
            "style" :                                                                                    
            {
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            

        },
    	{
            "path" : "pages/user/register",
            "style" :                                                                                    
            {
                "navigationBarTitleText": "",
                "enablePullDownRefresh": false
            }
            
        }
    ],
    "globalStyle": {
    	"navigationBarTextStyle": "black",
    	"navigationBarTitleText": "uni-app",
    	"navigationBarBackgroundColor": "#F8F8F8",
    	"backgroundColor": "#F8F8F8"
    },
    "uniIdRouter": {}

}
服务端项目启动:

注意:

​ 服务端需要启动对外服通过websocket与前端建立连接,本次项目为了方便,使用了iogame示例spring项目,一键启动的方式(包括对外服,网关,逻辑服),并另外启动我们的登录逻辑服接入。

iogame服务端游戏框架整合springboot + mybatisplus_第5张图片

前端项目启动:

iogame服务端游戏框架整合springboot + mybatisplus_第6张图片

在页面输入账号密码进行测试:

打开http://localhost:5173/#/user/register,或者在前端HbuildX内置浏览器测试。

iogame服务端游戏框架整合springboot + mybatisplus_第7张图片

查看服务端框架输出日志:

输出日志

查看数据库是否插入数据:

iogame服务端游戏框架整合springboot + mybatisplus_第8张图片

数据成功插入,至此就圆满结束了!

踩坑记录:

一开始配置文件使用applicaton.yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my_demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
application:
  name: demo

mybatis-plus:
  # 数据库映射实体类包路径
  type-aliases-package: com.io.game.login.mapper
  mapper-locations: classpath:/mapper/*.xml
  configuration:
    # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射
    map-underscore-to-camel-case: true

但是不知道为啥springboot读取不到配置文件,启动项目会报错。

***************************

APPLICATION FAILED TO START

***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).


Process finished with exit code 1

在网上百度了好久,报错的原因是使用了mybatis相关的数据库依赖,但是没有读取到配置文件。解决方法是@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)注解加上exclude = DataSourceAutoConfiguration.class才可以正常启动。

后面尝试使用application.properties配置文件,可以不需要加exclude = DataSourceAutoConfiguration.class,能正常读取到配置并启动。

你可能感兴趣的:(spring,boot,游戏,mybatis)