jql-schema-ext.md 7.3 KB
Newer Older
雪洛's avatar
雪洛 已提交
1 2 3
schema扩展主要用于用于扩展schema的能力,实现一些不方便在schema.json文件内进行描述或处理的逻辑。目前schema扩展仅增加了触发器功能。

## 触发器@trigger
雪洛's avatar
雪洛 已提交
4 5 6 7 8 9 10

新增于 HBuilderX 3.6.10,JQL触发器用于在执行一段数据库指令的同时触发相应的操作。可以使用触发器方便的实现如下功能:

1. 读取文章详情后阅读量加1
2. 发布一篇文章后自动给文章作者列表文章数量加1
3. 更新文章时自动将更新时间修改为当前时间

雪洛's avatar
雪洛 已提交
11
### 触发器配置@config
雪洛's avatar
雪洛 已提交
12

雪洛's avatar
雪洛 已提交
13
在项目的`uniCloud/database`目录下创建`${表名}.schema.ext.js`,内容如下。
雪洛's avatar
雪洛 已提交
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

```js
module.exports = {
  trigger: {
    beforeRead: async function ({
      collection,
      operation,
      where,
      field
    } = {}) {

    },
    afterRead: async function ({
      collection,
      operation,
      where,
      field
    } = {}) {

    }
  }
}
```

如上配置会在使用jql读取此表内容时触发`beforeRead``afterRead`。除这两个时机外还有`beforeCount``afterCount``beforeCreate``afterCreate``beforeUpdate``afterUpdate``beforeDelete``afterDelete`这些触发时机,后续章节会详细说明

