跳到主要内容

修复微信小程序中的 Cloudbase 认证常见报错

一句话定义:按错误码对号入座,快速定位微信小程序接入 Cloudbase 自定义登录时最常见的 10 个报错并修复。

预计耗时:10-20 分钟(取决于撞到哪个错) | 难度:入门

适用场景

适用于已经按 add-auth-wechat-miniprogram 跑通基础流程、但在某一步撞到具体报错的场景。按错误码对号入座,每个小节按「现象 → 根因 → 修复(带代码)→ 预防」四段组织,目的是让你最短时间内看懂为什么报错,而不是只知道怎么绕过。

  • 适用:已经接入 Cloudbase 自定义登录并撞到了报错的小程序项目
  • 不适用:还没跑通基础接入流程的项目,先看 add recipe 把环境搭起来再回来

用法:开发者工具 Console 面板 → 复制报错关键词 → 在下面的目录里 Ctrl+F → 直接跳到对应小节。

环境要求

沿用 add recipe 里的环境:

  • @cloudbase/js-sdk@2.27.3 + @cloudbase/adapter-wx_mp@1.3.1(小程序端)
  • @cloudbase/node-sdk@3.18.1(云函数)
  • 微信开发者工具 ≥ 1.06.x

错误 1 — require is not defined(小程序端)

现象:小程序编译后 Console 直接报 ReferenceError: require is not defined,通常指向 libs/cloudbase.jsminiprogram_npm/@cloudbase/js-sdk/index.js

根因:没有执行「构建 npm」。微信小程序的 runtime 不直接认 node_modules,必须用开发者工具把 node_modules 的依赖构建到 miniprogram_npm/ 目录才能用。

修复

  1. 微信开发者工具 → 工具 → 构建 npm
  2. 检查项目根目录出现了 miniprogram_npm/@cloudbase/miniprogram_npm/@cloudbase/adapter-wx_mp/
  3. 重新编译

预防:把"构建 npm"加到团队的本地启动 checklist 里。每次新装依赖或换分支切到 package.json 有改动的版本后都要重新构建,不会自动触发。


错误 2 — 微信返回 errcode: 40029

现象:云函数日志里出现:

{ "errcode": 40029, "errmsg": "invalid code, hints: ..." }

根因wx.login 返回的 code 已经被用过一次,或者已经过期(有效期 5 分钟)。微信官方规则是 code 换 session 只能换一次,再次调用就报 40029。

修复:每次登录流程都重新调 wx.login,不要把 code 缓存到 storage 里或者跨页面复用:

// ❌ 不要这样
const cachedCode = wx.getStorageSync('wx_login_code');

// ✅ 每次都新拿
const { code } = await new Promise((resolve, reject) => {
wx.login({ success: resolve, fail: reject });
});

预防ensureLogin 函数内部不要有任何绕开 wx.login 的快捷路径。所有检查「是否已登录」的逻辑应该通过 auth.hasLoginState(),而不是自己做一个 code 缓存。


错误 3 — 微信返回 errcode: 45011

现象

{ "errcode": 45011, "errmsg": "api minute-quota reach limit" }

根因jscode2session 接口对同一个 IP 有每分钟调用上限。这个错在灰度放量的时候最容易撞,单人调试时几乎看不到。

修复:在云函数里给 ticket 加一层缓存,key 用 openid,TTL 设 5-10 分钟(ticket 本身的有效期就是 10 分钟,缓存更久没意义)。最简单的方案是用云数据库当 cache:

const db = app.database();
const TICKET_CACHE = 'login_ticket_cache';

async function getCachedTicket(openid) {
const res = await db
.collection(TICKET_CACHE)
.where({ openid })
.get();
if (res.data.length === 0) return null;
const record = res.data[0];
if (Date.now() - record.createdAt > 5 * 60 * 1000) return null;
return record.ticket;
}

