题目重点:Fullstack 系统设计题目要求日历多视图、创建删除、循环事件及单个实例改期,后端可假设可用;本文聚焦 API 契约、例外实例与 UI 状态。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版提供日/周/月视图,后端支持基于用户权限的 series CRUD 和实例覆盖 API;界面与 API 都用稳定 eventId、seriesId、occurrenceKey。
- 循环规则用明确时区与本地时间表达,单次改期以原发生时刻作为 occurrenceKey,覆盖后的新开始时间单独存;这防止再次改期时失去实例身份。
- 客户端只展开当前可见日期范围及少量预取范围,历史无限循环不全量展开;跨设备最终以服务端版本为准。
解题路线
- 组件状态拆成 viewRange/viewMode、normalized event cache、编辑草稿与 mutation 状态;派生可见列表时按 range 扩展 series,再把实例 override/tombstone 叠加。
- 创建普通事件 POST events;创建系列 POST series+rule;删除时让用户明确选择仅本次、此后所有、整组,其中仅本次写 occurrence tombstone。
- 单次改期 PATCH series/{id}/occurrences/{originalKey} 写 override(newStart,newEnd),不要直接改 series rule,也不要用新时间当实例主键。
- 前端先做 optimistic patch 并保留 rollback snapshot,服务端返回 version 或冲突错误后合并/回退;切换视图应复用 normalized cache 并重新计算当前范围。
- 处理时区与 DST:存原始 IANA 时区和本地 recurrence 规则,渲染时按用户时区换算,明确不存在或重复的本地时间策略。
手动走一遍
每周一 09:00 的团队会 series S,原实例 occurrenceKey=2026-11-02T09:00 America/New_York。用户只把这一次移到周二 10:00:保留 S 原规则,写覆盖 (S,originalKey)→newStart。11 月 9 日以后仍是周一 09:00;删除该次只写 tombstone,不删除整个系列。
规模与取舍
若当前视图有 R 个系列、展开出 O 个实例与 X 个例外,客户端合成显示约 O(R+O+X) 时间和 O(O+X) 内存;无限系列必须按可见范围分页/截断。实际瓶颈还包括跨视图缓存失效、布局重叠事件和网络延迟。
易错点与追问
容易失分的边界
- 改单次实例时直接修改整个 recurrence rule,后续全错。
- 用改期后的新时间标识原实例,第二次编辑找不到同一个 occurrence。
- 把无限循环一次性展开,前端内存和渲染失控。
- 忽略 DST 与用户时区,周一 09:00 会在换时后漂移。
- 所有创建/删除都只在本地状态完成,刷新后数据丢失且跨设备冲突无解。
继续深挖
- ‘此后所有实例’修改从某次起如何拆 series?
- 多用户协作同一日历时,权限与版本冲突如何反馈?
- 月视图 10 万事件时怎样虚拟化布局与分页?
- 离线编辑回来后如何合并多端例外修改?
