一个可自由扩展的协议代码生成器
npm install unthink -g
创建一个存放协议描述文件的目录,执行如下操作
unthink init
会生成如下目录树:
config.json
---配置文件start.cmd
---执行命令的批处理- client_only ---只对客户端做代码输出
api.json
enum.json
struct.json
- common ---客户端服务器同时输出
api.json
---协议接口描述文件,以下统称interface
文件enum.json
---通用枚举描述文件,以下统称interface
文件struct.json
---通用数据结构描述文件,以下统称interface
文件
- server_only ---只对服务器做代码输出
api.json
enum.json
struct.json
common
目录下描述的内容客户端和服务器代码都会导出,client_only
描述的内容只有客户端代码会导出,
server_only
描述的内容只有服务器会导出。client_only
和server_only
在一般场景中不会用到。
client_only
设计意图是在服务器本身内置了一部分接口功能,但是没有或者没有必要提供客户端接口,
这时候可以在client_only
文件中补充描述,server_only
目录功能类似。
配置
config.json
代码路径,执行start.cmd
生成代码。
工具会自动记录协议版本号,提供给游戏逻辑做协议版本校验
版本号是以一个枚举提供的,在工具中自动生成,不需要描述文件添加,ts代码格式如下:
export class ProtoVersion { //工具自动生成的枚举,记录协议版本号
static versionCode = 2; //协议版本号数字表示
static versionName = '0.0.2'; //协议版本号字符表示
}
同时在
interface
根目录下生成一个version文件用来记录老的代码,内容如下:
{
"versionCode": 2,
"versionName": "0.0.2",
"versionMD5": "1299e5e90d906a07685a9d7e53b28b2f"
}
versionName
记录了版本号的字符表示,每10个小版本会提升一个大版本。可以手动修改此文件来矫正版本号提升。
versionMD5
字段是所有interface
文件字符计算出的md5,并且过滤掉了注释,只是修改注释,并不会导致版本号提升。
注意
:本项目下的所有json文件都支持注释,如果使用vscode
编辑,建议右下角文件类型由JSON
切换至JSON with Comments
。可以消除红线错误提示。高于1.20版本有这个文件类型。
config
完整内容如下:
{
"out": { /* 需要输出的文件种类,对应backend的种类*/
"unity": "./out/unity", /*unity工程Scripts目录*/
"unity-gameframework": { /*使用gameframework工程的Scripts目录*/
"path": "./out/unity-gfw/Assets/Scripts", /*unity工程Script目录*/
"namespace": "StarForce"
},
"thinkjs": "./out/thinkjs/", /*thinkjs 项目根目录*/
"json-schema": "./out/thinkjs/public/schema", /*json-schema目录*/
"sql": {
"path": "./out/thinkjs/public/sql/",
"dbname": "gamedb"
}
},
"request_required": true, /*是否坚持每个api声明了request*/
"response_required": true /*是否坚持每个api声明了response*/
}
out
属性配置了所有支持的导出,可以删掉不需要的导出unity
提供了原生unity的协议导出,并且提供了一个简单的http封装ServerContext.cs
,这个文件可以手动修改,不会被工具再次生成覆盖。unity-gameframework
提供了基于技术中心提供的gameframework框架的协议导出,框架包含了事件机制,请求顺序化机制等,详细介绍请阅读框架文档。namespace
是生成代码的命名空间。thinkjs
thinkjs服务器框架导出的协议文件,并且会在对应的controller文件中做处理函数声明。controller部分的修改是借助于于typescript
抽象语法树的分析修改,不会影响现有逻辑的实现。json-schema
是http请求参数的json格式校验,生成的描述文件符合json-schema标准,可以使用ajv
或者中心提供的think-game-validator
模块来校验。sql
所有interface
文件中声明的结构,如果是需要存数据库的,会生成一份数据库DDL文件,直接通过phpadmin或者其他工具导入该文件,即可完成数据库的更新。做到了完全不需要手动操作数据库的效果。
request_required
是否允许单方向协议接口,http必须是双向的,一个req对应一个res,在长连接协议中,可能不是对应的。该字段留给后续扩展,暂时全部为true。response_required
同上。
类型 | 名称 | 默认值 | DB默认类型 | 说明 |
---|---|---|---|---|
bool |
布尔型 | false | tinyint(4) | |
string |
字符串 | “” | varchar(64) | |
int |
int | 0 | int(11) | 区分各种数字类型是考虑到c#类型和db类型,js统一为number |
int64 |
64位int | 0 | bigint(20) | 同上 |
float |
浮点型 | 0.0 | float | 同上 |
double |
双精度浮点型 | 0.0 | double | 同上 |
array |
数组 | null | text(json字符串) | 数组支持嵌套 |
object |
对象 | null | text(json字符串) | 对象类型和自定义类型区别是,对象的属性是动态的 |
名称 | 效果 | 说明 | 可省略 |
---|---|---|---|
<type> |
指定变量的类型 | 可以是上表的内置类型或者自定义结构或者是纯数字枚举(当做int处理) | |
<default> |
指定变量的默认值 | 需要用户自己保证默认值在各种语言导出版本中语法正确,不指定采用内置默认值 | √ |
<dbtype> |
指定变量对应的db类型 | 当内置默认类型不能满足需求的时候指定对应的默认值 | √ |
<nodb> |
强制使继承DBObject对象的结构不导出到sql文件DDL中 | 某个中间类,不需要sql中对其建表的时候使用 | √ |
<dbindex> |
如果结构存库,指定对应表的索引 | 子类会覆盖父类指定的索引 | √ |
<extends> |
指定结构继承的父类 | 只支持单继承 | √ |
<req> |
接口声明中request参数 | http中是必须的 | |
<res> |
接口声明中require参数 | http中是必须的 |
下面
interface
文件会详细提供语法举例。
注意
:本项目所有的注释,直接在对应需要注释的对象后面使用//
形式编写。
举例如下:
{
"EItemState": { //物品状态
"normal": 0, //正常状态
"locked": 1, //被锁定
"countdown": 2, //冷却中
},
"ECommon": { // 通用枚举
"MaxNameLenth": 20, //名字最大长度
"DefaultName": "Brad Pitt", // 默认名字
"MoveSpeed": 1.3, // 移动速度
"CanJump": false // 开启跳跃
}
},
以上声明了两种枚举,一种是纯数字枚举
EItemState
,这种枚举可以作为类型提供给其他变量使用(当做int类型)。另外一种是数字,浮点,字符串或者布尔型混合的枚举ECommon
,这种枚举是用静态class
实现的,不能作为类型给其他变量使用。
"RewardItem": // 奖励使用的物品结构
{
"cid":"int",
"count":"int",
},
"Reward":
{
"gold":"int", // 奖励的金币
"items":"array<RewardItem>", //奖励的物品数组
"id":{ //对应的关卡id
"<type>":"int",// 类型
"<default>":"123" // 默认值,可以不写这个属性,默认值是安装前面表格的默认值
}
}
可以看到
Reward
和RewardItem
是结构名,定义结构不用区分先后顺序。Reward
有三个成员:gold
,items
,id
。
gold
是用简便方法声明的,id
是用详细语法声明的。详细声明方法中<type>
是必须的,<default>
是可以省略的,items
是一个数组并且成员类型是我们自己定义的类型。还可以给id
添加<dbtype>
描述数据库存储格式,当然要求Reward
对象必须声明成存储在数据库中的对象,下面会介绍语法。
注意
:关于简便语法和详细语法,起到的作用是一样的,只是详细语法可以更详细的指定一些属性,比如<default>
:默认值,<dbtype>
:对应的数据库表的字段类型。如果不用详细语法指定,会使用工具内置默认值,参见前面的关键字表格。建议使用简易描述所有结构,节省开发时间。在特殊需求的地方或者上线前的优化过程中,使用详细描述。
这个功能是可选的,如果需要自由的控制对象的存库行为,不导出sql文件。这个功能可以跳过。
首先明确一个设计概念,struct.json
的一个结构 对应了一条玩家数据(或者非玩家数据,如公会信息),同时对应了数据库中的一张表,这三个是一一对应的,结构名和玩家数据类型和数据库表名是一致的。 工具内置了3个相关结构:
"DBBase": { // 所有入库对象应该继承此类
},
"DBObject": { // 所有玩家个人数据应该继承此类。
"<extends>": "DBBase",
"<nodb>": true,
"<dbindex>": "PRIMARY KEY (`uuid`),KEY `USRID` (`usrid`)",
"uuid": "string",
"usrid": "int64"
},
"DBGlobalObject": { // 所有全局数据应该继承此类(例如公会)。
"<extends>": "DBBase",
"<nodb>": true,
"<dbindex>": "PRIMARY KEY (`uuid`)",
"uuid": "string",
},
DBBase
,继承该结构的所有结构,如果没有指定<nodb>
那么都会在数据库生成一张对应表格。DBObject
所有玩家所属对象的基类,比如物品对象,任务对象等等。带有两个字段uuid
和usrid
, 一个是该对象的唯一id,一个是玩家id。<dbindex>
描述了对应索引,如果某个表有特殊的索引要求,可以拥有自己的<dbindex>
DBGlobalObject
是全局对象的基类,比如公会表。
以上结构除了
DBBase
的结构名不能修改(判断是否导出数据库表的依据),其他都可以根据需求修改 下面是举例
"Item": { // 玩家物品的结构
"<extends>": "DBObject",
"cid":"int",//对应的策划表id
"count":"int", // 物品的数量
"state":"EItemState" // 对应物品的状态。
},
"Task": {
"<extends>": "DBObject",
"cid":"int",
"phase":"int" // 或者定义一个对应枚举
}
Item
,Task
代表玩家的物品和任务数据,数据库中会对应生成一下sql的DDL
DROP TABLE IF EXISTS `Item`;
CREATE TABLE IF NOT EXISTS `Item` (
`uuid` varchar(64) NOT NULL,
`usrid` bigint(20) NOT NULL,
`cid` int(11) NOT NULL COMMENT '对应的策划表id',
`count` int(11) NOT NULL COMMENT ' 物品的数量',
`state` text NOT NULL COMMENT ' 对应物品的状态。 ',
PRIMARY KEY (`uuid`),KEY `USRID` (`usrid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=' 玩家物品的结构';
DROP TABLE IF EXISTS `Task`;
CREATE TABLE IF NOT EXISTS `Task` (
`uuid` varchar(64) NOT NULL,
`usrid` bigint(20) NOT NULL,
`cid` int(11) NOT NULL,
`phase` int(11) NOT NULL COMMENT ' 或者定义一个对应枚举',
PRIMARY KEY (`uuid`),KEY `USRID` (`usrid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
如果是中文注释,注意导入时的编码格式,命令行导入可能需要指定编码
--default-character-set=utf8
另外中心仓库提供了一个unthink模块:think-game-unthink
,封装了玩家数据操作api,实现了玩家数据的加载,缓存(reids)和落地功能,结合该模块,可以让服务器开发只关注于编写逻辑。
描述客户端和服务器通讯接口的文件。 格式如下:
"System": { // System模块
"login": { // 玩家登陆api
"<req>": { // 请求登陆
"name": "string",
"pwd": "string"
},
"<res>": {
"usr": "UserInfo", // 玩家信息(需要在结构描述文件中提前定义)
"error": "EError" //错误类型(需要在枚举文件中提前定义)
}
},
"Logout": { //玩家登出api
"<req>": {},
"<res>": {
"error": "EError"
}
}
},
"Test": { // 测试模块
"Echo": { // 回显
"<req>": {
"msg": "string", // 回显消息
"time": "int" //时间戳
},
"<res>": {
"msg": "string", //客户端发来的消息
"time": "int" //客户端发来的时间戳
}
}
}
System
和Test
是模块名,建议不同的游戏模块采用不同的名字,login
是System
模块下的一个api,统一模块下可以声明多个api。比如login
有<req>
和<res>
对应了http的req
和res
;
如果该工具不能满足项目需求,可以从gi上获取源码进行修改,源码
res
目录就是interface
的目录lib/backend
文件夹下是各种代码的输出逻辑,可以根据需求新增代码输出和修改已有的输出。