async function saveTicketCache(openid, ticket) {
await db.collection(TICKET_CACHE).add({
data: { openid, ticket, createdAt: Date.now() },
});
}

如果流量更大,换 Redis(Cloudbase 扩展里有 Redis 实例)。

预防:QPS 压测时主动观察 45011 出现的阈值,超过阈值前提前加缓存,不要等上线才撞。


错误 4 — INVALID_TICKET / TICKET_EXPIRED

现象:小程序端调 signInWithCustomTicket 报:

{ code: 'INVALID_TICKET', message: 'ticket is invalid or expired' }

根因:有三种可能,按概率排序:

  1. tcb_custom_login.json 不是控制台最新下载的那份——控制台每次点「重新生成私钥」,旧私钥会在2 小时后失效。这是最高频的原因,通常发生在多人协作场景,一个人重下了私钥,另一个人本地还是旧的
  2. 云函数部署用了过期的私钥,ticket 签出来就是无效的
  3. ticket 在云函数和小程序之间传输时被截断(比如 HTTP 响应里被 JSON.stringify 两次)

修复

# 1. 去控制台重新下载最新 tcb_custom_login.json
# 2. 覆盖云函数目录里的旧文件
cp ~/Downloads/tcb_custom_login.json cloudfunctions/getLoginTicket/

# 3. 重新部署
tcb fn deploy getLoginTicket --dir ./cloudfunctions/getLoginTicket -e your-env-id

预防:在云函数里打印 ticket 长度做 sanity check:

const ticket = app.auth().createTicket(openid);
console.log('[getLoginTicket] ticket length:', ticket.length);

正常 ticket 长度应该稳定在几百字符数量级,如果长度对不上就说明签出来就不对。


错误 5 — SYSTEM_NOT_OPEN(自定义登录未启用)

现象

{ "code": "SYSTEM_NOT_OPEN", "message": "custom login is not enabled" }

根因:在 Cloudbase 控制台的「身份认证 → 登录方式」里,「自定义登录」没启用。控制台上可以下载私钥但不启用,这样 createTicket 不会报错,但签出来的 ticket 登录时会被拒绝。

修复:控制台 → 身份认证 → 登录方式 → 自定义登录 → 启用

预防:把启用状态做成部署前置检查的一部分,或者写一个启动自检云函数在 CI 里跑。


错误 6 — customUserId 格式错误

现象

