跳到主要内容

在微信小程序中读写 Cloudbase 云数据库

一句话定义:在已经接入 Cloudbase 自定义登录的微信小程序里,用 app.database() 做集合 CRUD、条件查询和实时 watch 监听,所有读写都受集合的权限模式约束。

预计耗时:30 分钟 | 难度:进阶

适用场景

这篇是 add-auth-wechat-miniprogram 的下一步。前置已经把 @cloudbase/js-sdk 装好、登录态拿到了,这篇接着讲怎么读写数据。

  • 适用:独立 Cloudbase 环境 + 微信小程序前端,登录态已就绪
  • 不适用:用的是 wx.cloud 的微信·云开发体系(那一套有自己的 wx.cloud.database(),API 形似但不通用)
  • 不适用:对一致性要求极高的金融/库存场景。前端直连数据库走的是「带权限的 SDK 调用」,极端并发下建议把写入收口到云函数

环境要求

依赖版本
@cloudbase/js-sdk2.27.3(已在前置 recipe 装好)
@cloudbase/adapter-wx_mp1.3.1
微信开发者工具1.06.x

另外需要:

  • 已在小程序中调通 add-auth-wechat-miniprogram,auth.hasLoginState() 返回 true
  • Cloudbase 控制台对要操作的集合有「数据库 → 集合管理」的访问权

第一步:创建集合并选好权限模式

去 Cloudbase 控制台 → 数据库 → 集合管理 → 新建集合,假设建一个叫 todos 的集合。

新建之后点进去,左上角能看到「数据权限」按钮,这是这一步的核心。Cloudbase 提供四种内置权限模式:

权限模式谁能读谁能写
所有用户可读,仅创建者可写所有登录用户_openid 等于自己 openid 的记录
仅创建者可读写_openid 等于自己 openid 的记录同左
仅管理端可读写云函数和控制台同左
自定义安全规则secure-database-multi-tenant-rules同左

「仅创建者可读写」是 todo / 私人笔记这类场景的默认选择。Cloudbase 在写入时会自动给文档加 _openid 字段,后续读写都按这个字段过滤,不需要业务代码自己写 where({ _openid: ... })

如果选了「仅管理端可读写」,小程序前端的所有读写都会返回 UNAUTHORIZED,要走云函数中转,本篇不展开。

第二步:在小程序里拿到 db 实例

接着 add-auth-wechat-miniprogramminiprogram/libs/cloudbase.js 的代码,加一行:

import cloudbase from '@cloudbase/js-sdk';
import adapter from '@cloudbase/adapter-wx_mp';

cloudbase.useAdapters(adapter);

const app = cloudbase.init({
env: 'your-env-id',
});

export const auth = app.auth();
export const db = app.database();
export default app;

app.database() 拿到的 db 是一个轻量对象,可以反复 import,不用担心创建多个实例。

为了防止页面在登录态还没就绪时就发起查询,建议在用 db 之前先 await ensureLogin():

import { ensureLogin } from './login';
import { db } from './cloudbase';

export async function getTodos() {
await ensureLogin();
return db.collection('todos').where({}).orderBy('createdAt', 'desc').get();
}

第三步:增删改查

新增

const res = await db.collection('todos').add({
title: '写 recipe',
done: false,
createdAt: db.serverDate(),
});
console.log('inserted id:', res.id);

db.serverDate() 是服务端时间占位符,落库时会被替换成数据库当前时间,避免客户端时钟漂移导致排序错乱。

查询

// 拿全部
const all = await db.collection('todos').get();

// 条件 + 排序 + 翻页
const page = await db
.collection('todos')
.where({ done: false })
.orderBy('createdAt', 'desc')
.skip(0)
.limit(20)
.get();

console.log('docs:', page.data);

返回值结构是 { data: [...], requestId: '...' },文档列表在 data 里,常见的疏忽是写成 page.docs(那是 watch 回调的字段,见第四步)。

复杂条件用 db.command:

const _ = db.command;
const recent = await db
.collection('todos')
.where({
createdAt: _.gte(new Date(Date.now() - 7 * 24 * 3600 * 1000)),
done: _.eq(false),
})
.get();

更新

_id 更新单条:

await db.collection('todos').doc('todo-id').update({
done: true,
updatedAt: db.serverDate(),
});

update 是局部更新,只覆盖传入的字段。如果想整条替换(包括没传的字段会被删掉),用 set:

await db.collection('todos').doc('todo-id').set({
title: '完整替换',
done: true,
// 没传的字段(比如原来的 createdAt)会被清掉
});

按条件批量更新:

await db.collection('todos').where({ done: true }).update({
archived: true,
});

删除

// 按 ID
await db.collection('todos').doc('todo-id').remove();

// 按条件
await db.collection('todos').where({ archived: true }).remove();

第四步:实时监听 watch

watch 让前端订阅一个查询的结果集,记录有变更时通过 onChange 推送。

// pages/todos/todos.js
import { db } from '../../libs/cloudbase';

Page({
data: { todos: [] },

onLoad() {
this.watcher = db
.collection('todos')
.where({ done: false })
.watch({
onChange: (snapshot) => {
// snapshot.docs 是当前查询的全部结果
this.setData({ todos: snapshot.docs });
},
onError: (err) => {
console.error('[watch] error', err);
},
});
},

onUnload() {
// 页面销毁时一定要关掉,否则会内存泄漏 + 重复推送
if (this.watcher) {
this.watcher.close();
this.watcher = null;
}
},
});

四个要点:

  • watch() 返回一个 closer,只能通过 closer.close() 取消监听,不能 removeListener
  • 第一次回调会推一份「初始快照」,内容是当前匹配查询的全部文档,类似一次 get()
  • 后续每次有匹配的文档发生 add / update / delete,都会触发 onChange,snapshot.docs 是「变更后的完整结果集」,不是「增量」
  • 页面 onUnload / 组件 detached 必须 close(),长时间不关掉会触发服务端连接上限

如果想拿增量(只关心这次变化的几条),用 snapshot.docChanges:

onChange: (snapshot) => {
for (const change of snapshot.docChanges) {
// change.dataType: 'init' | 'update' | 'add' | 'remove'
console.log(change.dataType, change.doc);
}
};

运行验证

  1. 微信开发者工具编译运行
  2. 在 todos 页面调 add 插入一条,Console 应输出 inserted id
  3. 控制台 → 数据库 → todos 集合,确认数据有 _openid 字段且值是当前用户的 openid
  4. 在控制台直接改一条记录的 title,小程序页面应该立即看到变化(watch 已生效)
  5. 退出页面,Console 不应再有 watch 推送的日志

常见错误

错误码 / 错误信息原因修复
UNAUTHORIZED / permission denied集合权限是「仅管理端可读写」,或者文档的 _openid 不是当前用户检查集合权限模式,或者在写入时确认登录态正常
collection not exist集合还没在控制台创建控制台手工建一次,SDK 不会自动建集合
INVALID_ARGUMENT 提示字段类型同一字段在不同记录里类型不一致(比如有的是 string 有的是 number)Cloudbase 不强制 schema,但比较 / 排序时类型混乱会报错。统一类型,或者写入前 Number(x) 转一次
watch onChange 不触发大概率页面的 closeronLoad 里被覆盖了多次,旧的没关掉if (this.watcher) this.watcher.close() 守护一下,只保留一个活跃监听
WX_ERR_NETWORK小程序合法域名没配公众平台 → 开发管理 → 服务器域名 → request 合法域名加上 *.tcloudbase.com

错误码定义参考 error-code

相关文档

下一步