From 46298c011f0672daff66865d4339691e12f0dc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Thu, 16 Jul 2026 17:01:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(login):=20=E6=81=A2=E5=A4=8D=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E5=BF=AB=E6=8D=B7=E7=99=BB=E5=BD=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E9=81=BF=E5=85=8D=E5=B8=A6=E6=97=A7Token?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 login.tsx 和 register.tsx 中登录请求从封装的 request 工具改回 Taro.request - 取消登录请求中自动注入 Authorization 头,避免携带过期/旧 token 导致认证失败 - 调整登录接口响应数据解析,适配 Taro.request 的数据结构 - fetchUserInfo 改回使用 Taro.request,手动携带 Authorization 头部 - 添加全局 getPhoneNumber 调用冷却机制,防止频繁调用导致微信报错 - 登录页新增手机授权冷却 --- .workbuddy/memory/2026-07-16.md | 14 +++++ login_recover.patch | 90 +++++++++++++++++++++++++++ register_recover.patch | 71 +++++++++++++++++++++ src/app.tsx | 23 ++++--- src/components/PrivacyModal/index.tsx | 2 + src/passport/login.tsx | 84 +++++++++++++++++++------ src/passport/register.tsx | 36 +++++++---- src/utils/phoneAuth.ts | 35 +++++++++++ src/utils/privacy.ts | 12 +++- 9 files changed, 322 insertions(+), 45 deletions(-) create mode 100644 login_recover.patch create mode 100644 register_recover.patch create mode 100644 src/utils/phoneAuth.ts diff --git a/.workbuddy/memory/2026-07-16.md b/.workbuddy/memory/2026-07-16.md index 9f27fac..a89cd9e 100644 --- a/.workbuddy/memory/2026-07-16.md +++ b/.workbuddy/memory/2026-07-16.md @@ -50,3 +50,17 @@ - 说明:登录时若用户尚未同意隐私协议,微信仍会在点击 `getPhoneNumber` 按钮时强制触发 `onNeedPrivacyAuthorization`。这是微信机制,无法避免;但进入登录页本身不会再主动弹框。 - 验证:待构建完成。 +## 恢复手机号快捷登录功能(回退 request → Taro.request) + +- 背景:origin/main 上 `ca78e73`(小程序支付隐私 commit)把 `passport/login.tsx` 和 `passport/register.tsx` 的登录请求从 `Taro.request` 改成了统一封装的 `request` 工具(`@/utils/request`)。 +- 问题根因:`request` 拦截器会自动从 storage 读取 `access_token` 并注入 Authorization 头;对于 `loginByMpWxPhone` 这种登录接口,如果本地残留旧的/过期 token,请求会带上它,后端返回 401 或认证冲突,导致快捷登录失败。原来的 `Taro.request` 版本不带这个头,所以没问题。 +- 恢复方式:通过 git diff 生成 patch,将两个文件的登录请求改回 `Taro.request`(直接请求,不带 Authorization 头),保留其他所有改进(隐私协议 PrivacyModal、UI、邀请关系处理、禁用检查等)。 +- 具体改动: + - `src/passport/login.tsx`: + - 移除 `import request from '@/utils/request'` 和 `SERVER_API_URL` 导入; + - `fetchUserInfo()` 改回 `Taro.request` GET `/api/auth/user`,手动带 `Authorization: Bearer ${token}` 头; + - `handleGetPhoneNumber` 中登录请求改回 `Taro.request` POST `/api/wx-login/loginByMpWxPhone`,不带 Authorization 头; + - 响应数据结构从 `res.code/res.data` 调整为 `res.data.code/res.data.data`(Taro.request 多一层包裹)。 + - `src/passport/register.tsx`:同样改动,恢复 `Taro.request`,移除 `request` 和 `SERVER_API_URL` 导入。 +- patch 文件:`login_recover.patch`、`register_recover.patch`(已应用到工作区,未提交)。 + diff --git a/login_recover.patch b/login_recover.patch new file mode 100644 index 0000000..6be5002 --- /dev/null +++ b/login_recover.patch @@ -0,0 +1,90 @@ +--- a/src/passport/login.tsx 2026-07-16 15:54:54.000000000 +0800 ++++ b/src/passport/login.tsx 2026-07-16 15:53:09.000000000 +0800 +@@ -4,9 +4,8 @@ + import { TenantId } from '@/config/app' + import { getWxOpenId } from '@/api/layout' + import { getUserInfo } from '@/api/layout' +-import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server' ++import { saveStorageByLoginUser } from '@/utils/server' + import { isUserDisabled } from '@/utils/auth' +-import request from '@/utils/request' + import { + checkAndHandleInviteRelation, + hasPendingInvite, +@@ -73,21 +72,22 @@ + } + } + +-/** 获取用户信息(使用临时 token,避免本地 storage 尚未写入) */ ++/** 获取用户信息 */ + async function fetchUserInfo(token: string) { + try { +- const res: any = await request.get( +- `${SERVER_API_URL}/auth/user`, +- {}, +- { +- header: { +- Authorization: `Bearer ${token}`, +- }, +- showError: false, +- } +- ) +- if (res?.code === 0 && res?.data) { +- return res.data ++ const serverUrl = 'https://server.websoft.top' ++ const res: any = await Taro.request({ ++ url: `${serverUrl}/api/auth/user`, ++ method: 'GET', ++ header: { ++ Authorization: `Bearer ${token}`, ++ 'content-type': 'application/json', ++ TenantId: String(TenantId), ++ }, ++ }) ++ ++ if (res.data?.code === 0 && res.data?.data) { ++ return res.data.data + } + return null + } catch (e) { +@@ -215,21 +215,23 @@ + const inviteParams = parseInviteParams({ query: router?.params }) + const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter, 10) : 0 + +- const res: any = await request.post( +- `${SERVER_API_URL}/wx-login/loginByMpWxPhone`, +- { ++ const serverUrl = 'https://server.websoft.top' ++ const res: any = await Taro.request({ ++ url: `${serverUrl}/api/wx-login/loginByMpWxPhone`, ++ method: 'POST', ++ data: { + code: phoneCode, + notVerifyPhone: true, + refereeId, + sceneType: 'save_referee', + tenantId: Number(TenantId), + }, +- { showError: false } +- ) ++ header: { 'content-type': 'application/json', TenantId: String(TenantId) }, ++ }) + +- if (res?.code === 0 && res?.data?.access_token) { +- const token = res.data.access_token +- let user = res.data.user ++ if (res.data?.code === 0 && res.data?.data?.access_token) { ++ const token = res.data.data.access_token ++ let user = res.data.data.user + + // 获取最新的用户信息 + const freshUserInfo = await fetchUserInfo(token) +@@ -260,7 +262,7 @@ + Taro.showToast({ title: '登录成功', icon: 'success' }) + setTimeout(() => navigateAfterLogin(), 800) + } else { +- Taro.showToast({ title: res?.message || '登录失败', icon: 'none' }) ++ Taro.showToast({ title: res.data?.message || '登录失败', icon: 'none' }) + } + } catch (e: any) { + console.error('微信登录失败:', e) diff --git a/register_recover.patch b/register_recover.patch new file mode 100644 index 0000000..d5cdf1e --- /dev/null +++ b/register_recover.patch @@ -0,0 +1,71 @@ +--- a/src/passport/register.tsx 2026-07-16 15:55:24.000000000 +0800 ++++ b/src/passport/register.tsx 2026-07-16 15:54:30.000000000 +0800 +@@ -4,9 +4,8 @@ + import { Button, Checkbox } from '@nutui/nutui-react-taro' + import { TenantId } from '@/config/app' + import { getUserInfo, getWxOpenId } from '@/api/layout' +-import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server' ++import { saveStorageByLoginUser } from '@/utils/server' + import { isUserDisabled } from '@/utils/auth' +-import request from '@/utils/request' + import { + getStoredInviteParams, + parseInviteParams, +@@ -26,6 +25,17 @@ + detail: GetPhoneNumberDetail + } + ++interface LoginResponse { ++ data: { ++ code?: number ++ message?: string ++ data?: { ++ access_token: string ++ user: any ++ } ++ } ++} ++ + async function getWeappLoginCode(): Promise { + try { + const res = await new Promise<{ code?: string }>((resolve, reject) => { +@@ -181,9 +191,10 @@ + // 获取小程序登录 code(用于后续绑定 openid) + const wxLoginCode = await getWeappLoginCode() + +- const res: any = await request.post( +- `${SERVER_API_URL}/wx-login/loginByMpWxPhone`, +- { ++ const res = (await Taro.request({ ++ url: 'https://shop-api.websoft.top/api/wx-login/loginByMpWxPhone', ++ method: 'POST', ++ data: { + code: phoneCode, + encryptedData, + iv, +@@ -192,16 +203,19 @@ + sceneType: 'save_referee', + tenantId: TenantId, + }, +- { showError: false } +- ) ++ header: { ++ 'content-type': 'application/json', ++ TenantId, ++ }, ++ })) as unknown as LoginResponse + +- if (res?.code === 1) { +- Taro.showToast({ title: res.message || '登录失败', icon: 'none' }) ++ if ((res as any)?.data?.code === 1) { ++ Taro.showToast({ title: res.data.message || '登录失败', icon: 'none' }) + return + } + +- const token = res?.data?.access_token +- const user = res?.data?.user ++ const token = res?.data?.data?.access_token ++ const user = res?.data?.data?.user + if (!token || !user?.userId) { + Taro.showToast({ title: '登录失败,请重试', icon: 'none' }) + return diff --git a/src/app.tsx b/src/app.tsx index a4da4a3..9ff51e5 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -26,21 +26,20 @@ function App(props: AppProps) { } // 微信隐私协议(基础库 3.16.1+ 强制) - // 必须使用 open-type="agreePrivacyAuthorization" 的 Button 让用户点击同意, - // Taro.showModal 的按钮无法被微信识别为隐私授权,会导致后续敏感 API 仍被拒绝。 + // getPhoneNumber 等敏感能力必须由用户点击 agreePrivacyAuthorization 按钮授权。 const wxAny: any = Taro + if (typeof wxAny.getPrivacySetting === 'function') { + wxAny.getPrivacySetting({ + success: (res: any) => { + if (res?.privacyContractName) { + privacyManager.setPrivacyContractName(res.privacyContractName) + } + }, + }) + } if (typeof wxAny.onNeedPrivacyAuthorization === 'function') { wxAny.onNeedPrivacyAuthorization((resolve: any) => { - wxAny.getPrivacySetting({ - success: (setting: any) => { - privacyManager.setPrivacyContractName(setting?.privacyContractName) - privacyManager.show(resolve) - }, - fail: () => { - // 获取设置失败时,默认 resolve 同意,避免流程阻塞 - resolve({ event: 'agree', button: 'agree' }) - }, - }) + privacyManager.show(resolve) }) } }) diff --git a/src/components/PrivacyModal/index.tsx b/src/components/PrivacyModal/index.tsx index 8ed976b..a140d84 100644 --- a/src/components/PrivacyModal/index.tsx +++ b/src/components/PrivacyModal/index.tsx @@ -34,12 +34,14 @@ const PrivacyModal = () => {