2026-09-21 · 苏州畅达软件 小程序团队

微信云托管部署踩坑实录:从 ERR_FR_MAX_BODY_LENGTH_EXCEEDED 到 request 合法域名

微信云托管小程序Taro容器化部署踩坑实录

我们有一个 monorepo 结构的项目,需要把里面的 API 服务部署到微信云托管,让小程序切过去。 看起来是很标准的操作,实际踩了 5 个坑,每个都卡了大半天。 这篇文章按踩坑顺序还原全过程。如果你正准备做同样的事,能省掉这些时间。


先说结论

目标很朴素:把 apps/api 发到微信云托管服务上,让小程序切过去,顺便摆脱"每次都要配 request 合法域名"的麻烦。

做完之后有两条结论,第二条是踩了坑才真正明白的

  1. API 服务本身可以正常部署到微信云托管;
  2. "用了云托管就不用配请求域名"是个不完整的说法——只要小程序底层还在用 wx.request,哪怕请求目标是 https://*.sh.run.tcloudbase.com,照样受 request 合法域名 限制。想真正免配,必须改成 wx.cloud.callContainer

下面按我们踩坑的顺序展开。


坑 1:从 monorepo 根目录发版,代码包超限

现象

最自然的做法是站在仓库根目录直接发:

wxcloud run:deploy . -e <envId> -s <service-name>

结果上传阶段就失败了,报错是请求体过大:

ERR_FR_MAX_BODY_LENGTH_EXCEEDED

我们当时的判断

第一反应是"是不是要付费升级套餐"或者"是不是云托管对包大小限制太严"。这两个方向都错了。

根因

仓库是 monorepo,. 意味着把整个仓库根目录都当成部署上下文打包,包括:

  • 根目录的 node_modules
  • 各种临时目录
  • 其他 app / package 里与本服务无关的内容

打包体积远超云托管的上传限制。这跟套餐没关系,是打包范围的问题。

解法

不要从仓库根目录上传。我们先跑一个准备脚本,产出一个精简发布目录

node scripts/cloudrun/prepare-wechat-api.mjs
# 产出:tmp/wechat-cloudrun-api

然后发布这个目录,而不是仓库根目录:

wxcloud run:deploy tmp/wechat-cloudrun-api \
  -e <envId> \
  -s <service-name> \
  --dockerfile Dockerfile \
  --containerPort 3001 \
  --noConfirm --override

这一点值得记下来

"部署上下文"和"项目根目录"是两回事。 任何容器化部署(云托管、CloudBase、Vercel、Docker build)都会把指定目录整个打进去。monorepo 下这个目录必须是"发布目录",不能是仓库根。


坑 2:镜像构建成功,但健康检查一直失败

现象

换用精简目录之后,镜像构建和上传都成功了,但版本部署失败。日志里能看到健康检查探测失败,探测打的是 80 端口,而应用实际监听的是 3001

根因

云托管的容器端口配置是 80,服务实际监听 3001,两边对不上。我们的 API 服务读取 PORT 环境变量,默认值是 3001

apps/api/src/lib/env.ts
apps/api/Dockerfile

解法

发布时显式声明容器端口:

wxcloud run:deploy tmp/wechat-cloudrun-api \
  -e <envId> \
  -s <service-name> \
  --dockerfile Dockerfile \
  --containerPort 3001 \
  --noConfirm --override

部署完别只看控制台状态,直接打健康检查确认:

curl https://<cloud-hosting-domain>/health

一点经验

"构建成功"和"部署成功"是两个阶段。 构建只验证镜像能不能做出来;部署还要过健康检查。看日志时要分清卡在哪一步,否则容易在错误的方向上排查。


坑 3:/health 返回 200,业务接口全 500

现象

这次部署成功了,/health 返回 200。但一测业务接口:

/home/overview   → 500
/announcements   → 500

为什么这个现象容易误导人

健康检查过了,直觉会认为"服务是好的,是接口代码有问题"。但健康检查只证明进程活着,不证明依赖可用。

根因

服务本身起来了,但运行时依赖没准备好。业务接口依赖 Supabase,需要三个环境变量:

SUPABASE_URL
SUPABASE_ANON_KEY
SUPABASE_SERVICE_ROLE_KEY

云托管那边的环境变量没有正确注入到运行时,所以初始化客户端时就失败了。

排查方法(这篇里最值得抄走的一招)

先本地带同一套环境变量复现,不要直接猜云端配置。

set -a; source ./.env; set +a
pnpm --filter api start

然后本地打三个接口:

curl http://127.0.0.1:3001/health
curl http://127.0.0.1:3001/home/overview
curl http://127.0.0.1:3001/announcements

本地全绿,就说明三件事:

  1. .env 里的配置是真有效的
  2. 业务接口逻辑没有问题
  3. 问题一定出在"环境变量怎么进到云端运行时"这一步

这三条一确认,排查范围立刻从"整个链路"缩到"一个环节"。

沉淀成 checklist

/health 正常但业务接口 500 时,按这个顺序走:

  1. 先确认服务是不是只是活着(health 只代表这个)
  2. 再看业务依赖是否可用(数据库、缓存、第三方 API)
  3. 优先本地带同一套环境变量复现,别凭感觉改云端配置

坑 4:--envParams 同步环境变量不稳定

现象

既然问题出在环境变量,就想着用 --envParams 一次性同步上去:

wxcloud run:deploy ... --envParams=<...>

结果服务参数更新失败,后端报了两类错误:

UnknownParameter
Conf.OperationMode

