怎么用 这是格式与深度的参考,不是让你照抄的模板。数字全部是示例,直接抄进简历一问就穿——必须换成你自己的真实数据,而且要能说清是怎么测出来的。下面每个模块都有「数字是怎么测的」和「面试追问」两段,写不出那两段的模块就不要往简历上写

面试题库(电商 / 短视频 / 社区 / 本地生活 / 旅游 / 企业服务 多业务场景)

实习级这一档是干什么的 大文件分片上传、多维数据看板这些创作者后台的核心台面,实习生大概率碰不到。这一档收的是创作者中心里实习生真的会被分派、但仍然有明确技术含量的活。下面三个模块彼此独立——只做过一个就只写一个。
怎么讲才不显得 low 「我做了个封面裁剪」和「创作者裁完发现前台显示的和他裁的不一样,因为我按 CSS 显示尺寸算的裁剪参数,而图片的实际像素尺寸不同,必须按自然尺寸换算」——同一件事,后者面试官会顺着追问。这一档的破解办法是找到「涉及坐标换算、时间换算、状态同步」的那一层:裁剪的坐标系、定时发布的时区、轮询的状态机,每一个都有明确的对错。
三条自检 一、能说出不这么做会怎样(坐标算错裁出来的图偏了、时区没处理创作者定的晚八点发成了凌晨、轮询不停页面挂一天发几千个请求);二、能说出你踩过的具体坑;三、能说出量级(多少视频、轮询间隔多少、图多大)。三条都有就能写。
项目背景设定 短视频平台的创作者中心,Vue 3 + TypeScript + Pinia + Vite。使用者是内容创作者(不是内部运营),所以对错误提示和操作反馈的要求更高——他们不会看日志、也不会来问开发。
为什么这三块值得写 它们的技术含量集中在换算与状态:裁剪要处理显示坐标系和自然像素坐标系的换算、定时发布要处理时区与服务端时间、状态轮询要处理长时页面的资源与终止条件。这三类问题都有明确的对错,而且错了用户能直接看见,不是「我用了某个组件库」能糊过去的。

