在微信小程序中读写 Cloudbase 云数据库
一句话定义:在已经接入 Cloudbase 自定义登录的微信小程序里,用
app.database()做集合 CRUD、条件查询和实时watch监听,所有读写都受集合的权限模式约束。预计耗时:30 分钟 | 难度:进阶
适用场景
这篇是 add-auth-wechat-miniprogram 的下一步。前置已经把 @cloudbase/js-sdk 装好、登录态拿到了,这篇接着讲怎么读写数据。
- 适用:独立 Cloudbase 环境 + 微信小程序前端,登录态已就绪
- 不适用:用的是
wx.cloud的微信·云开发体系(那一套有自己的wx.cloud.database(),API 形似但不通用) - 不适用:对一致性要求极高的金融/库存场景。前端直连数据库走的是「带权限的 SDK 调用」,极端并发下建议把写入收口到云函数
环境要求
| 依赖 | 版本 |
|---|---|
@cloudbase/js-sdk | 2.27.3(已在前置 recipe 装好) |
@cloudbase/adapter-wx_mp | 1.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-miniprogram 里 miniprogram/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);
}
};
运行验证
- 微信开发者工具编译运行
- 在 todos 页面调
add插入一条,Console 应输出 inserted id - 控制台 → 数据库 → todos 集合,确认数据有
_openid字段且值是当前用户的 openid - 在控制台直接改一条记录的
title,小程序页面应该立即看到变化(watch 已生效) - 退出页面,Console 不应再有 watch 推送的日志
常见错误
| 错误码 / 错误信息 | 原因 | 修复 |
|---|---|---|
UNAUTHORIZED / permission denied | 集合权限是「仅管理端可读写」,或者文档的 _openid 不是当前用户 | 检查集合权限模式,或者在写入时确认登录态正常 |
collection not exist | 集合还没在控制台创建 | 控制台手工建一次,SDK 不会自动建集合 |
INVALID_ARGUMENT 提示字段类型 | 同一字段在不同记录里类型不一致(比如有的是 string 有的是 number) | Cloudbase 不强制 schema,但比较 / 排序时类型混乱会报错。统一类型,或者写入前 Number(x) 转一次 |
watch onChange 不触发 | 大概率页面的 closer 在 onLoad 里被覆盖了多次,旧的没关掉 | 用 if (this.watcher) this.watcher.close() 守护一下,只保留一个活跃监听 |
WX_ERR_NETWORK | 小程序合法域名没配 | 公众平台 → 开发管理 → 服务器域名 → request 合法域名加上 *.tcloudbase.com |
错误码定义参考 error-code。
相关文档
- Web SDK 数据库 API —
collection / doc / where / get完整参考 - 数据库权限管理 — 四种内置模式说明
- 实时数据推送 —
watch的协议层细节 - add-auth-wechat-miniprogram — 前置:登录接入
下一步
- 多租户隔离场景:secure-database-multi-tenant-rules
- 给写入加订阅消息触达:add-subscribe-message-cloud-function
- 数据更新后跑日报:schedule-cloud-function-cron-job