题目重点:基于 Oracle OCI 长帖的明确约束,设计跨 POST 调用保持状态、原子占位和幂等重试的预约接口。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 医生 ID 为 0–999;一天 8 小时共 32 个起始时间槽,最后一个 16:45–17:00。日期为医生所在时区的本地日期,跨夏令时的时区转换在 API 边界处理。
- 训练版请求带唯一 requestId 作为幂等键;同键重试返回原预约,同键改医生/日期报错。题目未明确幂等协议,这是生产扩展。
- 下面代码是单进程内存参考;“跨 API 调用保持状态”满足同一实例内的多个调用。多实例或重启后持久性需数据库事务。
解题路线
- 以 (doctorId,date) 为键保存 32 位占用位图。按索引 0..31 扫描第一位 0,设置后返回对应 HH:MM;一天全满时返回无空位。
- 占位与 requestId 幂等记录必须在同一原子边界内;训练版用锁,生产环境用数据库唯一约束、行锁或乐观版本 CAS,避免两个请求同时预订同一槽。
- POST /appointments 的请求至少带 doctorId、localDate、requestId;成功返回 doctorId、日期、槽位与预约 ID,满额返回明确冲突码。医生不存在与非法日期分别验证。
- 部署多副本时把状态从进程移到持久共享存储,按医生和日期分片;防止医生排班变化后继续出售已关闭槽位。监控重复请求率、冲突、耗时和满额率。
手动走一遍
同一医生同一天连续两个不同 requestId 返回 09:00 与 09:15;重试第一个 requestId 仍返回 09:00,不再占新槽。另一医生同日独立从 09:00 开始。第 33 个不同请求返回无空位。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""In-memory reference for a 9:00-17:00, 15-minute first-slot booking API."""
from datetime import date
from threading import Lock
class HospitalBooking:
SLOTS_PER_DAY = 32
def __init__(self, doctor_count: int = 1000):
if doctor_count <= 0:
raise ValueError("doctor_count must be positive")
self.doctor_count = doctor_count
self.occupied: dict[tuple[int, date], int] = {}
self.requests: dict[str, tuple[int, date, str]] = {}
self.lock = Lock()
def book(self, doctor_id: int, day: date, request_id: str) -> str | None:
if not 0 <= doctor_id < self.doctor_count:
raise ValueError("unknown doctor")
if not request_id:
raise ValueError("request_id is required")
with self.lock:
previous = self.requests.get(request_id)
if previous is not None:
if previous[:2] != (doctor_id, day):
raise ValueError("idempotency key reused for a different booking")
return previous[2]
key = (doctor_id, day)
mask = self.occupied.get(key, 0)
for index in range(self.SLOTS_PER_DAY):
bit = 1 << index
if mask & bit == 0:
mask |= bit
self.occupied[key] = mask
minute = 9 * 60 + index * 15
slot = f"{minute // 60:02d}:{minute % 60:02d}"
self.requests[request_id] = (doctor_id, day, slot)
return slot
return None
复杂度
每个预约最多检查 32 个槽,固定营业规则下为 O(1) 时间;D 个医生、H 个有记录日期的状态存储为 O(DH),幂等记录随成功请求数增长。训练版全局锁会限制并发,生产应按医生日期细化事务粒度。
易错点与追问
容易失分的边界
- 先读第一空槽再无锁写入,两个并发 POST 可能拿到同一个时间。
- 把下午 17:00 当成可预约起点,实际最后起点是 16:45。
- 只存在进程内存却宣称多实例、进程重启后仍保持预约。
- 不处理重试,网络超时后客户再次 POST 被扣两个槽。
继续深挖
- 医生临时请假或已有预约取消时,位图与幂等记录如何更新?
- 跨时区患者看到的预约时间怎样避免夏令时歧义?
- 如果必须按专业或地点搜索首个可用医生,索引和锁粒度如何变化?
