微信小程序云开发实战:云函数、云数据库与云存储的正确使用姿势

微信小程序云开发实战:云函数、云数据库与云存储的正确使用姿势

admin
2026-06-13 / 0 评论 / 256 阅读

微信小程序云开发实战:云函数、云数据库与云存储的正确使用姿势

不想买服务器、不想做登录鉴权、不想配 HTTPS 证书——云开发(TCB)把这些全包了。但"免运维"不等于"无脑用",云开发的冷启动、数据库权限模型、计费陷阱都有讲究。这篇按真实项目经验整理云开发的正确打开方式。

一、云开发是什么

传统架构:小程序 → HTTPS 服务器(自己买、自己运维、自己备案域名)→ MySQL/Redis。

云开发架构:小程序 → 云函数(无服务器,微信鉴权自动透传)→ 云数据库(文档型,小程序端可直接查询)+ 云存储。

三个开箱即用的能力

  1. 免鉴权调用:云函数天然知道调用者 openid,wx.cloud.callFunction 不需要自己实现登录态
  2. 小程序直连数据库:数据库权限规则控制读写粒度,简单 CRUD 不用写后端
  3. 云存储直传:图片视频直传,无需走服务器中转

适用判断:中小型工具类、内容类小程序(日活几千到几万量级)用云开发能省一个人力;强事务、复杂查询、高并发秒杀类业务,老实上自建后端。

二、云函数

2.1 基础结构

// cloudfunctions/createOrder/index.js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()

exports.main = async (event, context) => {
  // 关键:不需要自己解 token,直接拿 openid
  const { OPENID, UNIONID, APPID } = cloud.getWXContext()

  const { goodsId, count } = event

  // 参数校验:云函数暴露在公网,event 里任何字段都不可信
  if (!Number.isInteger(count) || count < 1 || count > 99) {
    return { code: 400, msg: 'invalid count' }
  }

  const res = await db.collection('orders').add({
    data: {
      _openid: OPENID,
      goodsId,
      count,
      status: 'created',
      createdAt: db.serverDate()
    }
  })
  return { code: 0, orderId: res._id }
}

2.2 数据库权限规则:安全的边界

核心认知:小程序端代码是可以被反编译的。你写在 db.collection('orders').where(...) 里的查询,任何人都能改。安全只能靠权限规则兜底。

云数据库四种基础权限:

权限
仅创建者可读写本人本人
所有人可读,仅创建者可写所有本人
仅创建者可读写(管理端可读)本人本人
所有用户不可读写云函数云函数

进阶:自定义安全规则(JSON 声明式):

{
  "read": "auth.openid == resource._openid || get(`database.roles.${auth.openid}`).role == 'admin'",
  "write": "auth.openid == resource._openid"
}

这条规则实现了:本人可读写自己的文档,admin 角色可读所有。任何涉及金额、权限、敏感数据的操作,一律收进云函数(云函数走的是管理员权限,不受规则限制),前端直查只用于展示类数据。

2.3 冷启动

云函数实例闲置后回收,再次调用要冷启动(拉镜像、初始化 runtime),耗时 1~3 秒。缓解手段:

// 1. 依赖收进包体:wx-server-sdk 是大头,按需引入
// 2. 全局复用连接:把初始化放到函数体外面
const cloud = require('wx-server-sdk')
cloud.init()               // 模块加载时执行,冷启动后所有请求复用
const db = cloud.database()

exports.main = async (event) => { /* ... */ }
// config.json:配置预置并发(云开发控制台或函数配置)
{
  "triggers": [],
  "layers": []
}

控制台开启预置并发实例可彻底消除冷启动(额外计费),支付下单等核心链路建议开启。

三、云数据库

3.1 文档型思维

类 MongoDB 的文档模型,没有 join,关联靠冗余或多次查询:

// 订单列表:直接冗余商品快照,避免列表页 N+1 查询
const orders = await db.collection('orders')
  .where({ _openid: OPENID, status: 'paid' })
  .orderBy('createdAt', 'desc')
  .skip((page - 1) * 20)
  .limit(20)
  .get()

设计原则:列表页要展示什么,就冗余什么。订单里的商品标题、价格、缩略图做成快照存进订单文档,详情页再查全量。

3.2 事务

涉及多个文档一致性的操作必须用事务(仅云函数端支持):

