dzs/dzs-data-dispatcherv1.0.7ef5e342f644463936f5c7d4a38c126680ac6e338(base) → 943040bd92f2d9f3ad762478e73c396c404f0c73(head)internal/api、internal/contractcheck、internal/health、internal/metrics)、1 份契约快照(configs/contracts.json)、3 个测试文件代码注释与规则文档(.trae/rules/scheduler.md、.trae/rules/dynamic-params.md)一致表明,本次改动是对 2026-09-19 两起生产事故 的整改:
7ec4f2d 在生成侧 delete(params,"breedName") 并把 category 改写成 commodity_code,导致线上 MSTEEL 采集器(v1.0.75 只认 breedName)自 09-19 17:26 起全线 参数验证失败:missing required parameter: breedName。restart=always 不触发;本服务当时没有任何 HTTP 服务,外部无法探活。围绕这两条主线,本次提交是一组**「参数只增不删 + 写库前契约自检 + 心跳看门狗自愈 + HTTP 状态端点 + 构建信息可追溯」** 的组合加固。
param_values 关联键「只增不删」(internal/scheduler/scheduler.go)normalizeParamsToCode 语义被彻底反转:
| 键 | 旧行为(事故版) | 新行为(修复版) |
|---|---|---|
breedName | 删除(delete(params,"breedName")) | 原样保留(采集匹配参数,禁止删除) |
category | 改写成 commodity_code | 原样保留(须为中文分类名);仅缺失/为空时兜底为 code |
category_code | = commodity_code | = commodity_code(新增/保留) |
breed_code | = commodity_code | = commodity_code(新增/保留) |
generateTask 的注释同步改为「只增键」,并明确 category 是采集匹配参数(PPI100 采集器 IsValidCategory/GetCategoryID 只认「钢铁/能源/…」中文一级分类名),机器关联信息另放 category_code。这是本次最实质的正确性修复。
internal/contractcheck——写库前契约自检(fail fast)CONTRACT_SNAPSHOT(缺省 /app/configs/contracts.json)快照,与采集器 internal/contract 同构(Rule{Key,Required,Aliases,Note})。Check() 校验 TaskDriven=true 采集器的必填参数;别名满足即视为满足(MSTEEL 契约要求 breedName,兼容 breed_code);多余键一律放行(向后兼容优先)。CheckSkipped(跳过)与 Violation(违反,不可重试)。generateTask 中 buildTask/db.Create 之前、参数定型之后——这正是写入 dzs_collect_task.param_values 字节的唯一产生点。generateTaskWithRetry 识别 Violation 后不重试,避免同一份注定失败的参数白重试 3 次。internal/health——心跳 + 看门狗自愈Beat/Register/Unregister/Snapshot/StaleKeys/Watchdog;exitFunc 可注入便于单测。WATCHDOG_STALE 缺省 15m(严格大于最长合法静默 cron.retry_failed 的 10m),WATCHDOG_FATAL 缺省 30m,WATCHDOG_INTERVAL 缺省 60s;Watchdog 内部再兜底 fatalAfter = 3×staleAfter。exitFunc(1),由容器 restart=always 拉起自愈。internal/api——HTTP 状态端点(标准库 net/http,无新依赖)GET /health → 200 {status,version,commit,uptime_s};GET /status → heartbeat、stale_keys(与 Watchdog 同函数同口径)、bindings、contracts、tasks_generated_total、contract_rejected_total。net.Listen 探一次(失败即明确 Error 日志,不出现「日志说已启动、实际没绑上」的误导),Serve 放 goroutine;端口占用不影响主流程。internal/metrics——进程内计数器(纯 atomic)IncTasksGenerated/TasksGeneratedTotal、IncContractRejected/ContractRejectedTotal,解耦 scheduler 与 api,避免循环依赖。
Dockerfile:新增 ARG BUILD_VERSION/BUILD_TIME/GIT_COMMIT,-ldflags "-X main.version=… -X main.commit=… -X main.buildTime=…";并 COPY configs/(含契约快照)。.gitea/workflows/build.yml:docker build 显式传 --build-arg,否则镜像里没有版本、/status 只能显示 unknown。cmd/dispatcher/main.go:新增 version/commit/buildTime 变量并透传到日志与 api.BuildInfo。internal/scheduler/scheduler.go)BindingStats、ContractStats 供 /status。registerJobs 为 cron.config_refresh / cron.timeout_check / cron.retry_failed 增加 health.Register + health.Beat,并统一加 monitor 心跳。runBindingJob:外层同步 health.Register("binding.<id>");循环内、进入等待前打点;等待期新增 bindingHeartbeatInterval=1m 的空闲心跳 ticker(只新增 select 分支,不改变既有退出语义);协程退出 defer 中 health.Unregister(关键:后台禁用/删除绑定的正常取消若不注销,陈旧 key 会在 fatalAfter 后误杀进程)。.env.local.example:新增 STATUS_PORT / CONTRACT_SNAPSHOT / WATCHDOG_STALE / WATCHDOG_FATAL / WATCHDOG_INTERVAL,并写明口径与理由。.trae/rules/dynamic-params.md、.trae/rules/scheduler.md:新增「生成侧参数兼容铁律」「可靠性加固」MANDATORY 章节。configs/contracts.json:4 个采集器契约快照(MSTEEL/MSTEEL_COAL/MSTEEL_PRC/PPI100),由采集器 cmd/contractdump 产出,跨仓契约单一事实源。pkg/models/models.go、internal/config/config_manager.go:仅 gofmt 结构体字段对齐与空行/缩进重排,无语义变化(diff 大但可忽略)。| 严重度 | 类型 | 位置 | 建议 |
|---|---|---|---|
| Medium | Dependency | cmd/dispatcher/main.go:16 | 拆分函数/引入接口降低耦合 |
| Medium | Dependency | internal/scheduler/scheduler.go:119 | 拆分函数/引入接口降低耦合 |
| Medium | Complexity | internal/scheduler/scheduler.go:163 | 拆分更小函数 |
| Medium | Complexity | internal/scheduler/scheduler.go:163 | 抽取嵌套逻辑 |
| Medium | Complexity | internal/scheduler/scheduler.go:163 | 拆分(单一职责) |
| Low | Dependency | internal/scheduler/scheduler.go:163 | 平衡调用结构 |
| Medium | Dependency | internal/scheduler/scheduler.go:163 | 拆分/引入接口 |
| Medium | Complexity |
⚠️ 重要解读:这 15 条「修复」绝大多数是行号漂移造成的同源问题「搬家重登记」,并非真实消除。 新旧两侧的问题类型与函数几乎一一对应(例如 base
scheduler.go:163/269的复杂度问题,在 head 变成scheduler.go:231(runBindingJob)/371(shouldGenerateTask)的同类问题;base 的 393/398 重复块对应 head 的 495/500;base 459 长行对应 head 617)。因此不应把它计为净改善。
全部为 Medium/Low,按主题归类:
A. 新增可靠性模块自身的复杂度/耦合(Medium)
internal/health/health.go:114 Watchdog 圈复杂度 13、直接依赖 16 个函数(>10)internal/contractcheck/contractcheck.go:96 Check 圈复杂度 12internal/api/status.go:113 goroutine 启动未带 context 取消(并发,见风险 R4)internal/api/status_test.go:145 同类 goroutine(并发)internal/api/status_test.go:42 圈复杂度 13B. 被改动函数规模膨胀(Medium)
internal/scheduler/scheduler.go:231 runBindingJob 函数过长(138 行)、圈复杂度 18、嵌套过深、扇出 27internal/scheduler/scheduler.go:371 shouldGenerateTask 函数过长(98 行)、圈复杂度 11、扇出 30internal/scheduler/scheduler.go:67 New 直接依赖 15 个函数;:178 registerJobs 依赖 19 个;:485 generateTask 依赖 16 个cmd/dispatcher/main.go:30 main 函数过长(103 行)、扇出 27、直接依赖 27 个函数C. 维护性(Low)
cmd/dispatcher/main.go:136/145(envDuration/envInt 共享 4 行)、internal/api/status.go:131、internal/api/status_test.go:20、internal/contractcheck/contractcheck_test.go:35、internal/health/health.go:40、internal/scheduler/params_contract_test.go:24/87、internal/scheduler/scheduler.go:495/500internal/api/status_test.go:48、internal/contractcheck/contractcheck_test.go:72、internal/contractcheck/snapshot_gate_test.go:30、internal/scheduler/scheduler.go:617any 滥用:internal/contractcheck/contractcheck_test.go:13/140、internal/contractcheck/snapshot_gate_test.go:23、internal/scheduler/params_contract_test.go:114D. 大文件耦合(Medium,多为存量问题被重新登记)
internal/config/config_manager.go:51 上帝类(21 方法);:90 RefreshFromDB 过长(122 行)、圈复杂度 26internal/database/database.go:17、internal/monitor/*、internal/dynamicparams/resolver.go:126 等高耦合——这些文件本次基本未改,属伴随扫描的存量问题。结论:新增 40 条中,真正的「本次引入」集中在 A/B/C 三组(新模块 + 接线后膨胀的
main/runBindingJob/generateTask);D 组是存量问题被一并列出。
normalizeParamsToCode 不再删除名称键,回归测试 params_contract_test.go 断言同时含 breedName 与 breed_code、category 保留中文名。Violation 经 errors.Is 识别,避免无意义重试与日志刷屏。Unregister,避免误杀进程。staleAfter(15m) > 最长合法静默(10m),并有假时钟回归测试(TestStaleKeysAt_ThresholdBoundary)。R1(高,建议优先)契约自检的「别名放行」与实际线上采集器版本不一致,可能漏检事故本身。
configs/contracts.json 中 MSTEEL 的 breedName 声明了别名 breed_code,而 normalizeParamsToCode 又总是注入 breed_code。因此只要参数里没有 breedName,门禁仍会因 breed_code 存在而放行(snapshot_gate_test.go 的「事故当天参数」即被设计为放行)。但线上 v1.0.75 采集器只认 breedName,并不支持 breed_code。
→ 结果:本次修复对「模板本就产出 breedName」的绑定有效(不再被删),但对「依赖 code、没有 breedName」的绑定,门禁是假阴性,仍会写出线上必失败的任务。建议:在采集器支持 breed_code 发版前,快照/门禁应把「breedName 实际存在」作为硬约束(或按采集器版本区分契约),并监控 contract_rejected_total。
R2(中)契约自检只校验「存在性」,不校验「取值合法性」。
contracts.json 明确 PPI100 的 category「必须为固定枚举之一(能源/有色/钢铁/…)」,但 contractcheck.Check 仅判断键是否存在/非空,不校验枚举。若 category 被写成 code(正是事故形态),门禁不会拦截。建议补充枚举/取值校验(可由快照声明 enum)。
R3(中)快照缺失时门禁静默关闭,且无告警。
缺 CONTRACT_SNAPSHOT 或挂载缺失时只 WARN 一次、按未启用处理——生产若未挂载快照,加固形同虚设。/status.contracts.loaded 已暴露状态,但需外部监控该字段并在为 false 时告警;否则「加固不可见」。
R4(低-中)api.StartStatusServer 的 Serve goroutine 无 context/无 WaitGroup。
质检标记 internal/api/status.go:113 为「goroutine 泄漏风险」。实为长期驻留 server goroutine,Close() 后即退出,不是真实泄漏;但进程退出前未等待其收尾,理论上可能截断在途响应。可接受,建议加 Shutdown(ctx) 优雅关闭并忽略该误报。
R5(中)看门狗存在「误杀」放大与「漏注销」面。
exit(1) 重启,任何一次超过 fatalAfter(30m) 的长时间停顿(极端 GC/IO/DB 卡顿)都会触发整进程重启;建议结合 /status.stale_keys 做分级(先告警、可配置 fatal 是否启用)。cron.* key 只注册不定期注销(靠周期打点维持),binding.* 靠 defer 注销;一旦新增承载协程忘记注销,就会被误杀。建议在代码规范中固化「凡 Register 必配对 Unregister」。R6(中,存量但本次加重)main/runBindingJob/generateTask 规模与耦合上升。
本次接线使这些函数进一步膨胀(main 103 行、扇出 27;runBindingJob 138 行、扇出 27)。建议把看门狗/状态端点启动、等待期心跳循环、契约自检分别抽成独立函数,缓解复杂度集中。
R7(低)ensureRequiredParams 与新逻辑的口径。
category 无值时由 EnsureRequiredParams 从 pz_name 兜底填充(中文名),与「category 存中文名」的新口径一致,未见冲突;但若历史数据里 category 已是 code,normalizeParamsToCode 会原样保留(不纠正),存在历史脏数据透传风险,建议一次性数据巡检。
数据来源:codescan_git_diff、codescan_issues_diff、codescan_quality_list(v1.0.7,head=943040b)及关键源码核查(scheduler.go / contractcheck.go / health.go / api/status.go / dynamicparams/*.go)。
| internal/scheduler/scheduler.go:269 |
| 拆分更小函数 |
| Medium | Complexity | internal/scheduler/scheduler.go:269 | 拆分(单一职责) |
| Low | Dependency | internal/scheduler/scheduler.go:269 | 平衡调用结构 |
| Medium | Dependency | internal/scheduler/scheduler.go:269 | 拆分/引入接口 |
| Medium | Dependency | internal/scheduler/scheduler.go:383 | 拆分/引入接口 |
| Low | Maintainability | internal/scheduler/scheduler.go:393 | 抽取重复块 |
| Low | Maintainability | internal/scheduler/scheduler.go:398 | 抽取重复块 |
| Low | Maintainability | internal/scheduler/scheduler.go:459 | 拆分长行 |