数据模型
Yao 可以读取数据模型定义,实现数据迁移、元数据原子操作、元数据输入校验和元数据管理后台。元数据原子操作方法被映射为处理器(process),支持模型间数据关系映射,可在数据流(Flow)和接口(API)中访问查询。如采用golang语言开发业务插件(Plugin),可以使用 Package Gou 访问模型的各个方法, 后续将提供 NodeJS 等语言 SDK。
1 命名规范
数据模型描述文件是以 小写英文字母 命名的 JSON 文本文件 <name>.mod.json
| 文件夹 (相对应用模型根目录) | 文件名 | 模型名称 | Process (在 API /Flow 中引用) |
|---|---|---|---|
| / | name.mod.json | name | models.name.<process> |
| /group/ | name.mod.json | gorup.name | models.gorup.name.<process> |
| /group1/group2/ | name.mod.json | gorup1.gorup2.name | models.gorup1.group2.name.<process> |
2 文档结构
数据模型定义文档,由基础信息、数据表、字段定义、索引定义、关系映射、默认数据和配置部分构成。查看完整示例
{
"name": "用户",
"table": {},
"columns": [],
"indexes": [],
"relations": {},
"values": [],
"option": {}
}| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 模型中文名称 | 是 |
| table | Object | 数据表定义 | 是 |
| columns | Array<Object> | 字段定义 | 是 |
| indexes | Array<Object> | 索引定义 | 是 |
| relations | [key:String]:Object | 关系映射 | 否 |
| values | Array<Object> | 默认数据 | 否 |
| option | Object | 配置选型 | 否 |
2.1 基础信息
基础信息包含 name 、 description 、version 等字段,主要用于在开发平台中呈现检索。
{
"name": "用户",
"version": "1.0.0",
"description": "网站用户元数据模型",
"table": {},
"columns": [],
"indexes": []
}| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 中文名称 | 是 |
| version | String | 版本号 x.x.x 遵循 Semantic Versioning 2.0.0 规范 | 是 |
| tun | String | 象传智慧共享仓库 Tun (tun.iqka.com) 地址 如:microcity/petstore/user | 否 |
| description | String | 数据模型详细介绍 | 否 |
| author | String | 数据模型作者 | 否 |
| String | 数据模型作者联系方式 | 否 | |
| license | String | 数据模型共享协议如 MIT | 否 |
| homepage | String | 数据模型提供者官网 | 否 |
2.2 数据表 table
数据表格包含 name、comment 等字段,定义模型存储在数据库中的数据表名称、注释等信息。支持 MySQL, PostgreSQL、SQLite等 Xun Database 或第三方提供驱动的数据库。
{
"table": {
"name": "user",
"comment": "用户表",
"engine": "InnoDB"
}
}| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 数据表名称 | 是 |
| comment | String | 数据表注释中文名 | 否 |
| engine | String | 数据表引擎(MySQL ONLY) 许可值 InnoDB, MyISAM | 否 |
2.3 字段定义 columns
一个模型可以包含多个字段定义,每个字段定义包含 label、name、type、 validations 等信息。
{
"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}}格式错误"
}
]
}
]
}| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 字段名称,对应数据表中字段名称 | 是 |
| type | String | 字段类型, | 是 |
| label | String | 字段显示名称,用于在管理表单,开发平台等成场景下呈现 | 是 |
| comment | String | 字段注释,对应数据表中字段注释 | 否 |
| title | String | 字段标题,可用于开发平台中呈现 | 否 |
| description | String | 字段介绍,可用于开发平台中呈现 | 否 |
| length | Integer | 字段长度,对 string 等类型字段有效 | 否 |
| precision | Integer | 字段位数(含小数位),对 float、decimal 等类型字段有效 | 否 |
| scale | Integer | 字段小数位位数,对 float、decimal 等类型字段有效 | 否 |
| option | Array<String> | 字段许可值,对 enum 类型字段有效 | 否 |
| default | String|Integer|Float | 字段默认值 | 否 |
| default_raw | String | 字段默认值,支持数据库函数,如 NOW() default 和 default_raw 同时存在 default_raw 优先级高 | 否 |
| crypt | String | 字段加密存储方式(MySQL Only)。许可值 AES, PASSWORD | 否 |
| nullable | Bool | 字段是否可以为空,默认为 false | 否 |
| index | Bool | 字段是否为索引,默认为 false | 否 |
| unique | Bool | 字段是否为唯一索引,默认为 false , 如为 true 无需同时将 index 设置为 true | 否 |
| primary | Bool | 字段是否为主键,每张表至多一个主键字段。默认为 false | 否 |
| validations | Array<Object> | 字段校验规则 | 否 |
字段类型
| 类型 | 说明 | 可选参数 | MySQL 字段类型 |
|---|---|---|---|
| string | 字符串 | length | VARCHAR(length ) |
| char | 字符 | length | CHAR (length ) |
| text | 文本 | TEXT | |
| mediumText | 中文本 | MEDIUMTEXT | |
| longText | 长文本 | LONGTEXT | |
| binary | 二进制数据 | VARBINARY | |
| date | 日期 | DATE | |
| datetime | 日期时间 | length | DATETIME |
| datetimeTz | 带时区的日期时间 | length | DATETIME |
| time | 时间 | length | TIME |
| timeTz | 带时区的时间 | length | TIME |
| timestamp | 时间戳 | length | TIMESTAMP |
| timestampTz | 带时区的时间戳 | length | TIMESTAMP |
| 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 | 小数(一般用于存储货币) | precision、scale | DECIMAL(precision,scale) |
| unsignedDecimal | 无符号小数 (一般用于存储货币) | precision、scale | DECIMAL (precision,scale) UNSIGNED |
| float | 浮点数 | precision、scale | FLOAT (precision,scale) |
| unsignedFloat | 无符号浮点数 | precision、scale | FLOAT (precision,scale) UNSIGNED |
| double | 双精度 | precision、scale | DOUBLE (precision,scale) |
| unsignedDouble | 无符号双精度 | precision、scale | DOUBLE (precision,scale) UNSIGNED |
| boolean | 布尔型 | BOOLEAN | |
| enum | 枚举型 | option | ENUM(option...) |
| json | JSON 文本 | JSON | |
| JSON | JSON 文本(同 json) | JSON | |
| jsonb | JSON (二进制格式存储) | JSON | |
| JSONB | JSON (二进制格式存储 同 jsonb) | JSON | |
| uuid | UUID 格式字符串 | VARCHAR(36) | |
| ipAddress | IP 地址 | INT | |
| macAddress | MAC 地址 | BIGINT | |
| year | 年份 | SMALLINT |
校验方法
一个字段可以包含多条校验规则,每条校验规则可以选用 min,max, pattern, typeof 等校验方法。
{
"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}}格式错误"
}
]
}
]
}校验规则定义
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| method | String | 校验方法名称,可选值 typeof, pattern 等 | 是 |
| args | Array<String|Integer|Float> | 校验方法参数,例如 [20], ["^1[3-9]\\d{9}$"] | 否 |
| message | String | 如校验不通过,返回的错误提示。支持使用 {{<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]} |
[] | 邮箱 | {"method":"email", "args":[]} | |
| mobile | [<String>] 区域列表(可选), 默认为 cn 许可值 cn,us | 手机号 | {"method":"mobile", "args":[]} {"method":"mobile", "args":["us"]} |
加密方式
当前支持 AES 和 PASSWORD 两种字段数值加密存储算法,其中 AES 仅支持 MySQL 数据库。
| 加密算法 | 说明 | 是否可逆 |
|---|---|---|
AES | AES 加密,需设定 XIANG_DB_AESKEY | 是 |
PASSWORD | PASSWORD HASH 加密 | 否 |
保留字
以下名称不能用于字段名称。
| 保留字 | 说明 |
|---|---|
| created_at | 用于记录创建时间戳 |
| updated_at | 用于记录更新时间戳 |
| deleted_at | 用于记录软删除标记 |
| __restore_data | 用于软删除时备份唯一字段数值 |
2.4 索引定义 indexes
一个数据模型,可以包含多个索引。对于单一索引,推荐在字段定义时,使用 index 、 unique 和 primary 修饰符定义,对于复合索引或全文检索索引,在 indexes 中定义。
{
"indexes": [
{
"comment": "厂商用户",
"name": "manu_id_mobile_unique",
"columns": ["manu_id", "mobile"],
"type": "unique"
},
{
"comment": "简历全文检索",
"name": "resume_fulltext",
"columns": ["resume"],
"type": "fulltext"
}
]
}| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 索引名称。命名规范为 字段 1_字段 2_字段 n_索引类型 | 是 |
| type | String | 索引类型 许可值 index 索引, unique 唯一索引, primary 主键, fulltext 全文检索 | 是 |
| columns | Array<String> | 关联字段名称列表(顺序有关) ["字段 1","字段 2"] 与 ["字段 2","字段 1"] 不同 | 是 |
| comment | String | 索引注释 | 否 |
2.5 关系映射 relations
当前关系映射部分为 beta 版本, 可能会依据用户使用反馈,调整数据结构
数据模型支持一对一、一对多两种关系映射,可以通过定义映射关系将多个数据模型关联,查询时使用with参数即可同时返回关联模型数据。
| 映射关系名称 | 关系 | 说明 |
|---|---|---|
hasOne | 一对一 | 模型 A 与模型 B 通过一对一关联 |
hasMany | 一对多 | 模型 A 与模型 B 通过一对多关联 |
关联关系使用 [key:String]:Object Relation 数据结构定义 ( {"关联名称1":{}, "关联名称2":{}}, 关联名称为 小写英文字母 )
在模型文件 user.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
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| type | String | 关系类型 许可值 hasOne, hasOneThrough , hasMany , hasManyThrough | 是 |
| key | String | 关联模型的关联字段名称 | 否 |
| model | String | 关联模型名称 | 否 |
| foreign | String | 当前模型的关联字段名称 | 否 |
| query | Object QueryParam | 关系查询参数默认值。如在查询时未指定关联查询参数,则替使用在模型中定义的查询参数 | 否 |
| links | Array<Object Relation> | hasOneThrough 或 hasManyThrough 多表关联关系定义 | 否 |
2.5.1 hasOne 一对一
数据模型 user 数据表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | ID | 用户 ID |
| manu_id | bigInteger | 所属厂商 ID |
| name | string | 姓名 |
数据模型 manu 数据表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | ID | 厂商 ID |
| short | string | 厂商简称 |
| company | string | 公司名称 |
在查询用户数据时,可以同时列出厂商信息或可以按关联厂商进行查询,则可以在定义 user 数据模型时,设定与 manu 数据模型关系.
在模型文件 user.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.id 和 user.manu_id 关联
5.可以在 query 字段中,设置默认的查询条件,如指定读取的字段等。
引擎解析后的 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 参数,即可同时返回厂商信息
GET /api/user/find/1?with=manu&manu.select=id,short2.5.2 hasMany 一对多
数据模型 user 数据表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | ID | 用户 ID |
| manu_id | bigInteger | 所属厂商 ID |
| name | string | 姓名 |
数据模型 address 数据表结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | ID | 地址 ID |
| user_id | bigInteger | 所属用户 ID (关联 user.id ) |
| location | string | 详细地址 |
对于类似一个用户有多个通信地址的业务场景,可以通过建立一对多的映射关系来实现。
在模型文件 user.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.id 和 address.user_id 关联
5.可以在 query 字段中,设置默认的查询条件,如指定读取的字段等。对于 hasMany 建议设置默认 limit 约束返回数据条目
对于 hasMany 类型关系映射,引擎将分两次查询。首次查询出主模型以及关联的 ID 列表,第二次根据 ID 列表,查询关联数据信息。
第一次查询:
SELECT `user`.* FROM `user` AS `user`引擎处理结果,并读取 user.id
第二次查询:
SELECT `address`.`user_id`, `address`.`location` FROM `address` AS `address`
WHERE `address`.`user_id` IN (<user.id...>)引擎处理结果,关联用户地址信息
访问
在调用 process 查询时,传入 with 参数,即可同时取得 addresses 的关联信息
GET /api/user/find/1?with=addresses2.7 配置选项 option
在 option 中设定模型配置参数
{
"name": "地址",
"option": {
"timestamps": true,
"soft_deletes": true
}
}| 选项 | 类型 | 说明 |
|---|---|---|
| timestamps | Bool | 为 true 时, 自动创建 created_at、updated_at 字段,并在插入和更新数据时,标记对应操作时间 |
| soft_deletes | Bool | 为 true 时, 自动创建 deleted_at 和 __restore_data 字段,数据删除时,备份唯一字段数据,并标记操作时间,查询时忽略已标记删除的数据 |
3 查询参数 QueryParam
在模型关联关系定义和调用处理器时,通过 Object QueryParam 描述查询条件。
{
"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 :
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 23.1 数据结构
QueryParam
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| select | Array<String> | 选择字段清单 | 否 |
| wheres | Array<Object Where> | 查询条件 | 否 |
| orders | Array<Object Order> | 排序条件 | 否 |
| limit | Integer | 返回记录条目 | 否 |
| page | Integer | 当前页码 | 否 |
| pagesize | Integer | 每页显示记录数量 | 否 |
| withs | [key:String]:Object With | 读取关联模型 | 否 |
Object Where
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| rel | String | 如按关联模型的字段查询,则填写关联模型名称 | 否 |
| column | String | 字段名称 | 否 |
| method | String | 查询方法 where,orwhere | 否 |
| op | String | 匹配关系 eq,like,in,gt 等 | 否 |
| value | Any | 匹配数值 | 否 |
| wheres | Array<Object Where> | 分组查询 | 否 |
| 查询方法 | 说明 |
|---|---|
| where | WHERE 字段 = 数值, 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
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| rel | String | 如按关联模型的字段排序,则填写关联模型名称 | 否 |
| column | String | 字段名称 | 否 |
| option | String | 排序方式,默认为 asc desc, asc | 否 |
Object With
| 字段 | 类型 | 说明 | 必填项 |
|---|---|---|---|
| name | String | 关联关系名称 | 否 |
| query | Object QueryParam | 查询参数 | 否 |
3.2 URL Query String 与 QueryParam 对照表
查询条件可以通过 URL Query String 传入
4 处理器(process)
数据模型提供一组原子操作处理器 process , 这些处理器可用于服务接口(API)和数据流(Flow)编排。
| 处理器 | 引用方式 | 说明 |
|---|---|---|
| find | models.模型名称.Find | 查询单条记录 |
| get | models.模型名称.Get | 按条件查询, 不分页 |
| paginate | models.模型名称.Paginate | 按条件查询, 分页 |
| create | models.模型名称.Create | 创建单条记录, 返回新创建记录 ID |
| update | models.模型名称.Update | 更新单条记录 |
| save | models.模型名称.Save | 保存单条记录, 不存在创建记录, 存在更新记录, 返回记录 ID |
| delete | models.模型名称.Delete | 删除单条记录(标记删除) |
| destroy | models.模型名称.Destroy | 删除单条记录(真删除) |
| insert | models.模型名称.Insert | 插入多条记录, 返回插入行数 |
| updatewhere | models.模型名称.UpdateWhere | 按条件更新记录, 返回更新行数 |
| deletewhere | models.模型名称.DeleteWhere | 按条件删除数据, 返回删除行数(标记删除) |
| destroywhere | models.模型名称.DestroyWhere | 按条件删除数据, 返回删除行数(真删除) |
| eachsave | models.模型名称.EachSave | 保存多条记录, 不存在创建记录, 存在更新记录, 返回记录 ID 集合 |
| eachsaveAfterDelete | models.模型名称.EachSaveAfterDelete | 删除一组给定 ID 的记录后,保存多条记录, 不存在创建记录, 存在更新记录, 返回记录 ID 集合 |
5. 完整示例
完整示例保存在 examples 目录
{
"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 }
}