feat(subscribe): 新增拉取小程序真实模板列表诊断(定位40037)
- 新增 GET /api/admin/message/templates:调用 wxaapi/newtmpl/gettemplate 返回当前 appId(脱敏) 下真实模板列表,并标出后台已填但不在列表中的ID - 测试推送 diagnostics 增加 appIdMasked,便于核对 appId - 后台新增「拉取小程序模板列表(核对ID)」按钮与不匹配高亮提示 - 文档补充 40037 排查步骤;回归 86 套件/632 用例全绿
这个提交包含在:
@@ -3759,6 +3759,33 @@ app.delete('/api/admin/posts/comments/:commentId', async (req: Request, res: Res
|
||||
}
|
||||
});
|
||||
|
||||
// 微信 GET 接口(返回 JSON)
|
||||
function wechatHttpGetJson(url: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(url, (resp) => {
|
||||
let data = '';
|
||||
resp.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/** 脱敏展示 appId(用于自检,避免完整明文) */
|
||||
function maskTail(s: string): string {
|
||||
const v = String(s || '');
|
||||
return v ? `${v.slice(0, 6)}****${v.slice(-4)}` : '';
|
||||
}
|
||||
|
||||
// 微信订阅消息错误码中文化,便于后台定位真实原因
|
||||
function describeWechatSubscribeError(r: any): string {
|
||||
const code = Number(r && r.errcode) || 0;
|
||||
@@ -3810,6 +3837,7 @@ app.post('/api/admin/message/test-send', async (req: Request, res: Response) =>
|
||||
hasOpenid: Boolean(openid),
|
||||
hasAppId: Boolean(cfg.appId),
|
||||
hasAppSecret: Boolean(cfg.appSecret),
|
||||
appIdMasked: maskTail(cfg.appId),
|
||||
};
|
||||
|
||||
if (templateId && openid && cfg.appId && cfg.appSecret) {
|
||||
@@ -3885,6 +3913,48 @@ app.post('/api/admin/message/test-send', async (req: Request, res: Response) =>
|
||||
}
|
||||
});
|
||||
|
||||
// 管理端:拉取当前小程序已存在的「订阅消息」模板列表
|
||||
// 用于核对后台填写的模板ID是否属于当前 appId(40037 invalid template_id 的根因排查)
|
||||
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 未配置' });
|
||||
}
|
||||
const accessToken = await getWechatAccessToken(cfg.appId, cfg.appSecret);
|
||||
const data = await wechatHttpGetJson(
|
||||
`https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=${encodeURIComponent(accessToken)}`
|
||||
);
|
||||
const list = data && Array.isArray(data.data) ? data.data : [];
|
||||
const record = await repo.getPlatformConfigByKey('message_template_rules');
|
||||
const rules = (record && record.valueJson) || {};
|
||||
res.json({
|
||||
code: 20000,
|
||||
data: {
|
||||
appIdMasked: maskTail(cfg.appId),
|
||||
errcode: Number(data && data.errcode) || 0,
|
||||
errmsg: (data && data.errmsg) || 'ok',
|
||||
count: list.length,
|
||||
templates: list.map((t: any) => ({
|
||||
templateId: t.priTmplId,
|
||||
title: t.title,
|
||||
type: t.type,
|
||||
content: t.content,
|
||||
example: t.example,
|
||||
})),
|
||||
configured: {
|
||||
orderStatus: String(rules.orderStatusTemplateId || ''),
|
||||
activityJoin: String(rules.activityJoinTemplateId || ''),
|
||||
withdrawSuccess: String(rules.withdrawSuccessTemplateId || ''),
|
||||
verifyCode: String(rules.verifyCodeTemplateId || ''),
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
res.status(400).json({ code: 40000, message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/posts', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { content, userId } = req.body || {};
|
||||
|
||||
在新工单中引用
屏蔽一个用户