From b77827522f338419e3333501b2c3fa5f69585e81 Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Mon, 19 Oct 2026 11:00:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(config):=20=E5=8D=A0=E4=BD=8D=E5=87=AD?= =?UTF-8?q?=E8=AF=81=E6=B8=85=E7=90=86=E6=94=B9=E4=B8=BA=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=E6=80=A7(=E6=A0=87=E8=AE=B0=E4=BD=8D=E4=BF=9D=E6=8A=A4)?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E8=AF=AF=E6=B8=85=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E5=A1=AB=E5=86=99=E7=9A=84=E7=9C=9F=E5=AE=9E=20AppID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 历史占位 appId/appSecret 的清理由"每次启动执行"改为"仅首启执行一次",写入 wechat_pay_placeholder_cleaned 标记 - 此后即使管理员填写的 AppID 恰好等于旧占位值(本账真实 AppID),重启也不会被再次清空 - 「拉取小程序模板列表」缺凭证时错误提示改为可操作指引(指明填写位置) - 回归 87 套件/636 用例全绿;冒烟 5/5 验证 Phase A 首启清理、Phase B 重启保留 --- server/src/db/mysql.ts | 33 +++++++++++-------- server/src/index.ts | 5 ++- .../wechat-appid-config-hardening.spec.ts | 3 +- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/server/src/db/mysql.ts b/server/src/db/mysql.ts index 72a7c87..462e842 100644 --- a/server/src/db/mysql.ts +++ b/server/src/db/mysql.ts @@ -1196,19 +1196,26 @@ async function seedInitialDataIfEmpty(conn: any): Promise { try { curVal = JSON.parse(curVal); } catch (_) {} } if (curVal && typeof curVal === 'object') { - let updated = false; - // 历史版本曾在源码/DB 中内置占位 appId/appSecret,这里一次性清空, - // 避免后端误用非本小程序的凭证(如订阅消息 40037、支付参数错乱)。 - if (curVal.appId === 'wx0c929f7e54024e67' || curVal.appId === 'wx_peiwan_miniapp') { - curVal.appId = ''; - updated = true; - } - if (curVal.appSecret === '63a412f3f73af8e73f6923943bf9683c') { - curVal.appSecret = ''; - updated = true; - } - if (updated) { - await conn.query("UPDATE platform_configs SET value_json = ? WHERE config_key = 'wechat_pay_config'", [JSON.stringify(curVal)]); + // 一次性清理:历史版本曾在源码/DB 内置占位 appId/appSecret。 + // 用标记位保证【仅执行一次】,此后绝不再改动管理员填写的值 + // (即使其恰好等于占位值,例如本账真实 AppID 就是该值,也不会被误清)。 + if (!existingKeys.has('wechat_pay_placeholder_cleaned')) { + let updated = false; + if (curVal.appId === 'wx0c929f7e54024e67' || curVal.appId === 'wx_peiwan_miniapp') { + curVal.appId = ''; + updated = true; + } + if (curVal.appSecret === '63a412f3f73af8e73f6923943bf9683c') { + curVal.appSecret = ''; + updated = true; + } + if (updated) { + await conn.query("UPDATE platform_configs SET value_json = ? WHERE config_key = 'wechat_pay_config'", [JSON.stringify(curVal)]); + } + await conn.query(` + INSERT INTO platform_configs (config_key, name, value_json, description, status) + VALUES ('wechat_pay_placeholder_cleaned', '小程序凭证占位清理标记', '{"done":true}', '内部标记:仅用于一次性清理历史占位凭证,勿手动修改', 1) + `); } } } diff --git a/server/src/index.ts b/server/src/index.ts index fe790a7..6662500 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -3920,7 +3920,10 @@ app.get('/api/admin/message/templates', async (_req: Request, res: Response) => try { const cfg = await resolveWechatPayRuntimeConfig(); if (!cfg.appId || !cfg.appSecret) { - return res.json({ code: 40000, message: '小程序 appId/appSecret 未配置' }); + return res.json({ + code: 40000, + message: '小程序 AppID/AppSecret 未配置:请在后台「平台配置 → 微信支付商户配置」填写本小程序的 AppID 与 32 位 AppSecret(或设置环境变量 WX_APP_ID/WX_APP_SECRET),保存后重试', + }); } const accessToken = await getWechatAccessToken(cfg.appId, cfg.appSecret); const data = await wechatHttpGetJson( diff --git a/server/tests/wechat-appid-config-hardening.spec.ts b/server/tests/wechat-appid-config-hardening.spec.ts index d523fe8..f94739f 100644 --- a/server/tests/wechat-appid-config-hardening.spec.ts +++ b/server/tests/wechat-appid-config-hardening.spec.ts @@ -29,10 +29,11 @@ describe('第七十一批小程序凭证去硬编码闸门', () => { it('建表种子为空值,并清理历史遗留的占位凭证', () => { expect(mysqlCode).toContain('appId: \'\',\n appSecret: \'\','); - // 自愈逻辑由"写入占位值"改为"清空占位值" + // 自愈逻辑由"写入占位值"改为"清空占位值",且用标记位保证只执行一次(不误清管理员后续填写的值) expect(mysqlCode).toContain("if (curVal.appId === 'wx0c929f7e54024e67'"); expect(mysqlCode).toContain("if (curVal.appSecret === '63a412f3f73af8e73f6923943bf9683c')"); expect(mysqlCode).toContain('curVal.appId = \'\''); + expect(mysqlCode).toContain('wechat_pay_placeholder_cleaned'); }); it('后台文案不再暴露具体 AppID', () => {