云数据库学习
# DB Schema表介绍
schema第一个作用是对数据库做初始化,第二个作用就是对权限进行校验,B Schema是基于 JSON 格式定义的数据结构的规范。
# 表结构
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":[
// 字段之间的约束关系。比如字段开始时间小于字段结束时间。也可以只校验一个字段。支持表达式
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# schema2code代码生成系统
写完schema后单击右键,点击schema2code,会自动生成页面,新建的页面会放到pages里

新建的schema如下:
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name":{
"title":"姓名", // 为了方便使用schema2code加上title
"description": "请输入姓名",
"bsonType": "string" // 需要加类型,不然生成的页面不知道是什么类型
},
"age":{
"title":"年龄",
"description": "请输入年龄",
"bsonType": "string"
}
}
}
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
点schema2code,直接点确定

会出现一个pages的注册页面,直接点击确定

然后点击合并

现在pages里面就创建好了4个页面

打开list页面看一下,里面有之前创建的一条数据,增删改查都是可以的

# DB schema常用的字段属性
# enum,defaultValue
我们给schema加上性别字段,然后重新生成,注意要重启一下项目才能显示出来
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name":{
"title":"姓名", // 为了方便使用schema2code加上title
"description": "请输入姓名",
"bsonType": "string" // 需要加类型,不然生成的页面不知道是什么类型
},
"age":{
"title":"年龄",
"description": "请输入年龄",
"bsonType": "int"
},
"gender":{
"title":"性别",
"bsonType": "int",
"defaultValue":0,
"enum":[{
"text":"保密",
"value":0
},{
"text":"男",
"value":1
},{
"text":"女",
"value":2
}]
}
}
}
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
37
38
39
40
41

# defaultValue/forceDefaultValue
我们新加一个时间选择
"createTime":{
"title":"时间",
"bsonType": "timestamp"
}
2
3
4

