HoloSens 摄像机控制平台 · 简历关键词速查

华为技术有限公司 · 前端工程师 | HoloSens 摄像机平台 | 项目合作 · 2020.05 – 2022.01
平台服务摄像机 200w+ 台,支持款型 300+,负责云台控制(PTZ)模块

2 任务池机制

简历原文:引入任务池机制,进行跨业务模块的请求缓存和优化,冗余请求减少 30%,平台性能提高 20%

口语版

口语版:平台多个业务模块会请求相同接口,我设计了一个请求缓存池,用 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 延迟任务与单例模式

简历原文:使用延迟任务和单例模式,实现页面视频流资源的跨页面复用,减少切换页面时控件重新加载导致的性能问题,覆盖 30% 核心页面,加载时间缩短 90%

口语版

口语版:摄像机的视频流组件打开一次要 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 多通道架构改造

简历原文:通过系统化的业务梳理和针对性解决遗留问题,实现项目重写和多通道架构改造,超预期交付,效率提高 40%,减少 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 分别表示无雨刷、老雨刷、维多雨刷