DB Schema概述
DB Schema是基于 JSON
格式定义的数据结构的规范。
作用:
- 描述现有的数据格式。可以一目了然的阅读每个表、每个字段的用途。
- 设定数据操作权限(permission)。什么样的角色可以读/写哪些数据,都在这里配置。
- 设定字段值域能接受的格式(validator),比如不能为空、需符合指定的正则格式。
- 设置数据的默认值(defaultValue/forceDefaultValue),比如服务器当前时间、当前用户id等。
- 设定多个表的字段间映射关系(foreignKey),将多个表按一个虚拟联表直接查询,大幅简化联表查询。
- 根据schema自动生成表单维护界面,比如新建页面和编辑页面,自动处理校验规则。
这些工具大幅减少了开发者的开发工作量和重复劳动。
编写DB Schema
在uniCloud下database目录右键,选择新建数据集合schema
上传到云端:在单个schema文件右键可以只上传当前选中的schema。在database目录右键可以上传全部schema
下载Schema:database目录右键可以下载所有schema及扩展校验函数
DB Schema表结构
Schema的一级节点
{
"bsonType": "object", // 固定节点
"description": "表的描述",
"required": [], // 必填字段
"permission": {
"read": false, // 前端非admin的读取记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"create": false, // 前端非admin的新增记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"update": false, // 前端非admin的更新记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"delete": false, // 前端非admin的删除记录权限控制。默认值是false,即可以不写。可以简单的true/false,也可以写表达式
"count": false // 前端非admin的求数权限控制。默认值是true,即可以不写。可以简单的true/false,也可以写表达式
},
"properties": { // 表的字段清单
"_id": { // 字段名称,每个表都会带有_id字段
"description": "ID,系统自动生成"
// 这里还有很多字段属性可以设置
}
},
"fieldRules":[
// 字段之间的约束关系。比如字段开始时间小于字段结束时间。也可以只校验一个字段。支持表达式
]
}
字段属性
properties里的字段列表,每个字段都有很多可以设置的属性,如下:
示例:
{
"bsonType": "object",
"required": ["name", "birth_year", "tel", "email"],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name": {
"bsonType": "string",
"title": "姓名",
"trim": "both",
"minLength": 2,
"maxLength": 17
},
"birth_year": {
"bsonType": "int",
"title": "出生年份",
"minimum": 1950,
"maximum": 2020
},
"tel": {
"bsonType": "string",
"title": "手机号码",
"pattern": "^\\+?[0-9-]{3,20}$",
"trim": "both"
},
"email": {
"bsonType": "string",
"title": "email",
"format": "email",
"trim": "both"
},
"address": {
"bsonType": "object",
"title": "地址",
"required": ["city"],
"properties": {
"city": {
"bsonType": "string",
"title": "城市"
},
"street": {
"bsonType": "string",
"title": "街道",
"trim": "both"
}
}
},
"intro":{
"bsonType": "string",
"title": "简介",
"trim": "both"
},
"photo": {
"bsonType": "file",
"title": "照片",
"fileMediaType": "image", // 可选值 all|image|video 默认值为all,表示所有文件,image表示图片类型文件,video表示视频类型文件
"fileExtName": "jpg,png", // 扩展名过滤,多个用 , 分割
},
"create_time": {
"bsonType": "timestamp",
"title": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
}
}
}
字段类型 bsonType
bool:布尔值,true|false
string:字符串
password:一种特殊的string。这类字段不会通过clientDB传递给前端,所有用户都不能通过clientDB读写,即使是admin管理员。uni-id-user表有示例
int:整数
double:精度数。由于浮点精度问题,慎用
object:json对象。地理位置也属于object
file:一种特殊的object,固定格式存放云存储文件的信息。不直接存储文件,而是一个json object,包括云存储文件的名称、路径、文件体积等信息。(HBuilderX 3.1.0+ )
array:数组
timestamp:时间戳
date:日期
默认值
defaultValue
没有强制力,是普通的默认值,如果客户端上传一个其他值,则按客户端传的值为准。
forceDefaultValue
则是schema强制约定的值,不管客户端传什么都无法修改。只要数据库新增一条记录,字段的值就会是forceDefaultValue。
forceDefaultValue常用于设置为当前服务器时间、当前登录用户id、客户端ip等。
"forceDefaultValue": {
"$env": "now"
}
预置变量$env可取值如下:
外键
配置
foreignKey
,除了清晰描述数据关系,它最大的作用是联表查询。JQL没有像SQL那样提供了join、leftjoin、innerjoin这些语法,只需要配置好数据关系,配好foreignKey,查询时就可以自动联表查询。
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"user_id": {
"bsonType": "string",
"description": "文章作者ID, 参考`uni-id-users` 表",
"foreignKey": "uni-id-users._id", //关联uni-id-users表中的_id字段
"defaultValue": {
"$env": "uid"
}
},
"title":{},
"content":{}
}
parentKey树形表
{
"bsonType": "object",
"required": ["name"],
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name": {
"bsonType": "string",
"description": "名称"
},
"parent_id": {
"bsonType": "string",
"description": "父id",
"parentKey": "_id", // 指定父子关系为:如果数据库记录A的_id和数据库记录B的parent_id相等,则A是B的父级。
},
"status": {
"bsonType": "int",
"description": "部门状态,0-正常、1-禁用"
}
}
}
枚举
{
"bsonType": "object",
"required": [],
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"gender": {
"bsonType": "int",
"title": "性别",
"defaultValue": 0,
"enum": [
{
"text": "未知",
"value": 0
},
{
"text": "男",
"value": 1
},
{
"text": "女",
"value": 2
}
]
}
}
}
其数据来源于另一张表
{
"bsonType": "object",
"required": [],
"properties": {
"_id": {
"description": "存储文档 ID(用户 ID),系统自动生成"
},
"nation": {
"bsonType": "string",
"title": "民族",
"foreignKey": "opendb-nation-china.name",
"enum": {
"collection": "opendb-nation-china",
"orderby": "first_letter asc",
"field": "name as value, name as text"
}
}
}
}
字段值域规则validator
DB Schema提供了一套完善的字段值域描述规则,并且自动进行数据入库校验,不符合规则的数据无法写入数据库。
注意只有要对数据库写入内容时(新增记录或修改记录)才涉及字段值域的校验问题。读与删不涉及。
DB Schema里的字段值域校验系统由4部分组成:
- 字段的属性配置:是否必填(required)、数字范围(maximum、minimum)、字符串长度范围(minLength、maxLength)、format、pattern正则表达式、enum、trim等所有属性表格中被分类在值域校验里的属性
- 字段间的关系约束:fieldRules。在schema一级节点,和properties平级。它比字段的属性配置更强大,可以描述字段之间的关系,比如字段结束时间需大于字段开始时间;可以简单编程
- 字段的扩展校验函数:validateFunction。当属性配置和fieldRules都不能满足需求,还可以写完整的js编程进行校验。
- 错误提示:errorMessage。常见错误有默认的错误提示语。开发者也可以自定义错误提示语
必填字段(错误提示)
{
"bsonType": "object",
"required": [],
"properties": {
"name": {
"bsonType": "string",
"title": "姓名",
"minLength": 2,
"errorMessage": {
"required": "{title}不能为空",
"minLength": "{title}不能小于 {minLength} 个字符"
}
}
}
}
fieldRules 字段间的校验
{
"bsonType": "object",
"required": ["title","create_date"],
"fieldRules": [{
"rule": "end_date == null || end_date != null && create_date < end_date",
"errorMessage": "结束时间需大于创建时间"
}],
"properties": {
"title": {
"bsonType": "string",
"title": "标题"
},
"create_date": {
"bsonType": "timestamp",
"title": "创建时间",
"forceDefaultValue": {
"$env": "now"
}
},
"end_date": {
"bsonType": "timestamp",
"title": "结束时间"
},
"age": {
"bsonType": "int",
"title": "年龄",
"minimum": 1,
"maximum": 150,
"errorMessage": "{title}应该大于 {minimum} 岁,小于 {maximum} 岁"
}
}
}
数据权限系统permission
DB Schema的permission规则,分为两部分,一边是对操作数据的指定,一边是对角色的指定,规则中对两者进行关联,匹配则校验通过。
表级权限控制
表级控制,包括增删改查四种权限,分别称为:create、delete、update、read
。
HBuilderX 3.1.0起还新增了count权限,即是否有权对该表进行统计计数。
所有的操作的默认值均为false。也就是不配置permission代表不能操作数据库(角色为admin用户例外)。
关于count权限的说明
○ 在HBuilderX 3.1.0之前,count操作都会使用表级的read权限进行验证。HBuilderX 3.1.0及之后的版本,如果配置了count权限则会使用表级的read+count权限进行校验,两条均满足才可以通过校验。
○ 如果schema内没有count权限,则只会使用read权限进行校验
○ 所有会统计数量的操作均会触发count权限校验
字段级权限控制
permission的字段级控制,包括读写两种权限,分别称为:read、write。
指定数据集权限控制
DB Schema提供了一个内置变量doc,表示要意图操作的数据记录。并支持用各种表达式来描述指定的记录。
// 示例 user表的schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": "doc.status==true", // 任何用户都可以读status字段的值为true的记录,其他记录不可读
"create": false, // 禁止新增数据记录(admin权限用户不受限)
"update": false, // 禁止更新数据(admin权限用户不受限)
"delete": false // 禁止删除数据(admin权限用户不受限)
},
"properties": {
"_id":{
},
"name":{
},
"age": {
"bsonType": "number",
"title": "年龄",
"permission": {
"read": false, // 禁止读取 age 字段的数据(admin权限用户不受限)
"write": false // 禁止写入 age 字段的数据(admin权限用户不受限)
}
},
"status": {
"bsonType": "bool",
"title": "用户状态",
"description": "true代表用户正常。false代表用户被禁用"
}
}
}
权限规则的变量和运算符
权限规则内可用的全局变量
注意
○ auth表示正在执行操作的用户对象
○ auth.xxx均由uni-id提供,依赖于uni-id公共模块
○ doc.xxx表示将要查询/修改/删除的每条数据(注意并不包括新增数据,新增数据应通过值域校验进行验证),如果将要访问的数据不满足permission规则将会拒绝执行
○ uni-id的角色和权限,也即auth.role和auth.permission是不一样的概念。注意阅读uni-id 角色权限
○ 如果想支持使用多个action的用法,可以通过"'actionRequired' in action"的形式配置权限,限制客户端使用的action内必须包含名为actionRequired的action
○ doc可以理解为将要访问的数据,因此create权限内不可使用doc变量。create时建议使用forceDefaultValue或自定义校验函数实现插入数据的值域校验。
权限规则内可以使用的运算符
// 示例:user表的schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": "doc.status==true", // 任何用户都可以读status字段的值为true的记录,其他记录不可读
"create": false, // 禁止新增数据记录(admin权限用户不受限)
"update": "'updateuser' in auth.permission", // 权限标记为updateuser的用户,和admin管理员,可以更新数据,其他人无权更新数据
"delete": false // 禁止删除数据(admin权限用户不受限)
},
"properties": {
"_id":{
},
"name":{
"bsonType": "string",
"title": "名称",
"permission": {
"read": true,
"write": "doc._id == auth.uid" // 允许登录的用户修改自己的name字段
}
},
"pwd": {
"bsonType": "string",
"title": "密码",
"permission": {
"read": false, // 禁止读取 pwd 字段的数据(admin权限用户不受限)
"write": false // 禁止写入 pwd 字段的数据(admin权限用户不受限)
}
},
"status": {
"bsonType": "bool",
"title": "用户状态",
"description": "true代表用户正常。false代表用户被禁用"
}
}
}
权限规则内的数据库查询get方法
权限规则内置了doc变量,但只能用于要操作的数据表的判断,如果要获取其他表的数据做判断就需要get方法了。
权限规则内通过get方法,根据id获取数据库中的数据。get方法接收一个字符串作为参数,字符串形式为database.表名.记录ID
"create": get(`database.uni-id-users.${auth.uid}`).score > 100"
"get(`database.shop.${doc.shop_id}`).owner == auth.uid"
前端js如下
db.collection('street').where("shop_id=='123123'").get()
触发器
JQL的数据库触发器,用于在执行一段JQL数据库指令(增删改查等)的同时触发相应的操作。
可以使用触发器方便的实现很多功能,例如:
更新数据时自动将更新时间修改为当前时间
读取文章详情后阅读量加1
发布一篇文章后自动给文章作者列表文章数量加1
创建触发器
在HBuilderX项目下,在目录 uniCloud/database/ 下可以创建${表名}.schema.ext.js
示例:读取后触发实现阅读量加1
// article.schema.ext.js
module.exports {
trigger: {
afterRead: async function({
collection,
operation,
where,
field,
clientInfo
} = {}) {
const db = uniCloud.database()
const id = where && where._id
// clientInfo.uniIdToken可以解出客户端用户信息,再进行判断是否应该加1。为了让示例简单清晰,此处省略相关逻辑
if(typeof id === 'string' && field.includes('content')) {
// 读取了content字段后view_count加1
await db.collection('article').where(where).update({
view_count: db.command.inc(1)
})
}
}
}
}
触发器入参
触发时机