From 99088ad461c4432b5c3ea679771b5fe758ac8f59 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Wed, 9 Sep 2026 09:17:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20H5=20=E5=BE=AE=E4=BF=A1=20OAuth=20?= =?UTF-8?q?=E4=BB=8E=20wechat=5Fmp=20=E8=AF=BB=E5=8F=96=20AppId=20?= =?UTF-8?q?=E4=B8=8E=E5=9B=9E=E8=B0=83=E6=A0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 去掉写死 AppId,跳转前拉取 /wechat/mp/oauth-config,与公众号网页授权域名对齐。 Co-authored-by: Cursor --- src/api/wechat/index.ts | 19 +++++- src/api/wechat/types.ts | 7 +++ src/utils/wechatOAuth.ts | 59 +++++++++++++++---- .../components/WechatOAuthLoginButton.vue | 9 ++- src/views/h5/customer-center/login/index.vue | 11 +++- src/views/h5/customer-center/me/index.vue | 4 +- src/views/wechat/bind/index.vue | 10 +++- src/views/wechat/register/index.vue | 10 +++- 8 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/api/wechat/index.ts b/src/api/wechat/index.ts index 0d1f75c..9c058ad 100644 --- a/src/api/wechat/index.ts +++ b/src/api/wechat/index.ts @@ -9,6 +9,7 @@ import type { WxFollowQrcodeResult, WxLoginQrcodeResult, WxLoginResult, + WxOAuthConfigResult, } from './types'; export * from './types'; @@ -22,7 +23,9 @@ function unwrapOrSelf(body: unknown): T { 'qrCodeUrl' in record || 'jumpUrl' in record || 'accessToken' in record || - 'openid' in record + 'openid' in record || + 'appId' in record || + 'AppId' in record ) { return body as T; } @@ -43,6 +46,20 @@ export async function getFollowQrcode(): Promise { return unwrapOrSelf(res.data); } +/** H5 网页授权:从其他配置 wechat_mp 读取 AppId / OAuth 回调根 */ +export async function getWeChatOAuthConfig(): Promise { + const res = await axiosInstance.get(omsUrl('/wechat/mp/oauth-config')); + const data = unwrapOrSelf>(res.data); + const appId = String(data.appId ?? data.AppId ?? '').trim(); + const oauthRedirectBase = String( + data.oauthRedirectBase ?? data.OAuthRedirectBase ?? '', + ).trim(); + return { + appId, + oauthRedirectBase: oauthRedirectBase || null, + }; +} + /** 查询扫码 key 是否已关联账号(轮询用,无值时不抛错) */ export async function getWxLoginStatus(loginUuid: string): Promise { const res = await axiosInstance.get('/rbac/auth/wx-login/status', { diff --git a/src/api/wechat/types.ts b/src/api/wechat/types.ts index 1bc2449..6f042fe 100644 --- a/src/api/wechat/types.ts +++ b/src/api/wechat/types.ts @@ -15,6 +15,13 @@ export interface WxFollowQrcodeResult { scene?: string; } +/** H5 网页授权公开配置(wechat_mp) */ +export interface WxOAuthConfigResult { + appId: string; + /** OAuth 回调根,如 https://ideship.com;空则用当前页 origin */ + oauthRedirectBase?: string | null; +} + export interface WxLoginStatusResult { ready: boolean; account?: string; diff --git a/src/utils/wechatOAuth.ts b/src/utils/wechatOAuth.ts index 4455072..a4d72a9 100644 --- a/src/utils/wechatOAuth.ts +++ b/src/utils/wechatOAuth.ts @@ -1,5 +1,6 @@ /** 微信公众号 H5 网页授权(注册 / 绑定页无 code 时跳转) */ +import { getWeChatOAuthConfig } from '@/api/wechat'; import { clearStashedInviteRef, readStashedInviteRef, @@ -8,11 +9,45 @@ import { export type WeChatH5Page = 'register' | 'bind' | 'login'; -const DEFAULT_MP_APP_ID = 'wxd5141ba46c856e1f'; +type WeChatOAuthRuntimeConfig = { + appId: string; + /** 规范化后的 origin,如 https://ideship.com;空则用当前页 */ + redirectOrigin: string; +}; -export function resolveWeChatMpAppId(): string { - const fromEnv = import.meta.env.VITE_WECHAT_MP_APPID as string | undefined; - return fromEnv?.trim() || DEFAULT_MP_APP_ID; +let oauthConfigPromise: Promise | null = null; + +function normalizeRedirectOrigin(base: string | null | undefined): string { + const raw = String(base ?? '').trim(); + if (!raw) return ''; + try { + const url = new URL(raw.includes('://') ? raw : `https://${raw}`); + return url.origin; + } catch { + return ''; + } +} + +/** 从其他配置 wechat_mp 取 AppId / OAuth 回调根(进程内缓存) */ +export async function resolveWeChatMpOAuthConfig(): Promise { + if (!oauthConfigPromise) { + oauthConfigPromise = getWeChatOAuthConfig() + .then((cfg) => { + const appId = String(cfg.appId ?? '').trim(); + if (!appId) { + throw new Error('未配置微信公众号 AppId(其他配置 wechat_mp.AppKey)'); + } + return { + appId, + redirectOrigin: normalizeRedirectOrigin(cfg.oauthRedirectBase), + }; + }) + .catch((err) => { + oauthConfigPromise = null; + throw err; + }); + } + return oauthConfigPromise; } /** @deprecated 使用 stashInviteRef */ @@ -59,25 +94,27 @@ export function resolveWeChatInviteRef(query: Record): string { return readStashedInviteRef(); } -function resolveOAuthRedirectPath(page: WeChatH5Page): string { +function resolveOAuthRedirectPath(page: WeChatH5Page, redirectOrigin: string): string { + const origin = redirectOrigin || window.location.origin; if (page === 'login') { - return `${window.location.origin}/h5/login`; + return `${origin}/h5/login`; } - return `${window.location.origin}/wechat/${page}`; + return `${origin}/wechat/${page}`; } /** * 跳转微信 OAuth。 + * - AppId / 回调根来自其他配置 wechat_mp(AppKey、BaseUrl) * - register/bind:邀请码写入 state + 7 天暂存 * - login:可选把登录后回跳路径写入 state(login:/h5/orders) */ -export function redirectToWeChatOAuth( +export async function redirectToWeChatOAuth( page: WeChatH5Page, inviteCode?: string | null, options?: { afterLoginPath?: string | null }, -): void { - const appId = resolveWeChatMpAppId(); - const redirectUri = encodeURIComponent(resolveOAuthRedirectPath(page)); +): Promise { + const { appId, redirectOrigin } = await resolveWeChatMpOAuthConfig(); + const redirectUri = encodeURIComponent(resolveOAuthRedirectPath(page, redirectOrigin)); let state = page; if (page === 'login') { diff --git a/src/views/h5/customer-center/components/WechatOAuthLoginButton.vue b/src/views/h5/customer-center/components/WechatOAuthLoginButton.vue index ff863c7..2ff8117 100644 --- a/src/views/h5/customer-center/components/WechatOAuthLoginButton.vue +++ b/src/views/h5/customer-center/components/WechatOAuthLoginButton.vue @@ -31,14 +31,19 @@ const label = computed(() => jumping.value ? t('h5.login.oauthRedirecting') : t('h5.login.oauthLogin'), ); -function handleClick() { +async function handleClick() { if (jumping.value || props.disabled) return; if (!inWeChat.value) { Message.warning(t('h5.login.openInWechat')); return; } jumping.value = true; - redirectToWeChatOAuth('login', null, { afterLoginPath: props.afterLoginPath }); + try { + await redirectToWeChatOAuth('login', null, { afterLoginPath: props.afterLoginPath }); + } catch (e: any) { + jumping.value = false; + Message.error(e?.message || t('h5.login.oauthFailed')); + } } diff --git a/src/views/h5/customer-center/login/index.vue b/src/views/h5/customer-center/login/index.vue index bb92aab..a84e2f3 100644 --- a/src/views/h5/customer-center/login/index.vue +++ b/src/views/h5/customer-center/login/index.vue @@ -176,7 +176,9 @@ function goRegister() { Message.warning(t('h5.login.openInWechat')); return; } - redirectToWeChatOAuth('register'); + void redirectToWeChatOAuth('register').catch((e: any) => { + Message.error(e?.message || t('h5.login.oauthFailed')); + }); } function openAgreement(type: 'privacy' | 'terms') { @@ -304,7 +306,12 @@ onMounted(async () => { if (isWeChatBrowser() && route.query.auto === '1') { oauthBusy.value = true; - redirectToWeChatOAuth('login', null, { afterLoginPath: resolveRedirectPath() }); + try { + await redirectToWeChatOAuth('login', null, { afterLoginPath: resolveRedirectPath() }); + } catch (e: any) { + oauthBusy.value = false; + Message.error(e?.message || t('h5.login.oauthFailed')); + } } }); diff --git a/src/views/h5/customer-center/me/index.vue b/src/views/h5/customer-center/me/index.vue index 7c3eec3..7e7d449 100644 --- a/src/views/h5/customer-center/me/index.vue +++ b/src/views/h5/customer-center/me/index.vue @@ -549,7 +549,9 @@ function bindWechat() { Message.warning(t('h5.me.bindWechatOpenInWechat')); return; } - redirectToWeChatOAuth('bind'); + void redirectToWeChatOAuth('bind').catch((e: any) => { + Message.error(e?.message || t('h5.me.bindWechatOpenInWechat')); + }); } function openOnlineKf() { diff --git a/src/views/wechat/bind/index.vue b/src/views/wechat/bind/index.vue index 00ee382..27bba2d 100644 --- a/src/views/wechat/bind/index.vue +++ b/src/views/wechat/bind/index.vue @@ -181,7 +181,9 @@ function goCustomerCenter() { function goLogin() { if (isWeChatBrowser()) { - redirectToWeChatOAuth('login', null, { afterLoginPath: '/h5/me' }); + void redirectToWeChatOAuth('login', null, { afterLoginPath: '/h5/me' }).catch(() => { + router.replace('/h5/login'); + }); return; } router.replace('/h5/login'); @@ -189,7 +191,9 @@ function goLogin() { function goRegister() { if (isWeChatBrowser()) { - redirectToWeChatOAuth('register'); + void redirectToWeChatOAuth('register').catch(() => { + router.replace('/wechat/register'); + }); return; } router.replace('/wechat/register'); @@ -197,7 +201,7 @@ function goRegister() { function startOAuth() { authModalVisible.value = false; - redirectToWeChatOAuth('bind'); + void redirectToWeChatOAuth('bind'); } function retryOAuth() { diff --git a/src/views/wechat/register/index.vue b/src/views/wechat/register/index.vue index e163d49..406827d 100644 --- a/src/views/wechat/register/index.vue +++ b/src/views/wechat/register/index.vue @@ -247,7 +247,9 @@ function goCustomerCenter() { function goLogin() { if (isWeChatBrowser()) { - redirectToWeChatOAuth('login', null, { afterLoginPath: '/h5/me' }); + void redirectToWeChatOAuth('login', null, { afterLoginPath: '/h5/me' }).catch(() => { + router.replace('/h5/login'); + }); return; } router.replace('/h5/login'); @@ -259,7 +261,9 @@ function goOAuthLogin() { function goBind() { if (isWeChatBrowser()) { - redirectToWeChatOAuth('bind'); + void redirectToWeChatOAuth('bind').catch(() => { + router.replace('/wechat/bind'); + }); return; } router.replace('/wechat/bind'); @@ -267,7 +271,7 @@ function goBind() { function startOAuth() { authModalVisible.value = false; - redirectToWeChatOAuth('register', inviteCode.value); + void redirectToWeChatOAuth('register', inviteCode.value); } function retryOAuth() {