如果想让这个字段记录我们提交时的时间,就需要用到forceDefaultValue
defaultValue和forceDefaultValue都是默认值,即新增一行数据记录时,如果字段内容未提供,则按默认值填充该字段内容。但2者也有区别,如下:
defaultValue没有强制力,是普通的默认值,如果客户端上传一个其他值,则按客户端传的值为准。
forceDefaultValue则是schema强制约定的值,不管客户端传什么都无法修改。只要数据库新增一条记录,字段的值就会是forceDefaultValue。
在实际开发中,forceDefaultValue常用于设置为当前服务器时间、当前登录用户id、客户端ip等。 这些数据都不能通过前端上传,不安全。过去只能在云端写云函数操作。在schema配置后则可以不用写云函数。使用JQL新增数据记录时会自动补齐这些数据。
$env有三个值可以设置:
| 变量 | 说明 |
|---|---|
| now | 当前服务器时间戳 |
| clientIP | 当前客户端IP |
| uid | 当前用户Id,基于uni-id。如果当前用户未登录或登录状态无效会报错 |
"createTime":{
"title":"时间",
"bsonType": "timestamp",
"forceDefaultValue":{
"$env": "now"
}
}
2
3
4
5
6
7
这样就可以自动记录提交时的时间戳了
# required必填字段
在schema一级节点的required中,可以以数组的方式填入多个字段名称。比如以下示例将name字段设为必填
{
"bsonType": "object",
"required": ["name"],
"properties": {
"name": {
"bsonType": "string",
"title": "姓名",
"errorMessage": "{title}不能为空"
}
}
}
2
3
4
5
6
7
8
9
10
11
# errorMessage错误提示
可以自定义错误提示
"name":{
"title":"姓名", // 为了方便使用schema2code加上title
"label":"姓名",
"description": "请输入姓名",
"minLength": 2,
"maxLength": 8,
"bsonType": "string" ,// 需要加类型,不然生成的页面不知道是什么类型
"errorMessage": {
"required": "{label}必填",
"minLength": "{label}不能小于{minLength}个字符",
"maxLength": "{label}不能大于{maxLength}个字符"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# maximum/minimum
如果bsonType为数字时,可接受的最大值和最小值
# 上传图片
"avatar":{
"title":"头像",
"bsonType": "file"
},
2
3
4

可以看到空间存储上有刚刚上传的图片了

但是图片回显有问题,因此我们需要配上跨域配置

(好像是阿里云的问题,我还是无法显示)
# JQL数据库操作说明
JQL,全称 javascript query language,是一种js方式操作数据库的规范。
# 新增
仅允许collection().add()这样的形式
比如在user表里新增一个叫王五的记录:
// 注意,此处的db是通过 uniCloud.databaseForJQL() 得到,而不是 uniCloud.database()
const db = uniCloud.databaseForJQL()
db.collection('user').add({name:"王五"})
2
3
废话不多说,我们新增一个页面来实验一下
<template>
<view>
demo1
<button @click="handleAdd">新增</button>
</view>
</template>
<script setup>
const db = uniCloud.databaseForJQL()
const handleAdd = () => {
db.collection("default").add({
name:"123",
age:15,
gender:0
}).then(res => {
console.log(res)
}).catch((err) => {
uni.showModal({
content:err.errMsg
})
})
}
const handelAdd1 = async () => {
let res = await db.collection("default").add({
name:"123",
age:15,
gender:0
})
console.log(res)
}
</script>
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
# 查询数据
数据查询用get
const getData = async () => {
let res = await db.collection("default").get()
if(res.result.errCode == 0) {
userList.value = res.result.data
}
}
2
3
4
5
6
# 排序orderBy
orderBy允许进行多个字段排序,以逗号分隔。每个字段可以指定 asc(升序)、desc(降序)。默认是升序。
写在前面的排序字段优先级高于后面。
orderBy('quantity asc, create_date desc') //按照quantity字段升序排序,quantity相同时按照create_date降序排序
// asc可以省略,上述代码和以下写法效果一致
orderBy('quantity, create_date desc')
// 注意不要写错成全角逗号
2
3
4
5
# 限制查询记录的条数limit
使用limit方法,可以查询有限条数的数据记录。
比如查询销量top10的书籍,或者查价格最高的一本书。
// 这以上面的book表数据为例,查价格最高的一本书
db.collection('book')
.orderBy('price desc')
.limit(1)
.get()
2
3
4
5
注意
limit默认值是100,即不设置的情况下,默认返回100条数据。limit最大值为1000。
# 查询列表分页
可以通过skip+limit来进行分页查询
// 注意,此处的db是通过 uniCloud.databaseForJQL() 得到,而不是 uniCloud.database()
const db = uniCloud.databaseForJQL()
db.collection('book')
.skip(20) // 跳过前20条
.limit(20) // 获取20条
.get()
// 上述用法对应的分页条件为:每页20条取第2页
2
3
4
5
6
7
8
# 字段过滤
查询时可以使用field方法指定返回字段。不使用field方法时会返回所有字段
列出字段名称,多个字段以半角逗号做分隔符。比如
db.collection('book').field("title,author")
查询结果会返回_id、title、author3个字段的数据。字符串写法,_id是一定会返回的
还可以用as给字段起别名
const db = uniCloud.databaseForJQL()
db.collection('book')
.field('title as book_title,author as book_author')
.get()
2
3
4
以上查询返回如下:
{
"code": "",
"message": "",
"data": [{
"_id": "3",
"book_author": "罗贯中",
"book_title": "三国演义"
}]
}
2
3
4
5
6
7
8
9
# 统计数量getcount
统计符合查询条件的记录数,是数据库层面的概念。
在查询的result里,有一个affectedDocs。但affectedDocs和count计数不是一回事。
- affectedDocs表示从服务器返回给前端的数据条数。默认100条,可通过limit方法调整。
- count则是指符合查询条件的记录总数,至于这些记录是否返回给前端,和count无关。
count计数又有2种场景:
- 单纯统计数量,不查询数据。使用count()方法
db.collection('order').count()
- 查询记录返回详情,同时返回符合查询条件的数量、使用getCount参数
const db = uniCloud.databaseForJQL()
db.collection('order')
.get({
getCount:true
})
2
3
4
5
# 只查一条记录getone
使用JQL的API方式时,可以在get方法内传入参数getOne:true来返回一条数据。
getOne其实等价于上一节的limit(1)。
一般getOne和orderBy搭配。
const db = uniCloud.databaseForJQL()
db.collection('book')
.get({
getOne:true
})
2
3
4
5
# where条件查询
jql对查询条件进行了简化,开发者可以使用where('a==1||b==2')来表示字段a等于1或字段b等于2
db.collection("default")
.where("name == '123'")
2
还可以这样,查询出name包含李四和王五的
db.collection("default")
.where("name in ['李四','王五']")
2
还可以使用正则查询
const res = await db.collection('goods').where(`${new RegExp(searchVal, 'i')}.test(name)`).get()
# doc配合getOne查询单挑记录查询详情
let res = await db.collection('default').doc(id).get({getOne:true})
# remove删除数据与控制台数据库回档
db.collection('default').doc(id).remove()
注意
如果不加doc(id)查询就删除,会把表里所有的都删除掉的!!!千万不要,不过如果误删了,web控制台里有数据库回档
# 条件查找文档后删除
db.collection("table1").where("a>2").remove()
# update更新操作
const db = uniCloud.databaseForJQL()
let collection = db.collection("table1")
let res = await collection.where({_id:'doc-id'})
.update({
name: "Hey",
count: {
fav: 1
}
});
2
3
4
5
6
7
8
9
# 联表查询
# init_data.json数据初始化文件
新建一个分类的schema
// classify.schema.json
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"name":{
"title": "分类名称",
"bsonType": "string"
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
创建一个和表名同名的json文件


表名为classify.schema.json, 那么初始化的文件就叫 classify.init_data.json
// classify.init_data.json
[{
"name":"可爱萌宠"
},{
"name":"明星美女"
}]
2
3
4
5
6
然后右键单击初始化云数据库按钮

然后就成功初始化了数据啦

再按刚才的步骤新建一个壁纸schema并初始化
# foreignKey字段外键数据关联关系
先把页面写出来,还是用之前的查单表的方式写一下页面
<template>
<view class="layout">
<view class="item" v-for="item in dataList" :key="item._id">
<view class="left">
<image :src="item.picurl" mode="widthFix"></image>
</view>
<view class="right">
<view class="desc">{{item.description}}</view>
<view class="name">{{item.classname}}</view>
</view>
</view>
</view>
</template>
<script setup>
import {ref} from "vue"
const db = uniCloud.database()
const dataList = ref([])
const getData = async() => {
let res = await db.collection("wallpaper").get()
console.log(res)
dataList.value = res.result.data
}
getData()
</script>
<style lang="scss" scoped>
.layout {
padding: 30rpx;
.item {
display: flex;
.left {
width: 200rpx;
image {
width: 100%;
}
}
.right {
flex: 1;
padding-left: 30rpx ;
.desc {
font-size: 38rpx;
}
.name {
font-weight: bold;
}
}
}
}
</style>
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

我们修改一下wallpaper的schema,加上classid
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"picurl":{
"title": "链接地址",
"bsonType": "string"
},
"description":{
"title": "壁纸描述",
"bsonType": "string"
},
"classname":{
"title": "分类",
"bsonType": "string"
},
"classid":{
"title": "分类ID",
"bsonType": "string"
}
}
}
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
# foreignKey字段外键
一个复杂的业务系统,有很多张数据表。表与表之间,存在的数据关联。foreignKey用于描述数据关联关系。我们给wallpaper表加上一个字段
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": [],
"permission": {
"read": true,
"create": true,
"update": true,
"delete": true
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"picurl":{
"title": "链接地址",
"bsonType": "string"
},
"description":{
"title": "壁纸描述",
"bsonType": "string"
},
"classname":{
"title": "分类",
"bsonType": "string"
},
"classid":{
"title": "分类ID",
"bsonType": "string",
"foreignKey": "classify._id"
}
}
}
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
就加上这样一个字段,我们就可以联表查询了
// 使用getTemp先过滤处理获取临时表再联表查询,推荐用法
const order = db.collection('order').where('_id=="1"').getTemp() // 注意结尾的方法是getTemp,对order表过滤得到临时表
const res = await db.collection(order, 'book').get() // 将获取的order表的临时表和book表进行联表查询
2
3
let wallTemp = db.collection("wallpaper").getTemp()
let classTemp = db.collection("classify").getTemp()
let res = await db.collection(wallTemp, classTemp).get()
console.log(res)
2
3
4
5

