小程序云开发数据库怎么用:从创建集合到读写数据

小程序云开发数据库怎么用:从创建集合到读写数据

小程序云开发数据库怎么用:从创建集合到读写数据

小程序云开发数据库是微信云开发提供的文档型数据库。它可以与小程序云环境配合使用,让开发者在不自行搭建数据库服务器的情况下保存和查询业务数据。本文从准备环境开始,逐步创建集合、读写文档,并说明前端直接访问与云函数访问的区别,以及权限和数据校验方面需要注意的边界。

文中的示例以微信小程序原生开发和云开发 JavaScript SDK 为背景。云开发控制台、SDK 版本和平台规则可能随产品更新而变化;实际项目应以当前控制台及官方文档展示的配置和额度为准。

一、云开发数据库适合做什么

云开发数据库适合保存小程序中需要持久化、可按条件查询的业务数据,例如用户提交的反馈、简单的内容列表、活动报名记录、轻量级的商品信息或用户设置。它使用集合和文档组织数据,数据结构灵活,适合业务模型变化较快的应用。

使用前要区分几个概念:

  • 云环境:云开发资源所在的环境。数据库、云函数和存储等资源都归属某个环境。
  • 集合:一组文档的容器,作用上可以粗略类比关系型数据库中的表,但文档结构不必完全一致。
  • 文档:集合中的一条数据,通常包含若干字段。每份文档有系统分配的 _id,也可以使用业务字段进行查询。
  • 数据库 SDK:小程序端或云函数端用来调用数据库的接口。两种调用位置的身份与权限边界不同。

云数据库不是任意类型业务的最佳选择。如果业务强依赖复杂关联查询、事务处理方式、固定关系模型或已有数据库生态,应先评估平台能力、查询方式和迁移成本。数据规模、并发量及具体费用也应结合当前套餐与产品规则核实,不宜仅凭“云开发”三个字推断资源没有限制。

二、开通环境并初始化 SDK

在微信开发者工具中创建或打开支持云开发的项目,进入云开发控制台并创建环境。环境 ID 可在控制台中查看。首次使用时,要确认开发者工具当前选择的环境与代码初始化使用的环境一致,否则可能出现“集合不存在”、读到空数据或请求落入另一个环境等问题。

在小程序入口文件(通常是 app.js)初始化云开发:

App({
  onLaunch() {
    if (!wx.cloud) {
      console.error('当前基础库不支持 wx.cloud')
      return
    }

    wx.cloud.init({
      env: '你的云环境ID',
      traceUser: true
    })
  }
})

将 你的云环境ID 替换为实际环境 ID。traceUser 用于配合云开发调用链路追踪,不是身份验证或授权开关;开启它并不能代替登录校验。若项目已经在其他位置初始化云开发,不要重复初始化或让不同模块使用互相矛盾的环境配置。

较大的项目可以把数据库操作集中到单独模块中,避免页面散落环境配置和查询细节。例如创建 utils/db.js:

const db = wx.cloud.database()

module.exports = {
  db
}

页面引用时使用相同的数据库实例:

const { db } = require('../../utils/db.js')

三、创建集合与设计文档

打开云开发控制台中的数据库页面,选择对应环境,创建一个名为 todos 的集合。集合名称在代码中要保持一致。也可以通过数据库 SDK 创建集合,但初学和人工管理时,直接在控制台创建更直观;创建集合并不等于设置好权限,权限需要单独检查。

一个待办文档可以设计为:

{
  "title": "准备项目周报",
  "done": false,
  "createdAt": "由数据库命令生成的时间",
  "owner": "用户标识"
}

上面的 createdAt 实际应保存为数据库可识别的日期类型,而不是示意文本。字段类型尽量稳定:例如 done 始终使用布尔值,title 始终使用字符串。文档结构虽灵活,但任意混用类型会让筛选、排序和前端展示更难维护。

设计字段时应考虑哪些数据由客户端提供,哪些数据应由可信服务端生成。例如标题可以由用户提交,而“当前用户是谁”“是否已付款”“管理员审核结果”等字段不能仅凭客户端传来的值认定为真实。客户端代码可被检查和修改,不能把界面上隐藏了按钮当成安全控制。

四、新增数据:add 与字段验证

下面的示例从输入框读取标题,做基础校验后新增一条待办。它适用于演示普通数据库写入;生产环境仍须配合合理的数据库权限,并根据业务需要在服务端校验数据。

const { db } = require('../../utils/db.js')

Page({
  data: {
    title: ''
  },

  onTitleInput(event) {
    this.setData({
      title: event.detail.value
    })
  },

  async addTodo() {
    const title = this.data.title.trim()

    if (!title) {
      wx.showToast({
        title: '请输入待办标题',
        icon: 'none'
      })
      return
    }

    try {
      const result = await db.collection('todos').add({
        data: {
          title,
          done: false,
          createdAt: db.serverDate()
        }
      })

      console.log('新增文档 ID:', result._id)
      this.setData({ title: '' })
    } catch (error) {
      console.error('新增失败:', error)
      wx.showToast({
        title: '保存失败',
        icon: 'none'
      })
    }
  }
})