模块一:封面选择与裁剪

  1. 封面选择与裁剪(抽帧候选 + 显示坐标到自然像素的换算 + 比例约束 + 裁剪参数交服务端出图)★★
    简历这样写 视频封面选择与裁剪(Vue 3 + Canvas + 坐标换算 + 服务端出图):封面支持从转码抽帧的候选里选或上传自定义图;裁剪框的坐标按图片自然像素尺寸换算而非 CSS 显示尺寸(原按显示尺寸传参,图片被容器缩放后裁出的区域整体偏移);按各分发位的比例要求做比例约束与最小尺寸校验,不满足时在裁剪阶段就提示而非提交后被拒;最终由服务端按裁剪参数出图,前端只负责交互与预览(前端出图受设备与浏览器差异影响且无法复现);上传前做格式与体积校验并给出可定位的提示。改造后「裁完和显示不一致」与「发布时才被告知封面不合规」两类反馈不再出现。
    展开完整拆解
    为什么要这么设计

    封面裁剪看起来是接一个裁剪组件就完事,第一版两天做完。上线后四类反馈,每一类都是用户能直接看见的错。

    一是裁出来的和看到的不一样。创作者在裁剪框里框住了人脸,出图之后人脸偏到了角落。原因是我把裁剪框的坐标(相对于页面上显示的图片)直接当成了裁剪参数传给服务端,而那张图在页面上是被容器缩放显示的——显示宽度 600px,实际是 1920px。坐标差了三倍多,裁出来的区域完全不对。这是最典型的坐标系错误。

    二是发布时才被告知封面不合规。分发位对封面有比例和最小尺寸要求。创作者裁完、填完标题、点发布,才被告知「封面尺寸不足」——他得回去重新裁,而且不知道要裁多大。这个校验放错了位置。

    三是前端出图在不同设备上不一致。我最初用 Canvas 在前端裁好再上传。结果不同浏览器的图片解码和缩放算法有差异,同一张图在不同设备上裁出来的清晰度和色彩略有不同;更麻烦的是出了问题无法复现——用户说图糊了,我在自己机器上裁一遍是清楚的。

    四是没有候选帧,创作者只能自己上传。大部分创作者懒得单独做封面图,他希望从视频里挑一帧。而转码时其实已经抽了帧,只是没暴露给前端。

    所以四个改动:坐标按自然像素换算比例与尺寸校验前置到裁剪阶段前端只传裁剪参数、由服务端出图暴露转码抽帧的候选让创作者直接选

    这个模块最想说的一句话是:涉及图像的交互,前端负责「让用户表达意图」,服务端负责「按意图产出结果」。前端出图看起来少一次往返,但它把产出质量绑定到了用户的设备和浏览器上,而这是你完全不可控也无法复现的变量。想清楚这个分工,后面的问题就少一半。

    整体链路
    封面来源(两条路,都通向同一套裁剪交互) │ ├─ 从视频抽帧候选里选(转码时已抽好,接口返回若干候选) │ 默认选中一帧,避免创作者什么都不选就发布 │ └─ 上传自定义图 入口就校验:格式白名单 / 体积上限 / 最小像素尺寸 不合格立刻拒绝并说清要求,不要等到裁剪或发布 裁剪交互(关键是两套坐标系的换算) │ ├─ 图片在页面上是被容器缩放显示的 │ displayWidth 容器里的显示宽度 │ naturalWidth 图片的实际像素宽度 │ scale = naturalWidth / displayWidth │ ├─ 用户拖出的裁剪框坐标是「显示坐标系」的 │ cropX_display / cropY_display / cropW_display / cropH_display │ ├─ 传给服务端前必须换算成「自然像素坐标系」 │ cropX_natural = round(cropX_display * scale) │ 同理换算 Y / W / H │ 不换算的后果:图被缩放显示时,裁剪区域整体偏移且尺寸错误 │ ├─ 换算后要做边界钳制(防止浮点误差导致超出图片范围) │ └─ 窗口 resize 或容器变化 → 重算 scale 忘了重算的话,用户调整窗口后裁剪就偏了 约束(在裁剪阶段就生效,不留到发布时) │ ├─ 比例锁定:按目标分发位的比例(如 16:9)限制裁剪框 │ ├─ 最小尺寸:裁剪框对应的自然像素不能小于阈值 │ 框太小时不允许继续缩小,并提示「已达最小尺寸」 │ └─ 多比例需求:同一张原图裁出多个比例 分别保存各比例的裁剪参数,而不是让用户重新上传 出图(服务端做) │ ├─ 前端只提交:原图标识 + 各比例的裁剪参数 │ ├─ 服务端按参数裁剪并生成各规格 → 返回封面标识与版本 │ 好处:结果与设备无关、可复现、可重新出图(参数留着) │ └─ 前端拿返回的地址展示预览(所见即最终结果) 预览 ├─ 预览用服务端出的图,不用前端 Canvas 的结果 └─ 同时给出各分发位的模拟位(信息流小卡 / 详情大图) 让创作者看到「在不同位置长什么样」,比只看一张裁剪图有用
    分步拆解
    1. 坐标必须按自然像素换算,这是这个模块最容易错也最致命的一条。页面上显示的图片几乎总是被容器缩放过的,显示坐标和自然像素坐标之间有一个缩放系数。不换算的后果是裁剪区域整体偏移且尺寸错误——用户框住人脸,出图后人脸在角落。
    2. 缩放系数要在图片真正加载完之后取。naturalWidth 在图片加载完成前是 0,此时算出的 scale 是无穷或 0。必须在 load 事件之后再初始化裁剪器。
    3. 窗口 resize 或容器尺寸变化时要重算系数。用户拖动浏览器窗口,图片显示尺寸变了,如果 scale 还是旧的,裁剪就偏了。这个坑很隐蔽,因为大部分测试不会去改窗口大小。
    4. 换算后要做边界钳制。浮点运算和取整可能让计算出的区域超出图片范围(比如 x + w 比图宽多 1 像素),服务端会报参数错误。前端钳制一下比让服务端拒绝更好。
    5. 比例和最小尺寸的校验要在裁剪阶段生效,不能留到发布时。发布时才拒绝,用户已经填完了所有内容。做法是比例锁定裁剪框、框到最小尺寸时不允许再缩小并提示——把约束变成交互的一部分,用户压根不会做出不合规的选择。
    6. 多比例需求要分别保存裁剪参数,不要让用户重新上传。信息流要 16:9、分享卡片要 1:1。同一张原图裁两次,保存两组参数,而不是让他上传两张图。
    7. 出图交给服务端,前端只传参数。前端 Canvas 出图的三个问题:不同浏览器的解码和缩放算法有差异(清晰度色彩不一致)、出了问题无法复现(你的设备上是好的)、参数没留下(想重新出图只能让用户重裁)。传参数则这三个问题都没有。
    8. 裁剪参数要存下来。以后新增一个规格,可以用原图加已存的参数重新出图,不需要打扰用户。这是「传参数」相比「传成品图」的额外收益。
    9. 抽帧候选要给默认选中。不给默认的话,很多创作者会跳过封面直接发布,最后用系统兜底图,效果不好。默认选一帧(挑清晰度高的)大幅提升封面质量。
    10. 上传入口就要校验格式、体积、最小像素。不合格立刻拒绝并说清要求(「图片宽度需不小于 X 像素,当前 Y」)。可定位的提示是这类功能好不好用的分水岭。
    11. 预览要用服务端出的图,不要用前端的 Canvas 结果。否则预览和最终结果可能不一致,而用户是按预览做判断的。
    12. 给各分发位的模拟展示。同一张封面在信息流小卡和详情大图里的观感差别很大(小卡上文字可能看不清)。提供模拟位让创作者提前发现问题,成本很低但价值高。
    关键决策与取舍

    服务端出图的代价是多一次往返、预览有延迟。前端 Canvas 出图可以立刻预览。但它把产出质量绑定到了用户的设备和浏览器上——这是完全不可控且无法复现的变量。用户说「裁出来的图糊了」,我在自己机器上裁一遍是清楚的,这种问题排查不了。服务端出图的结果是确定的、可复现的、而且裁剪参数留了下来(以后新增规格可以重新出图不打扰用户)。判据是「产出物的质量要不要可控」——要,就必须放服务端。

    比例约束选「锁定裁剪框」而不是「提交时校验」。锁定的代价是用户不能自由裁(想裁个方形也不行)。但自由裁加提交校验的体验更差:他裁完了才被告知比例不对,还得猜怎么裁才对。把约束变成交互的一部分,用户压根做不出不合规的选择——这比事后拒绝好得多。这个原则在表单设计里也一样:能用控件限制的就不要靠校验提示。

    抽帧候选数量控制在几张,不做「任意时间点抽帧」。让用户拖进度条任意选帧体验最好,但那需要前端解码视频(性能和兼容性都是问题)或者服务端按需抽帧(每次拖动一次请求)。预抽几张候选是成本和体验的折中,覆盖了大部分需求。

    踩过的坑一:按显示坐标传裁剪参数,裁出来整体偏移。这个 bug 上线后立刻有人反馈。更值得说的是我当时的第一反应是「服务端裁错了」,让后端同学查了半天。后来才发现是我传的参数就是错的——坐标系搞混了。教训是:凡是涉及坐标、单位、时区的参数传递,都要明确「这是哪个坐标系/哪个单位/哪个时区的值」,最好在字段名或注释里写清楚。我们后来把字段名改成了 cropXNatural 这种带坐标系标识的名字。

    踩过的坑二:在图片 load 之前初始化裁剪器,scale 算成了无穷。naturalWidth 此时是 0,除法结果是 Infinity,裁剪框一动就报错。修法是等 load 事件。这类「依赖的东西此刻还不存在」的时序问题在前端很常见,判据是「我读的这个属性,此刻真的有值了吗」

    踩过的坑三:窗口 resize 后没重算 scale,用户调整窗口再裁就偏了。这个坑很隐蔽——正常测试不会去改窗口大小,但用户会(尤其是笔记本外接显示器切换时)。修法是监听容器尺寸变化重算。这也提醒我:「一次算好就不再变」的假设,在响应式布局里通常不成立。

    没做的部分:没做封面的 A/B 测试(同一视频用两个封面看点击率)。产品提过,但它需要分发侧支持按用户分流展示不同封面,不是前端能独立完成的。前端这边留了扩展点:因为封面存的是「原图 + 多组裁剪参数」,支持多个封面版本在数据结构上是自然的。

    数字是怎么测的

    坐标换算的正确性用构造用例验证,这是最该测的一条:准备几张不同分辨率的图(比如 800px 宽和 4000px 宽),在同样的显示尺寸下裁同样的区域,断言换算出的自然像素参数与预期一致关键是要用「显示尺寸相同但自然尺寸不同」的图——如果测试图恰好没被缩放(自然尺寸等于显示尺寸),这个 bug 永远测不出来,而我最初就是这样。

    裁剪结果的一致性验证:同一组裁剪参数,断言服务端出的图与预览显示的区域一致。做法是拿出图后的图和原图对比,检查裁剪区域的四个角是否落在预期位置。这个可以半自动化(对比像素)。

    「发布时才被告知不合规」不再出现:断言型——尝试裁一个小于最小尺寸的区域,断言在裁剪阶段就被限制(框无法继续缩小并有提示),而不是提交时被拒。

    resize 场景:手工验证——裁好后改变窗口大小、再次调整裁剪框,断言参数仍然正确。这一条必须手工测,自动化测试不容易模拟窗口变化。

    上传校验的提示质量:不是数字指标,报「提示是否包含当前值与要求值」这个事实(「需不小于 800 像素,当前 640」)。可定位的提示是这类功能的关键。

    不要报什么:不要报「裁剪速度」——出图在服务端,前端的耗时就是一次网络往返。该报的是「不同分辨率下坐标换算正确」「裁剪结果与预览一致」「约束在交互阶段生效」这三件可验证的事。

    面试追问
    Q:为什么不在前端用 Canvas 直接裁好上传?少一次往返,预览也快。 A:因为它把产出物的质量绑定到了用户的设备和浏览器上,而这是我完全不可控也无法复现的变量。具体三个问题:一是不同浏览器的图片解码和缩放算法有差异,同一张图在不同设备上裁出来的清晰度和色彩略有不同;二是出了问题无法复现——用户说「图糊了」,我在自己机器上裁一遍是清楚的,这种问题压根没法排查;三是参数没留下,以后产品新增一个规格,只能让用户重新裁一遍。改成「前端只传裁剪参数、服务端出图」之后,这三个问题一起消失:结果确定可复现、参数存着可以随时重新出图我总结的分工原则是:前端负责让用户表达意图,服务端负责按意图产出结果。代价是预览要等一次往返,但这个代价换来的是可控性,我认为值得。另外前端 Canvas 仍然有用——用来做实时的裁剪框交互预览,只是不作为最终产出。
    Q:坐标换算这个 bug,为什么测试的时候没发现? A:因为我的测试图恰好没被缩放。我用的测试图宽度就在容器宽度范围内,显示尺寸等于自然尺寸,scale 正好是 1,换算与不换算结果一样。这个 bug 只在图片被缩放显示时才出现,而真实用户上传的都是几千像素宽的大图。这件事让我明白了一类测试盲区:当某个系数恰好是 1(或 0)时,用不用它都一样,错误被掩盖了。所以我后来的做法是刻意用「极端且不同」的样本测——一张很小的图(自然尺寸小于显示尺寸)、一张很大的图(远大于显示尺寸),两种情况的 scale 分别小于 1 和远大于 1,这样任何换算错误都会立刻暴露。这个思路对所有涉及单位换算、比例缩放、时区偏移的功能都适用:不要用「恰好不需要换算」的样本测试换算逻辑。

