Skip to content

2026-04-16 合同断点续传面试口述训练稿

这份材料怎么用

这份文档专门给你练“说”的,不是给你再看一遍技术说明。

建议你按这个顺序练:

  1. 先背 1 分钟版
  2. 再练 3 分钟版
  3. 然后练 5 分钟版
  4. 最后拿 模拟追问 对答

一、1 分钟版

这是你最短的稳定回答。

我这次做的是合同管理场景里的大文件附件上传,业务落点是 draft-contractchange 两个既有页面。
一开始如果继续走普通整文件上传,文件一大就容易失败,网络断了还要整文件重传,而且编辑态附件增删改也很难处理。
所以我做了一套基于 Cloudflare R2 multipart 的分片上传和断点续传方案。前端负责切片、并发上传和恢复,Nitro 负责上传控制面,比如创建上传会话、查询已上传分片、签发分片上传 URL、完成合并,Neon Postgres 负责记录上传会话和分片状态。
另外我把“上传完成”和“业务提交成功”拆成了两阶段,避免出现文件已经传到对象存储里了,但合同主记录还没成功创建的脏数据问题。最终这套方案在两个页面里都形成了完整 CRUD 闭环。

你这段最核心的 3 个关键词

  • 浏览器直传 R2
  • 上传控制面
  • 上传完成和业务提交解耦

二、3 分钟版

这是最适合大多数面试场景的版本。

我这次做的不是一个简单的上传按钮,而是把大文件分片上传、断点续传和合同业务 CRUD 真正打通。
业务上有两个页面,一个是合同草稿 draft-contract,一个是合同变更 change,它们都要支持附件上传。问题在于,如果继续走普通整文件上传,大文件在弱网下很容易失败,一旦中断通常要整文件重传,而且在编辑态下,旧附件保留、删除和新增附件混在一起,处理起来也不清楚。
所以我做了一个分层方案。数据面上,我让浏览器直接把分片传到 Cloudflare R2,不让业务服务端中转大文件流量;控制面上,我用 Nitro 来管理上传会话、查询分片状态、给每个分片签发临时上传 URL,以及最后完成 multipart merge;状态持久化上,我用 Neon Postgres 记录上传会话表和分片表,这样页面刷新之后还能继续恢复。
前端这边,我把上传能力抽成了共享组件和状态机,让 draft-contractchange 两个页面共用。状态机会管理文件指纹、上传会话 ID、分片进度、暂停、恢复、失败重试这些状态。
我觉得这里比较关键的设计点,是我没有把“对象上传完成”和“业务提交成功”绑死在一起。上传完成只代表文件已经到了 R2,但只有当合同的 create 或 update 成功时,才把它物化成正式附件记录。这样就避免了无主附件和脏数据。
另外在 change 页面里,我还处理了编辑态附件差量同步的问题,也就是旧附件不是简单覆盖,而是拆成保留、删除、新增三类动作。这个点比单纯上传更接近真实业务。
最后这套方案在两个页面里都跑通了分片上传、断点恢复和完整 CRUD 闭环,上传链路和业务链路也分得比较清楚。

你讲 3 分钟时要刻意带出来的亮点

  • 不是单页 demo,而是两个真实业务页面落地
  • 不是只会传文件,而是考虑了编辑态附件差量维护
  • 不是单纯技术堆砌,而是做了过程态和结果态分离

三、5 分钟版

这是给面试官继续追问时,你能稳稳展开的版本。

我这次做的是合同管理场景中的大文件附件上传。业务落点有两个,一个是合同草稿页面 draft-contract,一个是合同变更页面 change
这类场景如果继续用普通整文件上传,问题会很明显。第一,大文件在弱网下失败率高,失败后通常只能整文件重传;第二,服务端如果做中转,会承受很大的文件流量和带宽压力;第三,编辑态下附件不是简单覆盖,而是既有旧附件的保留和删除,也有新附件的新增,所以单纯做一个上传控件并不能解决真实业务问题。
所以我的设计思路是,把这套系统拆成数据面和控制面。数据面由浏览器直接对接 Cloudflare R2 multipart,前端按固定 chunkSize 切片,并发上传每个 part。这样做的好处是,大文件流量不会经过业务服务端,失败时也只需要重传某一片。
控制面由 Nitro + Neon 来做。Nitro 负责创建上传会话、查询哪些分片已经成功、给某个分片签发 presigned URL、最后完成 multipart complete;Neon Postgres 用来记录上传会话主表和分片明细表,保证刷新页面后还能恢复上传状态。
我之所以要单独做 upload_sessionsupload_session_parts 两张表,是因为断点续传的关键不是知道“文件传了一半”,而是知道“第几片已经成功了”。只有把分片级状态建模出来,前端才能在恢复时只补传缺失分片。
前端这边,我没有把上传逻辑散落在页面里,而是抽成共享上传状态机和组件,统一给 draft-contractchange 用。状态机里管理的是文件指纹、上传会话 ID、当前状态、进度、暂停、恢复、失败重试。页面层只消费最终上传结果,不直接关心每个分片的底层细节。
另一个我认为比较重要的设计点,是“上传完成”和“业务提交成功”解耦。上传完成只表示对象已经到 R2,但并不代表它已经是正式业务附件。真正写入 ct_attachments,是在合同 create 或 update 成功之后才物化。这样能避免文件上传成功但业务主记录失败时留下无主附件。
change 页面比 draft-contract 更复杂,因为它要处理编辑态附件差量同步。不是简单把附件数组整体覆盖,而是要拆成保留旧附件、删除旧附件、新增新附件三类动作,所以我在提交时会显式传 retainAttachmentIdsdeleteAttachmentIdsnewUploadSessionIdsattachmentMetas
从结果上看,这套方案在两个页面里都形成了完整闭环,支持分片上传、断点续传、失败重试、编辑态附件差量更新,同时服务端也不会被大文件流量拖垮。我觉得这件事最能体现的,不只是我会接对象存储,而是我能把上传过程数据、业务结果数据和真实业务表单流转拆清楚。

