English
The json file of DB Schema cannot be programmed, and the programmable extended js will greatly enhance the control ability of the schema.
In the past, action was used in clientDB to deal with the shortcomings of schema.json. However, the action cloud function has a security defect, which cannot prevent the client from invoking the specified action.
From HBuilderX 3.6.11+, uniCloud provides a programmable schema, and each ${table name}.schema.json
can be configured with a ${table name}.schema.ext.js
.
${table name}.schema.ext.js
can be created under the directory uniCloud/database/.${table name}.schema.ext.js
next to schema.json.Schema extension js can achieve many things in planning, and currently only the database trigger function is online.
It is recommended that developers use JQL database triggers instead of action cloud functions.
A JQL database trigger is used to trigger corresponding operations while executing a JQL database command (addition, deletion, modification, etc.).
Only use JQL to operate the database, and both the client and the cloud can execute JQL. However, using the traditional MongoDB writing method does not support database triggers.
Triggers can be used to implement many functions conveniently, such as:
Since the database trigger is executed in the cloud, many codes that should not be written in the front end when clientDB operates the database can be implemented in the database trigger.
If the schema of the database is defined, including json and ext.js, then each business module can call the database at will, and the data consistency logic and security guarantee will be managed in a unified manner, without worrying about the destruction of bad business codes or Which call will miss the update time field.
Create ${table name}.schema.ext.js
under the uniCloud/database
directory of the project, the content is as follows.
module.exports = {
trigger: {
// Register the pre-read event of the data table
beforeRead: async function (
// Determine what kind of JQL command to listen to
{
collection,
operation,
where,
field
} = {}) {
// When the above jql directive is triggered, the code here will be executed. Here is the normal uniCloud code, which can call various APIs of uniCloud.
console.log("这个触发器被触发了")
},
afterRead: async function ({
collection,
operation,
where,
field
} = {}) {
}
}
}
The above configuration will trigger beforeRead
and afterRead
when using jql to read the content of this table.
In addition to these two timings, there are trigger timings such as beforeCount
, afterCount
, beforeCreate
, afterCreate
, beforeUpdate
, afterUpdate
, beforeDelete
, afterDelete
, which will be described in detail in subsequent chapters
The mechanism of introducing public modules in ext.js:
All triggers are executed after data verification and permission verification, beforeXxx will be executed before the actual execution of the database command, and afterXxx will be executed after the actual execution of the database command.
The input parameters of the trigger are as follows, and the trigger parameters at different times are slightly different
参数名 | 类型 | 默认值 | 是否必备 | 说明 |
---|---|---|---|---|
collection | string | - | 是 | 当前表名 |
operation | string | - | 是 | 当前操作类型:create 、update 、delete 、read 、count |
where | object | - | 否 | 当前请求使用的查询条件(见下方说明) |
field | array<string> | - | read必备 | 当前请求访问的字段列表(见下方说明) |
addDataList | array<object> | - | create必备 | 新增操作传入的数据列表(见下方说明) |
updateData | object | - | update必备 | 更新操作传入的数据(见下方说明) |
clientInfo | object | - | 是 | 客户端信息,包括设备信息、用户token等,详见:clientInfo |
userInfo | object | - | 是 | 用户信息 |
result | object | - | afterXxx内必备 | 本次请求结果 |
isEqualToJql | function | - | 是 | 用于判断当前执行的jql语句和执行语句是否相等 |
triggerContext | object | - | 是 | 用于在before和after内共享数据,新增于3.6.16 |
array | - | 否 | 请使用secondaryCollection替代此参数,此参数仍可访问只是会给出警告 | |
secondaryCollection | array | - | 否 | 获取联表查询的副表列表,新增于3.7.1 |
rawWhere | object|string | - | 否 | 未经转化的原始查询条件,新增于3.7.0 |
rawGeoNear | object | - | 否 | 未经转化的原始geoNear参数,新增于3.7.0 |
skip | number | - | 否 | 跳过记录条数,新增于3.7.0 |
limit | number | - | 否 | 返回的结果集(文档数量)的限制,新增于3.7.0 |
sample | object | - | 否 | sample(随机选取)方法的参数,新增于3.7.0 |
docId | string | - | 否 | doc方法的参数,数据库记录的_id,新增于3.7.0 |
isGetTempLookup | boolean | - | 联表触发时必备 | 联表查询时用于标识,本次查询是否使用了getTemp,新增于3.7.1 |
primaryCollection | string | - | 副表read必备 | 联表查询时主表的表名,新增于3.7.13 |
仅read操作联表有此参数,新增于 3.7.1
Join table to query a list of sub-tables
// article.schema.ext.js
module.exports = {
trigger: {
beforeRead: async function({
secondaryCollection
} = {}) {
if(secondaryCollection && secondaryCollection.length > 1) {
throw new Error('仅允许关联一个副表')
}
}
}
}
read, count, delete, update operations may have this parameter
The where parameter received by the trigger is the converted query condition, which can be directly passed as a parameter to the where method of db.collection() and dbJql.collection(). When the jql statement uses the doc method, it will also be converted into where, such as: {_id: 'xxx'}
read, delete, update operations may have this parameter, new in 3.7.0
The document_id passed in the doc method received by the trigger
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeDelete: async function({
docId
} = {}) {
if(!docId) {
throw new Error('仅能指定文档id删除')
}
}
}
}
read, delete, update operations may have this parameter, new in 3.7.0
The original parameters of the where or match method, without jql conversion. If it is a string or a database method is used, it can only be passed to the database instance returned by databaseForJQL, and cannot be passed to the database instance returned by the database method.
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeDelete: async function({
rawWhere
} = {}) {
if(rawWhere && typeof rawWhere !== string) {
throw new Error('仅能使用字符串作为查询条件')
}
}
}
}
Only the read operation has this parameter, which was added in 3.7.0
The original parameters of the geoNear method have not been converted by jql. If the query is a string or a database method is used, it can only be passed to the database instance returned by databaseForJQL, and cannot be passed to the database instance returned by the database method.
Only the read operation has this parameter, which was added in 3.7.0
Number of documents skipped
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeRead: async function({
skip
} = {}) {
if(skip && skip > 10000) {
throw new Error('无法访问10000条以后的数据')
}
}
}
}
Only the read operation has this parameter, which was added in 3.7.0
The limit on the returned result set (number of documents)
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeRead: async function({
limit
} = {}) {
if(limit && limit > 100) {
throw new Error('每次最多访问100条数据')
}
}
}
}
Only the read operation has this parameter, which was added in 3.7.0
Parameters for the Random Screening Method
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeRead: async function({
sample
} = {}) {
if(sample && sample.size > 100) {
throw new Error('每次最多随机筛选100条数据')
}
}
}
}
Only the read operation has this parameter
field is an array of all accessed fields, and nested fields will be flattened. When no field is passed, all fields will be returned
Only the create operation has this parameter
The list of the parameters of the database command add method and the defaultValue and forceDefaultValue in the schema combined. Note that for a unified data structure, when only one object is passed in the add method, this parameter is also an array with only one item.
If the data of addDataList is intercepted and modified before inserting data into the database, then the newly modified data will be inserted into the database.
Only the update operation has this parameter
Parameter for the update method of the database directive.
If the updateData data is intercepted and modified before the data is modified in the database, the newly modified data will be updated into the database.
Added in HBuilderX 3.6.14
User information contains the following fields
Field Name | Type | Description |
---|---|---|
uid | string|null | user id, null when user information cannot be obtained |
role | array | role list, default is an empty array |
permission | array | permission list, default is an empty array |
Added in HBuilderX 3.6.14
As a result of this database operation, different operations return different structures. Modifications to the result object will be applied to the final returned result
Inquire
{
data: [] // 获取到的数据列表
}
Query with count
{
data: [], // 获取到的数据列表
count: 0 // 符合条件的数据条数
}
count
{
total: 0 // 符合条件的数据条数
}
** Add a single item **
{
id: '' // 新增数据的id
}
Add more items
{
ids: [], // 新增数据的id列表
inserted: 3 // 新增成功的条数
}
update data
{
updated: 1 // 更新的条数,数据更新前后无变化则更新条数为0
}
Added in HBuilderX 3.6.14
A method for judging whether the jql statement currently executed by the trigger is equivalent to the statement passed in by the method.
In addition to using decomposed objects such as field and where, developers can also use isEqualToJql to determine what the currently executing JQL statement is.
If you simply use string comparison, developers will encounter reasons such as single and double quotes, newlines, etc., which cause the comparison to fail. So the isEqualToJql method is provided.
usage
isEqualToJql(string JQLString)
return value
This method returns a bool value, true means that the currently executed jql statement is equal to the incoming statement, otherwise it is not equal
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeCount: async function({
isEqualToJql
} = {}) {
if(isEqualToJql('db.collection("article").count()')) {
console.log('成功匹配了JQL命令:对article表进行count计数且未带条件')
} else {
throw new Error('禁止执行带条件的count')
}
}
}
}
Added in HBuilderX 3.6.16
This parameter is an empty object, which is only used to mount data in before and get it in after
example
// article.schema.ext.js
module.exports = {
trigger: {
beforeUpdate: async function({
triggerContext
} = {}) {
triggerContext.shareVar = 1
},
afterUpdate: async function(){
if (triggerContext.shareVar === 1) {
console.log('获取到的triggerContext.shareVar为1')
}
}
}
}
触发时机 | 说明 |
---|---|
beforeRead | 读取前触发 |
afterRead | 读取后触发 |
beforeCount | 计数前触发 |
afterCount | 计数后触发 |
beforeCreate | 新增前触发 |
afterCreate | 新增后触发 |
beforeUpdate | 更新前触发 |
afterUpdate | 更新后触发 |
beforeDelete | 删除前触发 |
afterDelete | 删除后触发 |
beforeReadAsSecondaryCollection | 集合作为副表被读取前触发,仅使用了getTemp的联表查询才会触发 |
afterReadAsSecondaryCollection | 集合作为副表被读取后触发,仅使用了getTemp的联表查询才会触发 |
Notice
The following article table is taken as an example.
In order not to increase the complexity of the example, all permissions are set to true, do not imitate in actual projects.
// article.schema.ext.js
{
"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"
}
}
}
// article.schema.ext.js
module.exports = {
trigger: {
beforeUpdate: async function({
collection,
operation,
docId,
updateData,
clientInfo
} = {}) {
if(typeof docId === 'string' && (updateData.title || updateData.content)) { //如果字段较多,也可以不列举字段,删掉后半个判断
if(updateData.content) {
// updateData.summary = 'xxxx' // generate summary based on content
}
updateData.update_date = Date.now() // 更新数据的update_date字段赋值为当前服务器时间
}
}
}
}
// article.schema.ext.js
module.exports = {
trigger: {
afterRead: async function({
collection,
operation,
docId,
field,
clientInfo
} = {}) {
const db = uniCloud.database()
// clientInfo.uniIdToken可以解出客户端用户信息,再进行判断是否应该加1。为了让示例简单清晰,此处省略相关逻辑
if(typeof docId === 'string' && field.includes('content')) {
// 读取了content字段后view_count加1
await db.collection('article').doc(docId).update({
view_count: db.command.inc(1)
})
}
}
}
}
// article.schema.ext.js
module.exports = {
trigger: {
beforeDelete: async function({
collection,
operation,
docId,
clientInfo
} = {}) {
const db = uniCloud.database()
if(typeof docId !== 'string') { // 此处也可以加入管理员可以批量删除的逻辑
throw new Error('禁止批量删除')
}
const res = await db.collection('article').doc(docId).get()
const record = res.data[0]
if(record) {
await db.collection('article-archived').add(record)
}
}
}
}
// article.schema.ext.js
module.exports = {
trigger: {
beforeCreate: async function({
collection,
operation,
addDataList,
clientInfo
} = {}) {
for(let i = 0; i < addDataList.length; i++) {
const addDataItem = addDataList[i]
if(!addDataItem.content) {
throw new Error('缺少文章内容')
}
addDataItem.summary = addDataItem.content.slice(0, 100)
}
}
}
}
The jql syntax can be used in the jql trigger to operate the database.
Note: Using jql syntax to operate the database in the trigger will also execute the trigger, which can easily lead to an infinite loop!
For this reason, the uniCloud.databaseForJQL method adds a parameter skipTrigger
, which is used to specify that this database operation skips the execution of the trigger.
skipTrigger is a bool value, true skips the execution of the trigger, false continues to execute the trigger. The default is false.
This parameter does not take effect on the client side, but only on the cloud.
Examples are as follows:
uniCloud.databaseForJQL({
clientInfo,
skipTrigger: true // true跳过执行触发器,false则继续执行触发器。默认为false
})
We now add a reading record table, the schema is as follows
In order not to increase the complexity of the example, all permissions are set to true, do not imitate in actual projects.
// article.schema.ext.js
{
"bsonType": "object",
"required": ["title", "content"],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"bsonType": "string"
},
"article_id": {
"bsonType": "string",
"foreignKey": "article._id"
},
"reader_id": {
"bsonType": "string",
"foreignKey": "uni-id-users._id",
"forceDefaultValue": {
"$env": "uid"
}
}
}
}
Using the jql syntax can automatically verify the identity of the client. Still taking the above article table as an example, insert the reading record in the afterRead trigger. At this point, the reader id will be inserted into the reader_id field
module.exports = {
trigger: {
afterRead: async function ({
docId,
field,
clientInfo
} = {}) {
if(typeof docId !== 'string' || !field.includes('content')) {
return
}
const dbJQL = uniCloud.databaseForJQL({
clientInfo,
skipTrigger: true
})
await dbJQL.collection('article-view-log')
.add({
article_id: docId,
reader_id: dbJQL.getCloudEnv('$cloudEnv_uid')
})
}
}
The public modules and extension libraries that schema extension depends on can also be used by action and validateFunction.
Built-in dependencies: Currently, the schema extension relies on uni-id
or uni-id-common, and uni-id 3.0.7 and above also relies on uni-config-center, these two public modules can be used directly in the trigger. If redis is enabled in the service space, the redis extension can be directly used in the schema extension.
From HBuilderX 3.7.0
, you can right-click on the uniCloud/database
directory of the project to manage the public modules and extension libraries that the schema extension depends on. Also right-click on this directory and select Upload schema extension Js configuration
to synchronize the configuration dependencies to the cloud.
For versions between HBuilderX 3.2.7
and HBuilderX 3.7.0
, you can associate public modules with schema extensions by configuring "includeInClientDB":true
in the package.json of the public modules to be used,This usage is not recommended for HBuilderX 3.7.0
and later versions
An example package.json of a common module used within JQL is below.
{
"name": "test-common",
"version": "1.0.0",
"description": "",
"main": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"includeInClientDB": true
}
After the association relationship is established through the above steps, the public module can be used normally in the database trigger or action cloud function.
Notice