readme.md 9.4 KB
Newer Older
芊里 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
## 简介

每个查询业务,都要写一个云函数,很麻烦,也占用云函数的总数量。

本插件提供了一个通用的数据库查询云函数,由前端向云函数传递要查询的条件,比如查询哪个表、查询哪些字段、where条件和排序是什么。

> 举例:在前端的list.vue列表页面,可以调用本云函数,传入列表查询条件。然后写content.vue详情页面时,无需再新建一个云函数,可以直接在content.vue页面继续调用相同的本云函数,传入详情的查询条件。

客户端的查询条件语法,与云函数里查询数据库的语法是相同的。[查询api手册](https://uniapp.dcloud.io/uniCloud/cf-database?id=%e6%9f%a5%e8%af%a2%e6%96%87%e6%a1%a3)

与某些小程序云的客户端直接操作数据库不同,本方案有2个明显优势:
- 安全:查询权限是在云函数里控制台的,能查什么表、什么字段,在云端控制。如果全部放在前端,由于前端的不可信任,相当于无法控制权限了。而某些小程序云提供的客户端权限,并不可编程,无法满足开发者的实际业务权限需求
- 包体积:某些小程序云的客户端操作数据库是一个数M大小的js sdk,如果迁移到H5和App,对应用的启动速度、性能、体积影响非常大

综上,本插件的优势在于:
1. 提高开发效率
2. 减少云函数数量
3. 安全,可控制权限
4. 性能好

本项目包括云函数和客户端两部分,需要搭配使用,具体请参考下面文档。

**示例项目使用须知**

1. 下载示例项目后选择服务空间
2. 在db_init.json上右键初始化数据库
3. 上传公共模块及云函数,公共模块用法参考[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
4. 运行项目即可

示例项目文件介绍:

<pre v-pre="" data-lang="">
	<code class="lang-" style="padding:0">
┌─cloudfunctions    云函数
│  ├─common         公共模块
|  │  └─uni-curd    数据库查询通用公共模块
L
123  
linju 已提交
37
|  ├─unicloud-db   在云函数中控制权限,并调用uni-curd完成查询
芊里 已提交
38 39
|  └─db_init.json   初始化数据库
├─js_sdk            前端公共js目录
L
123  
linju 已提交
40
|  └─unicloud-db   前端的js库,封装了查询语法
芊里 已提交
41 42 43 44 45 46 47 48 49 50
├─pages             业务页面文件存放的目录
│  ├─index
│  │  └─index.vue   index示例页面
├─main.js           Vue初始化入口文件
├─App.vue           应用配置,用来配置App全局样式以及监听 <a href="/frame?id=应用生命周期">应用生命周期</a>
├─manifest.json     配置应用名称、appid、logo、版本等打包信息,<a href="/collocation/manifest">详见</a>
└─pages.json        配置页面路由、导航条、选项卡等页面类信息,<a href="/collocation/pages">详见</a>
	</code>
</pre>

L
123  
linju 已提交
51 52
- 云函数里包括一个公共模块`uni-curd`和一个云函数`unicloud-db`
- 前端包括一个js sdk`unicloud-db`,然后就是index.vue里的示例调用
芊里 已提交
53 54 55 56 57 58 59 60 61 62
- index示例页面,里面包含两个示例一个简单查询一个分页查询

## 客户端公共模块

客户端js-sdk主要负责组装查询逻辑

**示例代码**

```js
// 引入公共模块
L
123  
linju 已提交
63
import db from '@/js_sdk/unicloud-db/index.js'
芊里 已提交
64 65
const dbCmd = db.command

L
123  
linju 已提交
66
// 使用unicloud-db
芊里 已提交
67
uniCloud.callFunction({
L
123  
linju 已提交
68
	name: 'unicloud-db',
芊里 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	data: {
		command: db.collection('list').where({
			name: new RegExp('','g'),
			time: dbCmd.gt(1105885393581)
		}).field({
			extra: false
		}).get()
	},
	success(res) {
		// ...
	},
	fail(err) {
		// ...
	}
})
```

**使用说明**

语法与云函数写查询数据库一致,目前有以下限制:

- 不可使用db.serverDate、db.Geo、db.RegExp
- 上传时会对query进行序列化,除Date类型、RegExp之外的所有不可序列化的参数类型均不支持(例如:undefined)
- 为方便控制禁止前端使用set方法,一般情况下也不需要前端使用set

## 云函数公共模块

云函数公共模块`uni-curd`主要负责解析客户端查询逻辑,对客户端行为做简单的限制

**使用示例**

```js
'use strict';
const uniCurd = require('uni-curd')
const db = uniCloud.database()
const dbCmd = db.command
exports.main = async (event, context) => {
	// 这里可以判断用户身份给予不同权限,例如:可以从event里拿到uni-id的token,根据token和客户端参数决定查询权限限制
	try {
		const res = await uniCurd({
			command: event.command,
			pagination: event.pagination,
			rules: {
				list: { // 数据表名
					// CRUD权限
					create: false,
					read: true,
					update: false,
					delete: false,
					// 是否允许使用聚合
					aggregate: false,
					// 是否允许使用联表查询,联表查询时blockedField不会对被连接的数据表生效
					lookup: false,
					// 使用聚合时blockField不会覆盖客户端的project,而是在聚合第一阶段插入project,不使用聚合时会在最后阶段插入一个field(会覆盖客户端的field方法)
					blockedField: ['extra'],
					// 不使用聚合时mixinCondition会在没有where的时候在collection方法之后插入where,有where时会跟where条件进行合并,取原条件且mixinCondition。使用聚合时会在第一阶段插入match使用混入的条件,如果有blockedField会插入在blockedField对应的project之前
					mixinCondition: {
						time: dbCmd.gt(1000000000000)
					},
					// 更多用法请参考下方参数说明文档
				}
			}
		})
		return res

	} catch (e) {
		return {
			code: 10001,
			msg: e.message
		}
	}
};

```

**参数说明**

|参数名		|类型	|是否必填	|默认值	|说明						|
|:-:		|:-:	|:-:		|:-:	|:-:						|
|command	|Object	|是			|-		|客户端上传的查询条件		|
|pagination	|Object	|否			|-		|如需分页,请在此字段内配置	|
|rules		|Object	|是			|-		|权限规则					|

**pagination参数说明**

|参数名		|类型	|是否必填	|默认值	|说明		|
|:-:		|:-:	|:-:		|:-:	|:-:		|
|pageSize	|Object	|是			|-		|每页数量	|
|current	|Object	|是			|-		|当前页码	|

**rules参数说明**

rules下可以对不同的数据表配置不同的权限,比如以下规则代表”数据表list允许插入,数据表goods允许更新“

```js
{
	list: {
		create: true,
	},
	goods: {
		update: true
	}
}
```

|参数名			|类型	|是否必填	|默认值	|说明								|
|:-:			|:-:	|:-:		|:-:	|:-:								|
|create			|Boolean|否			|false	|是否开启插入权限					|
|read			|Boolean|否			|true	|是否开启读权限						|
|update			|Boolean|否			|false	|是否开启更新权限					|
|delete			|Boolean|否			|false	|是否开启删除权限					|
|aggregate		|Boolean|否			|false	|是否允许聚合						|
|lookup			|Boolean|否			|false	|是否允许联表查询					|
|blockedField	|Array	|否			|-		|屏蔽的数据库字段,请阅读注意事项	|
|mixinCondition	|Object	|否			|-		|混入条件,请阅读注意事项			|
|hooks			|Object	|否			|-		|回调方法							|

**hooks参数说明**

|参数名				|类型		|是否必填	|默认值	|说明																							|
|:-:				|:-:		|:-:		|:-:	|:-:																							|
|beforeStageAppend	|Function	|否			|-		|每个阶段被添加之前执行																			|
|afterStageAppend	|Function	|否			|-		|每个阶段被添加之后执行																			|
|beforeSend			|Function	|否			|-		|最终阶段'get', 'end', 'count', 'add', 'remove', 'update'添加之前执行,在beforeStageAppend之后	|

**回调方法的使用**

回调参数如下:

```js
{
	state: {
		useAggregate, // 是否使用了聚合
		useLookup, // 是否使用了联表查询
		type, // 操作类型,可能的值为create、read、update、delete
		collection, // 当前数据表名
		methodList // 使用到的方法列表
	},
	stage: {
		method, // 当前阶段方法名
		args // 当前阶段方法参数
	},
	exec // 已经组装的查询指令
}
```

回调方法中可以通过返回结果修改数据库指令,例如以下示例在skip之后插入一个limit

```js
afterStageAppend: function({
	state,
	stage,
	exec
}) {
	if(stage.method === 'skip') {
		return exec.limit(1)
	}
},
```

**注意事项**

- 关于blockedField
  + 使用聚合时blockField不会覆盖客户端的project,而是在聚合第一阶段插入project
  + 不使用聚合时会在最后阶段插入一个field(会覆盖客户端的field方法)
  + blockedField仅对读操作生效

- 关于mixinCondition
  + mixinCondition内可以使用数据库操作符
  + 不使用聚合时mixinCondition会在没有where的时候在collection方法之后插入where,有where时会跟where条件进行合并,取原条件且mixinCondition。
  + 使用聚合时会在第一阶段插入match使用混入的条件,如果有blockedField会插入在blockedField对应的project之前
  + mixinCondition会对除插入以外的所有操作生效
  + 使用mixinCondition时客户端不可使用`collection('xxx').doc('xxx')`方法(1.0.8版本起即使有mixinCondition客户端也可以使用doc方法)

- 关于联表查询
  + 连接的数据表也会受所配置的权限规则中对应数据表规则限制,主要是read,目前连接的数据表不会受blockedField限制

**参考**

在线通讯录项目,完整的演示了如何基于clientDB在客户端代码里实现数据的增删改查,是学习clientDB的重要示例项目。该项目插件地址:[https://ext.dcloud.net.cn/plugin?id=2574](https://ext.dcloud.net.cn/plugin?id=2574)