# artifact **Repository Path**: tbs005/artifact ## Basic Information - **Project Name**: artifact - **Description**: Java快速游戏服务器框架 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 17 - **Created**: 2020-02-13 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Artifact artifact是一个java游戏服务器框架。取名的意义为`神器`,提供**高效率、高并发、高负载、结构清晰**的轻量级框架。让你在游戏开发中专注于游戏逻辑节省开发时间! [视频教程](https://www.bilibili.com/video/av79674974/) [示例项目](https://gitee.com/sojoys/example-parent) artifact **artifact优点** - 插件式编程(*已提供多种的插件*) - 一键切换数据库 - 与客户端rpc形式通信(*不用再去和客户端对协议号、参数、返回数据。使用协议文件简单明了*) - 网络层、数据层代码一键生成 **第三方JAR** artifact中使用了大量的第三方优秀的jar,不重复造轮子的开发理念。artifact只对其整合或者扩展。第三方的jar的交给各自自己的开发者维护,我们只是专注于游戏开发 - [hutool](https://gitee.com/loolly/hutool) - lombok - [fastjson](https://gitee.com/wenshao/fastjson) - [beetl](https://gitee.com/xiandafu/beetl) - dbutils - quartz - netty - ... 这其中的jar很多不是必须,需要根据自己的项目定位自己选择,比如网络层不想用netty要用tio。也可以不使用netty的jar。 > artifact中大量使用JDK8的特性,所有需要JDK8+ ```xml io.gitee.sojoys artifact-all 1.0.4 ``` ## 1.项目结构 - artifact-orm : 数据层模块 - artifact-core : 核心包 - artifact-actor : 一个轻量级Actor线程模型 - artifact-extra : 第三方包扩展 - artifact-network : 网络层模块 - artifact-csv : 策划配置表 ## 2.支持数据库 - mongodb - redis - mysql 自己扩展也很方便 ## 3.网络模块 ​ 在游戏开发中我们需要使用各种命令和客户端完成通信,传统的写法都是与客户端协商手写。缺点是风格不统一、效率低、维护成本等等。在artifact中有类似于`protobuf`或者是`grpc`的协议文件。通过协议文件生成代码,使得客户端和服务器通信变得和我们服务器之间RPC通信一样 ​ 我先介绍协议文件的格式,至于为什么要自定义而不直接用`protobuf`在后面的使用教程中会进行说明。 ```java send:loginGame(String loginServerId,String token) -> (PlayerDTO player); //登陆游戏 push:notifyPlayer(String loginServerId,String token); //推送 // 道具 struct ItemDTO{ long id; // 唯一标示 int cid; // 配置ID int num; // 数量 } // 道具 -> 装备 struct EquipmentDTO extends ItemDTO{ int lv; // 等级 } enum MailStatus{ unread:0, // 未读 read:1, // 已读 receive:2; // 已领 delete:3; // 已删 } ``` 在协议文件中主要分为4类 1. `send`:表示一个客户端到服务器的请求接口,`->`后表示返回的数据 2. `push`:表示服务器主动向客户端推送数据的接口 3. `enum`:枚举的定义 4. `struct`:结构体,结构体就是我们在网络通信中,传输的对象结构,支持**单继承**关键字`extends`,其字段类型,基本数据类型都支持此处我就不一一罗列了,列举常用的 - int - long - string - boolean - struct - enum - list - map ## 4.数据模块 ​ 数据模块的代码生成,方便我们的增、删、改、查。数据定时入库,数据库切换。也有对应的生成协议文件。 ```java
``` 1. `methods`:可以设置实体生成的方法,有**array、map、hashmap、byte、json**5种取值 2. `type`:java字段类型,如果是Date这种引用类型,请使用完成包路径 3. `dbType`:数据库类型,只有`Mysql`数据库需要 4. `fk`:外键 key:为外键表名称,val:此外键的索引是否是唯一索引 5. `index`:**columns**索引关联字段多字段以`,`分割。 比如:`uid,name`,**unique**:标示是否是唯一索引 6. `auto`:表示是否自动设值 > 如果要在一个字段上存储复杂的数据结构,可以定义为字符串或者Byte[],然后在代码逻辑中进行序列化/反序列化 > 为什么要采用xml来作为生成文件的格式,这里我就简单的说一下,在之前的几个项目中,都是通过关系型数据库反向取得数据库关系来进行生成 > > 但是在团队开发中,发现很不方便团队协作、扩展都不是很方便。所有现在改为xml文件来做。便于团队协作和版本管理 # 示例项目 下面我会使用Artifact来一步一步的做一个示例项目`artifact-example`,我使用的是`IntelliJ IDEA`,使用其他IDE的可以更具自己的IDE构建项目。项目、文件的命名、或者maven的此处就不多讲解了,主要讲解在项目中如何使用`artiface` ## 1.创建项目 ![1560424302738](C:\Users\apple\Desktop\imgs\1560424302738.png) 然后我们在maven中添加artifact的引用,为了方便我直接添加在根项目的pow.xml中,和JD8的编译 ```xml io.gitee.sojoys artifact-all 1.0.0-SNAPSHOT org.slf4j slf4j-api 1.7.25 ch.qos.logback logback-core 1.2.3 ch.qos.logback logback-classic 1.2.3 org.apache.maven.plugins maven-compiler-plugin 1.8 1.8 utf-8 -parameters ``` ## 2.Hello World 我们现在开始配置游戏服务器并且启动它一个最简单的示例 1. 首先我们需要实现`org.artifact.core.lang.IConfig`的接口完成配置 ```java public class GameConfig implements IConfig { @Override // 做一些配置读取和常量的初始化操作 public void configContext(IContext iContext) { } @Override // 配置插件 public void configPlugin(Plugins plugins) { } @Override // 启动之后执行 public void afterServerStart() { } @Override // 停止之前执行 public void beforeServerStop() { } } ``` 2. 创建启动类 ```java public class Bootstrap { public static void main(String[] args) { IServer.me().setConfig(new GameConfig()); IServer.me().start(); } } ``` 3. 运行Bootstrap ``` 19:27:11.229 [main] DEBUG cn.hutool.log.LogFactory - Use [Slf4j] Logger As Default. 19:27:11.232 [main] INFO org.artifact.core.lang.IServer - -> Server Starting 19:27:11.232 [main] INFO org.artifact.core.lang.IServer - -> Server Start Finish Process finished with exit code 0 ``` 看到**Server Start Finish**就表示我们服务器已经可以完好的启动了,但是启动后立马就退出了,这是因为我们没有启动任何的线程。也就是说我们还没有任何功能,再后面我们来一步步的添加。现在只需要知道我们的服务器能够正常启动了。 ## 3.数据建模 上面我们创建了一个空的项目,现在我们来做数据层,上面我们介绍过数据模块的协议文件。 > 首先在测试目录创建一个存放我们协议文件的文件夹`resources/design`,为了协议文件看上去更加简洁我使用redis数据库。nosql数据库不需要`dbType`属性,可以让协议文件看起来更加简洁 因为我们使用的redis数据库我们首先在maven中添加个jedis的引用 ```xml redis.clients jedis 2.9.0 ``` t_plater.xml ```xml
``` t_hero.xml ```xml
``` 好了~现在我们数据文件已经创建完毕,下面我们开始生成数据层代码! ```java public class DBGenerate { public static void main(String[] args) throws Exception { // 协议文件夹路径 (不同的IDE中会不一样) String designSourcePath = "design"; DesignConfig designConfig = new DesignConfig(); // 数据库名称 designConfig.setDatabaseName("example_game"); // 表前缀 designConfig.setTablePrefix("t_"); // 选择数据库类型 designConfig.setType(BuilderDatabaseEnum.redis); // 生成那些方法 designConfig.setMethods(new BuilderMethodEnum[] {}); // 开始构建 DBModulebuilder.newBuilder() // 生成的代码包名称 .setPackageName("io.gitee.sojoys.game.db") // 生成的代码存放路径(不同的IDE中会不一样) .setSourcePath("artifact-example-game/src/main/java/io/gitee/sojoys/game/db") // 协议文件夹路径 .setDesignSourcePath(designSourcePath) // 相关配置 .setDesignConfig(designConfig) .build(); } } ``` 运行生成代码 ``` 20:05:13.300 [main] DEBUG cn.hutool.log.LogFactory - Use [Slf4j] Logger As Default. 20:05:13.303 [main] DEBUG cn.hutool.extra.template.engine.TemplateFactory - Use [Beetl] Engine As Default. ********************************************* * * **********代码生成完成-请刷新项目!********** * * ********************************************* Process finished with exit code 0 ``` 看到这个打印信息后表示我们的代码已经生成完成(有的ide需要刷新才能看到),此时我们的代码就已经生成完毕了! ![1560481331733](C:\Users\apple\AppData\Roaming\Typora\typora-user-images\1560481331733.png) 生成的源码我就不在此处贴出来了,我们看到分别生成了2张表的bean(*对象实体*)、dao(*操作接口*),后期不论我们是需要更换成mysql、mongodb数据库我们只需要在生成的时候选择对应的数据库即可。 * **我们不能直接使用生成的类,推荐的方式是继承扩展。(*避免再此生成代码的时候,覆盖掉我们在生成类中扩展或修改的内容*)** ~好了数据层的代码我们已经生成了,我们现在要就来使用生成的代码。并且开启定时存储插件。这里我们是直接用的大家熟知的spring来做ioc,所有我们再在maven中添加spring的引用 ```xml org.springframework spring-context 4.3.4.RELEASE ``` 创建一个service来继承dao接口,然后我们就可以在service中只有扩展我们的数据操作了! ![1560492330487](C:\Users\apple\AppData\Roaming\Typora\typora-user-images\1560492330487.png) ```java @Service public class PlayerService extends PlayerDAO { @Autowired private HeroService heroService; @Override protected HeroDAO getHeroDAO() { return heroService; } } ``` 有主外键关联时我们需要注入其他表的Service实现 再然后我们创建一个存储插件创建工厂,此处只是先使用一下插件。后面会详细的介绍插件的使用。 ![1560481192934](C:\Users\apple\AppData\Roaming\Typora\typora-user-images\1560481192934.png) ```java public class StoragePluginFactory implements IPluginFactory{ public IPlugin create() { // crontab 为定时表达式,此处表示每分钟 StoragePlugin landingPlugin = new StoragePlugin("0/1 * * * * ?",()->{ Set> daos = new HashSet<>(); // 在此包下面扫描BaseDao的实现类 Set> clazzs = ClassUtil.scanPackageBySuper("io.gitee.sojoys.game.db", BaseDao.class); for (Class clazz : clazzs) { if(ClassUtil.isNormalClass(clazz)) { String className = clazz.getSimpleName(); String key = StrUtil.lowerFirst(className); BaseDao baseDao = IServer.me().getContext().getSpringContext().getBean(key,BaseDao.class); daos.add(baseDao); } } return daos; }); return landingPlugin; } } ``` 然后我们回到我们的GameConfig类中,配置我们spring、redis连接池、和定时存储插件、初始化配置 1. 添加定时器 ```java public void configPlugin(Plugins plugins) { // 添加定时存储插件 plugins.add(new StoragePluginFactory()); } ``` 2. 添加配置文件 ![1560492555370](C:\Users\apple\AppData\Roaming\Typora\typora-user-images\1560492555370.png) > app.properties中配置了redis的连接信息 3. 初始化 ```java @Override // 做一些配置读取和常量的初始化操作 public void configContext(IContext me) { // 初始化Spring me.setSpringContext(new ClassPathXmlApplicationContext("app-init.xml")); // 设置配置文件 me.setSetting(new Setting("app.properties")); // Redis连接池 me.setRedisDS(new RedisDS(me.getSetting(),null)); // mysql连接池 // me.setDataSource(me.getSpringContext().getBean("dataSource", DataSource.class)); // mogodb连接池 //me.setMongoDatabase(MongoFactory.getDS(me.getSetting(), "master").getDb("kapai")); // 设置ID工厂 me.setIdFactory(new SnowflakeFactory(new Snowflake(0, 0))); } ``` 到现在我们就可以来测试一下数据层是否正确了!方便起见我们创建一个SystemBO的接口来进行示例编写 ![1560493470921](C:\Users\apple\AppData\Roaming\Typora\typora-user-images\1560493470921.png) ```java @Override // 启动之后执行 public void afterServerStart() { ApplicationContext context = IServer.me().getContext().getSpringContext(); SystemBO systemBO = context.getBean(SystemBO.class); systemBO.chapter_1(); } ``` 示例1:插入一条玩家数据 ```java public void chapter_1() { // 插入一条数据 Player player = new Player() { }; player.setName("SandKing").setUid(784245545451231L).setLastLoginDate(new Date()); playerService.save(player); } ```