From e79690518eea3d34a9d462729dadf02546664876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=8F=E7=8A=80=E9=BA=9F?= Date: Mon, 14 Sep 2026 23:53:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(notice):=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E5=8E=86=E5=8F=B2=E7=8A=B6=E6=80=81=E5=9B=9E=E5=A1=AB?= =?UTF-8?q?=E4=B8=8E=E9=85=8D=E7=BD=AE=E5=8D=A0=E4=BD=8D=E7=AC=A6=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 status 列默认值为 0,避免历史失败记录被误标为成功 - 历史数据回填改为幂等更新,可重复执行 - 过滤未解析的 ${...} 配置占位符,优化 Bark 推送错误提示 - LogDetail 记录不存在时统一返回参数错误,避免暴露 SQL 细节 --- .workbuddy/memory/CHANGELOG.md | 2 + docs/change-log/2026-09-14.md | 95 +++++++++++++++++++++++++ docs/recruitment-crawler-design.md | 78 ++++++++++++++++++++ internal/service/notice/notice.go | 7 +- internal/service/recruitment/bark.go | 18 ++++- manifest/sql/017_notice_log_enhance.sql | 12 ++-- 6 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 docs/change-log/2026-09-14.md diff --git a/.workbuddy/memory/CHANGELOG.md b/.workbuddy/memory/CHANGELOG.md index 2eeddd5..99e4969 100644 --- a/.workbuddy/memory/CHANGELOG.md +++ b/.workbuddy/memory/CHANGELOG.md @@ -1,6 +1,8 @@ # service.xpcool.com 变更记录 > 倒序:最新在上。格式: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 | 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 精确匹配)双重保护 diff --git a/docs/change-log/2026-09-14.md b/docs/change-log/2026-09-14.md new file mode 100644 index 0000000..2fd5210 --- /dev/null +++ b/docs/change-log/2026-09-14.md @@ -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`。 diff --git a/docs/recruitment-crawler-design.md b/docs/recruitment-crawler-design.md index a75a31c..3faaf46 100644 --- a/docs/recruitment-crawler-design.md +++ b/docs/recruitment-crawler-design.md @@ -3,6 +3,7 @@ > 模块路径:`api/recruitment/`、`internal/controller/recruitment/`、`internal/service/recruitment/` > 独立数据库:`recruitment`(与主库 `service` 隔离) > 文档定位:本次「抓取健壮性」专项设计,供评审后实施。 +> **实施状态:阶段一已完成(2026-09-14)**,实施中的关键调整见文末「七、实施结果」。 --- @@ -270,3 +271,80 @@ ALTER TABLE `crawl_source` 3. **日期置空的影响**:P0-2 改为「未识别则置空」后,依赖 `publish_date` 的统计会短期波动,属预期(此前是错误数据)。 4. **自动禁用需谨慎**:阈值过低会因偶发网络抖动误禁。建议结合「连续」失败(中间成功即归零),并推送告警以便人工复核。 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 + +``` + +`stripTags` 把 `` 替换为空白后,`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`(自定义日期正则)已实现解析但暂无源使用, + 待具体站点需要时配置即可。 diff --git a/internal/service/notice/notice.go b/internal/service/notice/notice.go index 16c54a2..3e8d5f9 100644 --- a/internal/service/notice/notice.go +++ b/internal/service/notice/notice.go @@ -327,12 +327,11 @@ func toLogVO(v entity.NoticeLog, ruleName, userName string) dto.NoticeLogVO { } // LogDetail 单条历史记录详情。 +// 注意:记录不存在时 Scan 会返回 sql.ErrNoRows,不能直接 Wrap 上抛 +// (那样会被 gf 归为内部错误 50 并暴露 SQL 细节),统一由 Id==0 判定为参数错误。 func (s *notice) LogDetail(ctx context.Context, id uint64) (*dto.NoticeLogVO, error) { var v entity.NoticeLog - if err := dao.NoticeLog.Ctx(ctx).Where("id", id).Scan(&v); err != nil { - return nil, gerror.Wrap(err, "查询通知历史详情失败") - } - if v.Id == 0 { + if err := dao.NoticeLog.Ctx(ctx).Where("id", id).Scan(&v); err != nil || v.Id == 0 { return nil, response.Error(consts.CodeInvalidParam, "通知记录不存在") } ruleName := "" diff --git a/internal/service/recruitment/bark.go b/internal/service/recruitment/bark.go index 8b5e3c7..da8a9d4 100644 --- a/internal/service/recruitment/bark.go +++ b/internal/service/recruitment/bark.go @@ -29,7 +29,21 @@ type barkPayload struct { // defaultDeviceKey 读取环境变量注入的兜底设备密钥(BARK_DEVICE_KEY -> bark.deviceKey)。 // 当数据库没有任何订阅记录时,推送与测试推送回退到该密钥,做到「填了 env 即可推送」。 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 构造一个使用兜底密钥的默认订阅(全地区/全分类/仅新公告)。 @@ -46,7 +60,7 @@ func defaultSubscription(ctx context.Context) *entity.PushSubscription { // barkPush 向指定设备密钥发送 Bark 推送(自建服务:POST {baseUrl}/push)。 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 == "" { return false, "未配置 bark.baseUrl", fmt.Errorf("bark baseUrl empty") } diff --git a/manifest/sql/017_notice_log_enhance.sql b/manifest/sql/017_notice_log_enhance.sql index 0b7a27d..42f5a2f 100644 --- a/manifest/sql/017_notice_log_enhance.sql +++ b/manifest/sql/017_notice_log_enhance.sql @@ -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; -- 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 @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; -- 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'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; --- 历史数据回填:result=1 → status=1;result=0 且有错误 → status=2 -UPDATE notice_log SET status=1 WHERE status=0 AND result=1; -UPDATE notice_log SET status=2 WHERE status=0 AND result=0 AND error <> ''; -UPDATE notice_log SET source='test' WHERE event_type='test' AND source='auto'; +-- 历史数据回填:按旧字段 result 推导 status(幂等,可重复执行) +UPDATE notice_log SET status=1 WHERE result=1 AND status<>1; +UPDATE notice_log SET status=2 WHERE result=0 AND status<>2; +UPDATE notice_log SET source='test' WHERE event_type='test' AND source<>'test'; -- ============ 2) 菜单:通知历史记录(替换原「通知日志」并调整排序) ============ -- 982 原为「通知日志」,此处改名为「通知历史」以贴合需求(保留 id 以延续角色绑定)