💰 设计一个支付系统:金库的守护者!

61 阅读12分钟

📖 开场:银行金库

想象你是银行金库的管理员 🏦:

不规范的金库(危险)

客户A:转账100元给客户B
    ↓
你:好的!
    ↓
A账户:-100元 ✅
    ↓
系统崩溃 💀
    ↓
B账户:没收到钱 ❌

结果:
- A100元消失了 💀
- B也没收到钱 💀
- 银行亏了100元 ❌

规范的金库(安全)

客户A:转账100元给客户B
    ↓
你:好的!
    ↓
1. 记录账务流水(AB100元)📝
2. 冻结A账户的100元 🔒
3. A账户:-100元 ✅
4. B账户:+100元 ✅
5. 解冻 🔓
6. 更新流水状态:成功 ✅

中间任何步骤失败:
    ↓
回滚,钱不会丢 ✅

结果:
- 数据一致 ✅
- 资金安全 ✅
- 可追溯 ✅

这就是支付系统:资金的守护者!


🤔 核心挑战

挑战1:资金安全 🔐

问题:
- 转账失败,钱丢了 💀
- 重复扣款 💀
- 金额篡改 💀

必须:
- 数据一致性 ✅
- 幂等性 ✅
- 不能多扣也不能少扣 ✅

挑战2:高并发 🔥

双11:
- 1秒10万笔支付
- 每笔涉及:
  - 余额扣减
  - 订单更新
  - 流水记录
  - 通知发送

必须:
- 高性能 ✅
- 不能阻塞 ✅

挑战3:对账 📊

问题:
- 系统数据:今天收入100万
- 银行对账单:99万
- 差了1万!💀

原因:
- 系统BUG
- 网络故障
- 人为操作

必须:
- 每天对账 ✅
- 发现差异立即处理 ✅

🎯 核心设计

设计1:账户模型 💳

账户类型

账户类型:
1. 用户账户(User Account)
   - 余额
   - 冻结金额

2. 商家账户(Merchant Account)
   - 收入
   - 提现

3. 平台账户(Platform Account)
   - 手续费收入

4. 担保账户(Escrow Account)
   - 资金担保

数据库设计

-- ⭐ 账户表
CREATE TABLE t_account (
    id BIGINT PRIMARY KEY COMMENT '账户ID',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    account_type TINYINT NOT NULL COMMENT '账户类型:1-用户 2-商家 3-平台',
    balance DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '余额',
    frozen_amount DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '冻结金额',
    total_income DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '累计收入',
    total_expense DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '累计支出',
    version INT NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁)',
    create_time DATETIME NOT NULL,
    update_time DATETIME,
    
    UNIQUE KEY uk_user_type (user_id, account_type),
    INDEX idx_balance (balance)
) COMMENT '账户表';

-- ⭐ 账务流水表(核心)
CREATE TABLE t_account_log (
    id BIGINT PRIMARY KEY COMMENT '流水ID',
    trans_no VARCHAR(64) NOT NULL COMMENT '交易流水号(唯一)',
    from_account_id BIGINT COMMENT '出账账户ID',
    to_account_id BIGINT COMMENT '入账账户ID',
    amount DECIMAL(15,2) NOT NULL COMMENT '金额',
    trans_type TINYINT NOT NULL COMMENT '交易类型:1-充值 2-提现 3-转账 4-消费',
    biz_type VARCHAR(50) COMMENT '业务类型:order/refund/withdraw',
    biz_id VARCHAR(64) COMMENT '业务ID(订单号等)',
    status TINYINT NOT NULL COMMENT '状态:1-处理中 2-成功 3-失败',
    remark VARCHAR(500) COMMENT '备注',
    create_time DATETIME NOT NULL,
    update_time DATETIME,
    
    UNIQUE KEY uk_trans_no (trans_no),
    INDEX idx_from_account (from_account_id, create_time),
    INDEX idx_to_account (to_account_id, create_time),
    INDEX idx_biz (biz_type, biz_id)
) COMMENT '账务流水表';

设计2:支付流程 💸

订单支付流程

用户购买商品(100元):

1. 创建订单(待支付)
    ↓
2. 用户点击"支付"
    ↓