const transaction = await db.startTransaction()
try {
  const goods = await transaction.collection('goods').doc(goodsId).get()
  if (goods.data.stock < count) throw new Error('库存不足')

  await transaction.collection('goods').doc(goodsId).update({
    data: { stock: _.inc(-count) }       // 原子自减,避免并发超卖
  })
  await transaction.collection('orders').add({ data: orderDoc })

  await transaction.commit()
} catch (e) {
  await transaction.rollback()
  throw e
}

小程序端直查没有事务,这是"扣库存必须进云函数"的根本原因。

3.3 索引

单字段查询超过一定量级后必须建索引(控制台 → 数据库 → 索引管理)。复合查询用组合索引,注意字段顺序(把等值条件字段放前面、范围/排序字段放后面):

// 订单列表查询 where({_openid, status}).orderBy(createdAt desc) 的组合索引
{
  "name": "openid_status_created",
  "unique": false,
  "keys": [
    { "name": "_openid", "direction": "1" },
    { "name": "status", "direction": "1" },
    { "name": "createdAt", "direction": "-1" }
  ]
}

没有索引的大集合查询会直接报错或超时,上线前用 explain 验证一遍。

四、云存储

4.1 上传

// 小程序端直传
wx.cloud.uploadFile({
  cloudPath: `avatars/${OPENID}-${Date.now()}.jpg`,  // 路径自己拼,注意唯一
  filePath: tempFilePath,                             // 本地临时文件
  success: res => {
    // res.fileID: cloud://xxx —— 存这个!不要存临时链接
  }
})

必须存 fileID 而不是临时 URLcloud:// 开头的 fileID 可以通过 wx.cloud.getTempFileURL 换成带签名的临时链接(默认 2 小时有效);存死了 URL 过期就是裂图。

4.2 图片处理

临时链接可以拼 OSS 风格的图片处理参数,缩略图不用自己生成:

const { fileList } = await wx.cloud.getTempFileURL({ fileList: [fileID] })
const url = fileList[0].tempFileURL
// 拼接缩放参数
const thumb = `${url}&imageMogr2/thumbnail/300x300`

列表页用缩略图、详情页用原图,流量和加载速度双赢。

五、定时触发器

运营态需求(每日结算、数据同步、优惠券过期)用定时触发器,免服务器 crontab:

// cloudfunctions/dailySettle/config.json
{
  "triggers": [
    {
      "name": "dailySettle",
      "type": "timer",
      "config": "0 30 2 * * * *"   // 每天凌晨 2:30
    }
  ]
}
// 函数内判断触发来源
exports.main = async (event) => {
  if (event.TriggerName) {
    // 定时触发,无 OPENID 上下文
    await settleYesterday()
  } else {
    // 用户调用
  }
}

六、计费与成本控制

云开发按量付费(有免费额度),三个计费大头:

  1. 云函数调用次数 + 运行时长:高频轮询接口最烧钱,用数据库 watch(实时推送)替代轮询
  2. 数据库读写次数:小程序端直查 + 分页加载做不好,读次数暴涨
  3. CDN 流量:图片直接原图全量输出是流量刺客,必须上缩略参数

成本红线自查:日活 1 万的小程序,如果月账单超过几百块,大概率是"该进云函数的没进、该建索引的没建、该用缩略图的没缩"三宗罪之一。

七、避坑清单

现象解法
前端直写数据库被刷数据被恶意篡改写操作收进云函数,规则只开读
存了临时 URL图片过两天裂图只存 fileID,用时换临时链
列表页 N+1 查询首页加载 3 秒+文档冗余快照字段
并发超卖库存变负数云函数事务 + _.inc 原子操作
函数冷启动慢首次点击卡 2 秒预置并发 + 模块级初始化
大集合无索引查询查询报错/超时控制台建组合索引
event 参数直接入库注入脏数据云函数内白名单校验

写在最后

云开发的甜点区是"快速验证的中小型业务"——一套前端 + 少量云函数就能上线,验证失败损失极小。规模上去后(复杂事务、数据分析、多端复用),再平滑迁移到自建后端也不迟:云数据库导出 JSON、云函数逻辑改写成 HTTP 接口,工作量可控。选型不是信仰,是成本和阶段的匹配。

0

评论 (0)

取消
0:00