fix(wechat): jscode2session 需显式 responseType=json,否则 ofetch 不解析响应

This commit is contained in:
2026-09-27 08:47:56 +08:00
parent 565d6e0fcb
commit a080144a86
+72 -13
View File
@@ -44,27 +44,86 @@ export default defineEventHandler(async (event) => {
}
// 1. 用 code 换取 openid
let session: Jscode2SessionResult
let session: any
try {
session = await $fetch<Jscode2SessionResult>(
'https://api.weixin.qq.com/sns/jscode2session',
{
session = await $fetch<any>('https://api.weixin.qq.com/sns/jscode2session', {
query: { appid, secret, js_code: code, grant_type: 'authorization_code' },
timeout: 8000
}
)
timeout: 8000,
/*
* ★★★ 关键修复(线上「微信登录一直 401」的真正根因)★★★
*
* ofetch 按响应的 **content-type** 决定要不要解析 JSON。
* 微信的 jscode2session 返回的 content-type 不是 application/json,
* 于是 ofetch **原样返回字符串**,`session` 就成了 string 而不是对象 ——
* 结果 session.openid / errcode / errmsg **全是 undefined**,
* 代码走到 `if (!session.openid)` 抛 401,日志打印「undefined undefined」,
* 用户看到「微信登录失败:未知错误(undefined)」。
*
* 实测(容器内直接跑 ofetch):
* GOT type=string "{\"errcode\":40029,\"errmsg\":\"invalid code\"}"
*
* 显式声明 responseType: 'json' 后才会解析成对象。
*/
responseType: 'json'
})
} catch (e: any) {
console.error('[WeChatMP] jscode2session 请求失败:', e?.message)
throw createError({ statusCode: 502, message: '微信登录服务暂时不可用,请稍后重试' })
}
// 兜底:万一某天 responseType 仍没生效(或上游改了 content-type),手动解析一次,
// 不要再让「字符串被当对象用」这种问题静默成 undefined。
if (typeof session === 'string') {
try {
session = JSON.parse(session)
} catch {
console.error('[WeChatMP] 响应无法解析为 JSON:', String(session).slice(0, 300))
session = null
}
}
/*
* ⚠️ 诊断日志(这次是踩坑加的)。
*
* 线上曾出现 `[WeChatMP] jscode2session 返回异常: undefined undefined` ——
* errcode / errmsg **双 undefined**。这不是「appid 错了」:
* 微信出错时会返回 { errcode, errmsg },两个都取不到说明**响应体根本不是微信的 JSON**。
* 最可能是 $fetch 拿回了一段非 JSON 文本(被代理/防火墙拦截返回的 HTML、
* 或重定向页),此时 ofetch 解析失败会把原始字符串返回,于是所有字段都是 undefined。
*
* 所以这里把**响应的真实类型和内容**打出来,否则下次还是只能看到 undefined。
* appid 打前 6 位后 4 位,用于和微信公众平台里的小程序 AppID 核对(两者必须完全一致,
* 且**不能**填公众号的 AppID —— 那套凭据不通用)。
*/
const appidMasked =
appid.length > 10 ? appid.slice(0, 6) + '****' + appid.slice(-4) : appid.slice(0, 3) + '****'
console.error(
'[WeChatMP] 诊断: appid=' + appidMasked +
' secretLen=' + String(secret).length +
' 响应类型=' + typeof session +
' 是否数组=' + Array.isArray(session) +
' 顶层键=' + (session && typeof session === 'object' ? Object.keys(session).join(',') : '(非对象)')
)
console.error(
'[WeChatMP] 诊断: 原始响应=' +
(typeof session === 'string'
? session.slice(0, 500)
: JSON.stringify(session)?.slice(0, 500))
)
// 微信接口失败时仍返回 HTTP 200,必须检查 openid
if (!session.openid) {
console.error('[WeChatMP] jscode2session 返回异常:', session.errcode, session.errmsg)
throw createError({
statusCode: 401,
message: `微信登录失败:${session.errmsg || '未知错误(' + session.errcode + ')'}`
})
if (!session || typeof session !== 'object' || !session.openid) {
// 区分「微信明确报错」和「响应体不是微信的 JSON」——两者排查方向完全不同
const notJson = !session || typeof session !== 'object'
const errcode = session && session.errcode
const errmsg = session && session.errmsg
console.error('[WeChatMP] jscode2session 返回异常:', errcode, errmsg, notJson ? '(响应非 JSON)' : '')
const message = notJson
? '微信登录失败:服务器访问微信接口时未收到合法响应(可能被网络策略拦截),请查看服务端日志'
: `微信登录失败:${errmsg || '未知错误(' + (errcode ?? '无错误码') + ')'}`
throw createError({ statusCode: 401, message })
}
const openid = session.openid