3. ⭐ 生成支付单(payment_no,幂等性保证)
    ↓
4. 检查余额(100元够不够)
    ↓
5. ⭐ 冻结金额(100元)
    ↓
6. 调用支付网关(微信/支付宝)
    ↓
7. 支付网关返回:支付成功
    ↓
8. ⭐ 扣减余额(100元)
    ↓
9. ⭐ 记录账务流水
    ↓
10. 更新订单状态:已支付
    ↓
11. ⭐ 异步通知商家
    ↓
12. 发货 ✅

任何步骤失败:
    ↓
回滚,释放冻结金额 ✅

代码实现

@Service
public class PaymentService {
    
    @Autowired
    private AccountService accountService;
    
    @Autowired
    private PaymentLogMapper paymentLogMapper;
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private PaymentGateway paymentGateway;
    
    /**
     * ⭐ 支付订单
     */
    @Transactional(rollbackFor = Exception.class)
    public PaymentResult pay(Long userId, Long orderId, BigDecimal amount) {
        // 1. 查询订单
        Order order = orderService.getById(orderId);
        if (order == null) {
            throw new OrderNotFoundException("订单不存在");
        }
        
        if (order.getStatus() != OrderStatus.WAIT_PAY) {
            throw new OrderStatusException("订单状态不是待支付");
        }
        
        // ⭐ 2. 生成支付单号(幂等性)
        String paymentNo = generatePaymentNo(orderId);
        
        // 检查支付单是否已存在(防止重复支付)
        PaymentLog existingLog = paymentLogMapper.selectByPaymentNo(paymentNo);
        if (existingLog != null) {
            if (existingLog.getStatus() == PaymentStatus.SUCCESS) {
                // 已支付成功,直接返回
                return PaymentResult.success(existingLog);
            } else if (existingLog.getStatus() == PaymentStatus.PROCESSING) {
                // 正在处理中,拒绝重复请求
                throw new PaymentProcessingException("支付正在处理中");
            }
        }
        
        // 3. 创建支付流水(状态:处理中)
        PaymentLog paymentLog = new PaymentLog();
        paymentLog.setPaymentNo(paymentNo);
        paymentLog.setUserId(userId);
        paymentLog.setOrderId(orderId);
        paymentLog.setAmount(amount);
        paymentLog.setStatus(PaymentStatus.PROCESSING);
        paymentLog.setCreateTime(new Date());
        paymentLogMapper.insert(paymentLog);
        
        try {
            // ⭐ 4. 冻结金额
            accountService.freeze(userId, amount, paymentNo);
            
            // ⭐ 5. 调用支付网关
            PaymentGatewayResponse response = paymentGateway.pay(paymentNo, amount);
            
            if (response.isSuccess()) {
                // ⭐ 6. 扣减余额(解冻并扣除)
                accountService.deduct(userId, amount, paymentNo);
                
                // ⭐ 7. 更新支付流水状态:成功
                paymentLog.setStatus(PaymentStatus.SUCCESS);
                paymentLog.setGatewayTradeNo(response.getTradeNo());
                paymentLog.setUpdateTime(new Date());
                paymentLogMapper.updateById(paymentLog);
                
                // 8. 更新订单状态:已支付
                orderService.updateStatus(orderId, OrderStatus.PAID);
                
                // ⭐ 9. 异步通知商家
                notifyMerchant(orderId);
                
                return PaymentResult.success(paymentLog);
                
            } else {
                // 支付失败
                throw new PaymentFailedException(response.getErrorMsg());
            }
            
        } catch (Exception e) {
            // ⭐ 失败,释放冻结金额
            accountService.unfreeze(userId, amount, paymentNo);
            
            // 更新支付流水状态:失败
            paymentLog.setStatus(PaymentStatus.FAILED);
            paymentLog.setErrorMsg(e.getMessage());
            paymentLog.setUpdateTime(new Date());
            paymentLogMapper.updateById(paymentLog);
            
            throw e;
        }
    }
    
    /**
     * 生成支付单号(幂等性保证)
     */
    private String generatePaymentNo(Long orderId) {
        // 格式:PAY + 订单ID + 时间戳
        return "PAY" + orderId + System.currentTimeMillis();
    }
    
