fix(notify): 平台回 204 被误判为失败 + 诊断工具改名 scrip_

三个改动:

1) sendNotify.js 的 webhook 判据从 statusCode !== 200 改成 2xx 一律成功。
   平台 /api/hooks/ql 按设计回 204(表示已接收且不写 event_logs),
   原判据把 204 当失败 → 每轮日志都打「自定义发送通知消息失败!!」,
   而投递其实成功了。这是纯假警报,也是这次误判的根因。
   同时把状态码打进日志,真失败时能直接看出是 401/403/400/429。

2) _diag_notify.js → scrip_diag_notify.js。
   订阅白名单是 scrip_,不带前缀的文件拉不下来,工具等于不存在。
   加 // cron: 0 0 1 1 * 与 // name:,避免青龙自动建任务时落随机 cron。
   正文补到六段:sendNotify 加载 / env / webhook 三前置条件与 body+headers
   解析复刻 / 直打平台按状态码定位 / pushplus txt-vs-html 模板对比 /
   完整 sendNotify 对照。

3) 文档补齐平台侧的两道硬校验:来源 IP 必须 127.0.0.1 或 172.16/12
   (走公网域名会变成反代地址 → 403),回环带 XFF 一律 403。
   给出指向平台的完整 env 写法(WEBHOOK_BODY 必须多行,单行 JSON
   会被 parseString 整段吞掉 —— 已实测解析结果为 0 个 key)。

验证:node --check(sendNotify + 诊断)/ 5 个 mock 状态码用例
(204/401/403/400/429)全部被正确识别 / _probe_meta 四文件 PASS
This commit is contained in:
2026-11-05 09:34:06 +08:00
parent 483bc29424
commit 3a711d0a47
5 changed files with 303 additions and 146 deletions

View File