add 会新增一份文档,成功结果中可以取得新文档的 _id。db.serverDate() 表示由数据库侧生成当前时间,避免完全依赖设备本地时钟;它不能代替业务层对时间范围、重复提交等规则的校验。

示例只检查标题非空。真实业务还应按产品需求处理长度、格式、敏感内容和重复提交。不要把“前端已经校验过”当作服务端校验的替代:用户可以绕过页面直接构造请求。若数据会影响账户权益、审核结果或资金相关状态,应由云函数等可信服务端入口重新校验,并只允许必要字段写入。

五、查询数据:get、where、排序与分页

读取集合数据可以调用 get:

const { db } = require('../../utils/db.js')

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

  async loadTodos() {
    try {
      const result = await db.collection('todos')
        .orderBy('createdAt', 'desc')
        .get()

      this.setData({
        todos: result.data
      })
    } catch (error) {
      console.error('读取失败:', error)
    }
  }
})

result.data 是本次查询返回的文档数组。不要假设一次 get 会自动返回集合中的全部数据。查询接口有返回条数限制,具体上限可能因调用端、SDK 和平台规则而异;需要读取较多数据时,应使用分页,并核对当前文档规定的限制。

按条件筛选时使用 where。例如只读取未完成的待办:

const result = await db.collection('todos')
  .where({
    done: false
  })
  .orderBy('createdAt', 'desc')
  .get()

console.log(result.data)

也可以使用数据库命令表达比较条件:

const db = wx.cloud.database()
const _ = db.command

const result = await db.collection('todos')
  .where({
    priority: _.gte(2),
    done: false
  })
  .get()

查询字段、字段类型和索引要与实际数据保持一致。对大量数据或常用排序条件,应按控制台提示和查询需求配置索引;缺少合适索引时,查询可能失败或性能不理想。不要为了“查起来方便”无条件读取整个集合,再在客户端过滤,这既增加传输量,也可能暴露本不该下发的数据。

分页可以通过 skip 和 limit 实现简单的偏移分页。以下示例每次读取一页,并将结果追加到页面列表:

const { db } = require('../../utils/db.js')

Page({
  data: {
    todos: [],
    pageSize: 20,
    pageIndex: 0,
    loading: false,
    hasMore: true
  },

  async loadNextPage() {
    if (this.data.loading || !this.data.hasMore) return

    this.setData({ loading: true })

    const { pageSize, pageIndex, todos } = this.data

    try {
      const result = await db.collection('todos')
        .orderBy('createdAt', 'desc')
        .skip(pageIndex * pageSize)
        .limit(pageSize)
        .get()

      this.setData({
        todos: todos.concat(result.data),
        pageIndex: pageIndex + 1,
        hasMore: result.data.length === pageSize
      })
    } catch (error) {
      console.error('分页读取失败:', error)
    } finally {
      this.setData({ loading: false })
    }
  }
})

pageSize 是应用选择的单页条数,不代表平台一定允许该值。请求前应确认当前环境对单次查询条数的限制,必要时调小。偏移分页在数据持续新增或删除时可能出现重复、遗漏或页码变化;数据量较大且排序字段稳定时,可考虑使用游标式分页:记住上一页最后一条记录的排序值或文档 ID,再查询其后的数据。具体写法取决于排序字段、唯一性和查询条件。

六、更新与删除文档

更新已有文档时,可以通过文档 ID 定位:

const { db } = require('../../utils/db.js')

async function markTodoDone(todoId) {
  if (!todoId) {
    throw new Error('缺少待办 ID')
  }

  return db.collection('todos')
    .doc(todoId)
    .update({
      data: {
        done: true
      }
    })
}

update 面向已存在的文档;文档不存在、权限不允许或参数不符合要求时,调用会失败。它与 set 的语义不同:通常应在明确需要创建或覆盖文档时才使用 set,不要把几种写操作当成可以互换的接口。更新时只提交需要改动的字段,避免把客户端缓存的整份对象原样写回,覆盖其他地方已经更新的字段。

删除操作同样应使用明确的文档 ID:

async function deleteTodo(todoId) {
  if (!todoId) {
    throw new Error('缺少待办 ID')
  }

  return db.collection('todos')
    .doc(todoId)
    .remove()
}

删除通常不可直接恢复。涉及重要业务数据时,可以考虑软删除,例如增加 deletedAt 或 isDeleted 字段,并在所有查询中统一过滤;但软删除也需要相应的权限与数据保留策略,不能只加一个字段就认为完成了合规处理。

七、权限与安全:小程序端不是可信服务器

云数据库权限决定哪些调用者可以读写数据。控制台中通常可以按集合配置权限规则,具体可选项和表达能力以当前控制台为准。开发时不要为了排错长期把业务集合设置成所有人可读写;这样可能导致任何符合该权限条件的客户端都能读取、修改或删除数据。

