fix(ops): skip WeCom alerts for mini-program audit navigate noise
Filter anonymous mini-app page-not-found client errors (often WeChat review bots) from webhook while still logging and persisting. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,6 +23,14 @@ export class ClientErrorService {
|
||||
const stack = dto.stack?.trim().slice(0, 4000);
|
||||
const pagePath = dto.pagePath?.trim().slice(0, 128);
|
||||
const fingerprint = `${dto.level}|${dto.category}|${message.slice(0, 120)}`;
|
||||
const anonymous = user?.actorId == null;
|
||||
const skipWecom = shouldSkipWecomClientErrorAlert({
|
||||
message,
|
||||
pagePath,
|
||||
clientApp,
|
||||
anonymous,
|
||||
category: dto.category,
|
||||
});
|
||||
|
||||
const logLine = {
|
||||
level: dto.level,
|
||||
@@ -34,6 +42,7 @@ export class ClientErrorService {
|
||||
actorId: user?.actorId != null ? String(user.actorId) : undefined,
|
||||
stack: stack?.slice(0, 800),
|
||||
extra: dto.extra,
|
||||
skipWecom,
|
||||
};
|
||||
|
||||
if (dto.level === 'fatal') {
|
||||
@@ -61,6 +70,7 @@ export class ClientErrorService {
|
||||
actorType: user?.actorType ?? null,
|
||||
actorId: user?.actorId != null ? String(user.actorId) : null,
|
||||
storeId: user?.storeId != null ? String(user.storeId) : null,
|
||||
skipWecom,
|
||||
...(dto.extra && typeof dto.extra === 'object' ? { clientExtra: dto.extra } : {}),
|
||||
} as Prisma.InputJsonValue,
|
||||
},
|
||||
@@ -71,7 +81,7 @@ export class ClientErrorService {
|
||||
);
|
||||
}
|
||||
|
||||
if (WECOM_LEVELS.has(dto.level)) {
|
||||
if (WECOM_LEVELS.has(dto.level) && !skipWecom) {
|
||||
const alertLevel: AlertLevel = dto.level === 'fatal' ? 'P0' : 'P1';
|
||||
this.alert.notify({
|
||||
level: alertLevel,
|
||||
@@ -91,12 +101,56 @@ export class ClientErrorService {
|
||||
dedupeKey: `client_error|${fingerprint}`,
|
||||
dedupeTtlSec: 600,
|
||||
});
|
||||
} else if (skipWecom && WECOM_LEVELS.has(dto.level)) {
|
||||
this.logger.log(
|
||||
`[client_error] skip WeCom alert (likely mini-program audit noise): ${message.slice(0, 160)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤企微推送:微信小程序审核机 / 自动化探测常见噪声。
|
||||
* 仍落库与写服务端日志,仅跳过 webhook。
|
||||
*
|
||||
* 典型特征(与本次协议页报错一致):
|
||||
* - 匿名 USER_MINI
|
||||
* - navigateTo/redirectTo 等 page … is not found(常带 .html,Taro H5 路径形态)
|
||||
*/
|
||||
export function shouldSkipWecomClientErrorAlert(input: {
|
||||
message: string;
|
||||
pagePath?: string | null;
|
||||
clientApp: string;
|
||||
anonymous: boolean;
|
||||
category?: string;
|
||||
}): boolean {
|
||||
const msg = input.message || '';
|
||||
const app = input.clientApp || '';
|
||||
const isMini = app === 'USER_MINI' || app === 'PARTNER_MINI' || app === 'HQ_MINI';
|
||||
|
||||
// 路由页不存在:审核机点协议/隐私链接触发最常见
|
||||
const isNavPageMissing =
|
||||
/(navigateTo|redirectTo|reLaunch|switchTab):fail/i.test(msg) &&
|
||||
/is not found/i.test(msg);
|
||||
|
||||
// Taro 把路径拼成 *.html 的形态,几乎不可能是真·原生页路径
|
||||
const isTaroHtmlPagePath =
|
||||
/\.html(\b|"|')/i.test(msg) && /is not found|page /i.test(msg);
|
||||
|
||||
if (isMini && input.anonymous && (isNavPageMissing || isTaroHtmlPagePath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 即使带登录态,纯 *.html not found 也视为框架/探测噪声
|
||||
if (isMini && isTaroHtmlPagePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isClientApp(v: string): v is ClientApp {
|
||||
return [
|
||||
'USER_MINI',
|
||||
|
||||
Reference in New Issue
Block a user