    /**
     * 异步通知商家
     */
    private void notifyMerchant(Long orderId) {
        // 发送MQ消息
        // ...
    }
}

设计3:账户服务 💳

@Service
public class AccountService {
    
    @Autowired
    private AccountMapper accountMapper;
    
    @Autowired
    private AccountLogMapper accountLogMapper;
    
    /**
     * ⭐ 冻结金额
     */
    @Transactional(rollbackFor = Exception.class)
    public void freeze(Long userId, BigDecimal amount, String transNo) {
        // 1. 查询账户
        Account account = accountMapper.selectByUserId(userId);
        
        if (account == null) {
            throw new AccountNotFoundException("账户不存在");
        }
        
        if (account.getBalance().compareTo(amount) < 0) {
            throw new InsufficientBalanceException("余额不足");
        }
        
        // ⭐ 2. 冻结金额(乐观锁)
        int rows = accountMapper.freeze(account.getId(), amount, account.getVersion());
        
        if (rows == 0) {
            throw new ConcurrentUpdateException("账户更新失败,请重试");
        }
        
        // 3. 记录账务流水
        AccountLog log = new AccountLog();
        log.setTransNo(transNo);
        log.setAccountId(account.getId());
        log.setAmount(amount);
        log.setTransType(TransType.FREEZE);
        log.setStatus(TransStatus.SUCCESS);
        log.setCreateTime(new Date());
        accountLogMapper.insert(log);
    }
    
    /**
     * ⭐ 解冻并扣除金额
     */
    @Transactional(rollbackFor = Exception.class)
    public void deduct(Long userId, BigDecimal amount, String transNo) {
        Account account = accountMapper.selectByUserId(userId);
        
        // ⭐ 解冻并扣除(乐观锁)
        int rows = accountMapper.deduct(account.getId(), amount, account.getVersion());
        
        if (rows == 0) {
            throw new ConcurrentUpdateException("账户更新失败,请重试");
        }
        
        // 记录账务流水
        AccountLog log = new AccountLog();
        log.setTransNo(transNo);
        log.setAccountId(account.getId());
        log.setAmount(amount);
        log.setTransType(TransType.DEDUCT);
        log.setStatus(TransStatus.SUCCESS);
        log.setCreateTime(new Date());
        accountLogMapper.insert(log);
    }
    
    /**
     * ⭐ 释放冻结金额
     */
    @Transactional(rollbackFor = Exception.class)
    public void unfreeze(Long userId, BigDecimal amount, String transNo) {
        Account account = accountMapper.selectByUserId(userId);
        
        // ⭐ 释放冻结金额(乐观锁)
        int rows = accountMapper.unfreeze(account.getId(), amount, account.getVersion());
        
        if (rows == 0) {
            throw new ConcurrentUpdateException("账户更新失败,请重试");
        }
        
        // 记录账务流水
        AccountLog log = new AccountLog();
        log.setTransNo(transNo);
        log.setAccountId(account.getId());
        log.setAmount(amount);
        log.setTransType(TransType.UNFREEZE);
        log.setStatus(TransStatus.SUCCESS);
        log.setCreateTime(new Date());
        accountLogMapper.insert(log);
    }
}

Mapper实现

@Mapper
public interface AccountMapper {
    
    /**
     * ⭐ 冻结金额(乐观锁)
     */
    @Update("UPDATE t_account " +
            "SET balance = balance - #{amount}, " +
            "    frozen_amount = frozen_amount + #{amount}, " +
            "    version = version + 1 " +
            "WHERE id = #{accountId} " +
            "  AND version = #{version} " +
            "  AND balance >= #{amount}")
    int freeze(@Param("accountId") Long accountId, 
               @Param("amount") BigDecimal amount, 
               @Param("version") Integer version);
    
    /**
     * ⭐ 解冻并扣除金额(乐观锁)
     */
    @Update("UPDATE t_account " +
            "SET frozen_amount = frozen_amount - #{amount}, " +
            "    total_expense = total_expense + #{amount}, " +
            "    version = version + 1 " +
            "WHERE id = #{accountId} " +
            "  AND version = #{version} " +
            "  AND frozen_amount >= #{amount}")
    int deduct(@Param("accountId") Long accountId, 
               @Param("amount") BigDecimal amount, 
               @Param("version") Integer version);
    