可以看到classid字段直接是一个数组了
# 临时表和虚拟联表可以使用的方法
临时表可以使用以下方法(需按照下面的顺序调用)
collection
geoNear // 新增于 HBuilderX 3.6.10
where
field
orderBy
skip
limit
getTemp
2
3
4
5
6
7
8
虚拟联表可以使用以下方法(需按照下面的顺序调用)
collection
foreignKey
where
field
groupBy
groupField
distinct
orderBy
skip
limit
get
2
3
4
5
6
7
8
9
10
11
# arrayElemAt
返回在指定数组下标的元素。
示例集合users包含以下文档:
{"_id":1,"name":"dave123",favorites:["chocolate","cake","butter","apples"]}
{"_id":2,"name":"li",favorites:["apples","pudding","pie"]}
{"_id":3,"name":"ahn",favorites:["pears","pecans","chocolate","cherries"]}
{"_id":4,"name":"ty",favorites:["ice cream"]}
2
3
4
返回favorites数组中的第一个和最后一个元素:
db.collection('users').field('arrayElemAt(favorites, 0) as first, arrayElemAt(favorites, -1) as last').get()
执行结果:
{"_id":1,"first":"chocolate","last":"apples"}
{"_id":2,"first":"apples","last":"pie"}
{"_id":3,"first":"pears","last":"cherries"}
{"_id":4,"first":"ice cream","last":"ice cream"}
2
3
4
对于上面的例子可以直接:
let wallTemp = db.collection("wallpaper").getTemp()
let classTemp = db.collection("classify").getTemp()
let res = await db.collection(wallTemp, classTemp)
.field("_id,picurl,description,arrayElemAt(classid.name,0) as classname")
.get()
2
3
4
5
6

# 使用uni-file-picker组件实现本地文件上传到云存储
页面使用uni-file-picker
<template>
<view>
<uni-file-picker
v-model="imageValue"
mode="grid"
/>
</view>
</template>
<script setup>
import { ref } from 'vue';
const imageValue = ref('')
</script>
<style lang="scss" scoped>
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
随便选择一个图片,会发现有请求

打开云存储,发现刚刚选择的图片已经上传到云存储了

# 联表查询
const getData = async() => {
console.log("获取数据")
let wallTemp = db.collection('wallpaper').field("description, classid, picurl.url as picurlpath,_id,createTime").orderBy("createTime desc").getTemp()
let classTemp = db.collection("classify").getTemp()
let res = await db.collection(wallTemp, classTemp).field("description, picurlpath, createTime, _id, arrayElemAt(classid.name,0) as classname").get()
listData.value = res.result.data
}
2
3
4
5
6
7
8