前端小程序代码会交付到用户设备上。写在小程序里的环境 ID、页面判断、隐藏按钮和字段校验,都不应被当作秘密或强制安全边界。客户端直连数据库时,权限规则必须足以限制用户能做的操作;如果规则无法表达业务要求,应把敏感操作放到云函数等受控服务端流程中。

例如,用户提交一条反馈时,云函数可以从调用上下文中获取用户身份,再在服务端构造文档,而不是接受客户端任意指定的 owner:

// 云函数示例:云函数项目中需已配置并初始化 wx-server-sdk
const cloud = require('wx-server-sdk')

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})

const db = cloud.database()

exports.main = async (event, context) => {
  const { OPENID } = cloud.getWXContext()
  const title = String(event.title || '').trim()

  if (!title || title.length > 200) {
    throw new Error('标题不合法')
  }

  const result = await db.collection('todos').add({
    data: {
      title,
      done: false,
      owner: OPENID,
      createdAt: db.serverDate()
    }
  })

  return { id: result._id }
}

这里的 200 是示例业务自行选定的长度上限,不是数据库或平台强制规定的参数上限。调用方仍需根据实际业务调整;若要限制字符数,还要明确采用何种字符计数规则。云函数能在服务端执行校验和控制写入字段,但也不是自动安全:仍应检查输入、授权调用者、限制操作范围,并避免把高权限凭据或不必要的数据返回给客户端。

从小程序端调用云函数的示例:

const result = await wx.cloud.callFunction({
  name: 'createTodo',
  data: {
    title: '准备项目周报'
  }
})

console.log(result.result)

云函数适合处理需要可信身份判断、跨集合业务逻辑或敏感字段控制的操作。普通、低风险且权限规则足以约束的读写,也可以由小程序端直接访问。选择哪种方式,应以权限模型和业务风险为依据,而不是简单认定“全部放云函数更安全”或“客户端直连更省事”。

八、常见错误与排查方法

  • 环境 ID 不一致:确认开发者工具当前环境、wx.cloud.init 中的环境以及云函数使用的环境相符。测试环境和正式环境的数据通常是分开的。
  • 集合名称或大小写不一致:检查代码中的集合名是否与控制台创建的名称完全相同。
  • 权限拒绝:检查集合权限规则和当前调用方式。开发者工具里能读取,不代表真实用户身份下也能读取;不要用放开全部权限作为长期解决方案。
  • 查询结果为空:确认文档确实位于当前环境和集合中,再检查查询字段名、字段类型、筛选条件及时间范围。
  • 把返回结果当成单条记录:集合的 get 返回数组;按文档 ID 查询或处理集合查询结果时,要根据接口返回结构使用正确的数据层级。
  • 更新无效或报错:确认传入的是有效文档 ID、目标文档存在、字段可写且权限允许。不要把集合查询条件写成文档 ID。
  • 一次读取数据过多:为列表设置合理的 limit 并分页。不要依赖一次请求返回全量数据,也不要假设不同 SDK 的单次条数限制完全相同。
  • 日期排序异常:检查字段保存的是日期类型还是字符串;字符串格式不统一时,排序结果可能与时间先后不一致。
  • 异步调用没有等待:数据库方法返回 Promise。使用 await 或正确处理 .then() 和 .catch(),避免页面显示尚未完成的旧状态。
  • 只做前端校验:前端校验主要改善交互体验。重要规则必须由权限规则或服务端逻辑执行,客户端提交的身份、价格、状态和时间都不能无条件信任。

九、开发时的实用检查清单

  1. 确认项目已开通云开发,初始化时使用正确环境。
  2. 先在控制台创建集合,并确定字段名称、类型和含义。
  3. 新增时只接受业务允许的字段,服务器生成的字段由可信端写入。
  4. 查询时明确筛选条件、排序方式和分页策略,不依赖全量读取。
  5. 更新和删除前确认文档 ID,并处理失败情况。
  6. 检查集合权限是否满足最小授权原则,区分开发环境与正式环境。
  7. 对敏感或有业务后果的操作,在云函数等可信服务端入口重新校验身份和参数。
  8. 上线前核对当前平台关于单次查询、请求频率、存储和费用的规则,不把示例中的页面参数误认为平台配额。

总结

小程序云开发数据库的基本使用路径是:准备云环境、初始化 SDK、创建集合、通过 add 新增文档、用 where 和 get 查询,再通过文档 ID 调用 update 或 remove。当数据量增加时,查询应使用排序和分页;当操作涉及用户身份、权限或重要业务状态时,应明确客户端与可信服务端的责任边界。

把集合权限、字段设计和错误处理一开始就纳入实现,通常比后期补救更省事。数据库接口本身并不能保证业务数据正确,也不会自动替开发者做安全判断;可靠的实现需要同时考虑数据模型、权限规则、服务端校验和平台当前限制。

© 版权声明

相关文章