From 67eed112766011160c0bcf280bcee0ff997eb978 Mon Sep 17 00:00:00 2001 From: jacy-dukang Date: Tue, 7 Jul 2026 17:55:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=95=86=E9=93=BA=E7=AB=AF=E6=A0=B8=E9=94=80?= =?UTF-8?q?=EF=BC=8C=E8=B0=83=E7=94=A8=E7=9B=B8=E6=9C=BA=E6=8A=A5=E6=97=A0?= =?UTF-8?q?=E6=95=88=E7=AD=BE=E5=90=8D=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/h5-shop/src/pages/HomePage.tsx | 14 ++++++++-- packages/weixin-sdk/src/index.ts | 2 +- packages/weixin-sdk/src/jssdk.ts | 27 ++++++++++++++++++- .../src/modules/common/wechat.controller.ts | 16 ++++++++++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/apps/h5-shop/src/pages/HomePage.tsx b/apps/h5-shop/src/pages/HomePage.tsx index cc17170..b0321f1 100644 --- a/apps/h5-shop/src/pages/HomePage.tsx +++ b/apps/h5-shop/src/pages/HomePage.tsx @@ -12,6 +12,7 @@ import { sessionFromWechatLogin, } from '../lib/wechat-auth'; import { isWechatEnv, weixinSdk } from '../lib/weixin'; +import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk'; import WechatScanAuthModal from '../components/WechatScanAuthModal'; const PENDING_SCAN_KEY = 'shop_pending_scan'; @@ -20,6 +21,14 @@ function formatMoney(n: number) { return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 }); } +function formatScanError(e: unknown): string { + const msg = e instanceof Error ? e.message : '扫码失败,请重试'; + if (/invalid signature/i.test(msg)) { + return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名为 shop.runxian.top,并刷新页面后重试'; + } + return msg; +} + export default function HomePage() { const navigate = useNavigate(); const { applySession } = useStoreSession(); @@ -51,11 +60,12 @@ export default function HomePage() { } setAuthModalOpen(false); setAuthError(''); + stripOAuthParamsFromLocation(); setSearchParams({}, { replace: true }); const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1'; sessionStorage.removeItem(PENDING_SCAN_KEY); if (shouldScan) { - void runScan(); + window.setTimeout(() => void runScan(), 0); } }) .catch((e) => { @@ -81,7 +91,7 @@ export default function HomePage() { } navigate(`/redeem?token=${encodeURIComponent(token)}`); } catch (e) { - setScanMsg(e instanceof Error ? e.message : '扫码失败,请重试'); + setScanMsg(formatScanError(e)); } finally { setScanning(false); } diff --git a/packages/weixin-sdk/src/index.ts b/packages/weixin-sdk/src/index.ts index 3cec4f0..d94f1be 100644 --- a/packages/weixin-sdk/src/index.ts +++ b/packages/weixin-sdk/src/index.ts @@ -1,5 +1,5 @@ export { isWechatBrowser, isMiniProgram, getRuntimePlatform } from './env'; -export { initWechatJssdk, ensureJssdkReady, isJssdkReady } from './jssdk'; +export { initWechatJssdk, ensureJssdkReady, isJssdkReady, normalizeJssdkPageUrl, stripOAuthParamsFromLocation } from './jssdk'; export { getWechatLocation, getWechatLocationDetailed, diff --git a/packages/weixin-sdk/src/jssdk.ts b/packages/weixin-sdk/src/jssdk.ts index 7b56c36..ec3dd42 100644 --- a/packages/weixin-sdk/src/jssdk.ts +++ b/packages/weixin-sdk/src/jssdk.ts @@ -8,7 +8,32 @@ let configured = false; let configuredUrl: string | null = null; function currentPageUrl() { - return typeof window !== 'undefined' ? window.location.href.split('#')[0] : ''; + return typeof window !== 'undefined' ? normalizeJssdkPageUrl(window.location.href) : ''; +} + +/** 参与 JSSDK 签名的页面 URL(去掉 hash、OAuth 回跳参数) */ +export function normalizeJssdkPageUrl(rawUrl: string): string { + try { + const url = new URL(rawUrl); + url.hash = ''; + url.searchParams.delete('code'); + url.searchParams.delete('state'); + const query = url.searchParams.toString(); + return `${url.origin}${url.pathname}${query ? `?${query}` : ''}`; + } catch { + return rawUrl.split('#')[0]; + } +} + +export function stripOAuthParamsFromLocation(): void { + if (typeof window === 'undefined') return; + const url = new URL(window.location.href); + if (!url.searchParams.has('code') && !url.searchParams.has('state')) return; + url.searchParams.delete('code'); + url.searchParams.delete('state'); + const query = url.searchParams.toString(); + const next = `${url.pathname}${query ? `?${query}` : ''}${url.hash}`; + window.history.replaceState({}, '', next); } function loadScript(): Promise { diff --git a/server/dukang-api/src/modules/common/wechat.controller.ts b/server/dukang-api/src/modules/common/wechat.controller.ts index 9eeaa53..6589430 100644 --- a/server/dukang-api/src/modules/common/wechat.controller.ts +++ b/server/dukang-api/src/modules/common/wechat.controller.ts @@ -75,13 +75,27 @@ export class WechatController { @Get('jssdk-config') async jssdkConfig(@Query('url') url: string, @Req() req: Request) { if (!url) throw new BadRequestException('url 参数必填'); - const pageUrl = decodeURIComponent(url).split('#')[0]; + const decoded = decodeURIComponent(url).split('#')[0]; + const pageUrl = this.normalizeJssdkUrl(decoded); const user = (req as Request & { user?: AuthUser }).user; const actorRef = user?.actorType === 'USER' ? { refType: 'USER', refId: user.actorId } : undefined; return this.wechat.createJssdkConfig(pageUrl, actorRef); } + private normalizeJssdkUrl(rawUrl: string): string { + try { + const parsed = new URL(rawUrl); + parsed.hash = ''; + parsed.searchParams.delete('code'); + parsed.searchParams.delete('state'); + const query = parsed.searchParams.toString(); + return `${parsed.origin}${parsed.pathname}${query ? `?${query}` : ''}`; + } catch { + return rawUrl; + } + } + @Get('oauth-url') oauthUrl( @Query('redirectUri') redirectUri: string,