    /**
     * ⭐ 释放冻结金额(乐观锁)
     */
    @Update("UPDATE t_account " +
            "SET balance = balance + #{amount}, " +
            "    frozen_amount = frozen_amount - #{amount}, " +
            "    version = version + 1 " +
            "WHERE id = #{accountId} " +
            "  AND version = #{version} " +
            "  AND frozen_amount >= #{amount}")
    int unfreeze(@Param("accountId") Long accountId, 
                 @Param("amount") BigDecimal amount, 
                 @Param("version") Integer version);
}

设计4:异步通知 🔔

问题

支付成功后:
    ↓
通知商家发货
    ↓
商家服务器宕机 💀
    ↓
通知失败 ❌

必须:
- 重试机制 ✅
- 确保商家收到通知 ✅

解决方案:MQ + 重试

支付成功:
    ↓
发送MQ消息
    ↓
消费者:调用商家回调接口
    ↓
成功 → ACK ✅
失败 → 重试(最多3次)
    ↓
3次都失败 → 人工介入

代码实现

@Service
public class PaymentNotifyService {
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    /**
     * ⭐ 发送支付成功通知
     */
    public void sendPaymentNotify(Long orderId, String paymentNo) {
        PaymentNotifyMessage message = new PaymentNotifyMessage();
        message.setOrderId(orderId);
        message.setPaymentNo(paymentNo);
        message.setRetryCount(0);
        
        // 发送到MQ
        rocketMQTemplate.syncSend("payment-notify-topic", message);
    }
}

/**
 * ⭐ 支付通知消费者
 */
@Component
@RocketMQMessageListener(
    topic = "payment-notify-topic",
    consumerGroup = "payment-notify-consumer"
)
public class PaymentNotifyConsumer implements RocketMQListener<PaymentNotifyMessage> {
    
    @Autowired
    private MerchantService merchantService;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    
    @Override
    public void onMessage(PaymentNotifyMessage message) {
        try {
            // ⭐ 调用商家回调接口
            boolean success = merchantService.notifyPaymentSuccess(
                message.getOrderId(), 
                message.getPaymentNo()
            );
            
            if (success) {
                System.out.println("⭐ 商家通知成功");
            } else {
                // 通知失败,重试
                retry(message);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            // 异常,重试
            retry(message);
        }
    }
    
    /**
     * 重试
     */
    private void retry(PaymentNotifyMessage message) {
        int retryCount = message.getRetryCount();
        
        if (retryCount < 3) {
            // 重试次数未达到上限,继续重试
            message.setRetryCount(retryCount + 1);
            
            // 延迟发送(指数退避:1分钟、2分钟、4分钟)
            int delayLevel = getDelayLevel(retryCount + 1);
            rocketMQTemplate.syncSend(
                "payment-notify-topic",
                MessageBuilder.withPayload(message).build(),
                3000,
                delayLevel
            );
            
            System.out.println("⭐ 重试第" + (retryCount + 1) + "次");
        } else {
            // 重试次数达到上限,记录到数据库,人工处理
            System.out.println("⭐ 重试失败,需要人工处理");
            // saveToFailedLog(message);
        }
    }
    
    /**
     * 获取延迟级别
     */
    private int getDelayLevel(int retryCount) {
        // RocketMQ延迟级别:1=1分钟, 2=5分钟, 3=10分钟
        switch (retryCount) {
            case 1: return 1;  // 1分钟
            case 2: return 2;  // 5分钟
            case 3: return 3;  // 10分钟
            default: return 3;
        }
    }
}

设计5:对账系统 📊

为什么要对账?

问题:
- 系统记录:今天收入100万
- 支付宝对账单:99.5万
- 差了5000元!💀

原因可能:
1. 系统BUG(少记录了)
2. 网络故障(支付成功但通知丢失)
3. 退款(系统未更新)
4. 人工操作(手动调整)

必须:
每天对账,发现差异 ✅

对账流程

对账流程(每天凌晨1点执行):

1. 下载支付宝对账单(昨天的)
    ↓
2. 查询系统账务流水(昨天的)
    ↓
3. 逐条比对:
   - 金额是否一致
   - 状态是否一致
    ↓
4. 生成差异报表
    ↓
5. 发送给财务人员 ✅

代码实现

@Service
public class ReconciliationService {
    
