Skip to content
导航栏

数据模型

Yao 可以读取数据模型定义,实现数据迁移、元数据原子操作、元数据输入校验和元数据管理后台。元数据原子操作方法被映射为处理器(process),支持模型间数据关系映射,可在数据流(Flow)和接口(API)中访问查询。如采用golang语言开发业务插件(Plugin),可以使用 Package Gou 访问模型的各个方法, 后续将提供 NodeJS 等语言 SDK。

1 命名规范

数据模型描述文件是以 小写英文字母 命名的 JSON 文本文件 <name>.mod.json

文件夹 (相对应用模型根目录)文件名模型名称Process (在 API /Flow 中引用)
/name.mod.jsonnamemodels.name.<process>
/group/name.mod.jsongorup.namemodels.gorup.name.<process>
/group1/group2/name.mod.jsongorup1.gorup2.namemodels.gorup1.group2.name.<process>

2 文档结构

数据模型定义文档,由基础信息、数据表、字段定义、索引定义、关系映射、默认数据和配置部分构成。查看完整示例

json
{
  "name": "用户",
  "table": {},
  "columns": [],
  "indexes": [],
  "relations": {},
  "values": [],
  "option": {}
}
字段类型说明必填项
nameString模型中文名称
tableObject数据表定义
columnsArray<Object>字段定义
indexesArray<Object>索引定义
relations[key:String]:Object关系映射
valuesArray<Object>默认数据
optionObject配置选型

2.1 基础信息

基础信息包含 namedescriptionversion 等字段,主要用于在开发平台中呈现检索。

json
{
  "name": "用户",
  "version": "1.0.0",
  "description": "网站用户元数据模型",
  "table": {},
  "columns": [],
  "indexes": []
}
字段类型说明必填项
nameString中文名称
versionString版本号 x.x.x 遵循 Semantic Versioning 2.0.0 规范
tunString象传智慧共享仓库 Tun (tun.iqka.com) 地址 如:microcity/petstore/user
descriptionString数据模型详细介绍
authorString数据模型作者
emailString数据模型作者联系方式
licenseString数据模型共享协议如 MIT
homepageString数据模型提供者官网

2.2 数据表 table

数据表格包含 namecomment 等字段,定义模型存储在数据库中的数据表名称、注释等信息。支持 MySQL, PostgreSQLSQLiteXun Database 或第三方提供驱动的数据库。

json
{
  "table": {
    "name": "user",
    "comment": "用户表",
    "engine": "InnoDB"
  }
}
字段类型说明必填项
nameString数据表名称
commentString数据表注释中文名
engineString数据表引擎(MySQL ONLY) 许可值 InnoDB, MyISAM

2.3 字段定义 columns

一个模型可以包含多个字段定义,每个字段定义包含 labelnametypevalidations 等信息。

json
{
  "columns": [
    { "label": "ID", "name": "id", "type": "ID" },
    {
      "label": "厂商",
      "name": "manu_id",
      "type": "bigInteger",
      "length": 50,
      "comment": "所属厂商",
      "nullable": true,
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["integer"],
          "message": "{{input}}类型错误, {{label}}应为数字"
        },
        {
          "method": "min",
          "args": [0],
          "message": "{{label}}应大于0"
        }
      ]
    },
    {
      "label": "手机号",
      "name": "mobile",
      "type": "string",
      "length": 50,
      "comment": "手机号",
      "index": true,
      "crypt": "AES",
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^1[3-9]\\d{9}$"],
          "message": "{{input}}格式错误"
        }
      ]
    }
  ]
}
字段类型说明必填项
nameString字段名称,对应数据表中字段名称
typeString字段类型,
labelString字段显示名称,用于在管理表单,开发平台等成场景下呈现
commentString字段注释,对应数据表中字段注释
titleString字段标题,可用于开发平台中呈现
descriptionString字段介绍,可用于开发平台中呈现
lengthInteger字段长度,对 string 等类型字段有效
precisionInteger字段位数(含小数位),对 floatdecimal 等类型字段有效
scaleInteger字段小数位位数,对 floatdecimal 等类型字段有效
optionArray<String>字段许可值,对 enum 类型字段有效
defaultString|Integer|Float字段默认值
default_rawString字段默认值,支持数据库函数,如 NOW() default 和 default_raw 同时存在 default_raw 优先级高
cryptString字段加密存储方式(MySQL Only)。许可值 AES, PASSWORD
nullableBool字段是否可以为空,默认为 false
indexBool字段是否为索引,默认为 false
uniqueBool字段是否为唯一索引,默认为 false , 如为 true 无需同时将 index 设置为 true
primaryBool字段是否为主键,每张表至多一个主键字段。默认为 false
validationsArray<Object>字段校验规则

