# 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优点**
- 插件式编程(*已提供多种的插件*)
- 一键切换数据库
- 与客户端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.创建项目

然后我们在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需要刷新才能看到),此时我们的代码就已经生成完毕了!

生成的源码我就不在此处贴出来了,我们看到分别生成了2张表的bean(*对象实体*)、dao(*操作接口*),后期不论我们是需要更换成mysql、mongodb数据库我们只需要在生成的时候选择对应的数据库即可。
* **我们不能直接使用生成的类,推荐的方式是继承扩展。(*避免再此生成代码的时候,覆盖掉我们在生成类中扩展或修改的内容*)**
~好了数据层的代码我们已经生成了,我们现在要就来使用生成的代码。并且开启定时存储插件。这里我们是直接用的大家熟知的spring来做ioc,所有我们再在maven中添加spring的引用
```xml
org.springframework
spring-context
4.3.4.RELEASE
```
创建一个service来继承dao接口,然后我们就可以在service中只有扩展我们的数据操作了!

```java
@Service
public class PlayerService extends PlayerDAO {
@Autowired
private HeroService heroService;
@Override
protected HeroDAO getHeroDAO() {
return heroService;
}
}
```
有主外键关联时我们需要注入其他表的Service实现
再然后我们创建一个存储插件创建工厂,此处只是先使用一下插件。后面会详细的介绍插件的使用。

```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. 添加配置文件

> 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的接口来进行示例编写

```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);
}
```