    @Autowired
    private AlipayClient alipayClient;
    
    @Autowired
    private AccountLogMapper accountLogMapper;
    
    /**
     * ⭐ 对账(定时任务,每天凌晨1点)
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void reconcile() {
        // 昨天的日期
        LocalDate yesterday = LocalDate.now().minusDays(1);
        
        System.out.println("⭐ 开始对账:" + yesterday);
        
        // 1. 下载支付宝对账单
        List<AlipayBill> alipayBills = alipayClient.downloadBill(yesterday);
        
        // 2. 查询系统账务流水
        List<AccountLog> accountLogs = accountLogMapper.selectByDate(yesterday);
        
        // 3. 比对
        List<ReconciliationDiff> diffs = compare(alipayBills, accountLogs);
        
        if (diffs.isEmpty()) {
            System.out.println("⭐ 对账成功,无差异");
        } else {
            System.out.println("⭐ 对账发现差异:" + diffs.size() + "条");
            
            // 4. 保存差异记录
            saveDiffs(diffs);
            
            // 5. 发送报警
            sendAlert(diffs);
        }
    }
    
    /**
     * 比对账单
     */
    private List<ReconciliationDiff> compare(List<AlipayBill> alipayBills, 
                                             List<AccountLog> accountLogs) {
        List<ReconciliationDiff> diffs = new ArrayList<>();
        
        // 将系统流水转换为Map(key=交易流水号)
        Map<String, AccountLog> logMap = accountLogs.stream()
            .collect(Collectors.toMap(AccountLog::getTransNo, log -> log));
        
        // 逐条比对
        for (AlipayBill bill : alipayBills) {
            String transNo = bill.getTransNo();
            AccountLog log = logMap.get(transNo);
            
            if (log == null) {
                // 系统中没有这条记录
                ReconciliationDiff diff = new ReconciliationDiff();
                diff.setTransNo(transNo);
                diff.setType(DiffType.MISSING_IN_SYSTEM);
                diff.setAlipayAmount(bill.getAmount());
                diffs.add(diff);
                
            } else {
                // 比对金额
                if (!bill.getAmount().equals(log.getAmount())) {
                    ReconciliationDiff diff = new ReconciliationDiff();
                    diff.setTransNo(transNo);
                    diff.setType(DiffType.AMOUNT_DIFF);
                    diff.setAlipayAmount(bill.getAmount());
                    diff.setSystemAmount(log.getAmount());
                    diffs.add(diff);
                }
                
                // 标记已比对
                logMap.remove(transNo);
            }
        }
        
        // 系统中多余的记录
        for (AccountLog log : logMap.values()) {
            ReconciliationDiff diff = new ReconciliationDiff();
            diff.setTransNo(log.getTransNo());
            diff.setType(DiffType.MISSING_IN_ALIPAY);
            diff.setSystemAmount(log.getAmount());
            diffs.add(diff);
        }
        
        return diffs;
    }
    
    /**
     * 发送报警
     */
    private void sendAlert(List<ReconciliationDiff> diffs) {
        // 发送钉钉通知/邮件
        // ...
    }
}

🎓 面试题速答

Q1: 支付系统如何保证幂等性?

A: 支付单号 + 状态校验

// 生成唯一支付单号
String paymentNo = "PAY" + orderId + timestamp;

// 检查支付单是否已存在
PaymentLog log = paymentLogMapper.selectByPaymentNo(paymentNo);

if (log != null && log.getStatus() == PaymentStatus.SUCCESS) {
    return PaymentResult.success(log);  // 已支付,直接返回
}

关键:支付单号唯一 + 数据库唯一索引


Q2: 如何防止余额超扣?

A: 冻结金额 + 乐观锁

-- 冻结金额
UPDATE t_account 
SET balance = balance - #{amount},
    frozen_amount = frozen_amount + #{amount},
    version = version + 1
WHERE id = #{accountId}
  AND version = #{version}
  AND balance >= #{amount}  -- ⭐ 余额充足检查

流程

