Luban快速上手
请先安装 dotnet sdk 8.0或更高版本sdk
官方文档 https://www.datable.cn/docs/beginner
官方样例demo https://gitee.com/focus-creative-games/luban_examples
概念术语
一、表结构
上图是一张excel表。红框单元格声明该行类型,蓝框单元格为各类型对应的值。数据按照类型,按行填充。
| 行首单元格 | 行类型 | 说明 |
|---|---|---|
| ##var | 字段名行 | 标识该行数据全为字段名。 |
| ##type | 字段类型行 | 常见类型有int,string,bool,double,datetime等 |
| ##group | 分组行 | c表示字段属于客户端,s表示属于属于服务器,c,s表示同时属于所有,留空也表示属于所有。 |
| ## | 注释行 | 注释行可以有多行,可以出现在任何位置。 |
| 空 | 数据行 | 填充数据的行,可以有多行 |
注意:
- 以
##为首的行,顺序可以交换。其中##,##group是可选的。 - 建议使用蛇形字段命名风格。
- 当标题行字段名为空或者以’#’开头时,这个列会被当作注释列而忽略。
二、数据类型
byte、short、int、long 符合常识,直接填整数
float、double符合常识,直接填浮点数
| 数据类型 | 有效值 | 说明 |
|---|---|---|
| bool | true、false、0、1、是、否 | 大小写不敏感 |
| string | 一切字符串 | 如果希望将字符串中\n替换为换行符,则需要添加上escape=1tag,如string#escape=1 |
| datetime | excel中的内置日期格式yyyy-mm-dd hh:mm:ss 字符串格式yyyy-mm-dd hh:mm 字符串格式。此时秒自动取0yyyy-mm-dd hh 字符串格式。此时分与秒取0yyyy-mm-dd 字符串格式。此时时分秒都取0 | 除了datetime以外的基础数据格式都可以留空,自动取默认值 |
2.1 enum类型
枚举(enum)就是“一个字段只能从一组固定选项里选”。这组固定选项里的每一个选项,就叫枚举项。
枚举定义方式:在官方demo中Datas目录下,可在__enums__.xlsx中定义枚举类型。
枚举类型:通过enum表中的flags字段区别一个枚举类型是普通枚举,还是flags枚举。
-
普通 enum:一个字段只能取一个枚举项。(比如颜色非黑即白)。其枚举项的值可以是0进制或者16进制整数,也可以为其他枚举项的或值,如 **A B C** - flags enum:一个字段可以同时包含 多个枚举项(比如权限同时有 READ 和 WRITE)。其枚举项的值为整数时,要求值只能是2的幂(值%2==0)。
| flags枚举的枚举值,可以直接写成或值,如 A | B | C 。 |
也可以拆成多列,每列下面填0或1,以此标识该flags枚举字段的值。
左右两红框写法是等价的。
2.2 bean类型
bean,即自定义结构体/对象类型,将多个字段(含基础类型、enum、列表、嵌套 bean)打包成一个可复用的数据结构。
bean定义方式:在官方demo中Datas目录下,可在__beans__.xlsx中定义bean类型。
典型的bean文件格式如下:
| 字段 | 可空 | 默认值 | 说明 |
|---|---|---|---|
| full_name | 否 | 类型全名,即可以是不包含命名空间,如 Hello,也可以包含命名空间如 item.Item | |
| parent | 是 | 父类名,如果名字不包含命名空间,会优先从当前命名空间找,再从全局命名空间找 | |
| valueType | 是 | FALSE | 对应schema逻辑结构中isValueType字段 |
| sep | 是 | ||
| alias | 是 | FALSE | |
| comment | 是 | ||
| group | 是 | ||
| tags | 是 | ||
| fields | 否 | 字段列表 |
现有一个结构体Item,我们要将其写成一个bean。如图所示。
type Item struct{
id int
count int
desc string
}
对于这种属性字段都是基本数据类型的bean,填写起来很简单。但如果一个bean的字段是其他bean类型(姑且称之为子bean)情况就有些不同。
现有以下几个结构体 Shape、Circle、Rectangle。在Luban中如图定义。
type Shape struct{}
type Circle struct{
Shape
radius float32
}
type Rectangle struct{
Shape
width float32
height float32
}
现将Shape称为父Bean,Circle、Triangle称为子Bean。
根据上图案例可知:
- 若一字段声明为父 bean 类型,则需先填写子bean类型名/别名,再依次填充子bean属性值。
- 一条记录/行数据 只能选择一个子 bean,因为一个字段最终只能实例化成一种具体类型。你不可能让同一个
shape同时既是Circle又是Rectangle。
我们通过上述案例也可以看出,Shape与Circle、Triangle…之间存在“继承关系”。Shape是父类,Circle、Triangle等是子类。我们将Shape 这种有1个及以上子类的类型称为多态类型。详见此处
容器类型有array/list/set,map。数据填充格式有以下三种:
- 单行格式:合并单元格,把一个容器的所有数据写在一行内,
- array/list/set :一格一个元素
- map:第一格 key,第二格 value;然后再下一对
- 多行格式:只要字段名前加’*‘,则表示以多行方式填写数据,每行一个元素,详细见Excel格式
- 单格格式:只占一行,一行只占一个单元格。
array/list/set :(collection**#sep= ** ). 如上图所示 - map:(map#sep= , ) 如
1:hello,4:world
“可空类型”就是这个字段允许没有值;没有值用
null(或留空)表示。
这张表的 ##type 里很多字段写成了 bool? / int? / datetime? / Quality? / Item? / Shape?。这里的 ? 表示 可空(nullable):这一列可以是一个正常值,也可以是“空”。
注意:
- 除了容器类型外,几乎所有类型都可以加
?。 - 填
null表示空值。对于 原子类型(int/bool/float/datetime…):单元格留空通常也等价于null。 - 对于
string?,如果你想表达 空字符串(长度为 0),必须填:"" - 对于非多态bean 。直接填null表示该行数据为空。非空时,为了让解析器明确“这是一个 bean 的开始”,常见写法是用
{}作为非空标记,然后按顺序填字段:
例:
{},1001,10,道具1(意思是:这个 Item 非空,然后依次是 id/count/desc)
- 多态bean填法不变
一张 Excel 配置表对应一个 table,导出后变成程序里可按 id/主键查询。通常会变成 map(key→record) 或者提供
Get(id)这类接口。
table定义方式:在官方demo中Datas目录下,可在__tables__.xlsx中定义table类型。
典型的table文件格式如下:
| 字段 | 可空 | 默认值 | 说明 |
|---|---|---|---|
| full_name | 否 | 类型全名,即可以是不包含命名空间,如 Hello,也可以包含命名空间如 item.Item | |
| value_type | 否 | 表记录类型 | |
| read_schema_from_file | 是 | FALSE | 是否从input的excel文件的标题头行读取value_type定义。此时不能再定义value_type对应的bean,否则会出现定义重复的错误 |
| input | 否 | 对应schema逻辑结构中inputFiles字段 | |
| index | 是 | ||
| mode | 是 | ||
| comment | 是 | ||
| group | 是 | ||
| tags | 是 | ||
| output | 否 | 对应schema逻辑结构中outputFileName字段 |
table类型:
-
无主键表:mode=”list”并且index为空,表示无主键表。
-
单主键表:有且只有一个主键(index为空时默认取第一个字段当主键)
-
多主键表(联合索引):mode = “list”, 多个key构成联合唯一主键。使用”+”分割key,表示联合关系。
| ##var | full_name | value_type | define_from_excel | input | index | mode |
|---|---|---|---|---|---|---|
| - | UnionMultiKey | TRUE | -.xlsx | key1+key2+key3 | (可空) |
- 多主键表(独立索引):mode = “list”, 多个key,各自独立唯一索引。与联合索引写法区别在于使用 “,”来划分key,表示独立关系。
| ##var | full_name | value_type | define_from_excel | input | index | mode |
|---|---|---|---|---|---|---|
| - | MultiKey | TRUE | -.xlsx | key1,key2,key3 | (可空) |
- 单例表:mode = “one”,只有一条行数据的表,适合配置全局只有一份的数据。如背包初始大小、背包最大容量。
- 纵表:大多数表都是横表,一行一记录。纵表就是一列一记录,需要在A1单元格添加
##column或##vertical表示使用纵表模式。纵表模式比较适合单例表场景,看着舒服一点。
在定义表时,
mode即使留空也会按照index进行判断:
index为空或1个主键 =>推断该表为”map”(用第一个字段或主键当key)index为多个主键 => 推断为”list”- 但定义单例表必须显式写
mode="one"(或 singleton)
三、填充格式
限定列格式:多个单元格,显式指定每个字段所占的列,然后读取
在 限定列格式 下:多态 bean 和可空 bean 都用一列 $type 来表达“类型/是否为空”,因为列展开后必须有一个明确的开关信号。
- 多态 bean:
$type填具体子类型名(比如Circle/Rectangle),解析器才知道后面这些列该按哪个子类型的字段来读;不填就不知道该用哪种结构。 - 可空 bean:
$type用来表态“这条到底有没有这个 bean”。$type为空/null⇒ 这个 bean 是空(不存在)$type有值 ⇒ 这个 bean 存在,并且按该类型读取后续列数据
在“限定列”里,如果某个字段最底层是 bean 或 容器,限定列只能圈出“这一坨数据占哪几列”,但没法再细到“里面每个子字段/子元素各占哪几列”。所以解析时会改用 流式读取:从左到右按顺序把子数据一个个读进去,读满/读完就停。
- bean:按 bean 的字段定义顺序依次读(第 1 个格→第 1 个字段,第 2 个格→第 2 个字段…)
- 容器:按元素顺序依次读(每个格一个元素;map 就按 key/value 的顺序成对读)
流式格式:当某段子数据没有被进一步限定到具体列时,解析器会在该范围内从左到右按顺序读入(常见于 bean/容器 的场景)。
- 多个单元格,按顺序读取 由于流式格式无法限定每个字段的范围,当其按顺序读取复合数据的每个元素(字段)时,会跳过读到的所有空白单元格。
- 一个单元格,使用分割符分割后按顺序读取:使用sep分割后,每个聚合数据填在一个单元格内。
只有容器类型才能使用多行读取方式。在字段名前加’***‘,表示此字段以多行方式读取。在读取每行数据时,既支持流式格式,也支持列限定格式**。
在标题头字段添加#format=lite表示使用lite格式。其配置数据中不含字段名,适合复杂的嵌套数据结构。
在标题头字段添加#format=json表示使用json格式。
在标题头字段添加#format=lua表示使用lua格式。
四、数据检验器
在使用关系型数据库时(如mysql),我们可以为表加上参照完整性约束(外键)。在Luban配置表中,我们也可以为配置表字段加上数据校验实现类似的效果。例如item_id字段必须是有一个有效的item表的id。
luban中最常用的数据校验器有两种:
- ref 引用校验
- path 路径校验
ref引用
就是类似外键校验的效果,type代表当前单元格元素的类型,target用来指明被引用表的“主键”。
格式: {type}#ref={target}
| mode=”map” | mode=”list” | mode=”one” / “singleton” | |
|---|---|---|---|
| 导出形态 | 字典:key -> record | 列表:[record…](可带索引) | 单例:只有一条 record |
| index(主键) | 0 或 1 个 | 0 个(无主键 list)或多个(联合/独立 ) | 无主键 |
| mode 留空时怎么推断 | index 为空或 1 个主键 ⇒ 推断 map(并且 index 为空时默认取 valueType 第一个字段当 key) | index 多个主键 ⇒ 推断 list | 不会因为“只有一条行数据”自动推断成 one;要 one 必须显式写 mode=”one” |
| ref 目标写法 | ref=表名(因为只有一个 key) | ref=索引名@表名(要指明用哪个主键索引) | ref=map字段名@表名(引用单例里某个 map 成员字段) |
- mode=map target填表名,如
ref=item.TbItem - mode=list的表。要求此表至少有1个及以上主键。此时target必须指定是哪个主键,如
ref=index1@test.TbMultiKey - mode=one的表。由于单例表只有一个记录,此时必须指定一个map类型的成员字段,如
ref=items@test.TbTestSingleton
type可以出现在任意位置,但要求它必须是简单数据类型(即int、string、枚举之类)。例如以下皆合法:
int#ref=item.TbItem要求这个int类型数据必须为有效的item.TbItem表idlist,(int#ref=item.TbItem)要求list的每个元素都必须为有效的item.TbItem表idmap,(int#ref=item.TbItem),(string#ref=test.TbString)要求map的每个key都必须为有效的item.TbItem表id,要求每个value都有有效的test.TbString表id
path路径校验
与ref的定义方法相似,但path只能作用于string类型数据。path校验器有几种子类型,参数有细微不同。详见此处
实战案例
在此主要讨论如何将常见的Apollo配置形式转换为Luban配置形式
一、Object型表格
特点是一表只有一行数据
- Apollo样例. 地址
其中,有一列数据是引用其他表的数组。被引用的表如下:
- 转换为Luban
需要把被引用的任务配置定义为一个Bean(结构体)
然后填充
- json数据
[
{
"activity_id": 1,
"type": 1,
"edition": 1,
"task_group": [
{
"id": 1,
"score_target": 100,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
10,
5,
500,
300
]
},
{
"id": 2,
"score_target": 200,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
10,
5,
500,
300
]
},
{
"id": 3,
"score_target": 500,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
30,
10,
1500,
900
]
},
{
"id": 4,
"score_target": 1000,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
30,
15,
2500,
1500
]
},
{
"id": 5,
"score_target": 2000,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
70,
30,
5000,
3000
]
},
{
"id": 6,
"score_target": 3000,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
90,
30,
5000,
3000
]
},
{
"id": 7,
"score_target": 5000,
"item_ids": [
1000028,
1010006,
1000002,
1010009
],
"item_num": [
200,
60,
10000,
6000
]
}
],
"activity_background_path": "测试路径2",
"help_text": "i18n:recharge_desc_20",
"activity_name": "i18n:recharge_name_20",
"activity_introduction": "i18n:recharge_tips_20",
"score_item_id": 1010027
}
]
二、Array型表格
特点是一张表有多行数据
- Apollo样例 地址
- 转换为Luban
- json数据
[
{
"id": 1,
"shop_id": 2801,
"item_num": 10000,
"first_multiplier": 2,
"gear": 1
},
{
"id": 2,
"shop_id": 2802,
"item_num": 5000,
"first_multiplier": 2,
"gear": 2
},
{
"id": 3,
"shop_id": 2803,
"item_num": 2000,
"first_multiplier": 2,
"gear": 3
},
{
"id": 4,
"shop_id": 2804,
"item_num": 1000,
"first_multiplier": 2,
"gear": 4
},
{
"id": 5,
"shop_id": 2805,
"item_num": 500,
"first_multiplier": 2,
"gear": 5
},
{
"id": 6,
"shop_id": 2806,
"item_num": 100,
"first_multiplier": 2,
"gear": 6
}
]
三、Map型表格
- Apollo样例 地址
- Luban
Apollo 中的 KV型(Map),在Luban中适合用纵表
- json数据
[
{
"migration_cool_down_duration": 30000,
"search_monster_create_radius": 10,
"building_idle_time": 60,
"building_recover_durability_interval": 30,
"main_city_shield_items": [
1002004,
1002005,
1002006
],
"main_city_shield_time": [
28800,
43200,
86400
],
"shield_cost_item_nums": [
1500,
2000,
4000
],
"assemble_waiting_duration": [
60000,
300000,
600000,
1800000
],
"test-string_1": "测试文本1",
"test-string_2": "测试文本2",
"test-string_array_1": [
"测试文本1",
"测试文本2"
],
"test-string_array_2": [
"测试文本1",
"测试文本2"
]
}
]