字段类型

类型说明可选参数MySQL 字段类型
string字符串lengthVARCHAR(length )
char字符lengthCHAR (length )
text文本TEXT
mediumText中文本MEDIUMTEXT
longText长文本LONGTEXT
binary二进制数据VARBINARY
date日期DATE
datetime日期时间lengthDATETIME
datetimeTz带时区的日期时间lengthDATETIME
time时间lengthTIME
timeTz带时区的时间lengthTIME
timestamp时间戳lengthTIMESTAMP
timestampTz带时区的时间戳lengthTIMESTAMP
tinyInteger微整型TINYINT
tinyIncrements无符号微整型+自增TINYINT UNSIGNED AUTO_INCREMENT
unsignedTinyInteger无符号微整型TINYINT UNSIGNED
smallInteger小整型SMALLINT
smallIncrements无符号小整型+自增SMALLINT UNSIGNED AUTO_INCREMENT
unsignedSmallInteger无符号小整型SMALLINT UNSIGNED
integer整型INT
increments无符号整型+自增INT UNSIGNED AUTO_INCREMENT
unsignedInteger无符号整型INT UNSIGNED
bigInteger长整型BIGINT
bigIncrements无符号长整型+自增BIGINT UNSIGNED AUTO_INCREMENT
unsignedBigInteger无符号长整型BIGINT UNSIGNED
id长整型+自增BIGINT UNSIGNED AUTO_INCREMENT
ID长整型+自增(同 id)BIGINT UNSIGNED AUTO_INCREMENT
decimal小数(一般用于存储货币)precisionscaleDECIMAL(precision,scale)
unsignedDecimal无符号小数 (一般用于存储货币)precisionscaleDECIMAL (precision,scale) UNSIGNED
float浮点数precisionscaleFLOAT (precision,scale)
unsignedFloat无符号浮点数precisionscaleFLOAT (precision,scale) UNSIGNED
double双精度precisionscaleDOUBLE (precision,scale)
unsignedDouble无符号双精度precisionscaleDOUBLE (precision,scale) UNSIGNED
boolean布尔型BOOLEAN
enum枚举型optionENUM(option...)
jsonJSON 文本JSON
JSONJSON 文本(同 json)JSON
jsonbJSON (二进制格式存储)JSON
JSONBJSON (二进制格式存储 同 jsonb)JSON
uuidUUID 格式字符串VARCHAR(36)
ipAddressIP 地址INT
macAddressMAC 地址BIGINT
year年份SMALLINT

校验方法

一个字段可以包含多条校验规则,每条校验规则可以选用 min,max, pattern, typeof 等校验方法。

json
{
  "columns": [
    {
      "label": "手机号",
      "name": "mobile",
      "type": "string",
      "length": 50,
      "comment": "手机号",
      "index": true,
      "crypt": "AES",
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^1[3-9]\\d{9}$"],
          "message": "{{input}}格式错误"
        }
      ]
    }
  ]
}

校验规则定义

字段类型说明必填项
methodString校验方法名称,可选值 typeof, pattern
argsArray<String|Integer|Float>校验方法参数,例如 [20], ["^1[3-9]\\d{9}$"]
messageString如校验不通过,返回的错误提示。支持使用 {{<name>}} 引用字段信息, 如{{label}}将被替换为字段 label中定义的数值; {{input}} 被替换为用户输入数值。

校验方法清单