模块二:视频列表与状态轮询

  1. 视频列表与状态轮询(仅对中间态轮询 + 退避与终止条件 + 页面不可见时暂停 + 状态变化的局部更新)★★
    简历这样写 创作者视频列表与状态同步(Vue 3 + Pinia + 退避轮询 + 可见性感知):视频存在转码中、审核中等中间态,需要在列表上持续反映进展;原实现对整页无条件定时轮询,页面挂一天会产生大量无效请求,改为只对处于中间态的条目轮询间隔退避、并在页面不可见时暂停、恢复可见时立即补拉一次;轮询有明确终止条件(全部进入终态、或超过最长等待时长后转为提示手动刷新),避免永久轮询;状态返回后做局部更新而非整列表重渲染,避免用户正在操作时列表跳动。改造后该页面的日均请求量大幅下降,长时间挂页不再出现请求堆积。
    展开完整拆解
    为什么要这么设计

    视频发布之后不是立刻可见的:要转码、要机审、可能还要人工审核。创作者会盯着列表等,所以列表必须能反映进展。第一版的做法是最直觉的:定时器每几秒拉一次整个列表。四个问题。

    一是请求量失控。创作者习惯把这个页面挂在一个标签页里一整天。每几秒一次,一天就是上万次请求,而其中绝大多数返回的数据完全没变——他的视频早就都发布完了,没有任何中间态。这些请求纯属浪费,而且乘上创作者数量之后对服务端是实打实的压力。

    二是切到后台还在轮询。浏览器切到别的标签页,定时器还在跑(虽然会被节流但仍在发请求),手机端更明显:切到后台继续轮询会耗电

    三是整列表重渲染导致操作被打断。轮询回来直接替换整个列表数据,用户正在某个条目上悬停操作菜单,列表一刷新菜单就关了;如果他正在输入搜索框,滚动位置也可能跳。这个体验问题被反馈了好几次。

    四是永久轮询。有个视频转码失败了但状态没更新(服务端的 bug),它永远停在「转码中」。前端就永远轮询下去,一天几万次请求都在问同一个视频。没有终止条件是个隐患。

    所以四个改动:只对中间态条目轮询(没有中间态就不轮询)、间隔退避(越等越久,因为越等越可能是慢任务)、页面不可见时暂停、恢复时立即补拉有明确终止条件(超过最长时长转为提示手动刷新)。

    这个模块最想说的一句话是:轮询的设计重点不是「多久拉一次」,是「什么时候不拉」。只对中间态拉、不可见时不拉、超时后不拉——三个「不拉」加起来把请求量降了一个量级,而功能上用户完全感觉不到差别。这个视角我觉得比调间隔参数有价值得多。

    整体链路
    状态模型(先分清哪些是中间态) │ ├─ 中间态:上传中 · 转码中 · 待审核 · 审核中 · 定时待发 │ 只有这些需要轮询 │ └─ 终态:已发布 · 已下架 · 审核不通过 · 转码失败 进入终态就不再轮询该条目 轮询的启动条件 │ ├─ 列表加载后扫描:有中间态条目吗 │ 没有 → 压根不启动定时器(这一条就消掉了大部分请求) │ 有 → 启动,并记下需要关注的条目 ID 列表 │ └─ 轮询请求只带这些 ID,不拉整个列表 返回体积小、服务端也只查这几条 间隔退避 │ ├─ 起始间隔短(刚提交时状态变化快,用户也在盯着) ├─ 每轮乘一个系数,直到上限 │ 理由:等得越久说明是慢任务,再高频问也不会更快 └─ 有条目状态发生变化 → 间隔重置为起始值 因为状态在动,说明处理正在推进,值得多看几眼 页面可见性 │ ├─ 不可见(切标签页 / 切后台)→ 暂停定时器 │ 不是继续跑而是真的停,省请求也省电 │ └─ 恢复可见 → 立即补拉一次,再按退避继续 用户切回来最关心「现在什么状态」,等下一个周期太慢 终止条件(三个,任一满足即停) │ ├─ 全部关注条目进终态 → 停止(这是正常结束) │ ├─ 超过最长等待时长 → 停止并提示「处理时间较长,可手动刷新」 │ 防的是服务端状态卡住导致前端永久轮询 │ └─ 连续请求失败达阈值 → 停止并提示网络异常,给手动重试 不要在断网时无限重试 状态更新:局部而非整体 │ ├─ 按条目 ID 定位并只更新变化的字段 │ 不替换整个列表数组,避免整列表重渲染 │ ├─ 列表用稳定的 key(视频 ID),保证 DOM 复用 │ 用索引做 key 会导致条目错位与组件状态错乱 │ └─ 用户正在交互的条目(菜单展开 / 正在编辑)→ 延后更新 正在操作时被刷新是很差的体验 终态的呈现 ├─ 失败态要给原因与下一步(重新上传 / 申诉 / 查看规则) └─ 审核不通过要能看到具体不通过的点,而不是只有结论
    分步拆解
    1. 先明确划分中间态和终态,这是整个设计的前提。只有中间态需要轮询。列表里没有任何中间态时压根不启动定时器——这一条消掉的请求量最大,因为创作者大部分时候的视频都已经是终态了。
    2. 轮询请求只带需要关注的条目 ID,不拉整个列表。返回体积小、服务端只查这几条、前端也不用担心整列表被替换。
    3. 间隔要退避。刚提交时状态变化快、用户也在盯着,所以起始间隔短;等了几分钟还没完成说明是慢任务,再高频问也不会更快。退避到一个上限就不再增加。
    4. 状态发生变化时把间隔重置。说明处理正在推进,值得多看几眼。这个细节让「快任务快反馈、慢任务少打扰」两个目标同时达成。
    5. 页面不可见时要真的暂停,不是靠浏览器节流。浏览器对后台标签页的定时器有节流但不会停,而移动端切后台继续轮询是实打实的耗电。用可见性变化事件显式暂停。
    6. 恢复可见时立即补拉一次。用户切回来最关心「现在什么状态」,等下一个周期(可能已退避到几十秒)太慢,会让他觉得页面卡住了。
    7. 必须有最长等待时长的终止条件。防的是服务端状态卡住导致前端永久轮询——我们真的遇到过一个视频永远停在「转码中」,前端一天问了几万次同一个视频。超时后停止并提示「处理时间较长,可手动刷新」。
    8. 连续失败要停止并给手动重试。断网时无限重试没有意义,只是耗电和产生错误日志。
    9. 状态更新要局部,不要替换整个列表数组。整体替换会导致整列表重渲染,用户正在悬停的操作菜单会关闭、正在编辑的输入会丢。做法是按 ID 定位条目、只更新变化的字段。
    10. 列表的 key 必须是稳定的业务 ID。用数组索引做 key,条目顺序一变就会导致 DOM 错位复用、组件内部状态串到别的条目上。这是列表渲染最经典的坑。
    11. 用户正在交互的条目要延后更新。菜单展开着、正在编辑标题时,先把更新暂存,等交互结束再应用。这一条成本不高但体验差别明显。
    12. 失败态必须给原因和下一步。「转码失败」要说明可能的原因(格式不支持、文件损坏)并给重新上传入口;「审核不通过」要能看到具体不通过的点。只给结论的失败态等于把问题丢回给用户而不给出路。
    关键决策与取舍

    用轮询而不是长连接或服务端推送。推送能做到即时且请求量最小,但它需要服务端支持长连接、要处理连接管理与重连、而这个页面的实时性要求其实不高(转码要几十秒到几分钟,晚几秒知道无所谓)。判据是「实时性要求」和「引入长连接的边际成本」——这个场景下优化好的轮询已经够用,而长连接是一整套基础设施。如果平台本来就有长连接通道(比如做私信用的),那复用它是更好的选择。

    退避的上限设得不太大,接受慢任务的反馈延迟。上限设很大能进一步省请求,但用户等了十分钟终于转码完成,却要再等一分钟才看到状态变化,体感很差。所以上限控制在可接受的延迟内,并配合「恢复可见时立即补拉」来兜住主要场景——用户真正关心的时刻通常是他刚切回页面的时候。

    最长等待时长设置的依据是「正常处理时长的上界再乘一个余量」。不能拍脑袋——设短了正常的慢任务会被误判为卡住,设长了起不到防护作用。做法是先统计真实的处理时长分布,取一个覆盖绝大多数正常情况的值再放宽。这个值也要可配置,因为处理时长会随业务变化。

    踩过的坑一:整列表替换导致用户操作被打断。创作者反馈「我点开菜单它自己就关了」。排查出来是轮询回来替换了整个列表数据,Vue 重新渲染了所有条目。修法是局部更新加稳定 key。这件事让我理解了「数据更新的粒度」和「渲染的粒度」是两件事——数据全量返回没问题,但应用到视图时要 diff 到字段级。

    踩过的坑二:一个视频永远停在「转码中」,前端一天问了几万次。是服务端的状态没更新(转码失败但没写回)。但这暴露的是前端的问题:没有终止条件。教训是任何等待外部状态的轮询都必须有超时退出——不能假设外部状态一定会到达终态。这和后端「异步任务必须有卡死兜底扫描」是同一个道理,只是换到了前端。

    踩过的坑三:用数组索引做列表 key,删除一个视频后剩下的条目状态错乱。某个条目的展开状态跑到了另一个条目上。这是 Vue/React 列表渲染的经典坑,根因是索引不稳定(删除会让后面所有元素的索引变化)。修法是用视频 ID。

    没做的部分:没做「转码进度百分比」。产品想要一个进度条,但转码的进度需要转码服务上报,而它只有开始和结束两个事件,中间的百分比是假的(用时间估算)。假进度条会带来新的问题(卡在 99% 不动比没有进度条更让人焦虑),所以我们选择只显示状态文字加预估剩余时间范围。

    数字是怎么测的

    请求量下降:「单个页面挂一小时产生的请求次数」,分两种场景:列表全是终态(改造后应为 0 或极少)、列表有中间态(按退避序列可以精确算出)。分场景报比报一个平均值有说服力得多,因为节省主要来自第一种场景。

    退避序列可以直接算不用测:给出起始间隔、退避系数、上限,就能算出「N 分钟内发多少次请求」。把这个序列写出来比给一个模糊的「减少了很多」清楚。

    可见性暂停的验证:切到别的标签页,观察网络面板确认没有新请求;切回来确认立即有一次请求。这是手工可验证的,而且必须验证——因为浏览器的节流行为容易让人误以为已经停了。

    终止条件的验证:构造一个永远停在中间态的条目(mock 接口固定返回中间态),断言超过最长时长后轮询停止且出现手动刷新提示。这一条必须测,它防的是最严重的隐患。

    局部更新的验证:展开某条目的操作菜单,等一轮轮询回来,断言菜单仍然是展开的。这是个可以自动化的断言,而且它直接对应用户反馈的那个问题。

    不要报什么:不要报「状态同步延迟降低」——退避实际上让慢任务的反馈延迟变大了,硬说降低是不诚实的。该报的是「请求量按场景的下降」「有终止条件」「操作不被打断」这三件事,并诚实说明退避带来的延迟以及用可见性补拉来缓解。

    面试追问
    Q:为什么不用 WebSocket 推送?那样最省请求也最即时。 A:如果平台已经有长连接通道(比如做私信用的),复用它确实是更好的选择,我会优先考虑。但如果要为这个页面单独引入长连接,我认为不值得,理由是实时性要求和边际成本不匹配:转码要几十秒到几分钟,晚几秒知道状态对创作者没有实质影响;而引入长连接意味着服务端要支持连接管理、要处理认证与重连、要考虑连接数与网关配置,这是一整套基础设施的成本而优化好的轮询已经把请求量降到很低——关键不在于「多久拉一次」,而在于「什么时候不拉」:没有中间态就不启动、页面不可见就暂停、超时就停止。这三个「不拉」加起来降了一个量级,而用户感觉不到差别。我觉得这个判断方法比「哪个技术更先进」更实用:先看优化现有方案能不能达到要求,达不到再换方案。
    Q:轮询没有终止条件这个问题,你觉得根本原因是什么? A:根本原因是我假设了「外部状态一定会到达终态」。写代码时的心智模型是「转码总会结束的,要么成功要么失败」,所以只写了「到终态就停」这一个退出条件。但真实系统里状态可能卡住——服务端转码失败了但没写回状态,那个视频就永远是「转码中」,前端就永远轮询下去,一天问了几万次同一个视频。这件事让我总结出一条:任何等待外部状态的循环都必须有独立于「期望结果」的退出条件,也就是超时。而且这条原则和后端「异步任务必须有卡死兜死扫描」是完全同一个道理,只是一个在前端表现为无限轮询,一个在后端表现为任务永远停在执行中。我后来做任何等待类逻辑(轮询、重试、等待回调)都会先问「如果它永远不来,怎么退出」。