createTicket failed: customUserId must match /^[A-Za-z0-9_\-#@(){}\[\]:.,<>+#~]{4,32}$/

根因:传给 createTicket 的第一个参数不是合法的 customUserId。规则是 4-32 位,字符只能是字母/数字和部分符号。

修复:检查 jscode2session 的返回值:

const session = await jscode2session(code);

if (session.errcode) {
// 微信侧错误,这里先 return
return { error: 'WX_API_ERROR', errcode: session.errcode };
}

if (!session.openid) {
// 理论上不会走到这里,但防御一下
return { error: 'NO_OPENID', raw: session };
}

const ticket = app.auth().createTicket(session.openid);

真实 openid 是 28 位字母数字,符合规则。出现这个错多半是因为微信接口报错时返回里没有 openid 字段,代码没做防御导致把 undefined 传给了 createTicket。

预防:任何对外部 API 的返回值都先验证关键字段再传下去,不要假设调用成功。


错误 7 — 小程序端报 NETWORK_ERROR/request:fail url not in domain list

现象:调云函数 HTTP 接口时:

request:fail url not in domain list

或者类似的域名白名单错误。

根因:微信小程序要求所有 wx.request 的目标域名必须在小程序后台的「服务器域名」白名单里。Cloudbase 云函数 HTTP 访问服务的 URL 默认不在。

修复:两步:

  1. 去微信公众平台 → 你的小程序 → 开发 → 开发管理 → 开发设置 → 服务器域名
  2. request 合法域名 里添加你的 Cloudbase HTTP 访问服务域名,形如 https://your-env.service.tcloudbase.com

注意这里填的是域名不是完整 URL,不要带 path。

预防:开发时可以在开发者工具的「详情 → 本地设置」里勾选「不校验合法域名」快速绕过,但上线前必须在公众平台正式配置,否则体验版和正式版都会报这个错。


错误 8 — 签名错误 / SIGN_PARAM_INVALID

现象:云函数调 app.auth().createTicket() 时报:

SIGN_PARAM_INVALID: credential signature verification failed

根因cloudbase.init 时传入的 credentials 路径读取到的 JSON 格式不对,或者是从错误的环境导出的私钥(比如你在 A 环境下载了私钥,但 env 字段填成了 B 环境的 ID)。

修复

// 调试用:确认读到的 credentials 和当前 env 一致
const cred = require('./tcb_custom_login.json');
console.log('[getLoginTicket] cred env:', cred.env);
console.log('[getLoginTicket] init env:', process.env.TCB_ENV);
// 这两个必须相等

如果不相等,重新从目标环境的控制台下载私钥。

预防tcb_custom_login.json 文件名不要直接作为唯一标识,建议在 CI/CD 里用环境前缀重命名,比如 tcb_custom_login.prod.jsontcb_custom_login.staging.json,从源头避免混环境。


错误 9 — auth is not a function(小程序端)

现象

TypeError: cloudbase.useAdapters is not a function

或者

TypeError: app.auth is not a function

根因@cloudbase/js-sdk 的版本不对。常见有两种情况:

  1. 装了 @cloudbase/js-sdk@1.x,这是老版本,API 和 2.x 不一样
  2. 装了 @cloudbase/js-sdk@next(即 v3),内置适配器后已经没有 useAdapters 方法

修复:锁定到 2.27.3:

cd miniprogram
npm install --save @cloudbase/js-sdk@2.27.3 @cloudbase/adapter-wx_mp@1.3.1

然后在 package.json 里把版本号锁死(不要用 ^):

{
"dependencies": {
"@cloudbase/js-sdk": "2.27.3",
"@cloudbase/adapter-wx_mp": "1.3.1"
}
}

重新「构建 npm」。

预防:小程序前端依赖版本一定要锁死到 patch,这是前面所有错误里不锁版本最容易撞的连锁反应。


错误 10 — 登录成功,但后续数据库/云函数调用报 UNAUTHORIZED

现象signInWithCustomTicket 看起来成功了,auth.currentUser.uid 也能拿到,但接下来调 db.collection('xxx').get()UNAUTHORIZED

根因:登录成功只代表「知道你是谁」,但访问具体资源还要看「权限控制」那一层。Cloudbase 的新用户默认角色是「外部用户」,如果你的数据库集合权限设的是「仅管理员」或者「仅组织成员」,外部用户是读不了的。

修复:两种方式二选一。

方式 A——放宽集合权限(只适合公开数据):

控制台 → 数据库 → 集合 → 权限设置 → 所有用户可读

方式 B——给这类用户分配角色(推荐生产环境用):

控制台 → 身份认证 → 权限控制 → 角色 → 给「外部用户」或自定义角色配置具体资源的策略。

预防:开发阶段先在控制台把数据库权限设成宽松模式(所有用户可读写),跑通后再一点点收紧。反过来从严到宽排查会浪费很多时间。


如果你撞到的错没列在上面

按这个顺序自查:

  1. 云函数日志——Cloudbase 控制台 → 云函数 → getLoginTicket → 日志。先确认 event 是否带着 code、jscode2session 返回了什么、createTicket 是否被调用、返回了什么
  2. 小程序端 Console——确认 wx.request 的 statusCode 和 data,很多问题其实是 HTTP 层而不是认证层
  3. Cloudbase 控制台 → 身份认证 → 用户管理——看新用户有没有真的落库,能看到就说明 ticket 环节通了

日志链条上缺失的那一环就是问题所在。

相关文档