校验方法参数说明示例
typeof[<String>] 许可值 string, integer, float, number, datetime, timestamp数值类型{"method":"typeof", "args":["integer"]}
min[<Integer|Float>]最小值{"method":"min", "args":[20]}
max[<Integer|Float>]最大值{"method":"max", "args":[0.618]}
enum[String...]枚举选项{"method":"enum", "args":["enabled", "disabled"]}
pattern[String]正则匹配{"method":"pattern", "args":["^1[3-9]\\d{9}$"]}
minLength[<Integer>]最小长度{"method":"minLength", "args":[20]}
maxLength[<Integer>]最大长度{"method":"maxLength", "args":[100]}
email[]邮箱{"method":"email", "args":[]}
mobile[<String>] 区域列表(可选), 默认为 cn 许可值 cn,us手机号{"method":"mobile", "args":[]} {"method":"mobile", "args":["us"]}

加密方式

当前支持 AESPASSWORD 两种字段数值加密存储算法,其中 AES 仅支持 MySQL 数据库。

加密算法说明是否可逆
AESAES 加密,需设定 XIANG_DB_AESKEY
PASSWORDPASSWORD HASH 加密

保留字

以下名称不能用于字段名称。

保留字说明
created_at用于记录创建时间戳
updated_at用于记录更新时间戳
deleted_at用于记录软删除标记
__restore_data用于软删除时备份唯一字段数值

2.4 索引定义 indexes

一个数据模型,可以包含多个索引。对于单一索引,推荐在字段定义时,使用 indexuniqueprimary 修饰符定义,对于复合索引或全文检索索引,在 indexes 中定义。

json
{
  "indexes": [
    {
      "comment": "厂商用户",
      "name": "manu_id_mobile_unique",
      "columns": ["manu_id", "mobile"],
      "type": "unique"
    },
    {
      "comment": "简历全文检索",
      "name": "resume_fulltext",
      "columns": ["resume"],
      "type": "fulltext"
    }
  ]
}
字段类型说明必填项
nameString索引名称。命名规范为 字段 1_字段 2_字段 n_索引类型
typeString索引类型 许可值 index 索引, unique 唯一索引, primary 主键, fulltext 全文检索
columnsArray<String>关联字段名称列表(顺序有关) ["字段 1","字段 2"]["字段 2","字段 1"] 不同
commentString索引注释

2.5 关系映射 relations

当前关系映射部分为 beta 版本, 可能会依据用户使用反馈,调整数据结构

数据模型支持一对一、一对多两种关系映射,可以通过定义映射关系将多个数据模型关联,查询时使用with参数即可同时返回关联模型数据。

映射关系名称关系说明
hasOne一对一模型 A 与模型 B 通过一对一关联
hasMany一对多模型 A 与模型 B 通过一对多关联

关联关系使用 [key:String]:Object Relation 数据结构定义 ( {"关联名称1":{}, "关联名称2":{}}, 关联名称为 小写英文字母 )

在模型文件 user.json 中定义

json
{
  "relations": {
    "manu": {
      "type": "hasOne",
      "model": "manu",
      "key": "id",
      "foreign": "manu_id",
      "query": { "select": ["name", "short_name", "type"] }
    },
    "addresses": {
      "type": "hasMany",
      "model": "address",
      "key": "user_id",
      "foreign": "id",
      "query": {
        "select": ["province", "city", "location", "status"],
        "pagesize": 20
      }
    },
    "mother": {
      "type": "hasOneThrough",
      "links": [
        {
          "type": "hasOne",
          "model": "friends",
          "key": "user_id",
          "foreign": "user.id",
          "query": {
            "select": ["status", "type", "friend_id"],
            "wheres": [
              {
                "column": "type",
                "value": "monther"
              }
            ]
          }
        },
        {
          "type": "hasOne",
          "model": "user",
          "key": "id",
          "foreign": "user_mother_friends.friend_id",
          "query": {
            "select": ["name", "id", "status", "type", "secret", "extra"],
            "withs": {
              "manu": {},
              "roles": {},
              "address": {}
            }
          }
        }
      ]
    },
    "roles": {
      "type": "hasManyThrough",
      "links": [
        {
          "type": "hasMany",
          "model": "user_roles",
          "key": "user_id",
          "foreign": "id",
          "query": {
            "select": ["status"],
            "pagesize": 20
          }
        },
        {
          "type": "hasOne",
          "model": "role",
          "key": "id",
          "foreign": "role_id",
          "query": {
            "select": ["name", "label", "permission"]
          }
        }
      ]
    }
  }
}

Object Relation

