HoloSens 摄像机控制平台 · 简历关键词速查
2 任务池机制
口语版
口语版:平台多个业务模块会请求相同接口,我设计了一个请求缓存池,用 Map 管理三种状态:没请求过就发、正在 pending 就复用同一个 Promise、已经有结果就直接用缓存。需要实时数据时传 force 绕过缓存。核心是利用 Promise 引用共享来避免并发重复请求,冗余请求减少 30%,整体性能提升 20%。
面试官想听什么
- 你为什么要自己写,不用现成的库(React Query / SWR)?
- 任务池的核心逻辑是什么?你怎么处理 pending 状态和缓存失效?
我会怎么讲
背景:平台多个业务模块会请求相同的接口(如摄像机状态、预设位置、设备配置),但各模块独立发请求,导致大量冗余请求。我设计了一个请求缓存池来统一管理。
核心设计:三种状态
-
用
Map存储每个请求的状态和结果,key 是请求 URL - 未发送过:发起请求,标记为 pending,将 Promise 存入 Map
- pending 中:直接返回存储的 Promise,多个调用方共享同一个请求
- 已完成(done):直接返回缓存结果,不发请求
-
force 字段:业务需要实时数据时(如抓拍系统的车辆数量),传
force: true绕过缓存直接请求 - 请求失败时从 Map 中删除,下次重新请求
核心伪代码(TaskPool)
class TaskPool {
constructor() {
this.requests = new Map();
}
request(url, { force = false } = {}) {
if (!force && this.requests.has(url)) {
const entry = this.requests.get(url);
if (entry.status === 'done') return Promise.resolve(entry.data);
if (entry.status === 'pending') return entry.data; // 复用同一个 Promise
}
const promise = fetch(url)
.then(res => res.json())
.then(data => {
this.requests.set(url, { status: 'done', data });
return data;
})
.catch(err => {
this.requests.delete(url);
throw err;
});
this.requests.set(url, { status: 'pending', data: promise });
return promise;
}
}
业务中哪些数据适合缓存
- 适合:摄像机状态信息、预设位置、设备配置参数、多语言包 —— 短期内不变、多模块复用
- 不适合:抓拍实时车辆数、告警列表 —— 每次都需要最新数据,用 force 绕过
可追问点
-
为什么不用 React Query / SWR?
- 需要精细控制 pending 状态复用 + force 绕过 + 灵活存储方式(内存/sessionStorage/localStorage),现有库不够灵活
- 自写实现极其轻量,不引入额外依赖
- 不过如果现在再做一遍,会直接用 React Query —— 它已经内置了请求去重、缓存、staleTime、手动 invalidate 等能力,基本覆盖当时的需求
-
缓存存 localStorage 还是 sessionStorage?
- 看数据时效性:摄像机配置等长期有效的用 localStorage,实时状态等会话级数据用 sessionStorage
- 内存缓存(Map)适合"并发防抖"场景,页面刷新即失效
-
跟浏览器强缓存/协商缓存有什么区别?
- 浏览器缓存解决的是"同一资源别重复下载",粒度是 HTTP 层面
- 请求缓存池解决的是"同一接口别重复调用",尤其是多组件并发场景下共享一个 Promise,浏览器缓存做不到
3 延迟任务与单例模式
口语版
口语版:摄像机的视频流组件打开一次要 1.5 秒,以前每次切页面都要关了重开。我改成切页面时不立即关,设个 5 秒的延迟任务,新页面进来检查 UUID 是不是同一路流,是的话就 clearTimeout 直接复用,省掉 1.5 秒的重新请求。架构上我用了单例模式管理视频流状态,通过 Context 全局共享,然后把复用判断和延迟关闭逻辑封装成自定义 Hook,页面组件只需要调一行 useVideoStream(uuid) 就能自动接入。最终 30% 的核心页面加载时间从 1 秒多降到 200 毫秒。
面试官想听什么
- 什么是"延迟任务"?为什么能省 90% 加载时间?
- 单例模式怎么用的?为什么不让每个页面各管各的?
我会怎么讲
背景:摄像机有个实况视频组件,打开一次视频流需要约 1.5 秒。以前每次切换页面都要关闭旧视频流 → 打开新视频流,即使前后两个页面用的是同一路视频流,也会重新请求,导致体验很卡。
延迟任务的核心思路
- 旧逻辑:页面切换 → 立即停流 → 新页面再请流。即使是同一路视频也要经历 1.5s 的重新加载
- 新逻辑:页面切换时不立即关闭视频流,而是设一个延迟任务(setTimeout 5 秒后关闭)
- 进入新页面后,检查新页面是否也需要视频流,且 UUID 和上一页相同:
- UUID 相同:取消延迟关闭任务(clearTimeout),直接复用当前视频流 → 加载时间从 1.5s 变成 0ms
- UUID 不同或不需要视频:延迟任务正常执行,关闭旧视频流
- 结果:覆盖 30% 核心页面,这些页面加载时间从 1000+ms 降到 ~200ms(缩短 90%)
架构设计:单例 + 自定义 Hook
- 问题:以前视频流的创建、销毁逻辑散落在各个页面组件的 useEffect 里,和业务逻辑耦合在一起,维护困难
- 单例 — VideoStreamManager(通过 Context 共享)
- 视频流管理器设计为全局单例,通过 React Context 提供给所有页面
- 单例内部维护当前视频流状态(是否运行、当前 UUID)和延迟任务的 timeoutID
- 所有页面通过同一个实例操作视频流,延迟任务天然跨页面共享
- 为什么必须用单例:延迟任务需要跨页面保持——上一页设的 setTimeout,下一页要能 clearTimeout;如果各管各的,切页面时上一页实例已卸载,timeout 无法取消
- 自定义 Hook — useVideoStream(uuid)
- 核心逻辑封装在 Hook 里:从 Context 取单例 → mount 时检查 UUID 决定复用/新建 → unmount 时延迟关闭
- 任何需要视频流的页面组件,只需要一行
useVideoStream(uuid)就能自动接入 - 页面组件本身完全不感知视频流管理逻辑,只管传 UUID
伪代码:单例 + Hook
VideoStreamManager(单例 + Context)
class VideoStreamManager {
constructor() {
this.streamRunning = false;
this.delayStopTask = null;
this.currentUUID = null;
}
startStream(uuid) {
this.currentUUID = uuid;
this.streamRunning = true;
}
stopStream() {
this.streamRunning = false;
this.currentUUID = null;
}
scheduleDelayedStop() {
if (this.delayStopTask) clearTimeout(this.delayStopTask);
this.delayStopTask = setTimeout(() => this.stopStream(), 5000);
}
cancelDelayedStop() {
if (this.delayStopTask) {
clearTimeout(this.delayStopTask);
this.delayStopTask = null;
}
}
}
// Context 提供单例
const StreamCtx = createContext(null);
const StreamProvider = ({ children }) => {
const [mgr] = useState(() => new VideoStreamManager());
return <StreamCtx.Provider value={mgr}>{children}</StreamCtx.Provider>;
};
useVideoStream(自定义 Hook)
function useVideoStream(uuid) {
const mgr = useContext(StreamCtx);
useEffect(() => {
if (mgr.currentUUID === uuid) {
mgr.cancelDelayedStop(); // 同一路流,取消关闭,直接复用
} else {
mgr.startStream(uuid); // 不同流,新建
}
return () => mgr.scheduleDelayedStop(); // 离开时延迟关闭
}, [uuid]);
}
// 页面组件使用:
function CameraPage() {
useVideoStream('camera-uuid-001'); // 一行接入
return <div>...</div>;
}
可追问点
- 为什么用单例而不是每个页面各管各的?
- 延迟任务必须跨页面共享 —— 上一页设的 timeout,下一页要能 clearTimeout
- 如果各管各的,切页面时上一页的组件已卸载,useEffect cleanup 已执行,无法取消
- 单例通过 Context 存活于整个应用生命周期,天然支持跨页面状态共享
- 为什么封装成自定义 Hook?
- 涉及视频流的页面有几十个,如果每个页面都手写 useEffect + 判断 UUID + 延迟关闭,代码大量重复
- 封装成
useVideoStream(uuid)后,页面只需一行调用,视频管理逻辑完全内聚在 Hook 里
- 延迟任务怎么跨页面保存?
- SPA 路由切换时,Context 中的单例一直存在,timeoutID 自然保留
- 如果是完整页面刷新的场景,可以把视频流状态存到 sessionStorage 做兜底
4 多通道架构改造
口语版
口语版:摄像机要从单通道升级到多通道,但原来前端硬编码了 300 多个款型的能力集配置,每款上千行,根本没法适配多通道。我做的核心工作是把能力集从前端迁移到后端,通过接口按通道动态获取。难点是前后端字段对不上、很多老功能没人知道含义、提交者已离职,需要大量的跨团队业务梳理。同时把代码里按款型判断的 if/else 全部改成按能力判断,这样新设备上线前端不用改代码。最终 10 万行修改,5 个人 6 人月完成,比原计划省了 4 人月。
面试官想听什么
- 多通道改造的业务背景和技术复杂度在哪?
- 你是怎么做"系统化业务梳理"的?涉及跨团队协作吗?
- "超预期交付"具体是怎么做到的?
我会怎么讲
背景:摄像机从单通道(一个摄像头)升级到多通道(多个摄像头),不同通道支持的功能不一样。原来的前端架构是为单通道设计的,无法适配多通道场景,需要做架构改造。
核心问题:能力集冗余
- 能力集 = 一款设备支持哪些功能(哪些菜单显示、哪些功能可用)
- 原来前端维护了一套能力集:300+ 款型 × 每款 1000+ 行配置 = 极其冗余
- 后端也有一套自己的能力集字段,但和前端对不上
- 问题:多通道要求不同通道有不同能力,如果继续在前端硬编码,代码量爆炸且无法维护
具体改造手段
-
1. 能力集后移:将前端硬编码的能力集全部迁移到后端,通过接口按通道动态获取
- 难点:前后端字段对不上(命名不同、含义不同),需要逐个梳理映射关系
- 有些老功能连代码提交者都离职了,需要找 SE(产品经理)确认业务场景
- 有些字段有包含/重复关系(如"8方向"和"球机"其实表达同一个能力),需要去重整合
-
2. 去掉款型判断,改为能力判断:
-
原来代码里大量
if (is3516E)、if (isBallCamera)这种按款型判断 -
正确做法是按功能能力判断:
if (hasPTZ)、if (hasWiper) - 这样新增款型不需要改前端代码,只需后端配置能力集
-
原来代码里大量
- 3. 接口整改:老接口改成 RESTful 风格,用 UUID 标识不同通道
- 4. 业务流程整改:原来直接加载单通道页面 → 现在先获取通道列表 → 按当前通道加载对应能力和页面
业务梳理怎么做的
- 按模块分工(我负责 PTZ 云台模块),用 Excel 逐字段梳理前后端映射关系
- 自己了解的业务直接处理;不了解的和后端讨论;后端不确定的找设备支撑组、基础业务组
- 非常老的功能、提交者已离职 → 上升到 SE 确认原始业务场景
- 最终提交 10 万行代码修改,5 人团队用 6 人月完成(原计划 10 人月),节省 4 人月
可追问点
-
能力集为什么应该放后端?
- 能力集和设备硬件相关,不随用户操作变化 → 属于服务端数据
- 前端维护 300+ 款型配置,每次新设备上线都要改前端代码发版
- 后端维护后,新款型只需配置数据库,前端零改动
-
怎么做到超预期交付的?
- 前期业务梳理做得扎实(Excel 字段映射),减少了后期返工
- 和后端、SE 的沟通前置,避免开发到一半发现字段对不上再改
- 按模块并行开发,每人负责一个模块独立推进
-
举个"遗留问题"的例子?
- 雨刷功能:老款用一个字段控制,新款"维多雨刷"用另一个字段,前端和后端各有一套
- 我把前端的字段控制去掉,统一改为后端接口传值,用 0/1/2 分别表示无雨刷、老雨刷、维多雨刷