@@ -181,30 +181,57 @@ cron: 1 0,6 * * *
## 通知发不出去怎么查
`node _diag_notify.js`(放在仓库根,跟 `sendNotify.js` 同级)一次跑完查五件事:
```bash
# 青龙里手动跑一次(cron 设成 1 月 1 日,不会自动跑)
node scrip_diag_notify.js
```
六段逐项定位,token 全程脱敏:
```
1. sendNotify 能不能加载 ← 最常见:容器里缺 got,require 抛错被静默吞掉
2. 通知 env 有没有读到 ← 青龙「环境变量」是否真的保存了
3. webhook 前置条件 + body 解析 ← 见下方两个坑
4. 直连 pushplus 发 txt/html 两条 ← 对比哪个模板正文完整
5. 走一遍 sendNotify ← 与 4 对比,定位是通道问题还是展示问题
1. sendNotify 加载 ← 容器缺 got 时 require 抛错,被静默吞掉
2. 通知 env ← 青龙「环境变量」是否真的保存了
3. webhook 配置分析 ← 三个前置条件 + body/headers 解析复刻
4. 直打平台 /api/hooks/ql ← 按状态码定位卡在哪一道校验
5. 直连 pushplus 模板对比 ← txt vs html,确认是不是展示折叠
6. 完整 sendNotify ← 与第 4 节对照,区分通道问题与展示问题
```
### 坑 0:平台回 204,原版 sendNotify 判成失败(已修)
`/api/hooks/ql` 按设计返回 **204**(表示已接收,且不写 event_logs),
而原版 `sendNotify.js` 的判据是 `resp.statusCode !== 200`:
```js
if (resp.statusCode !== 200) {
console.log(`自定义发送通知消息失败!!\n${resp.body}`); // 204 也走这里
}
```
**所以「自定义发送通知消息失败!!」是假警报 —— 平台其实收到了。**
已改成 2xx 一律算成功,并把状态码打进日志:
```js
if (resp.statusCode < 200 || resp.statusCode >= 300) {
console.log(`自定义发送通知消息失败!!HTTP ${resp.statusCode}\n${resp.body}`);
} else {
console.log(`自定义发送通知消息成功🎉。HTTP ${resp.statusCode}\n${resp.body || ''}`);
}
```
### 坑 1:`require('../sendNotify')` 静默失败
脚本里原本是 `catch { /* 仅控制台 */ }`,缺 `got` 时**一条通知都发不出去且日志无痕**。
现在会打一行:
原本 `catch { /* 仅控制台 */ }`,缺 `got` 时**一条通知都发不出去且日志无痕**。现在会打一行:
```
[通知] sendNotify 不可用(Cannot find module 'got'),本轮不发推送
```
修:`docker exec -it qinglong pnpm install got`(sendNotify 只依赖 got + 内置模块)。
修:`docker exec -it qinglong pnpm install got`
### 坑 2:WEBHOOK_BODY 写成单行 JSON 会被解析成空
`sendNotify` 的 `parseString` 用 `/(\w+):\s*((?:(?!\n\w+:).)*)/g` 解析,**靠换行 + `\w+:` 断值**。
`sendNotify` 的 `parseString` 用 `/(\w+):\s*((?:(?!\n\w+:).)*)/g`,**靠换行 + `\w+:` 断值**。
单行 JSON 没有换行,整个 body 被吞掉,实测解析结果 `{}`(0 个 key):
```js
@@ -212,19 +239,41 @@ cron: 1 0,6 * * *
'title:$title\ncontent:$content' → 两个 key ✓
```
body 畸形 → 对方返回非 200 → 日志出现 `自定义发送通知消息失败!!`。
body 变成 `{}` → 平台 `JSON.parse` 得到空对象 → 400「title 与 content 不能同时为空」。
**三个前置条件缺一不可**(`webhookNotify` 逐行 return):
### 坑 3:webhook 三个前置条件,缺一个就静默跳过
`webhookNotify` 开头逐行 return,**不发请求也不报错**:
1. `WEBHOOK_URL` 或 `WEBHOOK_BODY` **至少一个**含 `$title`
2. `WEBHOOK_METHOD` **必须显式设**(空则走 else 分支 resolve,不发请求也不报错)
3. body 能被 `parseString` 正确解析(多行,或干脆用 URL 传参)
2. `WEBHOOK_METHOD` **必须显式设**(空则走 else 分支 resolve)
3. body 能被 `parseString` 正确解析(见坑 2)
### 坑 3:pushplus 的 html 模板可能只显示标题
### 坑 4:pushplus 只显示一行 ≠ 内容丢了
`sendNotify` 的 pushplus 通道固定发 `template` 缺省值(html),并把 `\n` 换成 `<br>`。
微信侧只显示一行是**展示折叠**,点开消息卡片才看全文。
要确认是折叠还是真丢内容:`_diag_notify.js` 第 4 节会同时发 txt 和 html 两条,对比即可。
微信通知卡片默认只展示开头,点开才展开全文。要确认是折叠还是真丢:
第 5 节会同时发 `txt` 和 `html` 两条,对比哪个正文完整。
- txt 完整 / html 一行 → 把 pushplus 目标改成 `template=txt`
- 两条都一行 → 微信卡片折叠,点开即全文,不是 bug
### 指向平台的完整配置
```bash
WEBHOOK_URL=https://<平台域名>/api/hooks/ql
WEBHOOK_METHOD=POST
WEBHOOK_CONTENT_TYPE=application/json
WEBHOOK_BODY=title:$title
content:$content
WEBHOOK_HEADERS=Content-Type: application/json
x-sh-hook-token: <平台 QL_HOOK_TOKEN>
```
平台侧还有两道硬校验(`hookGuard`):
- **来源 IP 必须是 127.0.0.1 或 172.16/12**(docker 内网)。青龙与平台不同容器时,
走公网域名会让源 IP 变成反代地址 → 403。要用**容器内网地址**(如 `http://script-helper:3000`)。
- 回环地址带 `X-Forwarded-For` 一律 403(说明穿过了反代)。
## 任务覆盖