雪洛's avatar
雪洛 已提交
40 41 42 43
在通过clientDB访问时触发器内可以使用包含在clientDB内的公共模块,如何将公共模块引入clientDB请参考:[jql依赖公共模块](jql.md#common-for-jql)

在通过云函数/云对象使用jql访问时,触发器可以使用云函数/云对象依赖的公共模块。

雪洛's avatar
雪洛 已提交
44
### 触发器入参@trigger-param
雪洛's avatar
雪洛 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58

所有触发器均在数据校验、权限校验之后执行,beforeXxx会在实际执行数据库指令之前执行,afterXxx会在实际执行数据库指令之后执行。

触发器的入参有以下几个,不同时机的触发器参数略有不同

|参数名			|类型								|默认值	|是否必备		|说明																												|
|--					|--									|--			|--					|--																													|
|collection	|string							|-			|是					|当前表名																										|
|operation	|string							|-			|是					|当前操作类型:`create``update``delete``read``count`|
|where			|object							|-			|否					|当前请求使用的查询条件(见下方说明)												|
|field			|array<string>|-			|read必备		|当前请求访问的字段列表(见下方说明)												|
|addDataList|array<object>|-			|create必备	|新增操作传入的数据列表(见下方说明)												|
|updateData	|object							|-			|update必备	|更新操作传入的数据(见下方说明)														|

雪洛's avatar
雪洛 已提交
59
#### where
雪洛's avatar
雪洛 已提交
60 61 62 63 64

> read、count、delete、update操作可能有此参数

触发器收到的where参数为转化后的查询条件,可以直接作为参数传给db.collection()和dbJql.collection()的where方法。jql语句使用doc方法时也会转成where,形如:{_id: 'xxx'}

雪洛's avatar
雪洛 已提交
65
#### field
雪洛's avatar
雪洛 已提交
66 67 68 69 70

> 仅read操作有此参数

field为所有被访问的字段的组成的数组,嵌套的字段会被摊平。

雪洛's avatar
雪洛 已提交
71
#### addDataList
雪洛's avatar
雪洛 已提交
72 73 74 75 76

> 仅create操作有此参数

数据库指令add方法的参数和schema内defaultValue、forceDefaultValue合并后的列表。注意为统一数据结构,add方法内只传递一个对象时此参数也是一个仅有一项的数组

雪洛's avatar
雪洛 已提交
77
#### updateData
雪洛's avatar
雪洛 已提交
78 79 80 81 82

> 仅update操作有此参数

数据库指令update方法的参数

雪洛's avatar
雪洛 已提交
83
### 触发时机@trigger-timing
雪洛's avatar
雪洛 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97

|触发时机			|说明				|
|---					|---				|
|beforeRead		|读取前触发	|
|afterRead		|读取后触发	|
|beforeCount	|计数前触发	|
|afterCount		|计数后触发	|
|beforeCreate	|新增前触发	|
|afterCreate	|新增后触发	|
|beforeUpdate	|更新前触发	|
|afterUpdate	|更新后触发	|
|beforeDelete	|删除前触发	|
|afterDelete	|删除后触发	|

雪洛's avatar
雪洛 已提交
98
### 示例@demo
雪洛's avatar
雪洛 已提交
99 100 101 102 103 104

以下article表为例。

为了不增加示例的复杂度,所有权限均设置为true,实际项目中切勿随意设置权限

```js
雪洛's avatar
雪洛 已提交
105
// article.schema.ext.js
雪洛's avatar
雪洛 已提交
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
{
  "bsonType": "object",
  "required": ["title", "content"],
  "permission": {
    "read": true,
    "create": true,
    "update": true,
    "delete": true
  },
  "properties": {
    "_id": {
      "bsonType": "string"
    },
    "title": {
      "bsonType": "string"
    },
    "summary": {
      "bsonType": "string"
    },
    "content": {
      "bsonType": "string"
    },
    "author": {
      "bsonType": "string"
    },
    "view_count": {
      "bsonType": "int"
    },
    "create_date": {
      "bsonType": "timestamp"
    },
    "update_date": {
      "bsonType": "timestamp"
    }
  }
}
```

雪洛's avatar
雪洛 已提交
144
#### 读取后触发实现阅读量加1
雪洛's avatar
雪洛 已提交
145 146

```js
雪洛's avatar
雪洛 已提交
147
// article.schema.ext.js
雪洛's avatar
雪洛 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
module.exports {
  trigger: {
    afterRead: async function({
      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').doc(id).update({
          view_count: db.command.inc(1)
        })
      }
    }
  }
}
```

雪洛's avatar
雪洛 已提交
169
#### 删除前备份
雪洛's avatar
雪洛 已提交
170 171

```js
雪洛's avatar
雪洛 已提交
172
// article.schema.ext.js
雪洛's avatar
雪洛 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
module.exports {
  trigger: {
    beforeDelete: async function({
      where,
      clientInfo
    } = {}) {
      const db = uniCloud.database()
      const id = where && where._id
      if(typeof id !== 'string') { // 此处也可以加入管理员可以批量删除的逻辑
        throw new Error('禁止批量删除')
      }
      const res = await db.collection('article').doc(id).get()
      const record = res.data[0]
      if(record) {
        await db.collection('article-archived').add(record)
      }
    }
  }
}
```

雪洛's avatar
雪洛 已提交
194
#### 修改文章更新时间
雪洛's avatar
雪洛 已提交
195 196

```js
雪洛's avatar
雪洛 已提交
197
// article.schema.ext.js
雪洛's avatar
雪洛 已提交
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
module.exports {
  trigger: {
    beforeUpdate: async function({
      where,
      updateData,
      clientInfo
    } = {}) {
      const id = where && where._id
      if(typeof id === 'string' && (updateData.title || updateData.content)) {
        if(updateData.content) {
          // updateData.summary = 'xxxx' // 根据content生成summary
        }
        updateData.update_date = Date.now()
      }
    }
  }
}
```

雪洛's avatar
雪洛 已提交
217
#### 新增文章时自动添加摘要
雪洛's avatar
雪洛 已提交
218 219

```js
雪洛's avatar
雪洛 已提交
220
// article.schema.ext.js
雪洛's avatar
雪洛 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234
module.exports {
  trigger: {
    beforeCreate: async function({
      addDataList,
      clientInfo
    } = {}) {
      for(let i = 0; i < addDataList.length; i++) {
        addDataList[i].summary = addDataList[i].content && addDataList[i].content.slice(0, 100)
      }
    }
  }
}
```

雪洛's avatar
雪洛 已提交
235
### 注意事项
雪洛's avatar
雪洛 已提交
236 237 238 239 240 241

- 非getTemp联表查询(不推荐的用法)在触发器内获取的where为null、field为当前表的所有字段。
- 联表查询时只会触发主表触发器,不会触发副表触发器
- getTemp联表时主表所在的getTemp内的where和field会传递给触发器,虚拟联表的where和field不会传给触发器
- getCount不会触发count触发器,只会触发read触发器
- 通过jql的redis缓存读取的内容不会触发读触发器
雪洛's avatar
雪洛 已提交
242
- jql数据管理不会触发任何触发器
雪洛's avatar
雪洛 已提交
243

雪洛's avatar
雪洛 已提交
244
#### 和action的关系
雪洛's avatar
雪洛 已提交
245 246 247 248

数据库触发器能实现很多常见的action功能,并且无需修改schema和数据库指令。

触发器的before会在所有action的before执行之前再执行,after会在所有action的after执行之后再执行。action无法捕获触发器抛出的错误。