字段类型说明必填项
typeString关系类型 许可值 hasOne, hasOneThrough , hasMany , hasManyThrough
keyString关联模型的关联字段名称
modelString关联模型名称
foreignString当前模型的关联字段名称
queryObject QueryParam关系查询参数默认值。如在查询时未指定关联查询参数,则替使用在模型中定义的查询参数
linksArray<Object Relation>hasOneThroughhasManyThrough 多表关联关系定义

2.5.1 hasOne 一对一

数据模型 user 数据表结构如下:

字段类型说明
idID用户 ID
manu_idbigInteger所属厂商 ID
namestring姓名

数据模型 manu 数据表结构如下:

字段类型说明
idID厂商 ID
shortstring厂商简称
companystring公司名称

在查询用户数据时,可以同时列出厂商信息或可以按关联厂商进行查询,则可以在定义 user 数据模型时,设定与 manu 数据模型关系.

在模型文件 user.json 中定义

json
{
  "name": "用户",
  "relations": {
    "manu": {
      "type": "hasOne",
      "model": "manu",
      "key": "id",
      "foreign": "manu_id",
      "query": { "select": ["short", "company"] }
    }
  }
}

说明

1.将关系映射类型指定为 hasOne

2.将关联模型 model 设置为 manu

3.将关联模型 key 设置为 id, 即:manu.id 引擎处理时,自动关联 manu 表的 id 字段。

4.将 foreign 设置为 manu_id, 即: user.manu_id 引擎处理时,将 manu.iduser.manu_id 关联

5.可以在 query 字段中,设置默认的查询条件,如指定读取的字段等。

引擎解析后的 SQL 为:

sql
SELECT `user`.*,
  `manu`.`short` AS `user_manu_short`,
  `manu`.`company` AS `user_manu_company`,
  FROM `user` AS `user`
  LEFT JOIN `manu` as `user_manu` ON `user_manu`.`id` = `user`.`manu_id`

访问

在调用 process 查询时,传入 with 参数,即可同时返回厂商信息

bash
GET  /api/user/find/1?with=manu&manu.select=id,short

2.5.2 hasMany 一对多

数据模型 user 数据表结构如下:

字段类型说明
idID用户 ID
manu_idbigInteger所属厂商 ID
namestring姓名

数据模型 address 数据表结构如下:

字段类型说明
idID地址 ID
user_idbigInteger所属用户 ID (关联 user.id )
locationstring详细地址

对于类似一个用户有多个通信地址的业务场景,可以通过建立一对多的映射关系来实现。

在模型文件 user.json 中定义