模块三:草稿箱与定时发布

  1. 草稿箱与定时发布(时间带时区提交 + 以服务端时间为准校验 + 定时可改可撤 + 草稿与定时状态分离)★★
    简历这样写 草稿箱与定时发布(Vue 3 + Pinia + 时区处理 + 服务端时间校准):定时发布的时间一律以带时区的绝对时刻提交并在界面上明示时区(原按浏览器本地时间提交,跨时区创作者设定的晚八点在服务端成了另一个时刻);「是否为未来时间」的校验以服务端时间为准(客户端时钟可能不准,本地校验通过而服务端拒绝会让用户困惑);定时任务可修改可撤销并在列表上明确展示「将于某时刻发布」;草稿与定时是两种状态而非一种(草稿是未完成、定时是已完成待触发),入口与操作各自独立;离开页面前有未保存改动时拦截提示。改造后跨时区创作者的定时发布时刻不再出现偏差,「以为存了草稿其实没存」的反馈消失。
    展开完整拆解
    为什么要这么设计

    这个功能的坑集中在两件事上:时间状态语义。四个具体问题。

    一是跨时区的定时发布错了。创作者在海外,他设定「晚上八点发布」。我把日期时间选择器的值直接格式化成字符串提交(2026-08-26 20:00:00),服务端按自己的时区解析,于是在服务端时区的晚八点发布了——对创作者来说可能是凌晨。他的粉丝在他的时区,这个错误直接影响了内容的曝光效果。这是最严重的问题。

    二是客户端时钟不准导致校验不一致。前端校验「必须是未来时间」用的是浏览器的当前时间。有的用户系统时钟慢了几分钟,他选了一个自己看来是未来的时间,前端通过了,服务端却拒绝了——提示「不能设置过去的时间」,而他明明选的是未来。这类问题用户完全无法理解。

    三是草稿和定时被混成了一种状态。我最初都归为「未发布」,列表上显示一样。但它们的语义完全不同:草稿是「还没写完,随时可以继续改」;定时是「已经写完了,等时间到自动发」。混在一起的后果是创作者搞不清哪些会自动发、哪些不会,有人以为定时的还能继续改,结果它自己发出去了。

    四是离开页面丢内容。编辑了一半点了别的菜单,内容没了。而这个页面的内容不像评论那样短,创作者可能写了很长的描述和标签

    所以四个改动:时间以带时区的绝对时刻提交并在界面明示时区「是否未来」的校验以服务端时间为准草稿与定时分成两种独立状态离开前拦截未保存改动

    这个模块最想说的一句话是:只要涉及时间,就必须回答「这是哪个时区的时间」和「以谁的时钟为准」这两个问题。我最初两个都没回答——提交了一个没有时区信息的字符串、用客户端时钟做校验,于是两个问题各自产生了一类 bug。这条经验在做任何跨端、跨地域的时间处理时都适用。

    整体链路
    时间的表示与传输(核心) │ ├─ 用户在界面上选的是「他所在时区的墙上时间」 │ 比如他看到并选择了「8 月 26 日 20:00」 │ ├─ 提交时转成带时区信息的绝对时刻 │ 两种可接受的做法: │ 带偏移的 ISO 串 2026-08-26T20:00:00+08:00 │ 或 时间戳 + 时区标识 │ 绝不提交 "2026-08-26 20:00:00" 这种裸字符串 │ 裸字符串的问题:服务端只能按自己的时区猜,猜错就是几小时偏差 │ ├─ 界面上明示时区:「将于 8 月 26 日 20:00(GMT+8)发布」 │ 让创作者能自己发现时区是否符合预期 │ └─ 列表展示已定时的时间时,同样按其时区展示并标注 「是否未来时间」的校验(以服务端时间为准) │ ├─ 页面加载时取一次服务端时间 → 算出与本地时钟的偏移 │ offset = serverTime - localTime │ ├─ 校验时用「本地时间 + offset」作为当前时刻 │ 这样客户端时钟不准也不影响判断 │ ├─ 还要留一个最小提前量(比如不能定在一分钟内) │ 否则提交的瞬间时间就过去了,服务端会拒绝 │ └─ 服务端仍然要校验(前端校验只为体验,不能替代) 状态语义:草稿与定时是两种状态 │ ├─ 草稿 未完成,不会自动发布,随时可改可删 │ 必填项可以缺失(允许存一半) │ ├─ 定时 已完成且校验通过,到点自动发布 │ 必填项必须齐全(等于已经过了发布校验) │ 列表上明确显示「将于某时刻发布」 │ ├─ 两者的入口与操作各自独立 │ 「存草稿」和「定时发布」是两个按钮,不是一个按钮两种模式 │ └─ 定时可以「转回草稿」(撤销定时),也可以改时间 撤销要有明确反馈:「已取消定时,当前为草稿」 草稿的保存 │ ├─ 手动「存草稿」按钮(明确的用户意图) │ ├─ 自动保存作为兜底:输入防抖后保存 + 离开页面前保存 │ 自动保存要有可见反馈(「已自动保存 12:30」) │ 没有反馈的自动保存等于没有 —— 用户不知道存没存 │ └─ 保存失败要提示并保留内容在页面上,不要静默失败 离开页面的拦截 ├─ 有未保存改动时,路由跳转前确认 ├─ 关闭标签页时用浏览器原生的离开提示 └─ 已成功保存则不拦截(避免每次离开都弹窗,那会让人麻木) 到点发布之后 ├─ 列表状态变为已发布(靠模块二的轮询或进入页面时刷新) └─ 发布失败(比如审核不通过)要通知创作者,不能静默
    分步拆解
    1. 时间一律以带时区的绝对时刻提交。带偏移的 ISO 串或者时间戳加时区标识都可以,绝不提交裸的日期时间字符串——服务端只能按自己的时区去猜,猜错就是几小时的偏差。这是这个模块最重要的一条。
    2. 界面上必须明示时区。「将于 8 月 26 日 20:00(GMT+8)发布」。让创作者能自己发现时区是否符合预期,尤其是他在旅行或者用了 VPN 导致浏览器时区变化时。
    3. 列表展示已定时的时间时也要标注时区。否则他在另一个时区打开后台,看到的时间和他设定时看到的不一样,会以为系统改了他的设置。
    4. 「是否未来时间」的校验以服务端时间为准。页面加载时取一次服务端时间算出偏移,校验时用「本地时间加偏移」。客户端时钟不准是真实存在的,用它做校验会出现「前端通过服务端拒绝」的困惑。
    5. 要留一个最小提前量。不能允许定在一分钟内——用户选了「一分钟后」,填完表单提交时那个时刻已经过去了,服务端会拒绝。在选择器上就限制可选范围。
    6. 前端校验不能替代服务端校验。前端校验只为提前告知,服务端必须再校验一次(客户端可以被绕过、而且提交到处理之间还有时间流逝)。
    7. 草稿和定时必须是两种独立状态,这是语义问题不是实现问题。草稿是「未完成」,定时是「已完成待触发」。混成一种的后果是创作者搞不清哪些会自动发——我们真的遇到有人以为定时的还能改,结果它自己发出去了。
    8. 入口要分开:「存草稿」和「定时发布」是两个按钮。不要做成一个按钮加一个「是否定时」的开关,那样用户容易误操作(想存草稿结果设成了定时)。
    9. 草稿允许必填项缺失,定时必须齐全。因为定时等于「已经通过了发布校验,只是延后执行」。如果允许定时时缺必填项,到点发布会失败,而用户已经不在页面上了。
    10. 定时要能改时间也能撤销。撤销后转回草稿并给明确反馈(「已取消定时,当前为草稿」)。不可撤销的定时是很让人紧张的功能。
    11. 自动保存必须有可见反馈。「已自动保存 12:30」。没有反馈的自动保存等于没有——用户不知道存没存,还是会担心内容丢失。
    12. 保存失败要提示并保留页面上的内容。不要静默失败,也不要清空输入。这一条和「用户输入过的内容一个字都不能丢」是同一个原则。
    13. 离开拦截只在有未保存改动时触发。每次离开都弹窗会让用户麻木,最后条件反射地点「确定离开」,反而在真的有改动时也丢了内容。
    14. 到点发布失败要通知创作者。比如审核不通过。他已经不在页面上了,只能靠通知——静默失败的话他以为发出去了,过几天才发现没有。
    关键决策与取舍

    时间提交格式选「带偏移的 ISO 串」而不是「时间戳」。时间戳最没有歧义,但它丢失了「用户当时看到的是哪个时区的几点」这个信息。而这个信息在展示和排查时都有用——列表要按创作者的时区展示、出问题时要能还原他当时的选择。带偏移的 ISO 串同时保留了绝对时刻和时区信息,是更好的选择。如果后端更习惯时间戳,那就时间戳加一个独立的时区字段,不要只传时间戳。

    服务端时间只在页面加载时取一次算偏移,不是每次校验都请求。每次都请求最准,但增加了一次网络往返和失败可能。一次取偏移的误差来源是「页面开着期间时钟漂移」,量级极小,对「是否未来时间」这种精度要求不高的判断完全够。判据是「所需精度」——如果是秒杀这种需要精确对齐的场景,就要更频繁地校准。

    草稿采用「手动为主、自动为辅」而不是纯自动保存。纯自动保存(像在线文档那样)体验更好,但它和「定时发布」这个状态容易混淆——用户改了一个已定时的内容,自动保存了,那这次改动算不算生效?手动按钮让用户的意图明确,自动保存只作为防丢失的兜底。这个取舍是为了状态语义的清晰。

    踩过的坑一:提交裸时间字符串,海外创作者的定时发布差了好几个小时。他设晚八点,实际在服务端时区的晚八点发了。他的粉丝在他的时区,这直接影响了内容的曝光。这件事让我记住了「时间字符串没有时区就没有意义」——它只是一串数字,谁解析谁按自己的时区来。

    踩过的坑二:用客户端时钟校验未来时间,用户系统时钟慢导致前端通过服务端拒绝。用户完全无法理解「我明明选的是未来」。修法是取服务端时间算偏移。教训是「凡是和当前时间比较的判断,都要问一句:以谁的时钟为准」——客户端时钟是用户可以随意修改的,也可能就是不准。

    踩过的坑三:草稿和定时混成一种状态,有创作者的内容自己发出去了。他以为那是草稿还在改,实际上它是定时状态。这不是技术 bug,是状态语义没设计清楚——两个语义不同的东西被塞进了同一个状态。教训是:状态设计要按「行为差异」划分,而不是按「都是未发布」这种表面共性划分。

    没做的部分:没做「按粉丝活跃时段推荐发布时间」。产品提过,需要数据侧支持(分析粉丝的活跃分布)。前端这边留了扩展点:定时选择器可以接受一组「推荐时刻」并高亮显示,数据能力具备时接上即可。

    数字是怎么测的

    时区正确性用构造用例,而且必须换时区测:把系统时区改成几个不同的时区(东八、西五、跨日期线的),各选同一个墙上时间提交,断言提交的绝对时刻符合预期关键是「换时区」这一步——如果只在本地时区测,时区 bug 永远测不出来,这和坐标换算那个 bug 是同一类盲区(系数恰好为 1 时错误被掩盖)。

    服务端时间校准的验证:把系统时钟故意调快或调慢几分钟,断言校验结果与服务端一致(本地看起来是未来但实际是过去的时间应该被拒绝,反之应该通过)。这一条也必须手工改时钟来测。

    状态语义的验证:断言草稿不会自动发布、定时会自动发布、定时可撤销后变回草稿。这些是行为断言,写成用例进回归。

    离开拦截的验证:有改动时离开断言有提示、已保存后离开断言无提示。后一条容易漏但很重要——每次都拦截会让用户麻木。

    自动保存的反馈:不是数字指标,报「保存后界面是否显示时间戳」这个事实。因为没有反馈的自动保存等于没有。

    不要报什么:不要报「定时发布准时率」——那取决于服务端的调度,不是前端的功劳。该报的是「跨时区提交的绝对时刻正确」「时钟不准时校验与服务端一致」「草稿与定时的行为符合各自语义」这三件可验证的事。

    面试追问
    Q:时间提交为什么不直接传时间戳?那样最没有歧义。 A:时间戳确实没有歧义,但它丢失了「用户当时看到的是哪个时区的几点」这个信息,而这个信息在两个地方有用:一是展示——列表要按创作者的时区展示「你设定的是 20:00」,如果只有时间戳,服务端不知道该按哪个时区渲染;二是排查——出问题时要能还原用户当时的选择,只有时间戳的话你只知道那个绝对时刻,不知道他以为自己选的是几点。所以我选带偏移的 ISO 串,它同时保留了绝对时刻和时区信息。如果后端更习惯时间戳,那就时间戳加一个独立的时区标识字段,两个信息都要有。核心原则是:时间的表示要能回答「哪个绝对时刻」和「用户视角的哪个时区」两个问题,只回答一个都会在某个环节出问题。我踩的那个坑(提交裸字符串)是两个都没回答,所以两类问题都出了。
    Q:客户端时钟不准这种情况很少吧,值得专门处理吗? A:比想象的多。手机和电脑的时钟会漂移(尤其是长期不联网同步的设备)、用户可能故意改时钟(有些人为了绕过某些限制)、虚拟机和容器环境的时钟经常不准。而这个 bug 的表现对用户来说是完全不可理解的:「我明明选的是未来时间,为什么说我选了过去」——他不会想到是自己的时钟问题,只会觉得是平台有 bug。而处理成本极低:页面加载时取一次服务端时间算出偏移,之后校验时用「本地时间加偏移」,就几行代码。低成本消除一类无法被用户理解的错误,性价比很高。顺带说一个相关的判断:凡是「和当前时间比较」的逻辑,都要先问以谁的时钟为准——倒计时、有效期、超时判断都属于这一类,而客户端时钟在所有这些场景里都不可信。
    Q:草稿和定时分成两种状态,是不是把事情搞复杂了?都是「未发布」啊。 A:表面上都是未发布,但行为完全不同:草稿不会自动发布、可以缺必填项、随时可改;定时会自动发布、必填项必须齐全、改动有生效边界。把行为不同的东西塞进同一个状态,结果就是用户搞不清系统会做什么——我们真实遇到过创作者以为那是草稿还在慢慢改,结果它到点自己发出去了,内容还没写完。这不是技术 bug,是状态语义没设计清楚。我从这里总结的原则是:状态划分要按「系统对它的行为差异」来,不能按「表面上的共性」来。都是未发布是共性,但「会不会自动发」是行为差异,而行为差异才决定用户需要区分它们。而且分开之后实现反而更简单——不用在一个状态里塞「是否定时」「定时时间」这些条件分支,两条路各自清晰。这也印证了「正确的建模往往让代码更简单,而不是更复杂」。

没有匹配的内容,换个关键词试试。

项目拆解 · 创作者中心常用页(Vue · 实习级)· 共 3 个模块 · 数字均为示例,需替换成自己项目的真实数据