  1. 冻结金额
  2. 调用支付网关
  3. 成功:扣除冻结金额
  4. 失败:释放冻结金额

Q3: 账务流水有什么作用?

A: 三大作用

  1. 资金追溯

    • 每笔交易都有记录
    • 可追溯资金流向
  2. 对账

    • 与支付宝对账单比对
    • 发现差异
  3. 审计

    • 监管要求
    • 防止资金风险

必须:每笔交易都记录流水


Q4: 异步通知如何保证可靠?

A: MQ + 重试机制

支付成功 → 发送MQ消息
    ↓
消费者:调用商家回调
    ↓
失败 → 重试(1分钟、5分钟、10分钟)
    ↓
3次都失败 → 人工处理

关键

  • 消息持久化(MQ不丢消息)
  • 重试机制(指数退避)
  • 最终人工兜底

Q5: 如何对账?

A: 下载对账单 + 逐条比对

每天凌晨1点:
1. 下载支付宝对账单(昨天)
2. 查询系统账务流水(昨天)
3. 逐条比对:
   - 金额是否一致
   - 状态是否一致
4. 发现差异 → 报警 → 人工处理

Q6: 支付系统如何设计才安全?

A: 五大保障

  1. 账务流水:每笔交易都记录
  2. 幂等性:防止重复扣款
  3. 乐观锁:防止并发问题
  4. 冻结金额:防止超扣
  5. 对账:每天对账,发现差异

🎬 总结

       支付系统核心设计

┌────────────────────────────────────┐
│ 1. 账户模型                        │
│    - 余额 + 冻结金额               │
│    - 乐观锁                        │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 2. 账务流水(核心)⭐               │
│    - 每笔交易都记录                │
│    - 可追溯                        │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 3. 幂等性                          │
│    - 支付单号唯一                  │
│    - 状态校验                      │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 4. 异步通知                        │
│    - MQ + 重试                     │
│    - 最终一致性                    │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 5. 对账系统 ⭐                      │
│    - 每天对账                      │
│    - 发现差异                      │
└────────────────────────────────────┘

🎉 恭喜你!

你已经完全掌握了支付系统的设计!🎊

核心要点

  1. 账务流水:每笔交易都记录,可追溯
  2. 幂等性:支付单号唯一,防止重复扣款
  3. 冻结金额:先冻结后扣除,防止超扣
  4. 异步通知:MQ + 重试,确保可靠
  5. 对账系统:每天对账,发现差异

下次面试,这样回答

"支付系统的核心是账务流水表。每笔交易都记录流水,包括交易流水号、出账账户、入账账户、金额、交易类型、业务ID、状态等。流水号全局唯一,作为幂等性保证。

支付流程分为三步:冻结、扣除、记录。首先冻结用户账户的金额,调用支付网关,成功后扣除冻结金额并记录流水,失败则释放冻结金额。账户表使用乐观锁,更新时检查version字段和余额是否充足,防止并发超扣。

幂等性通过支付单号保证。支付单号格式为'PAY+订单ID+时间戳',数据库设置唯一索引。支付前先查询支付单是否存在,如果已存在且状态为成功,直接返回不再重复扣款。

异步通知使用RocketMQ实现。支付成功后发送MQ消息,消费者调用商家回调接口。如果调用失败,采用指数退避重试,分别在1分钟、5分钟、10分钟后重试。3次都失败则记录到数据库,人工介入处理。

对账系统每天凌晨1点执行。下载支付宝的对账单,查询系统的账务流水,逐条比对交易流水号、金额、状态是否一致。发现差异后生成报表,发送钉钉通知给财务人员处理。对账是支付系统的最后一道防线,确保资金安全。"

面试官:👍 "很好!你对支付系统的设计理解很深刻!"


本文完 🎬

上一篇: 212-设计一个分布式缓存系统.md
下一篇: 214-设计一个优惠券系统.md

作者注:写完这篇,我觉得钱包更安全了!💰
如果这篇文章对你有帮助,请给我一个Star⭐!