fix(notice): 修复通知历史状态回填与配置占位符问题
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 54s
Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 54s
- 修正 status 列默认值为 0,避免历史失败记录被误标为成功
- 历史数据回填改为幂等更新,可重复执行
- 过滤未解析的 ${...} 配置占位符,优化 Bark 推送错误提示
- LogDetail 记录不存在时统一返回参数错误,避免暴露 SQL 细节
This commit is contained in:
parent
863b16b118
commit
e79690518e
@ -1,6 +1,8 @@
|
|||||||
# service.xpcool.com 变更记录
|
# service.xpcool.com 变更记录
|
||||||
> 倒序:最新在上。格式:YYYY-MM-DD | 类型 | 摘要
|
> 倒序:最新在上。格式:YYYY-MM-DD | 类型 | 摘要
|
||||||
|
|
||||||
|
2026-09-14 | CFG | **安装 chrome-devtools MCP(用户级配置)**:全局 `npm i -g chrome-devtools-mcp@1.9.0`(本机 Node v23.0.0 满足其 `engines: ^20.19.0 || ^22.12.0 || >=23`),在 `C:\Users\xxl\.codebuddy\mcp.json` 的 `mcpServers` 下新增 `chrome-devtools` 条目。**刻意沿用现有 `dbx` 条目的绝对路径写法**(`command` 指向 `...\nvm\v23.0.0\node.exe`,`args[0]` 指向 `...\node_modules\chrome-devtools-mcp\build\src\bin\chrome-devtools-mcp.js`),而非 `npx chrome-devtools-mcp@latest`——Windows 上 `npx` 实为 `npx.cmd`,由 IDE 直接 spawn 时存在解析风险,绝对路径最稳。附 `--no-usage-statistics` 关闭 Google 使用统计。Chrome 已就位(`C:\Program Files\Google\Chrome\Application\chrome.exe`,MCP 默认自动发现,未额外传 `--executablePath`)。冒烟测试:`initialize` 请求下进程正常启动、stderr 打印自身横幅、退出码 0。**需重启 IDE(或重载 MCP 面板)方生效**;默认行为是新开一个独立 profile 的 Chrome 实例,若要接管已开的浏览器需改用 `--browser-url http://127.0.0.1:9222`
|
||||||
|
|
||||||
2026-09-14 | CHG | **壁纸模块后端上线(12 接口 + 开源平台聚合 + 自建图库)**。为 xpcool.com 提供「开源壁纸平台 + 后台自建图库」双来源,**保留**原有浏览器实时生成能力。设计核心是**带宽经济学**(本机出口仅 5M≈625KB/s 且多站共用):① 开源平台(Bing 每日一图 / Picsum / Unsplash / Pexels / Wallhaven)图片**不经过服务器**,后端只代理元数据并返回官方 CDN 直链,`sources`/`list`/`random`/`download-track` 挂 `open` 分组,平台响应内存缓存(Bing 1h、其余 10min);② 自建图库分三档落盘(480px 缩略图 / 长边 1920 预览 / 原图),**由 nginx 直出 `/wallpaper/`**,后端只管元数据与上传。接口遵守本仓库强制规范:**全 POST、URL 不带参、入参走 body**。`manifest/sql/018_wallpaper.sql` 为幂等种子(菜单 + 5 平台,`ON DUPLICATE KEY UPDATE` 只更新 name/sort/remark)
|
2026-09-14 | CHG | **壁纸模块后端上线(12 接口 + 开源平台聚合 + 自建图库)**。为 xpcool.com 提供「开源壁纸平台 + 后台自建图库」双来源,**保留**原有浏览器实时生成能力。设计核心是**带宽经济学**(本机出口仅 5M≈625KB/s 且多站共用):① 开源平台(Bing 每日一图 / Picsum / Unsplash / Pexels / Wallhaven)图片**不经过服务器**,后端只代理元数据并返回官方 CDN 直链,`sources`/`list`/`random`/`download-track` 挂 `open` 分组,平台响应内存缓存(Bing 1h、其余 10min);② 自建图库分三档落盘(480px 缩略图 / 长边 1920 预览 / 原图),**由 nginx 直出 `/wallpaper/`**,后端只管元数据与上传。接口遵守本仓库强制规范:**全 POST、URL 不带参、入参走 body**。`manifest/sql/018_wallpaper.sql` 为幂等种子(菜单 + 5 平台,`ON DUPLICATE KEY UPDATE` 只更新 name/sort/remark)
|
||||||
|
|
||||||
2026-09-14 | FIX | **全量加密(fullBody)下 multipart 上传必被拒**(生产才暴露、dev 永不复现):`encrypt.fullBody=true` 时中间件对所有非豁免请求一律 `json.Unmarshal` body,而 multipart 是二进制流必然失败 → 上传被判定为「未加密请求」拒绝(前端对 `FormData` 本就跳过加密,**两端语义不一致**)。修法:`internal/middleware/encrypt.go` 新增 `isMultipartRequest` 豁免,与前端 `request.ts` 的 `config.data instanceof FormData` 分支对齐;**不影响安全** —— 上传接口同样挂在 `/api/service/admin` 分组下,仍受 JWT + RBAC(POST+path 精确匹配)双重保护
|
2026-09-14 | FIX | **全量加密(fullBody)下 multipart 上传必被拒**(生产才暴露、dev 永不复现):`encrypt.fullBody=true` 时中间件对所有非豁免请求一律 `json.Unmarshal` body,而 multipart 是二进制流必然失败 → 上传被判定为「未加密请求」拒绝(前端对 `FormData` 本就跳过加密,**两端语义不一致**)。修法:`internal/middleware/encrypt.go` 新增 `isMultipartRequest` 豁免,与前端 `request.ts` 的 `config.data instanceof FormData` 分支对齐;**不影响安全** —— 上传接口同样挂在 `/api/service/admin` 分组下,仍受 JWT + RBAC(POST+path 精确匹配)双重保护
|
||||||
|
|||||||
95
docs/change-log/2026-09-14.md
Normal file
95
docs/change-log/2026-09-14.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# 变更日志 — 2026-09-14
|
||||||
|
|
||||||
|
## 请求
|
||||||
|
|
||||||
|
通知模块新增「通知历史记录」表格界面:有分页、有查询,记录每次通知的渠道、类型、分组、详细内容等。
|
||||||
|
前端项目 `E:\Project\admin.xpcool.com` 一起实现并联调。
|
||||||
|
|
||||||
|
## 变更
|
||||||
|
|
||||||
|
### 后端 —— 通知历史记录能力扩展
|
||||||
|
|
||||||
|
- `api/notice/notice.go` — 通知历史记录契约扩展:
|
||||||
|
- `NoticeLogItem` 由 11 字段扩到 27 字段(新增 `batchId`/`ruleName`/`eventName`/`noticeType`/`typeName`/
|
||||||
|
`group`/`groupName`/`channelName`/`userName`/`status`/`statusName`/`retryCount`/`durationMs`/`source`/`remark`)。
|
||||||
|
- `NoticeLogListReq` 新增 `keyword`/`noticeType`/`group`/`userId`/`batchId`/`status`/`orderBy`/`orderDir`;
|
||||||
|
`NoticeLogListRes` 新增 `stats` 统计概览。
|
||||||
|
- 新增端点:`POST /notice/log/detail`(详情)、`/notice/log/delete`(批量删除)、
|
||||||
|
`/notice/log/clear`(按保留天数/时间范围清空)、`/notice/log/options`(字典选项)。
|
||||||
|
- `internal/model/dto/notice_meta.go`(**新增**)— 通知字典:渠道(bark/pushplus/webhook/email/internal)、
|
||||||
|
事件(7 种)、类型(job/security/recruit/test/manual/system)、分组(auto_job/server/recruitment/system)
|
||||||
|
的编码常量 + 中文名映射 + 选项下发 + 事件/类型/分组三级推导函数。
|
||||||
|
- `internal/model/dto/notice.go` — `NoticeLogVO`/`NoticeLogFilter` 同步扩展,并新增 `Normalize()`
|
||||||
|
归一化分页/排序(`Status` 与兼容字段 `Result` 的优先级合并)。
|
||||||
|
- `internal/model/entity/notice.go`、`internal/model/do/notice.go` — `NoticeLog` 新增
|
||||||
|
`batch_id`/`status`/`retry_count`/`duration_ms`/`source`/`remark` 六个字段。
|
||||||
|
- `internal/service/notice/notice.go` —
|
||||||
|
- `LogList`:重写为多维筛选(关键字/类型/分组/事件/渠道/接收人/批次/状态/时间范围/排序)+ 统计概览,
|
||||||
|
列表与统计共用同一套 `logQuery` 条件保证口径一致;批量补齐规则名与接收人(避免 N+1)。
|
||||||
|
- 新增 `LogDetail`/`LogDelete`/`LogClear`。
|
||||||
|
- `Send` 引入 `batchId`(同一次业务触发的多条投递共用);`deliver`/`Test` 记录耗时、来源、状态、备注。
|
||||||
|
- 写库统一改用 `do.NoticeLog` / `do.NoticeRule` / `do.NoticeChannel`(原先用 `map[string]interface{}`,
|
||||||
|
不符合 AGENTS.md「数据库操作必须用 DO 对象」)。
|
||||||
|
- `internal/controller/notice/notice.go` — 新增 `LogDetail`/`LogDelete`/`LogClear`/`MetaOptions`,
|
||||||
|
并抽出 `toLogItem`/`toMetaItems` 做 dto → 契约转换。
|
||||||
|
- `manifest/sql/017_notice_log_enhance.sql`(**新增**)— `notice_log` 加 6 列 + 3 个索引;
|
||||||
|
按旧字段 `result` 回填 `status`;菜单 982 改名「通知历史」;新增 5 条 type=2 接口权限
|
||||||
|
(`notice:log:list/detail/delete/clear/options`);超管角色绑定。
|
||||||
|
|
||||||
|
### 前端 —— `E:\Project\admin.xpcool.com`
|
||||||
|
|
||||||
|
- `apps/web-tdesign/src/api/notice.ts` — 通知历史记录 API 客户端:类型与查询参数扩展,
|
||||||
|
新增 `getNoticeLogDetail` / `deleteNoticeLog` / `clearNoticeLog` / `getNoticeMetaOptions`,
|
||||||
|
以及 `NOTICE_STATUS_OPTIONS` / `noticeStatusTheme` 等展示辅助。
|
||||||
|
- `apps/web-tdesign/src/views/system/notice/log/index.vue` — **重写**为完整「通知历史」页面:
|
||||||
|
- 顶部 4 张统计卡(总数/成功/失败/成功率),随筛选条件同步刷新;
|
||||||
|
- 筛选区 9 个条件(时间范围、关键字、分组、类型、事件、渠道、接收人、状态、排序);
|
||||||
|
- vxe 表格:多选列 + 通知时间/分组/类型/事件/渠道/接收人/标题/详细内容/结果/耗时/错误 + 行操作;
|
||||||
|
- 详情抽屉(基本信息 + 标题/内容/目标/错误分区展示,均支持一键复制);
|
||||||
|
- 批量删除、清空历史(可选「保留最近 N 天」);按权限码控制按钮显隐。
|
||||||
|
|
||||||
|
### 联调
|
||||||
|
|
||||||
|
- 本地库执行 `017_notice_log_enhance.sql`:`notice_log` 现 17 列、3 个新索引;
|
||||||
|
菜单 `982 / 9821-9825` 就位;14 条历史失败记录 `status` 正确回填为 2。
|
||||||
|
- 后端 5 个新接口 + 原有 list 全部通过 RBAC 权限映射,返回 `code=0`。
|
||||||
|
- 真实投递一条测试通知,验证 `batchId` / `durationMs` / `source` / `userName` / `target` / `remark`
|
||||||
|
六项新字段正确落库与回显;统计概览 `1/0/1` 与筛选条件联动正确。
|
||||||
|
- 经前端 Vite 代理(20100 → 10100)完整跑通 登录 → 字典选项 → 分页列表 → 详情。
|
||||||
|
- 前端 `vue-tsc` 类型检查、`oxfmt` 格式检查、`oxlint` 全部 0 错误。
|
||||||
|
- **联调中修复 1 个缺陷**:`LogDetail` 对不存在的 ID 会把 `sql.ErrNoRows` 包装上抛,
|
||||||
|
被 gf 归为内部错误 `code=50` 并回显 SQL 细节;改为与项目既有写法一致
|
||||||
|
(忽略 Scan 错误、以 `Id==0` 判定),现返回 `code=10001`「通知记录不存在」。
|
||||||
|
|
||||||
|
## 决策与理由
|
||||||
|
|
||||||
|
- **类型/分组不落库,由 `event_type` 派生**:二者与事件是一对多的确定映射,落库会引入双写不一致;
|
||||||
|
改为只存事件编码,展示与筛选在服务层推导(`NoticeEventToType` / `NoticeTypeToGroup`)。
|
||||||
|
- **`status` 与旧的 `result` 并存**:`result` 是已有数据在用的字段(1成功/0失败),直接改语义会破坏历史数据。
|
||||||
|
新增 `status`(0待发送/1成功/2失败)表达三态,查询时用 `(status=1 OR (status=0 AND result=1))` 兼容历史行。
|
||||||
|
- **字典选项由后端 `/notice/log/options` 统一下发**:前端原先把事件类型硬编码在页面里
|
||||||
|
(`NOTICE_EVENT_OPTIONS` 只有 4 项,且与 `notice_rule.event_type` 种子语义有偏差),
|
||||||
|
新增事件就要改两处;改为后端单一事实源,前端只做渲染。
|
||||||
|
- **清空提供「保留最近 N 天」而非纯全清**:历史记录是排查问题的依据,全清不可逆;
|
||||||
|
默认保留 30 天,`keepDays=0` 才真正全清,属于对误操作的兜底。
|
||||||
|
- **列表与统计共用 `logQuery`**:避免"列表按分组筛选、统计按全量"这类口径不一致,
|
||||||
|
前端统计卡与表格永远对同一批数据。
|
||||||
|
- **迁移脚本中 `status` 默认值取 0 而非 1**:`ALTER TABLE ADD COLUMN ... DEFAULT 1` 会把**所有历史行**
|
||||||
|
一并置为 1(含失败记录),造成"历史失败记录显示成功"。改为默认 0 后按 `result` 回填,语义正确且可重跑。
|
||||||
|
- **提示文案与枚举中文名收口到 `dto` 层**:中文名映射只在 `notice_meta.go` 定义一次,
|
||||||
|
API、服务、前端展示三处复用,避免同义词漂移。
|
||||||
|
|
||||||
|
## 待办与风险
|
||||||
|
|
||||||
|
- ⚠️ **`internal/service/recruitment/noise_test.go` 是 0 字节空文件**(未跟踪),会让
|
||||||
|
`go build ./...` 与 `go test ./...` 直接报 `expected 'package', found 'EOF'`。
|
||||||
|
本次联调为编译通过曾临时移走,**已原样还原**;需删除该文件或补上包声明。
|
||||||
|
- ⚠️ 工作区另有他人未提交改动:`internal/service/recruitment/bark.go` 修改、
|
||||||
|
`live_diag_test.go` 与 `testdata/rszk.html` 删除;前端 `api/wallpaper.ts`、
|
||||||
|
`views/wallpaper/*` 处于暂存状态。均非本次范围。
|
||||||
|
- 手动「测试发送」在目标凭据为空时会直接返回参数错误、**不写历史记录**(沿用原有行为)。
|
||||||
|
若希望失败尝试也可追溯,需在 `Test` 的提前返回分支补记日志。
|
||||||
|
- 历史记录只增不减,长期需要定期清理策略(目前靠页面「清空」手动处理),
|
||||||
|
后续可考虑挂到 `auto_job` 定时任务上。
|
||||||
|
- 本地联调环境:Node 22.22.2 + corepack `pnpm@11.16.0`;后端 10100、前端 dev 20100,
|
||||||
|
前端 `vite.config.ts` 已把 `/api/service` 代理到 `http://localhost:10100`。
|
||||||
@ -3,6 +3,7 @@
|
|||||||
> 模块路径:`api/recruitment/`、`internal/controller/recruitment/`、`internal/service/recruitment/`
|
> 模块路径:`api/recruitment/`、`internal/controller/recruitment/`、`internal/service/recruitment/`
|
||||||
> 独立数据库:`recruitment`(与主库 `service` 隔离)
|
> 独立数据库:`recruitment`(与主库 `service` 隔离)
|
||||||
> 文档定位:本次「抓取健壮性」专项设计,供评审后实施。
|
> 文档定位:本次「抓取健壮性」专项设计,供评审后实施。
|
||||||
|
> **实施状态:阶段一已完成(2026-09-14)**,实施中的关键调整见文末「七、实施结果」。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -270,3 +271,80 @@ ALTER TABLE `crawl_source`
|
|||||||
3. **日期置空的影响**:P0-2 改为「未识别则置空」后,依赖 `publish_date` 的统计会短期波动,属预期(此前是错误数据)。
|
3. **日期置空的影响**:P0-2 改为「未识别则置空」后,依赖 `publish_date` 的统计会短期波动,属预期(此前是错误数据)。
|
||||||
4. **自动禁用需谨慎**:阈值过低会因偶发网络抖动误禁。建议结合「连续」失败(中间成功即归零),并推送告警以便人工复核。
|
4. **自动禁用需谨慎**:阈值过低会因偶发网络抖动误禁。建议结合「连续」失败(中间成功即归零),并推送告警以便人工复核。
|
||||||
5. **表变更走迁移脚本**:禁止手工改库;生成代码(若后续跑 `gf gen dao`)需注意本模块 entity 为**手写**,勿被覆盖。
|
5. **表变更走迁移脚本**:禁止手工改库;生成代码(若后续跑 `gf gen dao`)需注意本模块 entity 为**手写**,勿被覆盖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、实施结果(2026-09-14)
|
||||||
|
|
||||||
|
阶段一全部完成。实施过程中基于**真实站点数据**校准,方案有两处重要调整,
|
||||||
|
这两点也是本模块最容易踩的坑,特此记录。
|
||||||
|
|
||||||
|
### 7.1 变更清单
|
||||||
|
|
||||||
|
| 文件 | 类型 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| `internal/service/recruitment/keywords.go` | 新增 | 词表与评分规则(含排除域名) |
|
||||||
|
| `internal/service/recruitment/source_config.go` | 新增 | `crawl_source.config` 解析与默认值兜底 |
|
||||||
|
| `internal/service/recruitment/keywords_test.go` | 新增 | 16 个评分用例 + 标题清洗 + 图片锚点回归 |
|
||||||
|
| `internal/service/recruitment/crawler.go` | 修改 | 评分制过滤、日期分段抽取、限速、自动禁用、图片锚点处理、调试日志清理 |
|
||||||
|
| `internal/service/recruitment/bark.go` | 修改 | 告警推送、英文日志中文化、占位符过滤 |
|
||||||
|
| `internal/service/recruitment/recruitment.go` | 修改 | `Sources()` 改读冗余列(零 JOIN) |
|
||||||
|
| `internal/model/entity/recruitment.go` | 修改 | `CrawlSource` 增 4 个冗余列 |
|
||||||
|
| `internal/model/do/recruitment.go` | 修改 | 同上 |
|
||||||
|
| `manifest/sql/recruitment/004_crawl_source_status.sql` | 新增 | 幂等迁移(存储过程判列存在)+ 启用源 config 初始化 |
|
||||||
|
|
||||||
|
### 7.2 关键调整一:评分以「URL 结尾形态」为准,而非路径关键词
|
||||||
|
|
||||||
|
**设计原方案**是按 URL 路径段(`/zfxxgk`、`/rszk` 等)加权。实测发现此方案**根本性错误**:
|
||||||
|
|
||||||
|
```
|
||||||
|
栏目页: /zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/ ← 以 / 结尾
|
||||||
|
正文页: /zfxxgk/fdzdgklm/zfxxgkrsxx/rszk/202609/t20260908_xxx.html ← 以 .html 结尾
|
||||||
|
```
|
||||||
|
|
||||||
|
两者**共享同一段栏目路径**,仅靠路径段无法区分;按路径段减分反而会把真实公告一并误杀
|
||||||
|
(实测第一版即因此把 19 条真实公告全部过滤,只剩 1 条)。
|
||||||
|
|
||||||
|
**最终方案**:只按「结尾形态」判定——
|
||||||
|
- 以 `.html`/`.shtml`/`.htm` 等结尾 → **+3**(正文)
|
||||||
|
- 以 `/` 结尾 → **-5**(栏目目录,必为列表页而非正文)
|
||||||
|
|
||||||
|
该判定经 16 个真实锚点用例验证,公告正文与栏目页/导航页全部分类正确。
|
||||||
|
|
||||||
|
### 7.3 关键调整二:图片型锚点会以「文件名」污染标题
|
||||||
|
|
||||||
|
实测发现 `依申请公开` 未被过滤,根因是:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<a href=".../ysqgk/index.html"><img src="ysqgk3.png" title="ysqgk3.png" /></a>
|
||||||
|
```
|
||||||
|
|
||||||
|
`stripTags` 把 `<img>` 替换为空白后,`title` 属性回退逻辑取到了 `title="ysqgk3.png"`,
|
||||||
|
于是锚点文本变成 `ysqgk3.png`——**绕过了所有中文排除词**,再叠加 `.html` 的 +3 分通过筛选。
|
||||||
|
|
||||||
|
**处理**:新增 `isImageFileName` 判定,图片型锚点优先取 `alt`,取不到则整体丢弃。
|
||||||
|
|
||||||
|
### 7.4 实测数据对比(源1:贵阳市人社局·人事招考)
|
||||||
|
|
||||||
|
| 指标 | 修复前 | 修复后 |
|
||||||
|
|---|---|---|
|
||||||
|
| 增量模式抓取 | 8 条(含 6 个导航页噪声) | 2 条(全为真实公告) |
|
||||||
|
| 全量回溯(force) | 8 条 | **21 条真实公告** |
|
||||||
|
| 发布日期识别率 | 大量误取(取页面头部日期) | **100%**(0 条为空) |
|
||||||
|
| 标题污染 | 含 `\n\t\t\t` 与图片文件名 | 已清洗 |
|
||||||
|
|
||||||
|
> 增量模式抓取条数少是正确的:30 天窗口内源站确实只发布了 2 条。
|
||||||
|
|
||||||
|
### 7.5 验证方式
|
||||||
|
|
||||||
|
- **单元测试**:`go test ./internal/service/recruitment/` 全绿(4 个测试函数 / 16 个评分用例);
|
||||||
|
- **端到端**:启动服务 → 管理端登录 → 触发抓取 → 核对入库数据与冗余列;
|
||||||
|
- **失败链路**:构造坏源(`http://127.0.0.1:1`),第 5 次失败时 `enabled` 自动置 0,
|
||||||
|
日志输出「数据源 N 连续失败 5 次,已自动禁用」,告警推送按预期优雅降级(本地未配 Bark 仅记警告,不影响抓取)。
|
||||||
|
|
||||||
|
### 7.6 遗留事项
|
||||||
|
|
||||||
|
- 阶段二(推送分时)、阶段三(源扩展)、阶段四(运维增强)未做,见「五、实施计划」。
|
||||||
|
- `push_time` 字段仍未生效(`recruit-push-daily` 固定 08:00),属阶段二范围。
|
||||||
|
- `crawl_source.config` 的 `MaxPages`(翻页)与 `DatePatterns`(自定义日期正则)已实现解析但暂无源使用,
|
||||||
|
待具体站点需要时配置即可。
|
||||||
|
|||||||
@ -327,12 +327,11 @@ func toLogVO(v entity.NoticeLog, ruleName, userName string) dto.NoticeLogVO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LogDetail 单条历史记录详情。
|
// LogDetail 单条历史记录详情。
|
||||||
|
// 注意:记录不存在时 Scan 会返回 sql.ErrNoRows,不能直接 Wrap 上抛
|
||||||
|
// (那样会被 gf 归为内部错误 50 并暴露 SQL 细节),统一由 Id==0 判定为参数错误。
|
||||||
func (s *notice) LogDetail(ctx context.Context, id uint64) (*dto.NoticeLogVO, error) {
|
func (s *notice) LogDetail(ctx context.Context, id uint64) (*dto.NoticeLogVO, error) {
|
||||||
var v entity.NoticeLog
|
var v entity.NoticeLog
|
||||||
if err := dao.NoticeLog.Ctx(ctx).Where("id", id).Scan(&v); err != nil {
|
if err := dao.NoticeLog.Ctx(ctx).Where("id", id).Scan(&v); err != nil || v.Id == 0 {
|
||||||
return nil, gerror.Wrap(err, "查询通知历史详情失败")
|
|
||||||
}
|
|
||||||
if v.Id == 0 {
|
|
||||||
return nil, response.Error(consts.CodeInvalidParam, "通知记录不存在")
|
return nil, response.Error(consts.CodeInvalidParam, "通知记录不存在")
|
||||||
}
|
}
|
||||||
ruleName := ""
|
ruleName := ""
|
||||||
|
|||||||
@ -29,7 +29,21 @@ type barkPayload struct {
|
|||||||
// defaultDeviceKey 读取环境变量注入的兜底设备密钥(BARK_DEVICE_KEY -> bark.deviceKey)。
|
// defaultDeviceKey 读取环境变量注入的兜底设备密钥(BARK_DEVICE_KEY -> bark.deviceKey)。
|
||||||
// 当数据库没有任何订阅记录时,推送与测试推送回退到该密钥,做到「填了 env 即可推送」。
|
// 当数据库没有任何订阅记录时,推送与测试推送回退到该密钥,做到「填了 env 即可推送」。
|
||||||
func defaultDeviceKey(ctx context.Context) string {
|
func defaultDeviceKey(ctx context.Context) string {
|
||||||
return g.Cfg().MustGet(ctx, "bark.deviceKey", "").String()
|
return resolveCfgValue(g.Cfg().MustGet(ctx, "bark.deviceKey", "").String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveCfgValue 过滤未解析的 ${...} 占位符。
|
||||||
|
//
|
||||||
|
// 背景:gf v2.10.2 不再自动替换配置中的 ${ENV},需由 cmd.injectEnv 显式注入。
|
||||||
|
// 若某环境变量未设置(如本地未配 BARK_BASE_URL),配置项会保留字面量
|
||||||
|
// "${BARK_BASE_URL}",直接使用会产生 "invalid character { in host name" 之类的
|
||||||
|
// 误导性错误。此处统一把未解析占位符视为空值,使错误信息更准确。
|
||||||
|
func resolveCfgValue(v string) string {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultSubscription 构造一个使用兜底密钥的默认订阅(全地区/全分类/仅新公告)。
|
// defaultSubscription 构造一个使用兜底密钥的默认订阅(全地区/全分类/仅新公告)。
|
||||||
@ -46,7 +60,7 @@ func defaultSubscription(ctx context.Context) *entity.PushSubscription {
|
|||||||
|
|
||||||
// barkPush 向指定设备密钥发送 Bark 推送(自建服务:POST {baseUrl}/push)。
|
// barkPush 向指定设备密钥发送 Bark 推送(自建服务:POST {baseUrl}/push)。
|
||||||
func barkPush(ctx context.Context, deviceKey, title, body string) (bool, string, error) {
|
func barkPush(ctx context.Context, deviceKey, title, body string) (bool, string, error) {
|
||||||
baseURL := g.Cfg().MustGet(ctx, "bark.baseUrl", "").String()
|
baseURL := resolveCfgValue(g.Cfg().MustGet(ctx, "bark.baseUrl", "").String())
|
||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
return false, "未配置 bark.baseUrl", fmt.Errorf("bark baseUrl empty")
|
return false, "未配置 bark.baseUrl", fmt.Errorf("bark baseUrl empty")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,10 @@ SET @sql := IF(@has_col=0, 'ALTER TABLE notice_log ADD COLUMN batch_id VARCHAR(3
|
|||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
-- status:0待发送 1成功 2失败(result 为旧字段 1成功 0失败,保留兼容)
|
-- status:0待发送 1成功 2失败(result 为旧字段 1成功 0失败,保留兼容)
|
||||||
|
-- 默认 0(待发送):新增列时历史行先统一为 0,再由下方「历史数据回填」按 result 推导,
|
||||||
|
-- 若默认写 1 会导致历史失败记录被误标为成功。
|
||||||
SET @has_col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='notice_log' AND COLUMN_NAME='status');
|
SET @has_col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='notice_log' AND COLUMN_NAME='status');
|
||||||
SET @sql := IF(@has_col=0, 'ALTER TABLE notice_log ADD COLUMN status TINYINT NOT NULL DEFAULT 1 COMMENT ''0待发送 1成功 2失败'' AFTER body', 'SELECT 1');
|
SET @sql := IF(@has_col=0, 'ALTER TABLE notice_log ADD COLUMN status TINYINT NOT NULL DEFAULT 0 COMMENT ''0待发送 1成功 2失败'' AFTER body', 'SELECT 1');
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
-- retry_count:已重试次数
|
-- retry_count:已重试次数
|
||||||
@ -49,10 +51,10 @@ SET @has_idx := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_
|
|||||||
SET @sql := IF(@has_idx=0, 'ALTER TABLE notice_log ADD INDEX idx_batch (batch_id)', 'SELECT 1');
|
SET @sql := IF(@has_idx=0, 'ALTER TABLE notice_log ADD INDEX idx_batch (batch_id)', 'SELECT 1');
|
||||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
-- 历史数据回填:result=1 → status=1;result=0 且有错误 → status=2
|
-- 历史数据回填:按旧字段 result 推导 status(幂等,可重复执行)
|
||||||
UPDATE notice_log SET status=1 WHERE status=0 AND result=1;
|
UPDATE notice_log SET status=1 WHERE result=1 AND status<>1;
|
||||||
UPDATE notice_log SET status=2 WHERE status=0 AND result=0 AND error <> '';
|
UPDATE notice_log SET status=2 WHERE result=0 AND status<>2;
|
||||||
UPDATE notice_log SET source='test' WHERE event_type='test' AND source='auto';
|
UPDATE notice_log SET source='test' WHERE event_type='test' AND source<>'test';
|
||||||
|
|
||||||
-- ============ 2) 菜单:通知历史记录(替换原「通知日志」并调整排序) ============
|
-- ============ 2) 菜单:通知历史记录(替换原「通知日志」并调整排序) ============
|
||||||
-- 982 原为「通知日志」,此处改名为「通知历史」以贴合需求(保留 id 以延续角色绑定)
|
-- 982 原为「通知日志」,此处改名为「通知历史」以贴合需求(保留 id 以延续角色绑定)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user