uniCloud DB Schema 学习笔记

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里的字段列表,每个字段都有很多可以设置的属性,如下:


字段属性.png

示例:

{
    "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可取值如下:

预制变量.png

外键
配置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代表用户被禁用"
     }
  }
}

权限规则的变量和运算符
权限规则内可用的全局变量

全局变量.png

注意
○ 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或自定义校验函数实现插入数据的值域校验。

权限规则内可以使用的运算符


运算符.png
// 示例: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)
        })
      }
    }
  }
}

触发器入参

触发器入参.png

触发时机
触发时机.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容