尧图精选

微信服务端开发避坑:44002与47001错误码的完整排查与解决方案

🕒 发布时间:2026/10/2 14:50:15 📁 来源:尧图网络
做微信服务端开发最让人抓狂的不是业务逻辑写错而是接口调不通返回一堆看不懂的错误码。尤其是44002和47001这两个平时在群里、论坛里看到频率极高几乎每个做微信公众号、小程序后端的人都被它们折磨过。说实话这两个错误码的官方说明非常简单——一个说 POST BODY 为空一个说 POST BODY 不是合法的 JSON 格式。听起来问题明确但真到自己排查的时候你往往会发现body 明明有数据JSON 看起来也合法可微信就是给你甩这两个错误码。这篇文章我就把自己在实际项目里踩过的坑、总结的定位思路和完整解决方案梳理出来。不管你是第一次接触微信服务端接口还是已经被这两个错误码折磨了一下午按着下面的思路一步步来基本都能解决。1. 理解errcode 44002与47001到底在报什么错1.1 两个错误码的官方定义与实际表现先看微信官方文档里对这两个错误码的定义虽然很简单但值得逐字分析错误码含义官方说明44002POST BODY 为空请求体Body没有携带任何数据47001POST BODY 不是合法的 JSON 格式请求体内容无法被 JSON 解析器正确解析从字面意思上看44002 是“我已经准备好接收参数了但你发给我的 Body 是空的”47001 是“你有发 Body但这个 Body 不是我能认的 JSON”。理解这个区别非常重要因为它直接决定了排查方向你是在处理“没有数据”的问题还是在处理“数据格式不对”的问题。但实际开发中这两个错误码常常是“连体婴儿”。比如你代码里确实拼了一个 JSON 字符串但因为编码问题或者 Content-Type 设置不对微信那边收到的 Body 直接变成了空字符串这时候返回的就是 44002。又比如你拼的 JSON 里有中文但转成 UTF-8 时出了问题或者数组里有个值是null被序列化成了字符串null或者干脆把键给吞了导致 JSON 解析失败返回的就是 47001。所以在排查时不能只盯着“Body 空不空”这一个点要把整个请求链路都检查一遍。1.2 微信服务器解析请求体的流程与排查方向要真正解决这两个错误码你得先知道微信服务器那边是怎么处理你发过去的请求的。我画一条简化版的链路这里不依赖任何图表纯文字描述你的服务器通过 HTTP POST 请求把数据发送到微信接口地址比如https://api.weixin.qq.com/cgi-bin/message/template/send?access_tokenACCESS_TOKEN微信网关收到请求后先检查 HTTP Header如果Content-Type标识的是 JSON它会尝试用 JSON 解析器读 Body解析器先把 Body 从原始字节流按字符编码通常是 UTF-8还原成字符串对这个字符串做 JSON 解析尝试还原成对象/数组如果第 3 步还原出来的字符串是空的就报 44002如果第 4 步解析失败就报 47001。所以你的排查方向其实也就明确了有没有可能 Body 在传输过程中丢了有没有可能 Body 本身是空的有没有可能 Body 的编码方式不是微信预期的有没有可能 Content-Type 字段让微信误判了 Body 的格式有没有可能 JSON 字符串本身带着肉眼看不见的字符比如 BOM 头、换行符、不可见字符这几个问题逐一排查下来基本就能定位问题所在。下面我展开讲具体的触发场景和实操方案。2. 常见触发场景与根因定位思路2.1 高发场景模板消息、客服消息、菜单创建先说说哪些接口最容易被这两个错误码命中。我总结了一下几乎所有需要提交 JSON 数据到微信服务端的接口都有可能但实际开发中频率最高的是这几个模板消息发送/cgi-bin/message/template/send需要传touser、template_id、data等字段data还是个嵌套对象最容易在构造时出问题。客服消息发送/cgi-bin/message/custom/send同样需要传接收用户、消息类型和消息内容。自定义菜单创建/cgi-bin/menu/create整个菜单结构是个多层嵌套的 JSON键名多、结构复杂少一个逗号、多一个引号都能让解析失败。公众号/小程序素材管理比如新增永久素材时需要传 JSON 字段描述素材信息。微信支付相关回调虽然支付接口大多是 XML但部分查询接口也要求 JSON 格式。为什么这些场景容易踩坑因为它们存在一个共性请求参数不是简单的一层 key-value而是嵌套对象或数组。一旦业务数据是动态拼装的比如从数据库里查出一组用户标签、商品列表再塞进data字段就非常容易出现“结构对不上”“字段缺失”“类型不对”的情况。比如data需要的是一个对象你传的是一个数组touser需要字符串你传的是数字template_id少了一个字母接口本身不会校验格式但 JSON 解析器会因为你多了一个逗号或多个花括号而报 47001。2.2 从“业务失败”到“参数错误”的层层定位回到实际问题当你收到 44002 或 47001 时怎么一步步缩小范围第一步复现请求。前端或定时任务触发后把报错日志记录下来连同当时发送的原始请求体一起保存。如果没有日志可以在代码里临时打点把http_build_query后的结果或json_encode的结果打印出来确保你能拿到“实际发送的内容”。第二步判断“空”还是“错”。如果报 44002优先检查你的代码是否真的把 Body 写进了请求。很多框架里POST 请求如果不显式设置 body默认就是空的。如果报 47001直接把打印出来的 Body 放到任意一个 JSON 校验工具里跑一下看看能不能通过。能通过说明 JSON 本身没问题问题出在编码或 Content-Type不能通过说明你的序列化代码需要修。第三步隔离变量。把业务参数写死成固定值比如data直接写{key: {value: test}}再调一次接口。如果固定值能通、动态值报错那问题一定出在数据构造环节比如字段拼接、类型转换、空值处理。3. 实战从错误请求到正确请求的完整改造3.1 一个典型错误请求的现场还原假设我们现在要发送一条模板消息用户在代码里这样写PHP 为例$data [ touser oXXXX-XXXXXXXX, template_id XXXXXXX, data [ keyword1 [value 订单号20240001], keyword2 [value 您的商品已发货], ], ]; // 假设这里把 $data 直接转成 JSON 发送 $postData json_encode($data);这个写法在本地跑json_encode出来的字符串是合法的用浏览器打开校验也没问题。但发到微信接口后返回errcode: 47001。为什么最常见的原因有两个第一个是编码问题。如果页面文件本身不是 UTF-8 编码PHP 的json_encode遇到中文字符时可能返回false。并且如果硬发的话发出去的字节流并不是 UTF-8微信按 UTF-8 去解析自然解析失败。要解决这个问题可以在json_encode时加上JSON_UNESCAPED_UNICODE和JSON_UNESCAPED_SLASHES参数并且确保你源文件保存为 UTF-8 无 BOM 格式。$postData json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);第二个是Content-Type 设置不对。很多 HTTP 客户端库在发送 JSON 时如果不在 Header 里明确写Content-Type: application/json; charsetutf-8服务端微信可能会按表单格式解析。虽然微信官网文档里没有强制要求 Content-Type但在实际测试中有些接口对 Content-Type 是敏感的。如果你用的是 curl可以这样设置curl -X POST \ -H Content-Type: application/json; charsetutf-8 \ -d $postData \ https://api.weixin.qq.com/cgi-bin/message/template/send?access_token$ACCESS_TOKEN3.2 正确请求的关键差异点梳理把错误请求改造成正确请求核心差异不在“请求框架”上而在几个容易被忽略的点上。我整理了一份对比表对比维度错误做法正确做法字符编码文件保存为 GBK/GB2312或未统一 UTF-8全程 UTF-8文件存储和请求输出都保持一致Content-Type不设置或设置为text/plain显式设置application/json; charsetutf-8JSON 序列化json_encode不检查返回值直接发送先判断json_encode是否返回false出错则记录json_last_error_msg()空数据处理数组中有null值时直接忽略键名手动定义空对象使用(object)[]或new stdClass()代替空数组签名/参数拼接直接把数组拼成字符串使用框架或库来序列化避免手拼 JSON调试不打印请求体只打印响应同时记录请求 URL、Header、Body 和响应以 PHP 为例正确姿势是$client new \GuzzleHttp\Client(); $response $client-request(POST, $url, [ headers [ Content-Type application/json; charsetutf-8, ], body json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); $result json_decode($response-getBody()-getContents(), true); if (isset($result[errcode]) $result[errcode] 0) { // 成功 } else { // 失败记录请求体日志 \Log::error(wechat request fail, [ body json_encode($data, JSON_UNESCAPED_UNICODE), response $result, ]); }这段代码的关键在于把请求体存进日志。如果你后期还要排查日志里必须有完整的请求体否则你根本不知道当时发出去的是什么。3.3 多语言开发中的JSON构造要点不同语言处理 JSON 的方式不太一样但“坑”是相通的。我挑 Python、Java、Go 三种高频后端语言说下我实测的要点Pythonrequests 库如果你用requests.post(url, jsondata)requests会自动把data序列化成 JSON并且自动设置Content-Type为application/json。这是最省心的方式。但要注意data里如果有bytes类型字段requests的 JSON 序列化可能失败必须先转成str或decode。另一个容易踩的是空对象Python 里空字典{}序列化后是{}但微信某些接口要求的是“空数组”你传{}它可能不认要根据接口文档明确传什么。import requests payload { touser: oXXXX-XXXXXXXX, template_id: XXXXXXX, data: { keyword1: {value: 订单号20240001}, keyword2: {value: 您的商品已发货}, }, } resp requests.post( https://api.weixin.qq.com/cgi-bin/message/template/send, params{access_token: access_token}, jsonpayload, timeout10, ) print(resp.json())JavaSpring 的 RestTemplate 或 HttpClientJava 里最容易出现的问题有两个发送时使用了MultiValueMap这个结构是表单格式不是 JSON 格式如果你把它直接放到 POST body 里微信收到的就是一个表单字符串JSON 解析必挂。使用RestTemplate时忘记设置HttpHeaders的ContentType默认可能是application/x-www-form-urlencoded。正确做法HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); MapString, Object data new HashMap(); data.put(touser, oXXXX-XXXXXXXX); data.put(template_id, XXXXXXX); MapString, Object keyword1 new HashMap(); keyword1.put(value, 订单号20240001); data.put(data, Collections.singletonMap(keyword1, keyword1)); HttpEntityMapString, Object request new HttpEntity(data, headers); ResponseEntityString response restTemplate.postForEntity(url, request, String.class);特别注意restTemplate.postForEntity传Map时即使你设置了ContentType: application/json某些旧版本 Spring 仍会把它序列化成{key:[value]}这种奇怪结构。原因在于RestTemplate对MultiValueMap的处理方式不同。如果发现响应体里 key 对应的 value 变成了数组需要手动把 JSON 字符串作为 body 发送String jsonBody objectMapper.writeValueAsString(data); HttpEntityString request new HttpEntity(jsonBody, headers);Gonet/httpGo 中比较常见的坑payload : map[string]interface{}{ touser: oXXXX-XXXXXXXX, template_id: XXXXXXX, data: map[string]interface{}{ keyword1: map[string]interface{}{value: 订单号20240001}, }, } jsonBytes, err : json.Marshal(payload) if err ! nil { // 处理错误 } req, err : http.NewRequest(POST, url, bytes.NewReader(jsonBytes)) if err ! nil { // 处理错误 } req.Header.Set(Content-Type, application/json; charsetutf-8) client : http.Client{Timeout: 10 * time.Second} resp, err : client.Do(req)Go 中容易忽略的是json.Marshal不会报编码错误但它会把map[string]interface{}里的nil值编码成null。如果微信接口要求某个字段必须有值传null同样会导致业务逻辑异常。另外http.NewRequest使用bytes.NewReader(jsonBytes)作为 body 时如果jsonBytes是空的bytes.NewReader会生成一个长度为 0 的 Reader最终发出去的 Body 就是空这就直接命中 44002。所以json.Marshal后一定要检查返回的字节数。4. 高频排查场景与问题清单4.1 典型问题空数组、编码、Content-Type、缓存我在多个项目里排查这两个错误码时遇到最多的情况就是下面几类每一个都值得单独说第一类空数组被序列化成[]而不是{}。微信的 JSON 接口对于“对象”类型的字段期望的是{key: value}这种花括号结构。如果某个字段需要的是对象而你在代码里用的是普通数组PHP 里就是$arr []那么json_encode后就会变成[]方括号结构。微信的 JSON 解析器在解析时一旦发现类型和文档要求的不一致很容易报 47001。比如模板消息的data字段官方文档里写的是一个对象如果你传了空数组结构就可能出错。解决方式是在 PHP 里把空数组强转为stdClass(object)[]。在 Go 里用map[string]interface{}{}在 Python 里用dict()在 Java 里用new HashMap()。第二类文件编码混用导致中文变成乱码。典型场景是你的源代码文件是 UTF-8但数据库连接字符集是 latin1查询出的中文变成了?或乱码。json_encode时这些乱码字符会破坏 JSON 结构轻则显示为乱码重则导致解析失败。解决办法是保证从数据源到 HTTP 输出链路的所有环节都是 UTF-8。比如在 PHP 中设置mysqli_set_charset($conn, utf8mb4)在 Java 中设置连接串参数characterEncodingutf8。第三类Content-Type 设置成表单类型。微信官方文档虽然没有强调必须用application/json但在实践中如果你用application/x-www-form-urlencoded或者multipart/form-data发送 JSON 字符串微信服务器那边对 Body 的解析方式会变得不确定。我建议统一使用application/json并且显式带上; charsetutf-8后缀。第四类缓存了旧的 access_token导致请求地址拼接错误或 token 失效。这个问题虽然不会直接导致 44002/47001但会在排查时造成干扰。如果你用了一个过期的 token微信会返回 40001/42001此时你可能会重新调接口而重新连续调用时代码里如果 token 刷新逻辑有并发问题可能导致请求体被并发地写坏间接引发 47001。这种情况比较少见但我在高并发推送场景里真踩过。第五类HTTP 库的 body 被二次编码。有些框架在发送请求时会对字符串再做一次urlencode这样微信那边收到的就是%7B%22touser%22...这种编码字符而不是 JSON 明文。JSON 解析器看到%开头的内容直接就报 47001。排查技巧是在服务端日志里看请求体原文如果是一堆%开头的内容说明被二次编码了需要改用强制 body 写入而不是 form-data 传参。4.2 排查实操工具与日志技巧遇到这两个错误码时我建议你按照下面的工具链来定位问题效率会高很多1. 本地复现 curl 模拟在正式环境排查前先在本地用 curl 模拟一次最原始的请求排除代码框架干扰curl -X POST https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credentialappidAPPIDsecretSECRET拿到 access_token 后再手动执行一次模板消息发送curl -X POST \ -H Content-Type: application/json; charsetutf-8 \ -d {touser:oXXXX-XXXXXXXX,template_id:XXXXXXX,data:{keyword1:{value:test}}} \ https://api.weixin.qq.com/cgi-bin/message/template/send?access_tokenACCESS_TOKEN如果这个 curl 请求能成功说明微信接口本身没问题问题出在你的业务代码如果这个 curl 也报 44002/47001说明你连测试的 Body 都是错的需要重新检查 JSON 结构。2. 请求体原文日志在代码里除了记录响应一定要记录请求体的原始字符串。我见过太多项目只记录响应{errcode:47001}却完全想不起当时发了什么。建议至少包括access_token 是否有值、是否过期完整的 URL包含 query 参数请求 Header特别是 Content-Type请求 Body 原文请求耗时3. 在线 JSON 校验器把日志里的 Body 原文复制到任何 JSON 校验工具里看是否能通过。能通过就说明 JSON 字符串没问题问题可能在传输层不能通过说明序列化环节有问题。4. 抓包工具如果本地调试仍然无法定位可以用抓包工具查看实际发送的 HTTP 报文。重点看 Body 部分是不是你预期中的 JSON 原文以及 HTTP Header 里的 Content-Type 是否是你设置的值。有些框架比如部分版本的 Apache HttpClient在自动重定向时会丢 Header或者在某些代理环境下改变 Body 编码这些肉眼很难发现只有抓包才能看到。4.3 问题速查表我把实际项目中最常见的故障点和对应解决方案整理成了一张速查表建议收藏现象可能原因检查方法解决方案报 44002Body 日志确实是空字符串代码没写 body或 body 被框架吞掉检查发送代码是否显式设置了请求体用body字段显式传 JSON 字符串报 44002但 Body 日志里有内容Body 被二次编码成一串%字符查看原始请求报文看是否有urlencode检查 HTTP 库参数改用 raw body 发送报 47001JSON 校验工具显示合法Content-Type 没设置或被错误设置确认 Header 中的 Content-Type 值统一设为application/json; charsetutf-8报 47001JSON 校验失败手拼 JSON 时少逗号、多引号将日志原文复制到校验工具使用语言的json_encode/json.dumps等方法报 47001中文变成乱码数据链路编码不一致检查文件编码、数据库连接编码全程统一 UTF-8设置字符集参数报 47001空数组字段变成[]空数组被序列化成数组结构查看 JSON 原文确认字段类型使用(object)[]或new stdClass()强制为对象报 47001data字段结构异常模板消息 data 字段键值错误对照微信官方模板结构检查data内部的键名是否与模板一致报 47001接口偶发不是必现并发下 token 刷新或 body 被并发修改查看日志中是否有多个线程写同一个变量增加锁或为每次请求独立构造 body 对象4.4 一个实际案例我用 3 个小时排查一个“看不到的字符”最后分享一个印象很深的案例。当时项目里有一个定时任务每天推送模板消息一直正常。某天突然开始报 47001而且只有一部分用户报错。我打印了请求体肉眼完全看不出任何问题JSON 格式正确、字段完整直接用 curl 发同样内容也能成功。但代码里一跑就报错。后来我把请求体的二进制内容打印出来才发现问题所在某些用户昵称里带有 emoji 表情数据库连接是 utf83 字节而 emoji 需要 utf8mb44 字节才能存储。在json_encode时这些字符被转成了\ud83d\ude00这种代理对但因为在 PHP 中某些函数对字符串长度的处理不当导致代理对被拆开形成了非法的 JSON 转义序列。微信的 JSON 解析器遇到这种情况自然就报 47001。解决方案是把数据库连接字符集改成utf8mb4并且在json_encode时始终检查返回值发现非法字节就记录日志。这件事给我最大的教训是任何接口报错都不要只盯着错误码本身要把请求链路里的每一个字节都当作嫌疑人。写在最后44002 和 47001 虽然看起来是两个独立的错误码但本质上都在告诉我们同一个道理微信服务端接口对请求体的要求非常严格空字符串不行、格式不对不行、编码不对也不行。在实际开发中与其反复去猜或者搜“微信 44002 怎么解决”不如从请求构造、传输编码、Content-Type 设置、日志记录这四个环节下手建立一套完整的排查流程。我个人的习惯是所有调用微信接口的地方一律把请求体原文和响应原文同时写入日志并且每次发版前用 curl 实测一次接口连通性。把这两件事做扎实大部分和 44002/47001 相关的坑都能提前避开。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →