一个「调通旧功能」如何变成四层重建——语音播报链路断裂复盘
任务原文写的是「调试旧版语音播报是否可用」。预期是「旧版已有,调通即可」。实际逐层调试后结论很反常:除了最底层的设备能力可复用,上面四层——Feign、触发、配置、素材持久化——全部断裂。这不是「调通旧功能」,而是在底层能力之上重建四层链路。这篇记录每一层的现状、证据和误判修正。
背景
AI 视频分析闭环里有三个联动:平台弹窗、信息屏发布、语音播报。语音播报子任务原文:
旧版本已有相关功能,需要调试是否可用。
预期「旧版已有,调通即可」。实际调试结论:旧版只有一副骨架,四截全断,需要补开发。
四层链路总览
语音播报从平台底层能力到用户听到声音,逻辑上分四层:
1 | flowchart TD |
绿色是唯一不用动的,红色是断裂的,橙色是半成品。

图 1:四层叠放——底层设备能力完好,上面三层全断。
第 1 层 · 设备能力——唯一可复用
integrate-iot 的 PlfVideoClientImpl 真实调用了视频平台的语音广播接口。
| 能力 | 接口 | 实现 |
|---|---|---|
| 开始广播 | POST /protocol/ProtocolApiService/requestVoiceBroadcas |
真实调用 |
| 停止广播 | POST /protocol/ProtocolApiService/stopVoice |
真实调用 |
| Provider 暴露 | /platformVideo/broadcast/broadcastStart、/broadcastStop |
已暴露 |
参数:mainId(设备主 Id)+ subId(设备子 Id)+ audioUrl(语音文件地址;不传则返回 websocket 地址,推 opus 音频流)。
这是整条链路里唯一不用动的部分。底层调用和 Provider 都在,能复用。问题全在它之上。
证据:PlfVideoClientImpl.java:870(broadcastStart)、:889(broadcastStop);IotPlatformVideoProvider.java:727、:742。
底层探活的一个间接证据:实时监控能看画面(走同一个视频平台
PlfVideoClient.playWsFlvUrl),说明 host 配的对、网络通、登录鉴权通。广播接口和视频流接口共一套prepareToken鉴权,大概率也活。
第 2 层 · Feign——断裂

