From abf1f5afbbf535e46cd3ff6b1d4cce6eb25cc57b Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Thu, 22 Oct 2026 10:05:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(subscribe):=20=E8=AF=86=E5=88=AB=E6=BC=94?= =?UTF-8?q?=E7=A4=BA=E8=B4=A6=E5=8F=B7=E5=81=87=20openid=EF=BC=8C=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E8=AE=A2=E9=98=85=E6=B6=88=E6=81=AF=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 isPlausibleWechatOpenid:wx_openid_ 前缀/长度异常的种子 openid 一律视为无效,notifyBySubscribe 与内容安全 openid 均跳过,避免 40003 无效 openid 噪音 - 后台模拟推送返回 openidValid 诊断,并对演示账号给出「请改用真实微信登录用户测试」的明确提示;表单默认 UID 清空 - 115 套件/751 用例全绿;实测 2010 已走到微信侧(43101 用户未授权,需小程序内点击允许) --- .../views/safety/platform-config/index.vue | 4 +-- server/src/index.ts | 30 ++++++++++++++----- server/tests/system-alerts-audit.spec.ts | 7 +++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue b/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue index 9e0f6a4..3cdcb85 100644 --- a/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue +++ b/admin/arco-design-pro-vite/src/views/safety/platform-config/index.vue @@ -724,7 +724,7 @@ - + @@ -1394,7 +1394,7 @@ const loadingTemplates = ref(false); const wechatTemplates = ref(null); const testMsgForm = reactive({ templateType: 'order_status', - targetUserId: 1001, + targetUserId: null, previewTitle: '订单履约开始提醒', previewDetail: '您的同城生活陪伴订单已开始服务,请出示4位核销码完成现场确认。' }); diff --git a/server/src/index.ts b/server/src/index.ts index e358254..4ef7d6c 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -250,12 +250,20 @@ async function assertUserNotBanned(userId: number, actionName: string = '操作' } } -// 读取用户微信 openid(用于微信官方内容安全 security.msgSecCheck 溯源),失败返回 undefined +// 微信 openid 形如 28 位字母数字(o 开头);演示种子数据里的 wx_openid_1001 不是真实 openid +function isPlausibleWechatOpenid(openid: any): boolean { + const value = String(openid || '').trim(); + if (!value) return false; + if (/^wx_openid_/i.test(value)) return false; + return /^[A-Za-z0-9_-]{20,64}$/.test(value); +} + +// 读取用户微信 openid(用于微信官方内容安全 security.msgSecCheck 溯源),失败或非真实 openid 返回 undefined async function resolveUserOpenid(userId: number): Promise { try { const [rows]: any = await pool.query('SELECT wechat_openid FROM users WHERE id = ? LIMIT 1', [Number(userId)]); const openid = rows && rows[0] && rows[0].wechat_openid; - return openid ? String(openid) : undefined; + return isPlausibleWechatOpenid(openid) ? String(openid) : undefined; } catch (_) { return undefined; } @@ -2789,7 +2797,10 @@ async function notifyBySubscribe( const [uRows]: any = await pool.query('SELECT wechat_openid FROM users WHERE id = ? LIMIT 1', [uid]); const openid = uRows && uRows[0] && uRows[0].wechat_openid; - if (!openid) return; + if (!isPlausibleWechatOpenid(openid)) { + logServerError('notifyBySubscribe', new Error(`订阅消息[${templateKey}]跳过:用户 ${uid} 没有有效的微信 openid`)); + return; + } const accessToken = await getWechatAccessToken(appId, appSecret); const result = await sendSubscribeMessageHttp(accessToken, { @@ -4444,12 +4455,15 @@ app.post('/api/admin/message/test-send', async (req: Request, res: Response) => const cfg = await resolveWechatPayRuntimeConfig(); const [uRows]: any = await pool.query('SELECT wechat_openid FROM users WHERE id = ? LIMIT 1', [Number(targetUserId) || 0]); - const openid = uRows && uRows[0] && uRows[0].wechat_openid; + const openidRaw = uRows && uRows[0] && uRows[0].wechat_openid; + const openidValid = isPlausibleWechatOpenid(openidRaw); + const openid = openidValid ? String(openidRaw) : ''; const diagnostics = { enablePush, hasTemplateId: Boolean(templateId), - hasOpenid: Boolean(openid), + hasOpenid: Boolean(openidRaw), + openidValid, hasAppId: Boolean(cfg.appId), hasAppSecret: Boolean(cfg.appSecret), appIdMasked: maskTail(cfg.appId), @@ -4517,9 +4531,11 @@ app.post('/api/admin/message/test-send', async (req: Request, res: Response) => ? '订阅消息总开关未开启' : (!templateId ? `未配置「${typeLabels[tKey] || tKey}」模板ID${reasonDetail}${tKey === 'verifyCode' ? '(该类型为可选:核销码已随「订单状态提醒」的温馨提示下发)' : ''}` - : (!openid + : (!openidRaw ? `目标用户(ID=${targetUserId || 0})没有 wechat_openid(该用户需通过微信登录过小程序)` - : '小程序 appId/appSecret 未配置')); + : (!openidValid + ? `目标用户(ID=${targetUserId || 0})的 openid 不是有效微信 openid(演示种子账号),请改用真实微信登录用户测试` + : '小程序 appId/appSecret 未配置'))); const result = { templateType: tKey, targetUserId: targetUserId || 1001, diff --git a/server/tests/system-alerts-audit.spec.ts b/server/tests/system-alerts-audit.spec.ts index 1ddf275..c109df1 100644 --- a/server/tests/system-alerts-audit.spec.ts +++ b/server/tests/system-alerts-audit.spec.ts @@ -53,4 +53,11 @@ describe('第一百批系统告警与后台审计', () => { expect(backupScript).toContain('gzip -t'); expect(backupScript).toContain("-mtime +14 -delete"); }); + + it('订阅消息必须识别演示账号的假 openid,避免 40003', () => { + expect(indexCode).toContain('function isPlausibleWechatOpenid'); + expect(indexCode).toContain("if (/^wx_openid_/i.test(value)) return false;"); + expect(indexCode).toContain('openidValid'); + expect(indexCode).toContain('请改用真实微信登录用户测试'); + }); });