这段的主线

  • 先讲问题
  • 再讲分层
  • 再讲建模
  • 再讲边界
  • 最后讲结果

四、你容易卡壳的地方,我替你先翻成人话

1. multipart upload

人话:

不是一次把整个文件传上去,而是拆成很多小片分别上传,最后再合并成一个完整文件。

2. presigned URL

人话:

服务端临时发给前端的一张“带时效的上传通行证”,前端拿着它就能直接把某一片传到对象存储。

3. upload session

人话:

这是一张“上传任务单”,记录这个文件是谁、传到哪一步了、已经成功了哪些分片。

4. ETag

人话:

可以理解成对象存储对每个分片返回的一个确认值,服务端最后合并时要靠它来确认每片都对上。

5. 断点续传

人话:

不是从头再来,而是系统知道上次已经传到哪里了,只补后面缺的部分。

6. 上传完成和业务提交解耦

人话:

文件先到对象存储,但只有业务表单最后提交成功,系统才把这个文件正式认定为“这个合同的附件”。


五、模拟追问

下面是我替你模拟的一轮比较常见的追问。

追问 1

面试官:你为什么不直接用普通上传?

你的回答:

因为我处理的是大文件和弱网场景。普通整文件上传一旦中断,通常就得整文件重传,失败成本太高;而且服务端中转也会吃掉很多带宽和资源。分片上传至少能做到按 part 重试,断点续传也更自然。

追问 2

面试官:为什么服务端不直接做代理中转?

你的回答:

我希望服务端保留控制权,但不承担大文件流量。让 Nitro 负责上传会话、签名和完成动作,让浏览器直传 R2,这样架构职责更清晰,也能减轻服务端压力。

追问 3

面试官:为什么你需要两张上传表?

你的回答:

因为断点续传要知道的不只是“这个文件传了一半”,而是“具体哪几片已经成功”。upload_sessions 负责整体任务状态,upload_session_parts 负责分片级状态,这样前端恢复时才能只补传缺失 part。

追问 4

面试官:那为什么不直接把成功上传的文件写进附件表?

你的回答:

因为上传成功不等于业务成功。比如文件已经传完了,但合同 create 失败了,如果这时候就写正式附件表,会留下无主附件。所以我把过程态和结果态拆开了。

追问 5

面试官:刷新页面后你怎么恢复上传?

你的回答:

前端会根据文件信息生成一个 resumeFingerprint,并在本地缓存它和 sessionId 的映射。刷新后先找回 sessionId,再去服务端查哪些分片已经成功,最后只补传缺失分片。

追问 6

面试官:change 页面为什么更复杂?

你的回答:

因为编辑态附件不是简单新增。它同时存在旧附件保留、旧附件删除和新增新附件三个动作,所以不能整体覆盖,而要做差量同步。这比单纯上传更接近真实业务。

追问 7

面试官:如果某一片一直失败怎么办?

你的回答:

我会把失败粒度控制在 part 级别,先做单片重试。超过重试阈值后,把文件整体状态标为 failed,让用户手动继续或重试,不影响其他已成功分片。

追问 8

面试官:这套方案最大的技术亮点是什么?

你的回答:

我觉得不是“会接 R2”这件事本身,而是我把对象上传、上传状态持久化和业务附件物化三个层次拆清楚了。这样既有可恢复性,又能保持业务数据干净。


六、你可以主动说出来的“加分句”

这些句子适合你在回答里穿插一下。

加分句 1

我做的不是一个上传 demo,而是把上传能力真正落到了两个已有业务页面里。

加分句 2

我比较看重的是边界清晰,所以把大文件数据流和上传控制流拆开了。

加分句 3

这套设计里最重要的不是上传本身,而是“上传结果如何安全地进入业务系统”。

加分句 4

change 页面的编辑态附件差量同步,其实比单纯上传更能体现真实业务复杂度。

加分句 5

我会刻意把过程态和结果态拆开建模,这样系统在失败和恢复场景下更稳。


七、如果你忘词了,就按这个顺序硬讲

你面试时哪怕忘了一半,也能靠下面这个顺序救回来:

  1. 我做的业务是什么
  2. 普通上传为什么不够
  3. 我为什么选分片上传和断点续传
  4. 我怎么分层
  5. 我怎么建模
  6. 我怎么解决脏数据
  7. 最后效果是什么

只要这 7 句顺着出来,整体就不会散。


八、30 分钟突击法

如果你只有半小时,就这样做。

前 10 分钟

只读 1 分钟版3 分钟版,不抠细节,先讲顺。

中间 10 分钟

只记住 4 个技术词:

  • multipart
  • upload session
  • presigned URL
  • 上传完成和业务提交解耦

最后 10 分钟

拿“模拟追问”练短答版,每题只说 2 句,不求完美,但求稳定。


九、最稳的结束语

如果面试官听完后问你“你觉得这件事最体现你的什么能力”,你可以这样收:

我觉得这件事最体现的不是我会接一个对象存储,而是我能把一个技术能力真正嵌进真实业务里。我不仅考虑了上传成功路径,还考虑了刷新恢复、失败重试、编辑态附件差量同步,以及上传结果怎样安全地进入业务表,这让我觉得这件事更像一个完整系统设计,而不只是一个组件开发。

贡献者

The avatar of contributor named as ruan-cat ruan-cat

页面历史

最近更新