试了几次,有时成功有时失败,没有稳定规律。

我们的处理策略

这里做了个取舍:不在工具链的坑里死磕。定下的原则是:

  1. 先试 --envParams
  2. 如果 CLI 或后端更新参数失败,改走临时精简包继续发布,不要在整仓上传或参数同步环节卡死
  3. 环境变量相关的问题,一律先在本地验证真值,再决定云端怎么注入

为什么值得单独记一笔

踩坑时最容易犯的错是在工具链问题上无限投入。判断标准很简单:

  • 如果这个坑阻塞主流程 → 想办法绕过,先让流程跑通
  • 如果这个坑只是不够优雅 → 记下来,别现在解决

--envParams 属于前者,所以我们绕过它,而不是花两天研究它为什么不稳定。


坑 5:小程序报 request:fail url not in domain list

这个问题最值得写,因为我们的初始认知就是错的

现象

API 服务已经在云托管上跑起来了,但小程序首页仍然报:

request:fail url not in domain list

我们当时以为的(错的)

"都用了微信云托管了,应该不需要配域名了吧?"

这个说法不完整,而我们把"不完整"当成了"成立"。

真正的原因

小程序底层的请求方式没有变,仍然是 wx.request(Taro 项目里对应 HTTP 请求)。

只要请求方式本质还是公网 HTTP 请求,不管目标地址是不是 https://*.sh.run.tcloudbase.com,都受 request 合法域名 限制。

云托管提供的是容器调用的通道,不是"自动把你的 HTTP 请求变成免配置的内部调用"。

关键区分

请求方式 是否受 request 合法域名 限制
wx.request → 云托管公网域名 受限
wx.request → 你自己的域名 受限
wx.cloud.callContainer 不受限

这个区别,是这次最大的收获。


最终方案:用 wx.cloud.callContainer 取代 wx.request

目标:把小程序底层的 API 调用改成走云开发容器调用,从而真正摆脱域名白名单。

改动一:SDK 请求层优先走 callContainer

在 SDK 的 client 里做能力探测 + 优雅降级

  1. 如果同时满足:wx.cloud.callContainer 可用、有云环境 ID、有服务名 → 优先走 callContainer
  2. 否则回退到 fetch / wx.request
// packages/api-sdk/src/client.ts(示意)
if (canUseCallContainer()) {
  return callContainer({ path, method, data })
}
return fallbackFetch({ path, method, data })

这个设计的价值在于:同一套 SDK 同时服务 H5 和小程序。 H5 走原来的 HTTP,小程序走容器调用,业务层完全无感。

改动二:小程序启动时初始化云开发

// apps/weapp/src/app.tsx(示意)
wx.cloud.init({
  env: injectedCloudEnv,
  traceUser: true,
})

injectedCloudEnv 不是写死的,而是构建时注入的编译常量。

改动三:云环境和服务名作为编译常量注入

// apps/weapp/config/index.ts(示意)
defineConstants: {
  __TARO_APP_CLOUDBASE_ENV__: process.env.TARO_APP_CLOUDBASE_ENV,
  __TARO_APP_CLOUDBASE_SERVICE__: process.env.TARO_APP_CLOUDBASE_SERVICE,
}

这样构建产物里能稳定拿到云环境 ID 和服务名,不用在代码里散布硬编码。

测试

SDK 侧补了两个用例,覆盖"优先走 callContainer"和"降级回 wx.request"两条路径:

pnpm exec tsx --test packages/api-sdk/src/__tests__/domain-sdk.test.ts

小程序侧确认构建产物里三样东西都在:云环境 ID、服务名、wx.cloud.init(...)

pnpm --filter weapp typecheck
pnpm --filter weapp build:weapp

为什么要专门测降级路径? 因为 H5 环境没有 wx.cloud,一旦降级逻辑写错,Web 端会直接挂掉——而 Web 端平时没人测。


最后沉淀出的排查方法论

这次最有价值的不是某个具体命令,而是把问题拆开的顺序

API 服务部署问题 → 拆成三层

1. 打包问题      → 部署上下文是不是对的?(坑 1)
2. 端口问题      → 容器端口和监听端口一致吗?(坑 2)
3. 运行时依赖问题 → 服务活着,但依赖活着吗?(坑 3、4)

三层依次排查,比笼统地"看看日志"快得多。

小程序访问问题 → 先确认底层方式

底层是 HTTP 请求   → 就是要配 request 合法域名
底层是 callContainer → 才真正免配

不要把"云托管公网域名"和"云托管容器调用"混为一谈——这是两个完全不同的通道。


目前我们稳定使用的发布流程

API 服务发布

node scripts/cloudrun/prepare-wechat-api.mjs

wxcloud run:deploy tmp/wechat-cloudrun-api \
  -e <env-id> \
  -s <service-name> \
  --dockerfile Dockerfile \
  --containerPort 3001 \
  --noConfirm --override

小程序上传

TARO_APP_CLOUDBASE_ENV='<env-id>' \
TARO_APP_CLOUDBASE_SERVICE='<service-name>' \
fnm exec --using 20 -- node scripts/upload-miniprogram.mjs

一句话总结

微信云托管解决的问题是"把服务跑起来",不是"让小程序免配域名"。

这两件事经常被混在一起说,而它们之间差着一个 wx.cloud.callContainer 改造。


作者:苏州畅达软件 小程序团队 | www.szchada.com

准备开始您的数字化项目?

留下需求,我们会在一个工作日内与您联系。

联系我们