Post

Luban快速上手

Luban快速上手

请先安装 dotnet sdk 8.0或更高版本sdk

官方文档 https://www.datable.cn/docs/beginner

官方样例demo https://gitee.com/focus-creative-games/luban_examples

概念术语

一、表结构

img

上图是一张excel表。红框单元格声明该行类型,蓝框单元格为各类型对应的值。数据按照类型,按行填充。

行首单元格 行类型 说明
##var 字段名行 标识该行数据全为字段名。
##type 字段类型行 常见类型有int,string,bool,double,datetime等
##group 分组行 c表示字段属于客户端,s表示属于属于服务器,c,s表示同时属于所有,留空也表示属于所有。
## 注释行 注释行可以有多行,可以出现在任何位置。
数据行 填充数据的行,可以有多行

注意:

  • ##为首的行,顺序可以交换。其中 ####group可选的。
  • 建议使用蛇形字段命名风格。
  • 当标题行字段名为空或者以’#’开头时,这个列会被当作注释列而忽略。

img

二、数据类型

  1. 基本数据类型

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以外的基础数据格式都可以留空,自动取默认值
  1. 自定义数据类型

官方文档参考

2.1 enum类型

枚举(enum)就是“一个字段只能从一组固定选项里选”。这组固定选项里的每一个选项,就叫枚举项。

枚举定义方式:在官方demo中Datas目录下,可在__enums__.xlsx中定义枚举类型。

img

枚举类型:通过enum表中的flags字段区别一个枚举类型是普通枚举,还是flags枚举

img

  • 普通 enum:一个字段只能取一个枚举项。(比如颜色非黑即白)。其枚举项的值可以是0进制或者16进制整数,也可以为其他枚举项的或值,如 **A B C**
  • flags enum:一个字段可以同时包含 多个枚举项(比如权限同时有 READ 和 WRITE)。其枚举项的值为整数时,要求值只能是2的幂(值%2==0)。

img

flags枚举的枚举值,可以直接写成或值,如 A B C

也可以拆成多列,每列下面填0或1,以此标识该flags枚举字段的值。

左右两红框写法是等价的。

2.2 bean类型

bean,即自定义结构体/对象类型,将多个字段(含基础类型、enum、列表、嵌套 bean)打包成一个可复用的数据结构。

bean定义方式:在官方demo中Datas目录下,可在__beans__.xlsx中定义bean类型。

img

典型的bean文件格式如下:

img

字段 可空 默认值 说明
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)情况就有些不同。

现有以下几个结构体 ShapeCircleRectangle。在Luban中如图定义。

type Shape struct{}

type Circle struct{
    Shape
    radius float32
}
type Rectangle struct{
    Shape
    width float32
    height float32
}

现将Shape称为父Bean,Circle、Triangle称为子Bean。

img

根据上图案例可知:

  • 若一字段声明为父 bean 类型,则需先填写子bean类型名/别名,再依次填充子bean属性值。
  • 一条记录/行数据 只能选择一个子 bean,因为一个字段最终只能实例化成一种具体类型。你不可能让同一个 shape 同时既是 Circle 又是 Rectangle

我们通过上述案例也可以看出,Shape与Circle、Triangle…之间存在“继承关系”。Shape是父类,Circle、Triangle等是子类。我们将Shape 这种有1个及以上子类的类型称为多态类型详见此处

  1. 容器类型

容器类型有array/list/set,map。数据填充格式有以下三种

  • 单行格式:合并单元格,把一个容器的所有数据写在一行内,

img

  • array/list/set :一格一个元素
  • map:第一格 key,第二格 value;然后再下一对
  • 多行格式:只要字段名前加’*‘,则表示以多行方式填写数据,每行一个元素,详细见Excel格式

img

img

  • 单格格式:只占一行,一行只占一个单元格。

