From 211d93a0e4480b53fb602606409ca1985e69aa5b Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 4 Sep 2026 14:46:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=9D=92=E5=B0=91=E5=B9=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=8B=AC=E7=AB=8B=E5=AF=86=E7=A0=81=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E6=8A=A5=E9=94=99=E6=98=BE=E7=A4=BA=E8=8B=B1=E6=96=87?= =?UTF-8?q?=E5=8E=9F=E6=96=87=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=88=E7=AC=AC?= =?UTF-8?q?10-11=E7=AB=A0=E5=A4=8D=E6=B5=8B=E5=8F=91=E7=8E=B0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 关闭/开启青少年模式都要先过 POST /settings/independent-password/verify 这一关, PIN 输错、连续错误触发限流这两种失败,后端 reason 分别是 CODE_INVALID/ TOO_MANY_ATTEMPTS——REASON_MESSAGES 表里没收录这两个 key,直接透传英文原文 message("code invalid"/"too many attempts"),且限流的具体等待时长(details. retry_after_seconds)完全没有展示给用户。 REASON_MESSAGES 原来只支持字符串值,限流提示需要按 retry_after_seconds 动态拼 文案,所以顺带把 imRequest 的取值逻辑改成支持函数值(传入 payload.details)。 已用真实账号端到端验证:对 https://api.118im.com 连续发送错误 PIN,前3次真实 返回 401 CODE_INVALID,UI 正确显示"独立密码不正确";第4次起后端真实触发限流 返回 429 TOO_MANY_ATTEMPTS(details.retry_after_seconds 从300开始递减),UI 正确显示"连续输错次数过多,请 240 秒后重试",秒数随后端返回值动态变化。 Co-Authored-By: Claude Opus 5 --- src/lib/im-api.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/lib/im-api.js b/src/lib/im-api.js index a66841af..7a10db89 100644 --- a/src/lib/im-api.js +++ b/src/lib/im-api.js @@ -44,7 +44,14 @@ const REASON_MESSAGES = { REALNAME_REQUIRED: '需要先完成实名认证', YOUTH_MODE_RESTRICTED: '青少年模式下无法使用此功能', RISK_CONFIRMATION_REQUIRED: '需要先完成安全确认', - RISK_ACTION_BLOCKED: '该操作已被安全策略拦截' + RISK_ACTION_BLOCKED: '该操作已被安全策略拦截', + // 独立密码校验的两个 reason(对接文档 809 行有写),青少年模式关闭/开启走的都是这条接口。 + // 原来没收录,报错时直接透传后端英文原文——PIN 输错显示 "code invalid",连续错误锁定后 + // 显示 "too many attempts",且不告诉用户要等多久能重试。 + CODE_INVALID: '独立密码不正确', + TOO_MANY_ATTEMPTS: details => details && details.retry_after_seconds + ? `连续输错次数过多,请 ${details.retry_after_seconds} 秒后重试` + : '连续输错次数过多,请稍后重试' }; function requestId() { @@ -123,7 +130,8 @@ export async function imRequest(path, {method = 'GET', body, headers, auth = tru // (not silently replaced with a generic string) since this backend's messages have consistently // been reasonable, specific, business-appropriate text in real testing, not raw internals -- only // reasons this app doesn't otherwise know how to react to fall through to it. - const known = payload.reason && REASON_MESSAGES[payload.reason]; + const entry = payload.reason && REASON_MESSAGES[payload.reason]; + const known = typeof entry === 'function' ? entry(payload.details) : entry; throw new ImApiError(known || payload.message || payload.reason || response.statusText || '请求失败', payload.reason, response.status, payload.details); }