fix(wechat): jscode2session 需显式 responseType=json,否则 ofetch 不解析响应
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user