fix(subscribe): 识别演示账号假 openid,完善订阅消息测试诊断
- 新增 isPlausibleWechatOpenid:wx_openid_ 前缀/长度异常的种子 openid 一律视为无效,notifyBySubscribe 与内容安全 openid 均跳过,避免 40003 无效 openid 噪音 - 后台模拟推送返回 openidValid 诊断,并对演示账号给出「请改用真实微信登录用户测试」的明确提示;表单默认 UID 清空 - 115 套件/751 用例全绿;实测 2010 已走到微信侧(43101 用户未授权,需小程序内点击允许)
这个提交包含在:
@@ -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<string | undefined> {
|
||||
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,
|
||||
|
||||
@@ -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('请改用真实微信登录用户测试');
|
||||
});
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户