在微信小程序中调用 CloudBase 云函数
一句话定义:用
wx.cloud.callFunction直连云函数,云函数侧用wx-server-sdk自动拿到 OPENID/APPID/UNIONID,业务里不需要自建身份系统、不需要 HTTPS 域名、不需要 token 校验。预计耗时:25 分钟 | 难度:入门
适用场景
这条路径走的是「微信·云开发」体系下的小程序前端调用云函数,跟 add-database-wechat-miniprogram 用的 @cloudbase/js-sdk 自定义登录是两条不同的链路,不要混着用。
- 适用:已经在小程序里启用了云开发(
wx.cloud.init能跑),想从前端调一个能拿到 OPENID 的服务端逻辑 - 适用:业务要拿 OPENID 才能跑(发模板/订阅消息、查老用户绑定关系、生成带身份的二维码)
- 适用:不想自建 HTTPS 服务、不想在小程序里维护 token 续签逻辑
- 不适用:想从 H5 / Web 端调同一个云函数(那一套要走 connect-openai-api-cloud-function 里的 HTTP 触发器)
- 不适用:已经从微信·云开发迁到独立 CloudBase 环境,见 migrate-wxcloud-to-cloudbase
环境要求
| 依赖 | 版本 |
|---|---|
| 小程序基础库 | ≥ 2.2.3(wx.cloud 起步版本) |
wx-server-sdk | 3.0.1 |
@cloudbase/cli(可选) | 1.27.0 起,用 tcb 命令行部署 |
| 微信开发者工具 | ≥ 1.06.x |
另外需要:
- 一个已经开通的 CloudBase 环境,环境 ID 形如
your-env-id-1234567 - 小程序 AppID 已在 CloudBase 控制台「环境配置 → 安全配置 → 小程序关联」里绑定到这个环境
第一步:控制台关联小程序 AppID 到 CloudBase 环境
进 CloudBase 控制台 → 选环境 → 环境配置 → 安全配置 → 小程序关联,把小程序的 AppID 填进去。
这一步是 wx.cloud.init({ env }) 鉴权的源头:小程序基础库会拿当前 AppID 去问云端「这个 AppID 能不能用这个 env」,关联记录不存在时直接报「非法的 env」,所有云函数调用、云数据库读写、云存储上传全部会失败。
后面写代码之前先把这一步完成,不然第三步 console 里只会看到一个语义不清的 errMsg。
第二步:写云函数(wx-server-sdk + getWXContext)
新建一个云函数目录(微信开发者工具里项目根的 cloudfunctions/ 下右键「新建 Node.js 云函数」,起名 add),里面有 index.js 和 package.json 两个文件。
// cloudfunctions/add/index.js
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
return {
sum: event.x + event.y,
openid: wxContext.OPENID,
appid: wxContext.APPID,
unionid: wxContext.UNIONID,
};
};
// cloudfunctions/add/package.json
{
"name": "add",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "3.0.1"
}
}
三个细节:
cloud.DYNAMIC_CURRENT_ENV让云函数运行时自动用「当前所在环境」,跨环境部署不需要改代码,这是从latest版本起的标准写法。cloud.getWXContext()返回的 OPENID/APPID/UNIONID 是从基础库到云函数全链路透传过来的,不是请求里带的字段,业务代码改不了也伪造不了,可以直接当身份用。- 没登录到任何账号的访客调用(比如分享出来的页面没授权)
OPENID仍然有值(基于会话的临时 openid),但UNIONID可能是空字符串,业务里要做空值兜底。
event 是小程序端 data 字段的原样透传,JSON 可序列化的都行。return 出去的对象会作为前端 res.result 拿到,不要返回 Date 实例(序列化会丢时区,改成 ISO 字符串再返)。
如果云函数里要操作数据库 / 存储 / 调其他云函数,wx-server-sdk 都已经把客户端封装好了,身份直接复用 getWXContext() 拿到的 OPENID,不需要再签 token:
// 在云函数里读数据库
const db = cloud.database();
const user = await db.collection('users').where({ _openid: wxContext.OPENID }).get();
// 在云函数里调另一个云函数
const res = await cloud.callFunction({ name: 'sendMessage', data: { ... } });
这是 BaaS 风格的核心价值:身份从客户端到云函数到数据库是一条直链,业务代码里看不到 token,也不需要管续签。
第三步:小程序端 wx.cloud.init + wx.cloud.callFunction
wx.cloud.callFunction 同时支持 Promise 和 callback,选哪个看场景:
- Promise / async-await:页面逻辑串行多步操作时用,异常走
try / catch,可读性最好,90% 的场景选这个 - callback(
success/fail):基础库较低、跟其他wx.*API 共用 callback 风格的工具函数(比如老项目的wx.request封装)时用,不需要再链一层 Promise.resolve
app.js 全局只初始化一次:
// app.js
App({
onLaunch() {
if (!wx.cloud) {
console.error('请使用 2.2.3 或以上的基础库以使用云能力');
return;
}
wx.cloud.init({
env: 'your-env-id-1234567',
traceUser: true,
});
},
});
traceUser: true 会在 CloudBase 控制台「用户管理」里记录一次本次会话的 openid + 最近活跃时间,方便后续在控制台直接看每天 DAU。不开也不影响功能。
页面里调用,只要走 Promise 形式就行:
// pages/index/index.js
Page({
async onTapAdd() {
try {
const res = await wx.cloud.callFunction({
name: 'add',
data: { x: 1, y: 2 },
});
console.log('result:', res.result);
// { sum: 3, openid: 'oABC...', appid: 'wxXXX...', unionid: '' }
} catch (err) {
console.error('callFunction failed', err);
}
},
});
也支持 callback 写法,传 success / fail 回调,跟 Promise 形式二选一:
wx.cloud.callFunction({
name: 'add',
data: { x: 1, y: 2 },
success: (res) => console.log(res.result),
fail: (err) => console.error(err),
});
name 是云函数名(不是文件路径),data 直接进云函数的 event,大小限制 5 MB。返回值在 res.result 里,不在 res.data,这是跟 wx.request 最容易记混的地方。
业务里常见的两个场景:
-
跨环境临时切换:有测试 / 生产两套 env 时,在某次
callFunction里临时指定环境,不用动wx.cloud.init全局配置:await wx.cloud.callFunction({name: 'add',data: { x: 1, y: 2 },config: { env: 'test-env-id-1234567' },}); -
超时控制:
wx.cloud.callFunction没有timeout参数,前端会一直等到云函数返回。如果云函数本身可能跑 20 秒以上(比如调外部 LLM),要么在云函数里收口超时然后早返,要么前端用Promise.race套一层超时,不要把 UI 卡死。
第四步:用 tcb 部署或开发者工具一键部署
两条路,选一条就够了。
A. 微信开发者工具(适合刚起步)
cloudfunctions/add/ 目录右键 → 上传并部署:云端安装依赖(不上传 node_modules) → 等 30 秒 → 看到「上传成功」就可以在小程序里调了。
B. CLI(tcb 命令,适合 CI / 自动化)
# 装 CLI
npm install -g @cloudbase/cli
# 部署单个事件触发函数(不带 --httpFn,因为是给小程序 callFunction 用)
tcb fn deploy add -e your-env-id-1234567
部署完后到控制台「云开发 → 函数 → 函数列表」能看到 add,点进去能看到运行内存、超时时间和最近调用日志。
注意:--httpFn 是给 HTTP 触发器用的(走公网域名调用),小程序 callFunction 是「事件触发」,加了 --httpFn 反而会让小程序端调用拿不到这个函数。一个云函数可以同时挂事件触发和 HTTP 触发,但走 HTTP 触发器进来的请求里没有 wxContext.OPENID,只有事件触发的调用才会自动透传身份。
tcb fn deploy 默认会读 cloudfunctions/<name>/package.json 里的 dependencies,在云端直接 npm install,本地不需要先装好 node_modules。如果有私有 npm 源或者要锁版本,提前在目录下放 .npmrc 和 package-lock.json 即可。
第五步:在云函数里打日志(可选但强烈建议)
云函数没有「本地 console」可以看,所有的 console.log / console.error 都打到云端日志里,排查必须靠这条:
exports.main = async (event, context) => {
console.log('[add] event =', event);
const wxContext = cloud.getWXContext();
console.log('[add] wxContext =', wxContext);
// ... 业务逻辑
};
查日志的两个入口:
- 微信开发者工具:左侧「云开发」面板 → 云函数 → 日志,只能看最近 24 小时
- CloudBase 控制台「云开发 → 函数 → 函数列表 → 选函数 → 日志查询」,能按时间区间 / 关键字 / RequestId 过滤,保留 7 天
调试期建议每个云函数入口都打一行 event 和 wxContext,出问题时第一手看到的就是「调用进来的参数和身份对不对」,比起前端报错看到的 errMsg 信息量大得多。生产环境再视情况降级成只打 error。
运行验证
- 微信开发者工具编译运行,触发那个调
wx.cloud.callFunction的按钮 - 开发者工具 Console 应该看到
result: { sum: 3, openid: 'o...', appid: 'wx...', unionid: '...' },sum 必须等于 3 - 控制台 → 云开发 → 函数 → add → 日志查询,选最近 5 分钟,能看到一条 INFO 级别的运行记录,里面
event是{ x: 1, y: 2 }、returnValue跟前端拿到的一致 - 把云函数里
cloud.init那行注释掉再部署一次,前端调用应该报「请先调用 init」,确认整条链路是真的走到云函数,没有被任何中间层缓存住
第 4 条做完记得把代码改回来。
如果第 3 步在控制台日志里看不到本次调用,而前端 console 拿到了 res.result,99% 是部署没真生效——开发者工具有时会把上次部署的 zip 缓存住。强制重新打包:删掉本地 cloudfunctions/add/node_modules,在目录上右键「清除缓存并重新部署」,再调一次。
常见错误
| 错误信息 | 原因 | 修复 |
|---|---|---|
errCode: -404011 cloud function execution error 或 console 提示「非法的 env」 | 第一步控制台没把 AppID 关联到这个 env,或者填错环境 ID | 控制台「环境配置 → 安全配置 → 小程序关联」加上当前小程序 AppID,确认 wx.cloud.init({ env }) 里的 env 跟控制台环境 ID 一致(末尾的随机串别漏) |
Cloud API isn't enabled, please call wx.cloud.init first | 页面里直接 wx.cloud.callFunction,但 app.js onLaunch 里没调 wx.cloud.init,或者基础库低于 2.2.3 | 把 wx.cloud.init 放进 app.js onLaunch,project.config.json 里 cloudfunctionRoot 也要配上 |
errCode: -501005 FUNCTIONS_EXECUTE_FAIL function not exists | 函数名拼错,或者本地写完没点「上传并部署」 | 检查 name 字段跟云端函数名是否完全一致(大小写敏感),控制台「函数列表」里有这条记录才算部署成功 |
wxContext.OPENID 是 undefined | 没用 wx-server-sdk 调 cloud.init,或者 event 解构覆盖了 context | 云函数顶部一定要 cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }),getWXContext() 才有数据;别把参数名写成 (event, wxContext) 之类 |
| 部署后立刻调用,第一次响应慢到 3-5 秒 | 函数实例冷启动 | 这是预期行为,Node.js 12 / 16 运行时冷启动一次后实例会保留 5-10 分钟,高频场景见 optimize-cloud-function-wechat-miniprogram |
errCode: -601001 RESOURCE_INSUFFICIENT | 当月免费额度用完,或者按量付费账户欠费 | 控制台「用量 → 资源用量」看本月额度,按量套餐欠费会在欠费后 24 小时停服 |
errMsg: cloud.callFunction:fail Error: errCode: -502005 DATABASE_PERMISSION_DENIED | 云函数里访问数据库,但集合权限是「仅创建者可读写」,而调用方 OPENID 不是文档创建者 | 云函数的执行身份继承调用方 OPENID,不是 admin。要绕过权限读全表,在云函数里改用 cloud.database({ env: ... }) 显式拿管理员客户端,或者把集合权限改成「仅管理端可读写」 |
完整错误码定义见 error-code。
前端拿到的 errCode 一般就是云端透传过来的,先用错误码定位是「调不进去」(-404 / -501)还是「调进去了但里面跑挂」(-502 / -601),再决定看前端日志还是云函数日志,这一步比直接 google 错误信息高效得多。
相关文档
- add-database-wechat-miniprogram — 走
@cloudbase/js-sdk自定义登录直连数据库的另一条链路,跟本文区分 - fix-auth-wechat-miniprogram — 小程序端登录态、AppID 关联出问题的兜底排查
- connect-openai-api-cloud-function — 同一个云函数怎么再加一个 HTTP 触发器给非小程序端用
- optimize-cloud-function-wechat-miniprogram — 调用链路成型后的冷启动 / 并发 / 超时调优