图 2:Provider 把接口的「门」打开了,但 Feign 客户端这层「路」没修——别的服务调不到。
IotPlatformVideoClient(Feign 客户端)只有摄像头 / 预置位方法,没有 broadcast 方法。
Provider 已经把广播接口暴露在 /platformVideo/broadcast/*,但 Feign 客户端里没有对应方法——其他服务(warning)调不到。等于门开了,但没修路。
附带问题:该 Feign 客户端的 fallbackFactory 指向了通用的 IotPlatformFallbackFactory,而项目里有一个专门的 IotPlatformVideoFallbackFactory 没被用上——熔断兜底也指错了。
要修三件事:
- 加
broadcastStart/broadcastStop两个 Feign 方法; fallbackFactory改指IotPlatformVideoFallbackFactory;iot-api是共享模块,改完要mvn install+ 重编依赖它的服务。
证据:IotPlatformVideoClient.java:29(fallbackFactory 指错),全文件无 broadcast 方法。
这一层和第 1 层合起来是一个经典陷阱:Provider 暴露了接口不等于别的服务调得到。中间隔着 Feign 客户端这一层「路」,路没修,门开了也走不通。
第 3 层 · 触发——空 stub
warning 服务的 AlarmingEvenSinkServiceImpl.pushBroadcastDevice() 是空方法——查出 broadcastIds 之后方法直接结束,既不调 Feign,也没有定时停止。
1 | // 现状:查出列表就 return,什么都没推 |
一个被二次核对纠正的误判
最初以为这个 stub 会把告警记录的 broadcast_push_flag 置成「已推送」,造成「显示推了实际没推」。核对常量后纠正:
BROADCAST_PUSH_FLAG_YES="0"(语义和直觉相反,”0” 才是「是」);- 真正的执行状态字段是
broadcast_push_status(默认1= 未推送)。
stub 不会误标已推送,但也没有任何真实执行结果落库。
要修:完整实现——遍历配置 → 校验 mainId/subId/audioUrl 非空(缺则跳过 + 记日志,不能传空参给平台)→ 调 Feign broadcastStart → 异步按插播时长调 broadcastStop(禁止 Thread.sleep 阻塞告警线程)→ 失败 / 部分成功处理 + 状态落 broadcast_push_status。
第 4 层 · 配置——半成品,两个子问题叠加
这一层问题最隐蔽。
4a. 设备 / 素材选择器数据源错误
告警配置页绑定广播时,「广播设备」和「广播素材」两个选择弹窗,拉的居然是 through 服务的通行通道(passagewayInfo)——风马牛不相及,是复制粘贴留下的半成品。
4b. 保存根本没调用接口
alarmConfig.vue 的 submitBroadcast() 方法只关闭弹窗:
1 | // 现状:只关弹窗,没调保存接口 |
updatealarmingPushBroadcast 虽然 import 了,但从未被调用——用户在弹窗里选完点确定,配置根本不会保存。
要修:
- 设备选择器换数据源(视频平台设备列表,带 mainId/subId);
- 素材选择器换数据源(真实音频素材,拿 audioUrl);
submitBroadcast补上调updatealarmingPushBroadcast;- 广播配置需能存
mainId/subId/audioUrl(对应数据库新增字段)。
附 · 素材持久化——缺失
广播 API 要 audioUrl(语音文件地址),但现有配置只存 broadcast_material_id(一个没映射关系的 id),没有稳定的 audioUrl 持久化链路。
audio_url必须是视频平台或 IoT 服务能访问的绝对地址,不能是浏览器相对路径;- 音频素材的上传(走附件服务传 mp3)、查询、URL 持久化,目前只有通用上传能力,没有完整链路。
工作量汇总
| 层 | 要做什么 | 后端 / 前端 | 工作量 |
|---|---|---|---|
| 1 设备能力 | 无(可复用) | — | 0 |
| 2 Feign | 加 2 个方法 + 修 fallback + install | 后端 | 0.5 天 |
| 3 触发 | 实现 stub(异步停播 + 状态落库 + 失败处理) | 后端 | 1~1.5 天 |
| 4a 选择器 | 设备 / 素材选择器换数据源 | 前端 | 0.5~1 天 |
| 4b 保存 | submitBroadcast 接通保存接口 | 前端 | 0.5 天 |
| 附 素材 | 音频上传 + audioUrl 持久化 | 后端 + 前端 | 0.5~1 天 |
| 联调 | 真实视频平台 + 带扬声器设备 | 联调 | 0.5~1 天(看环境) |
工作量误判风险
最初估「后端 12 天」只够 happy path(Feign + stub 接通)。加上 mainId/subId 映射、素材 URL 链路、状态落库、前端三个修复点、联调,**完整闭环远超 12 天**。前端「0.5~1 天」只有在设备树和素材 URL 接口确定后才成立。
未确认的外部依赖(主要风险)
以下项不确认,做完代码也联调不了:
- 哪些设备支持语音广播(摄像头自带扬声器,还是外接音柱?)——设备侧未确认;
- 音频格式要求(opus / mp3?)——视频平台文档未拿到;
- mainId/subId 从哪来(设备同步还是手工配置?)——无广播设备同步逻辑;
- 视频平台能否访问附件 URL(网络可达性)——未测。
已完成的前置改动
四层里最底层、不依赖任何外部确认的两步已经做完,为后续补开发铺好地基:
① Feign 方法(第 2 层)——IotPlatformVideoClient 新增 broadcastStart / broadcastStop,fallbackFactory 修正为 IotPlatformVideoFallbackFactory,iot-api 编译并 install。
② 数据承载(第 4 层的数据基础)——AlarmingPushBroadcast 实体新增 mainId / subId / audioUrl(允许为空,兼容旧数据),Flyway 迁移 V20260801.22.45__alter_alarming_push_broadcast_add_broadcast_device.sql,AlarmingPushBroadcastMapper.xml 三字段覆盖全部 12 处(resultMap / 列清单 / 条件块 / 增改)。
小结
语音播报的工作量被任务描述(「调试是否可用」)严重低估。实际是:底层能力可复用,但 Feign、触发、配置、素材持久化四层都要重建,且联调依赖设备侧能力确认。
几个值得带走的判断方法:
- 逐层探活:从底层设备能力往上,一层一层确认通断,比笼统说「不能用」精准得多。
- Provider 暴露 ≠ 调得到:中间隔着 Feign 客户端这层「路」。
- 布尔常量不能按常理猜:
YES="0"/NO="1"这种反直觉取值,看到任何标记先 grep 常量类确认。 - 空 stub 不等于「标记已执行」:要分清「配置 flag」和「执行状态字段」是两回事。
- 保存按钮只关弹窗:是最隐蔽的「功能不可用」,UI 看着正常,配置根本没落库。
建议视设备侧确认进度,决定本阶段是否纳入后续开发,或整体后移与设备接入同步推进。