img

  • array/list/set :(collection**#sep= ** ). 如上图所示
  • map:(map#sep= , ) 如1:hello,4:world
  1. 可空类型

“可空类型”就是这个字段允许没有值;没有值用 null(或留空)表示。

img

这张表的 ##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填法不变
  1. table类型

一张 Excel 配置表对应一个 table,导出后变成程序里可按 id/主键查询。通常会变成 map(key→record) 或者提供 Get(id) 这类接口。

table定义方式:在官方demo中Datas目录下,可在__tables__.xlsx中定义table类型。

img

典型的table文件格式如下:

img

字段 可空 默认值 说明
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 (可空)

img

  • 多主键表(独立索引):mode = “list”, 多个key,各自独立唯一索引。与联合索引写法区别在于使用 “,”来划分key,表示独立关系。
##var full_name value_type define_from_excel input index mode
  - MultiKey TRUE -.xlsx key1,key2,key3 (可空)
  • 单例表:mode = “one”,只有一条行数据的表,适合配置全局只有一份的数据。如背包初始大小、背包最大容量。

img

  • 纵表:大多数表都是横表,一行一记录。纵表就是一列一记录,需要在A1单元格添加##column##vertical表示使用纵表模式。纵表模式比较适合单例表场景,看着舒服一点。

img

在定义表时,mode即使留空也会按照index进行判断:

  • index为空或1个主键 =>推断该表为”map”(用第一个字段或主键当key)
  • index为多个主键 => 推断为”list”
  • 但定义单例表必须显式写 mode="one"(或 singleton)

三、填充格式

官方文档

  1. 限定列格式

限定列格式:多个单元格,显式指定每个字段所占的列,然后读取

img

在 限定列格式 下:多态 bean 和可空 bean 都用一列 $type 来表达“类型/是否为空”,因为列展开后必须有一个明确的开关信号

  • 多态 bean:$type 填具体子类型名(比如 Circle/Rectangle),解析器才知道后面这些列该按哪个子类型的字段来读;不填就不知道该用哪种结构。
  • 可空 bean:$type 用来表态“这条到底有没有这个 bean”。
    • $type 为空/null ⇒ 这个 bean 是空(不存在)
    • $type 有值 ⇒ 这个 bean 存在,并且按该类型读取后续列数据

img

在“限定列”里,如果某个字段最底层是 bean 或 容器,限定列只能圈出“这一坨数据占哪几列”,但没法再细到“里面每个子字段/子元素各占哪几列”。所以解析时会改用 流式读取:从左到右按顺序把子数据一个个读进去,读满/读完就停。

  • bean:按 bean 的字段定义顺序依次读(第 1 个格→第 1 个字段,第 2 个格→第 2 个字段…)
  • 容器:按元素顺序依次读(每个格一个元素;map 就按 key/value 的顺序成对读)
  1. 流式格式

流式格式:当某段子数据没有被进一步限定到具体列时,解析器会在该范围内从左到右按顺序读入(常见于 bean/容器 的场景)。

  • 多个单元格,按顺序读取 由于流式格式无法限定每个字段的范围,当其按顺序读取复合数据的每个元素(字段)时,会跳过读到的所有空白单元格。

img

  • 一个单元格,使用分割符分割后按顺序读取:使用sep分割后,每个聚合数据填在一个单元格内。

img

  1. 多行读取

只有容器类型才能使用多行读取方式。在字段名前加’***‘,表示此字段以多行方式读取。在读取每行数据时,既支持流式格式,也支持列限定格式**。

img

  1. lite格式

在标题头字段添加#format=lite表示使用lite格式。其配置数据中不含字段名,适合复杂的嵌套数据结构。

img

  1. json格式

在标题头字段添加#format=json表示使用json格式。

img

  1. lua格式

在标题头字段添加#format=lua表示使用lua格式。

img

四、数据检验器

官方文档

在使用关系型数据库时(如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表id
  • list,(int#ref=item.TbItem) 要求list的每个元素都必须为有效的item.TbItem表id
    • map,(int#ref=item.TbItem),(string#ref=test.TbString) 要求map的每个key都必须为有效的item.TbItem表id,要求每个value都有有效的test.TbString表id

img

path路径校验

与ref的定义方法相似,但path只能作用于string类型数据。path校验器有几种子类型,参数有细微不同。详见此处

实战案例

在此主要讨论如何将常见的Apollo配置形式转换为Luban配置形式

一、Object型表格

特点是一表只有一行数据

img

其中,有一列数据是引用其他表的数组。被引用的表如下:

img

  • 转换为Luban

需要把被引用的任务配置定义为一个Bean(结构体)

img

然后填充

img

  • 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型表格

特点是一张表有多行数据

img

  • 转换为Luban

img

  • 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型表格

img

  • Luban

Apollo 中的 KV型(Map),在Luban中适合用纵表

img

  • 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"
    ]
  }
]
This post is licensed under CC BY 4.0 by the author.