三层墙与两段缺失:1GB 上传放行记与告警联动发布屏联调

导读

300MB 视频上传前端直接超时,放大限制却发现墙不止一层;审批接口又被大华转码拖到超时,只能把设备同步挪出请求线程。同一天还把告警联动发布屏打通了——骨架是现成的,真正缺的是两段从没接上的代码。附一份当天沉淀的端到端测试七步配方。

前言

9 月 2 日承接前一天的素材审批超时修复,继续往深水区走:上传链路要放行到 1GB,审批接口要彻底摆脱设备转码的拖累。另一条线是告警联动发布屏——告警产生时自动往信息发布屏插播文案,处理完能撤播。两件事都不大,但各自藏着”层数比想象多”的教训。

一、1GB 放行:三层墙,少拆一层都白搭

现象是 300MB 视频上传前端直接 timeout of 30000ms exceeded。排查后发现限制叠了三层:

原限制 修法
前端组件默认值 fileUpload 组件默认 50MB 页面显式 :file-size="1048576"(单位 KB)放到 1GB
后端 multipart smart/aided 硬编码 50MB,iot 500MB bootstrap.yml 改占位符,Nacos 下发 1GB
nginx body client_max_body_size 50m 改 1024m + reload

前端 axios 的 30s 全局超时单独放行:上传请求逐请求 timeout: 600000,不动全局。

后端为什么用占位符而不是直接改 Nacos

这套框架的 Nacos 远程配置是 override-none: true——远程优先级最低,字面值覆盖不了 jar 内 yml。而 ${icfg.upload.max-file-size:50MB} 这种占位符在解析时会扫整个 Environment(含 Nacos 源),天然穿透。这是”jar 内写占位符 + Nacos 下发值”的固定套路:

bootstrap.yml + Nacos 下发
# jar 内 bootstrap.yml:占位符兜底 50MB,Nacos 有值就穿透
max-file-size: ${icfg.upload.max-file-size:50MB}

线上 Nacos application.yml:

icfg:
upload:
max-file-size: 1GB

integrate-iot.yml:挂在 screen.dh 层级下

gateway:
screen:
dh:
upload-read-timeout: 2000000

当天还踩了两个配置坑,都值得记住:其一,同事新上的配置项把 fire-alarm.trace-log-enabled 读成 Boolean,而线上存量值是 flash(false 的笔误),启动直接崩——新配置项 + 强类型解析,上线前必须核对线上存量值;其二,本地的 Nacos 参考副本是六月旧导出,钉钉凭证早换过,照抄上线会让钉钉通知静默挂掉——改 Nacos 前必须 diff 线上当前值,不能信历史导出

二、审批超时根修:设备同步异步化

前一天把读超时放宽到 180s 只是让审批”能等到结果”,代价是审批人干等几分钟。根修是把设备同步挪出审批请求线程:

上传链路放行与异步化
图 1:三层限制逐一放行后,审批线程只负责落库,设备同步交给异步执行器

  • 顺序不能反:先落审批状态,affectRows > 0 且审批通过才提交异步任务——否则可能出现”大华已有素材、本地审批没落”的不一致;
  • 线程池特意压到 1:素材全量进内存,并发大文件有堆压力,超出排队串行;
  • 异步线程没有请求上下文,不能走 WebExtUtils 取上下文数据;
  • 失败模式与审批解耦:素材照常过审显示,工厂字段为空,失败详情看日志。
auditMaterial 异步化骨架
int affectRows = super.updateSelective(entity); // 先落审批状态
if (affectRows > 0 && 审批通过) {
    DH_SYNC_EXECUTOR.execute(() -> syncMaterialToDevice(id)); // 再异步同步设备
}

同日还有个文件名净化的小修复值得单列:iot 侧对文件名做白名单校验,非法字符直接 return null——连 HTTP 都不发,静默吞掉。浏览器”另存为”自动加 (1) 后缀的文件必中。修法是删校验改净化:中文/数字/字母/小数点保留,其余替换成下划线后照常上传。

三、告警联动发布屏:骨架现成,缺的是两段接线

需求一句话:告警产生时自动往发布屏插播告警文案,处理完能撤播。排查后发现 smart 的 text 分支、推送骨架全是现成的,真正缺的就两处:

  • 文案没传:处理人配置的 screen_alarm_text 从来没拼进推送 DTO——不配素材就什么都不推;
  • planId 没回写:插播返回的 playId 只 set 进 DTO 就完了,而告警记录先落库、推屏在后,set 晚于落库等于丢。

第二处是撤播的命门:告警详情页的”停止显示屏”按钮靠 screen_plan_id 调撤播接口,不回写 = 停止按钮永远空转。

告警联动发布屏:插播与撤播闭环
图 2:插播成功必须回写 planId,否则撤播按钮永远空转

这里有个教训比功能本身值钱:改动前我 grep screenPlanId 得出”该字段无消费方、不必回写”的错误结论,被 review 指出——消费者写的是 getScreenPlanId()判定”字段无消费方”时,getter/setter 全写法都要 grep

四、沉淀:端到端测试七步配方

本次联调跑通后,把与具体功能无关的测试方法沉淀成七步框架:

E2E 七步框架
① 读链路定集合 → ② 外体内桩 → ③ SQL造数据 → ④ 认证穿透
→ ⑤ 触发+收证据 → ⑥ 正反闭环 → ⑦ 收尾还原

几个要点:缺服务与否看代码不看感觉(grep 旁路调用有没有 catch,本次 bpm 整个没起);mock 不可达的外部系统(20 行 node 接住大华网关,收到的报文 append 到日志文件当断言点);证据三处收(对端 mock 报文、服务日志、DB 终态);最重要的一条——正反闭环:只测正向会漏掉回写类 bug,插播能播不代表撤播能撤,”生成→可撤销”的功能必须把反向操作也走一遍。

结语

一天的三个主题——拆墙、异步化、补接线——其实是同一件事的不同形态:系统的真实层数永远比第一眼看到的多。限制有三层,链路有两段缺口,测试要正反两个方向。把所有层都翻出来看一遍,问题就自己现形了。