← 返回博客列表
Stripe

Stripe 面试真题解析:Sending Subscription Notifications

2025-12-29

这道题是 Stripe 订阅系统(Billing)团队的经典面试题。它考察的是事件调度(Scheduling)时间轴排序以及状态变更的传播

题目描述

Stripe 需要给订阅用户发送通知邮件。每个用户都有一个订阅计划(Plan),且不同的时间节点需要发送不同的邮件。

Part 1: 静态调度 (Static Schedule)

给定一个发送计划模板(Schedule Template)和一组用户订阅信息,请输出所有需要发送的邮件日志,按时间排序。

发送计划模板:

用户数据:

预期输出: 你需要计算出每个用户的具体发送日期(Day 0, Day 15, Day 30),将所有用户的邮件事件合并,并按时间戳排序输出。

Part 2: 动态变更 (Dynamic Changes)

用户可能会在订阅中途更改 Plan(例如从 Silver 升级到 Gold)。 新规则:

  1. 如果用户更改了 Plan,系统需要立即发送一封 "Plan Changed" 邮件。
  2. 后续的所有定时邮件(如 "Expired")都需要在主题中反映最新的 Plan 名称。

输入增加: PlanChanges = [{user: "Alice", time: 5, new_plan: "Gold"}]

难点: 你需要动态更新 Alice 在 Day 15 和 Day 30 的待发送邮件内容。

oavoservice 解题思路分析

这道题本质上是一个优先队列(Priority Queue)事件合并排序问题。

1. 数据结构设计

我们需要一个对象来表示“待发送邮件”:

class EmailEvent:
    time: int
    user_id: str
    template_type: str # e.g., WELCOME, EXPIRY
    original_plan: str
    
    # 用于排序
    def __lt__(self, other):
        return self.time < other.time

2. 处理流程

  1. 初始化:遍历所有用户,根据模板生成初始的 EmailEvents 列表。
  2. 合并变更:遍历 PlanChanges,生成 "Plan Changed" 事件加入列表。
  3. 状态回溯(关键)
    • 最简单的做法是:在生成输出时,实时查询该用户在 event.time 时刻的有效 Plan。
    • 或者:维护一个 User -> CurrentPlan 的映射。按时间顺序处理所有事件(包括邮件发送事件和 Plan 变更事件)。

3. 算法选择

代码片段 (Python)

def generate_notifications(users, schedule, changes):
    events = []
    
    # 1. 生成基础邮件事件
    for u in users:
        start = u['start']
        end = start + u['duration']
        # Add Welcome
        events.append({"time": start, "type": "EMAIL", "msg": "Welcome", "user": u})
        # Add Expiry
        events.append({"time": end, "type": "EMAIL", "msg": "Expired", "user": u})
        
    # 2. 生成变更事件
    for c in changes:
        events.append({"time": c['time'], "type": "CHANGE", "new_plan": c['new_plan'], "user_name": c['name']})
        
    # 3. 排序
    events.sort(key=lambda x: x['time'])
    
    # 4. 模拟时间轴
    user_plans = {u['name']: u['plan'] for u in users}
    
    for e in events:
        if e['type'] == "CHANGE":
            user_plans[e['user_name']] = e['new_plan']
            print(f"{e['time']}: Plan Changed for {e['user_name']} to {e['new_plan']}")
        else:
            current_plan = user_plans[e['user']['name']]
            print(f"{e['time']}: {e['msg']} for {e['user']['name']} ({current_plan})")

想知道如何处理更复杂的调度?

如果发送时间是“每月的第一个周一”怎么办?如果时区不同怎么办? oavoservice 的面试辅助服务会带你深入探讨这些 System Design 级别的 Follow-up,确保你在面试中不仅能写出代码,还能展现架构能力。

联系我们