json
{
  "name": "用户",
  "relations": {
    "addresses": {
      "type": "hasMany",
      "model": "address",
      "key": "user_id",
      "foreign": "id",
      "query": {
        "select": ["location"],
        "limit": 20
      }
    },
}

说明

1.将关系映射类型指定为 hasMany

2.将关联模型 model 设置为 address

3.将关联模型 key 设置为 user_id, 即:address.user_id 引擎处理时,自动关联 address 表的 user_id 字段。

4.将 foreign 设置为 id, 即: user.id 引擎处理时,将 user.idaddress.user_id 关联

5.可以在 query 字段中,设置默认的查询条件,如指定读取的字段等。对于 hasMany 建议设置默认 limit 约束返回数据条目

对于 hasMany 类型关系映射,引擎将分两次查询。首次查询出主模型以及关联的 ID 列表,第二次根据 ID 列表,查询关联数据信息。

第一次查询:

sql
SELECT `user`.* FROM `user` AS `user`

引擎处理结果,并读取 user.id

第二次查询:

sql
SELECT  `address`.`user_id`, `address`.`location` FROM `address` AS `address`
  WHERE `address`.`user_id` IN (<user.id...>)

引擎处理结果,关联用户地址信息

访问

在调用 process 查询时,传入 with 参数,即可同时取得 addresses 的关联信息

bash
GET  /api/user/find/1?with=addresses

2.7 配置选项 option

option 中设定模型配置参数

json
{
  "name": "地址",
  "option": {
    "timestamps": true,
    "soft_deletes": true
  }
}
选项类型说明
timestampsBool为 true 时, 自动创建 created_atupdated_at 字段,并在插入和更新数据时,标记对应操作时间
soft_deletesBool为 true 时, 自动创建 deleted_at__restore_data 字段,数据删除时,备份唯一字段数据,并标记操作时间,查询时忽略已标记删除的数据

3 查询参数 QueryParam

在模型关联关系定义和调用处理器时,通过 Object QueryParam 描述查询条件。

json
{
  "select": ["id", "name", "mobile", "status"],
  "withs": {
    "manu": {
      "query": {
        "select": ["name", "short_name", "status"]
      }
    },
    "addresses": {}
  },
  "wheres": [
    { "column": "status", "value": "enabled" },
    { "rel": "manu", "column": "status", "value": "enabled" },
    {
      "wheres": [
        { "column": "name", "value": "%张三%", "op": "like" },
        {
          "method": "orwhere",
          "column": "name",
          "value": "%李四%",
          "op": "like"
        }
      ]
    }
  ],
  "orders": [
    { "column": "id", "option": "desc" },
    { "rel": "manu", "column": "name" }
  ],
  "limit": 2
}

应用引擎将以上查询条件解析为如下 SQL :

SQL
SELECT
  `user`.`id`,`user`.`name`,`user`.`mobile`,`user`.`status`,
  `user_manu`.`name` AS `user_manu_name`,
  `user_manu`.`short_name` AS `user_manu_short_name` ,
  `user_manu`.`status` AS `user_manu_status`
FROM `user` AS `user`
LEFT JOIN `manu` AS `user_manu` ON `user_manu`.`id` = `user`.`manu_id`
WHERE  `user`.`status` = 'enabled'
AND `user_manu`.`status` = 'enabled'
AND (
   `user`.`name` like '%张三%' OR `user`.`name` like '%李四%'
)
ORDER BY `user`.`id` desc, `user_manu`.`name` asc
LIMIT 2

3.1 数据结构

QueryParam

字段类型说明必填项
selectArray<String>选择字段清单
wheresArray<Object Where>查询条件
ordersArray<Object Order>排序条件
limitInteger返回记录条目
pageInteger当前页码
pagesizeInteger每页显示记录数量
withs[key:String]:Object With读取关联模型

Object Where

字段类型说明必填项
relString如按关联模型的字段查询,则填写关联模型名称
columnString字段名称
methodString查询方法 where,orwhere
opString匹配关系 eq,like,in,gt
valueAny匹配数值
wheresArray<Object Where>分组查询
查询方法说明
whereWHERE 字段 = 数值, WHERE 字段 >= 数值
orwhere... OR WHERE 字段 = 数值
匹配关系说明
eq默认值 等于 WHERE 字段 = 数值
like匹配 WHERE 字段 like 数值
gt大于 WHERE 字段 > 数值
ge大于等于 WHERE 字段 >= 数值
lt小于 WHERE 字段 < 数值
le小于等于 WHERE 字段 <= 数值
null为空 WHERE 字段 IS NULL
notnull不为空 WHERE 字段 IS NOT NULL
in列表包含 WHERE 字段 IN (数值...)

Object Order

字段类型说明必填项
relString如按关联模型的字段排序,则填写关联模型名称
columnString字段名称
optionString排序方式,默认为 asc desc, asc

Object With

字段类型说明必填项
nameString关联关系名称
queryObject QueryParam查询参数

3.2 URL Query String 与 QueryParam 对照表

查询条件可以通过 URL Query String 传入

4 处理器(process)

数据模型提供一组原子操作处理器 process , 这些处理器可用于服务接口(API)和数据流(Flow)编排。

处理器引用方式说明
findmodels.模型名称.Find查询单条记录
getmodels.模型名称.Get按条件查询, 不分页
paginatemodels.模型名称.Paginate按条件查询, 分页
createmodels.模型名称.Create创建单条记录, 返回新创建记录 ID
updatemodels.模型名称.Update更新单条记录
savemodels.模型名称.Save保存单条记录, 不存在创建记录, 存在更新记录, 返回记录 ID
deletemodels.模型名称.Delete删除单条记录(标记删除)
destroymodels.模型名称.Destroy删除单条记录(真删除)
insertmodels.模型名称.Insert插入多条记录, 返回插入行数
updatewheremodels.模型名称.UpdateWhere按条件更新记录, 返回更新行数
deletewheremodels.模型名称.DeleteWhere按条件删除数据, 返回删除行数(标记删除)
destroywheremodels.模型名称.DestroyWhere按条件删除数据, 返回删除行数(真删除)
eachsavemodels.模型名称.EachSave保存多条记录, 不存在创建记录, 存在更新记录, 返回记录 ID 集合
eachsaveAfterDeletemodels.模型名称.EachSaveAfterDelete删除一组给定 ID 的记录后,保存多条记录, 不存在创建记录, 存在更新记录, 返回记录 ID 集合

5. 完整示例

完整示例保存在 examples 目录

json
{
  "name": "用户",
  "table": {
    "name": "user",
    "comment": "用户表",
    "engine": "InnoDB"
  },
  "columns": [
    { "label": "ID", "name": "id", "type": "ID" },
    {
      "label": "厂商",
      "name": "manu_id",
      "type": "bigInteger",
      "length": 50,
      "comment": "所属厂商",
      "nullable": true,
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["integer"],
          "message": "{{input}}类型错误, {{label}}应为数字"
        },
        {
          "method": "min",
          "args": [0],
          "message": "{{label}}应大于0"
        }
      ]
    },
    {
      "label": "类型",
      "name": "type",
      "type": "enum",
      "option": ["admin", "staff", "user"],
      "comment": "账号类型 admin 管理员, staff 员工, user 用户",
      "default": "staff",
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "enum",
          "args": ["admin", "staff", "user"],
          "message": "{{input}}不在许可范围, {{label}}应该为 admin/staff/user"
        }
      ]
    },
    {
      "label": "手机号",
      "name": "mobile",
      "type": "string",
      "length": 50,
      "comment": "手机号",
      "index": true,
      "crypt": "AES",
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^1[3-9]\\d{9}$"],
          "message": "{{input}}格式错误"
        }
      ]
    },
    {
      "label": "登录密码",
      "name": "password",
      "type": "string",
      "length": 256,
      "comment": "登录密码",
      "crypt": "PASSWORD",
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "minLength",
          "args": [6],
          "message": "{{label}}应该由6-18位,大小写字母、数字和符号构成"
        },
        {
          "method": "maxLength",
          "args": [18],
          "message": "{{label}}应该由6-18位,大小写字母、数字和符号构成"
        },
        {
          "method": "pattern",
          "args": ["[0-9]+"],
          "message": "{{label}}应该至少包含一个数字"
        },
        {
          "method": "pattern",
          "args": ["[A-Z]+"],
          "message": "{{label}}应该至少包含一个大写字母"
        },
        {
          "method": "pattern",
          "args": ["[a-z]+"],
          "message": "{{label}}应该至少包含一个小写字母"
        },
        {
          "method": "pattern",
          "args": ["[@#$&*]+"],
          "message": "{{label}}应该至少包含一个符号"
        }
      ]
    },
    {
      "label": "姓名",
      "name": "name",
      "type": "string",
      "length": 80,
      "comment": "姓名",
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "minLength",
          "args": [2],
          "message": "{{label}}至少需要2个字"
        },
        {
          "method": "maxLength",
          "args": [40],
          "message": "{{label}}不能超过20个字"
        }
      ]
    },
    {
      "label": "身份证号码",
      "name": "idcard",
      "type": "string",
      "length": 256,
      "comment": "身份证号码",
      "crypt": "AES",
      "nullable": true,
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^(\\d{18})|(\\d{14}X)$"],
          "message": "{{label}}格式错误"
        }
      ]
    },
    {
      "label": "账户余额",
      "name": "balance",
      "type": "integer",
      "length": 20,
      "comment": "账户余额(冗余)",
      "default": 0,
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["integer"],
          "message": "{{input}}类型错误, {{label}}应为数字"
        },
        {
          "method": "min",
          "args": [0],
          "message": "{{label}}应大于0"
        }
      ]
    },
    {
      "label": "API Key",
      "name": "key",
      "type": "string",
      "length": 256,
      "comment": "API Key",
      "nullable": true,
      "unique": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^[0-9A-Za-z@#$&*]{8}$"],
          "message": " {{label}}应该由8位,大小写字母、数字和符号构成"
        }
      ]
    },
    {
      "label": "API 密钥",
      "name": "secret",
      "type": "string",
      "length": 256,
      "nullable": true,
      "crypt": "AES",
      "comment": "API 密钥",
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "pattern",
          "args": ["^[0-9A-Za-z@#$&*]{32}$"],
          "message": "{{label}}应该由32位,大小写字母、数字和符号构成"
        }
      ]
    },
    {
      "label": "简历",
      "name": "resume",
      "type": "text",
      "comment": "简历",
      "nullable": true
    },
    {
      "label": "扩展信息",
      "name": "extra",
      "type": "json",
      "comment": "扩展信息",
      "nullable": true
    },
    {
      "label": "状态",
      "comment": "用户状态 enabled 有效, disabled 无效",
      "name": "status",
      "type": "enum",
      "default": "enabled",
      "option": ["enabled", "disabled"],
      "index": true,
      "validations": [
        {
          "method": "typeof",
          "args": ["string"],
          "message": "{{input}}类型错误, {{label}}应该为字符串"
        },
        {
          "method": "enum",
          "args": ["enabled", "disabled"],
          "message": "{{input}}不在许可范围, {{label}}应该为 enabled/disabled"
        }
      ]
    }
  ],
  "relations": {
    "manu": {
      "type": "hasOne",
      "model": "manu",
      "key": "id",
      "foreign": "manu_id",
      "select": ["name", "short_name", "type"]
    },
    "addresses": {
      "type": "hasMany",
      "model": "address",
      "key": "user_id",
      "foreign": "id",
      "query": {
        "select": ["province", "city", "location", "status"],
        "pagesize": 20
      }
    },
    "mother": {
      "type": "hasOneThrough",
      "links": [
        {
          "type": "hasOne",
          "model": "friends",
          "key": "user_id",
          "foreign": "user.id",
          "query": {
            "select": ["status", "type", "friend_id"],
            "wheres": [
              {
                "column": "type",
                "value": "monther"
              }
            ]
          }
        },
        {
          "type": "hasOne",
          "model": "user",
          "key": "id",
          "foreign": "user_mother_friends.friend_id",
          "query": {
            "select": ["name", "id", "status", "type", "secret", "extra"],
            "withs": {
              "manu": { "name": "manu" },
              "roles": { "name": "roles" },
              "address": { "name": "address" }
            }
          }
        }
      ]
    },
    "roles": {
      "type": "hasManyThrough",
      "links": [
        {
          "type": "hasMany",
          "model": "user_roles",
          "key": "user_id",
          "foreign": "id",
          "query": {
            "select": ["status"],
            "pagesize": 20
          }
        },
        {
          "type": "hasOne",
          "model": "role",
          "key": "id",
          "foreign": "role_id",
          "query": {
            "select": ["name", "label", "permission"]
          }
        }
      ]
    }
  },
  "values": [
    {
      "name": "管理员",
      "manu_id": 1,
      "type": "admin",
      "idcard": "230624198301170015",
      "mobile": "13900001111",
      "password": "cvSK@RY6",
      "key": "FB3fxCeQ",
      "secret": "XMTdNRVigbgUiAPdiJCfaWgWcz2PaQXw",
      "status": "enabled",
      "extra": { "sex": "" }
    },
    {
      "name": "员工",
      "manu_id": 1,
      "type": "staff",
      "idcard": "23082619820207024X",
      "mobile": "13900002222",
      "password": "qV@uT1DI",
      "key": "JDh2ZiUt",
      "secret": "wBeYjL7FjbcvpAdBrxtDFfjydsoPKhRN",
      "status": "enabled",
      "extra": { "sex": "" }
    },
    {
      "name": "用户",
      "manu_id": 2,
      "type": "user",
      "idcard": "23082619820207004X",
      "mobile": "13900003333",
      "password": "qV@uT1DI",
      "key": "XZ12MiPz",
      "secret": "wBeYjL7FjbcvpAdBrxtDFfjydsoPKhRN",
      "status": "enabled",
      "extra": { "sex": "" }
    }
  ],
  "indexes": [
    {
      "comment": "厂商用户",
      "name": "manu_id_mobile_unique",
      "columns": ["manu_id", "mobile"],
      "type": "unique"
    },
    {
      "comment": "简历全文检索",
      "name": "resume_fulltext",
      "columns": ["resume"],
      "type": "fulltext"
    }
  ],
  "option": { "timestamps": true, "soft_deletes": true }
}