微信小程序云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。
关系型数据库和 JSON 数据库的概念对应关系如下表:
关系型 | 文档型 |
---|---|
数据库 database | 数据库 database |
表 table | 集合 collection |
行 row | 记录 record / doc |
列 column | 字段 field |
部分样式我放在app.wxss文件中就没有写出来,样式可以自己美化,每个人审美标准不一样。
Page({
/**
* 页面的初始数据
*/
data: {
openid: "", // 用户身份ID
opName: "", // 数据库操作名称,如‘add’‘qry’等等
opResult: "", // 数据库操作结果字符串
opResult2: "", // 数据库操作结果字符串2
resData: null, // 数据库操作结果数据
resData2: null, // 数据库操作结果数据2
finished: false // 数据库操作是否完成的标记
},
// “增”按钮点击事件函数
addRecord: function() {
this.setData({
opName: "add",
finished: false
})
},
// “删”按钮点击事件函数
deleteRecord: function() {
this.setData({
opName: "del",
finished: false
})
},
// “改”按钮点击事件函数
updateRecord: function() {
this.setData({
opName: "upd",
finished: false
})
},
// “查”按钮点击事件函数
queryRecord: function() {
this.setData({
opName: "qry",
finished: false
})
},
// 拼接日期字符串的函数
makeDateString: function(dateObj) {
return dateObj.getFullYear() + '-' + (dateObj.getMonth() + 1) + '-' + dateObj.getDate();
},
// 拼接时间字符串的函数
makeTimeString: function(dateObj) {
return dateObj.getHours() + ':' + dateObj.getMinutes() + ':' + dateObj.getSeconds();
},
// 添加记录事件函数
doAdd: function(e) {
console.log(e)
var workContent = e.detail.value.workContent
if (workContent != "") { // 如果用户输入内容不为空
const db = wx.cloud.database() // 调用接口返回云开发数据库引用保存在常量db中
var myDate = new Date()
db.collection('work_done').add({ // 向集合‘work_done’中添加一条记录
data: { // 一条记录的字段数据
date: this.makeDateString(myDate), // 日期字符串
time: this.makeTimeString(myDate), // 时间字符串
content: workContent // 工作内容字符串
},
complete: res => { // 操作完成时的回调函数
this.setData({
finished: true
})
},
success: res => { // 操作成功时的回调函数
// 在返回结果中会包含新创建的记录的 _id
this.setData({
opResult: "操作完成,新增一条记录,_id为:\n ",
resData: res._id
})
wx.showToast({
title: '新增记录成功',
})
console.log('[数据库] [新增记录] 成功,记录 _id: ', res._id)
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '新增记录失败'
})
console.error('[数据库] [新增记录] 失败:', err)
}
})
} else {
wx.showToast({
title: '请输入事情描述!',
})
}
},
// 删除记录事件函数
doDelete: function(e) {
console.log(e)
var that = this
var itemID = e.detail.value.itemID
if (itemID != "") { // 如果用户输入的记录id不为空
const db = wx.cloud.database() // 调用接口返回云开发数据库引用保存在常量db中
db.collection('work_done').doc(itemID).get({ // 从集合‘work_done’中查询id为itemID的记录
success: res => { // 操作成功时的回调函数
console.log(res)
this.setData({
opResult: '查询记录成功:\n',
resData: res.data
})
db.collection('work_done').doc(itemID).remove({ // 操作接口从集合‘work_done’中删除这条记录
complete: res => { // 操作完成时的回调函数
that.setData({
finished: true
})
},
success: res => { // 操作成功时的回调函数
console.log('[数据库] [删除记录] 成功: ', res)
that.setData({
opResult2: '已成功删除上面的记录。'
})
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '删除记录失败'
})
console.error('[数据库] [删除记录] 失败:', err)
}
})
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '查询记录失败'
})
console.error('[数据库] [查询记录] 失败:', err)
}
})
} else {
wx.showToast({
title: '请输入itemID!',
})
}
},
// 更新记录事件函数
doUpdate: function(e) {
console.log(e)
var that = this
var itemID = e.detail.value.itemID
var workContent = e.detail.value.workContent
if (itemID != "") { // 如果用户输入的记录id不为空
const db = wx.cloud.database() // 调用接口返回云开发数据库引用保存在常量db中
db.collection('work_done').doc(itemID).get({ // 从集合‘work_done’中查询id为itemID的记录
success: res => { // 操作成功时的回调函数
this.setData({
opResult: '查询记录成功:\n',
resData: res.data
})
db.collection('work_done').doc(itemID).update({ // 更新集合‘work_done’中的这条记录
data: {
content: workContent,
},
complete: res => { // 操作完成时的回调函数
that.setData({
finished: true
})
},
success: res => { // 操作成功时的回调函数
console.log('[数据库] [更新记录] 成功: ', res)
that.setData({
opResult2: '已成功更新上面的记录内容为:\n',
resData2: workContent
})
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '更新记录失败'
})
console.error('[数据库] [更新记录] 失败:', err)
}
})
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '查询记录失败'
})
console.error('[数据库] [查询记录] 失败:', err)
}
})
} else {
wx.showToast({
title: '请输入itemID!',
})
}
},
// 查询记录事件函数
doQuery: function(e) {
console.log(e)
var workDate = e.detail.value.workDate
if (workDate != "") { // 如果用户输入的日期字符串不为空
const db = wx.cloud.database() // 调用接口返回云开发数据库引用保存在常量db中
db.collection('work_done').where({ // 从集合‘work_done’中查询记录(最多二十条)
date: workDate // 记录创建日期
}).get({
complete: res => { // 操作完成时的回调函数
this.setData({
finished: true
})
},
success: res => { // 操作成功时的回调函数
this.setData({
opResult: "操作完成,查询到" + res.data.length + "条记录:\n ",
resData: res.data
})
console.log('[数据库] [查询记录] 成功: ', res)
},
fail: err => { // 操作失败时的回调函数
wx.showToast({
icon: 'none',
title: '查询记录失败'
})
console.error('[数据库] [查询记录] 失败:', err)
}
})
} else {
wx.showToast({
title: '请输入查询日期!',
})
}
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function(options) {
this.setData({
openid: wx.getStorageSync('openID') // 读取本地存储的openID(“获取OpenID”案例中同步存储了用户的openid到本地)
});
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function() {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function() {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function() {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
}
})
<!--pages/cloud/Database/index.wxml-->
<view class="box">
<view class='title'>数据库操作</view>
<text class="preNote">请点击相应按钮,实现在数据库中增加、删除、更新或查询记录的操作</text>
<view class="Hcontainer">
<button class='DBbutton' bindtap="addRecord" style='background-color: {{opName=="add"?"#ae57a4":"blue"}}'>增</button>
<button class='DBbutton' bindtap="deleteRecord" style='background-color: {{opName=="del"?"#ae57a4":"blue"}}'>删</button>
<button class='DBbutton' bindtap="updateRecord" style='background-color: {{opName=="upd"?"#ae57a4":"blue"}}'>改</button>
<button class='DBbutton' bindtap="queryRecord" style='background-color: {{opName=="qry"?"#ae57a4":"blue"}}'>查</button>
</view>
<view wx:if="{{opName=='add'}}">
<!-- 新增记录 -->
<view class="record-op" style='{{opName!=""?"border: 1px solid #00007f":""}}'>
<form bindsubmit='doAdd'>
<text class="op-note">添加一件今日已完成的工作:</text>
<textarea name="workContent" class="content-input" maxlength="50" placeholder="事情描述(不超过50个字)" auto-height adjust-position cursor-spacing='20px'></textarea>
<button form-type='submit' type='primary'>确定</button>
</form>
</view>
<view wx:if="{{finished}}" class="op-result">
<text class="headline">操作结果信息:</text>
<text class='text-title'>{{opResult}}</text>
<text class="list" selectable>{{resData}}</text>
</view>
</view>
<view wx:if="{{opName=='del'}}">
<!-- 删除记录 -->
<view class="record-op" style='{{opName!=""?"border: 1px solid #00007f":""}}'>
<form bindsubmit='doDelete'>
<text class="op-note">指定删除item的ID:</text>
<input name="itemID" class="line-input" maxlength="32" placeholder="itemID(32位字符串)" />
<button form-type='submit' type='primary'>确定</button>
</form>
</view>
<view wx:if="{{finished}}" class="op-result">
<text class="headline">操作结果信息:</text>
<text class='text-title'>{{opResult}}</text>
<text class="list" selectable>{{resData._id}}:{{resData.date}} {{resData.time}} {{resData.content}}</text>
<text class='text-title'>{{opResult2}}</text>
</view>
</view>
<view wx:if="{{opName=='upd'}}">
<!-- 更新记录 -->
<view class="record-op" style='{{opName!=""?"border: 1px solid #00007f":""}}'>
<form bindsubmit='doUpdate'>
<text class="op-note">指定更新item的ID:</text>
<input name="itemID" class="line-input" maxlength="32" placeholder="itemID(32位字符串)" />
<text class="op-note">输入更新的内容:</text>
<textarea name="workContent" class="content-input" maxlength="50" placeholder="事情描述(不超过50个字)" auto-height adjust-position cursor-spacing='20px'></textarea>
<button form-type='submit' type='primary'>确定</button>
</form>
</view>
<view wx:if="{{finished}}" class="op-result">
<text class="headline">操作结果信息:</text>
<text class='text-title'>{{opResult}}</text>
<text class="list" selectable>{{resData._id}}:{{resData.date}} {{resData.time}} {{resData.content}}</text>
<text class='text-title'>{{opResult2}}</text>
<text class="list" selectable>{{resData2}}</text>
</view>
</view>
<view wx:if="{{opName == 'qry'}}">
<!-- 查询记录 -->
<view class="record-op" style='{{opName!=""?"border: 1px solid #00007f":""}}'>
<form bindsubmit='doQuery'>
<text class="op-note">指定查询日期(年-月-日,不需要无效的0):</text>
<input name="workDate" class="line-input" maxlength="10" placeholder="事件日期(年-月-日)" />
<button form-type='submit' type='primary'>确定</button>
</form>
</view>
<view wx:if="{{finished}}" class="op-result">
<text class="headline">操作结果信息:</text>
<text class='text-title'>{{opResult}}</text>
<block wx:for='{{resData}}' wx:key='{{item._id}}'>
<text class="list" selectable>{{item._id}}:{{item.date}} {{item.time}} {{item.content}}</text>
</block>
</view>
</view>
</view>
/* pages/cloud/Database/index.wxss */
.preNote {
padding: 20px;
font-size: 32rpx;
line-height: 40rpx;
color: #666;
box-sizing: border-box;
}
.Hcontainer {
margin: 20rpx 0rpx;
padding: 0 50rpx;
display: flex;
flex-direction: row;
justify-content: space-around;
}
.DBbutton {
width: 100rpx;
color: white;
background-color: #0066ff;
}
.record-op {
margin: 10rpx;
padding: 20rpx;
box-sizing: border-box;
}
.op-note {
color: #000;
font-size: 14px;
}
.content-input {
width: 90%;
padding: 20rpx;
margin: 20rpx;
min-height: 200rpx;
border: 1px solid #ccc;
box-sizing: border-box;
}
.op-result {
padding: 10rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.op-result .headline {
margin-top: 50rpx;
font-size: 40rpx;
font-weight: bold;
color: #0066ff;
}
.op-result .text-title {
margin-top: 20rpx;
margin-bottom: 10rpx;
padding-left: 20rpx;
padding-right: 20rpx;
font-size: 14px;
color: red;
}
.op-result .list {
margin-top: 20rpx;
font-size: 28rpx;
color: black;
display: block;
}
.line-input {
min-height: 30px;
line-height: 20px;
width: 100%;
padding: 5px;
margin: 10px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
}
下面对Data的字段做下补充说明。
Date 类型用于表示时间,精确到毫秒,在小程序端可用 JavaScript 内置 Date 对象创建。需要特别注意的是,在小程序端创建的时间是客户端时间,不是服务端时间,这意味着在小程序端的时间与服务端时间不一定吻合,如果需要使用服务端时间,应该用 API 中提供的 serverDate 对象来创建一个服务端当前时间的标记,当使用了 serverDate 对象的请求抵达服务端处理时,该字段会被转换成服务端当前的时间,更棒的是,我们在构造 serverDate 对象时还可通过传入一个有 offset 字段的对象来标记一个与当前服务端时间偏移 offset 毫秒的时间,这样我们就可以达到比如如下效果:指定一个字段为服务端时间往后一个小时。
那么当我们需要使用客户端时间时,存放 Date 对象和存放毫秒数是否是一样的效果呢?不是的,我们的数据库有针对日期类型的优化,建议大家使用时都用 Date 或 serverDate 构造时间对象。
以下是一个示例的集合数据,假设我们有一个 books 集合存放了图书记录,其中有两本书:
[
{
"_id": "Wzh76lk5_O_dt0vO",
"title": "The Catcher in the Rye",
"author": "J. D. Salinger",
"characters": [
"Holden Caulfield",
"Stradlater",
"Mr. Antolini"
],
"publishInfo": {
"year": 2019,
"country": "United States"
}
},
{
"_id": "Wzia0lk5_O_dt0vR",
"_openid": "ohl4L0Rnhq7vmmbT_DaNQa4ePaz0",
"title": "The Lady of the Camellias",
"author": "Alexandre Dumas fils",
"characters": [
"Marguerite Gautier",
"Armand Duval",
"Prudence",
"Count de Varville"
],
"publishInfo": {
"year": 1998,
"country": "France"
}
}
]
在图书信息中,我们用 title, author 来记录图书标题和作者,用 characters 数组来记录书中的主要人物,用 publishInfo 来记录图书的出版信息。在其中我们可以看到,字段既可以是字符串或数字,还可以是对象或数组,就是一个 JSON 对象。
每条记录都有一个 _id 字段用以唯一标志一条记录、一个 _openid 字段用以标志记录的创建者,即小程序的用户。需要特别注意的是,在管理端(控制台和云函数)中创建的不会有 _openid 字段,因为这是属于管理员创建的记录。开发者可以自定义 _id,但不可自定义和修改 _openid 。_openid 是在文档创建时由系统根据小程序用户默认创建的,开发者可使用其来标识和定位文档。
数据库 API 分为小程序端和服务端两部分,小程序端 API 拥有严格的调用权限控制,开发者可在小程序内直接调用 API 进行非敏感数据的操作。对于有更高安全要求的数据,可在云函数内通过服务端 API 进行操作。云函数的环境是与客户端完全隔离的,在云函数上可以私密且安全的操作数据库。
数据库 API 包含增删改查的能力,使用 API 操作数据库只需三步:获取数据库引用、构造查询/更新条件、发出请求。以下是一个在小程序中查询数据库的发表于美国的图书记录的例子:
// 1. 获取数据库引用
const db = wx.cloud.database()
// 2. 构造查询语句
// collection 方法获取一个集合的引用
// where 方法传入一个对象,数据库返回集合中字段等于指定值的 JSON 文档。API 也支持高级的查询条件(比如大于、小于、in 等),具体见文档查看支持列表
// get 方法会触发网络请求,往数据库取数据
db.collection('books').where({
publishInfo: {
country: 'United States'
}
}).get({
success: function(res) {
// 输出 [{ "title": "The Catcher in the Rye", ... }]
console.log(res)
}
})