本次测试为了记录第一次尝试游戏开发的过程,项目使用了游戏服务端开源框架iogame iogame gitee源码,前端采用uniapp框架,Vue3+js,后端使用springboot+mybatisplus,数据库版本为mysql5.7结合的方式开发一个h5 demo。对于iogame框架不熟悉的同学可以先移步 iogame说明介绍文档 观看相关介绍文档。
使用框架组件 | 版本 | 备注 |
---|---|---|
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
数据库使用了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
先来看一下整体项目目录结构:
下面开始上代码。
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`)
)
public interface LoginModule {
int cmd = SpringCmdModule.loginCmd;
int register = 0;
int login = 1;
}
@Slf4j
@Component
@ActionController(LoginModule.cmd)
public class UserAction {
@Resource
UserService userService;
@ActionMethod(LoginModule.register)
public String createUser(User user) {
return userService.createUser(user);
}
}
@Data
@ToString
@ProtobufClass
@FieldDefaults(level = AccessLevel.PUBLIC)
public class User {
String id;
String userName;
String password;
String email;
}
@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});
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();
}
}
@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;
}
}
启动类设置了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 错误提示。
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。
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编写的一个简陋页面(由于本人主要做后端的,前端不是很熟悉,可能会有错误的地方,欢迎大家指正)
先看下前端整体项目目录结构:
发送消息最主要的方法是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": [ //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项目,一键启动的方式(包括对外服,网关,逻辑服),并另外启动我们的登录逻辑服接入。
在页面输入账号密码进行测试:
打开http://localhost:5173/#/user/register,或者在前端HbuildX内置浏览器测试。
查看服务端框架输出日志:
查看数据库是否插入数据:
数据成功插入,至此就圆满结束了!
一开始配置文件使用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,能正常读取到配置并启动。