应使用自定义运行时异常PrizeDispatchException精准标识抽奖派发失败,包含userId、prizeId、dispatchStage等字段,在各失败节点即时抛出,统一在@ControllerAdvice中记录日志、返回友好提示并触发补偿。

在抽奖活动中,奖品派发失败属于业务层面的明确异常场景,适合用自定义异常来精准标识问题、统一处理逻辑,并向调用方(如接口层、日志系统或补偿任务)传递可读性强的上下文信息。关键不是“能不能抛”,而是“怎么抛得清楚、接得住、好排查”。
一、定义清晰语义的自定义异常类
继承 RuntimeException(非检查异常),避免强制 try-catch 干扰主流程;添加奖品 ID、用户 ID、失败原因等字段,方便日志记录和问题定位:
public class PrizeDispatchException extends RuntimeException {
private final String userId;
private final String prizeId;
private final String dispatchStage; // 如 "inventory_deduct", "sms_notify", "account_credit"
public PrizeDispatchException(String userId, String prizeId, String stage, String message) {
super("奖品派发失败[" + stage + "]:用户" + userId + ",奖品" + prizeId + ",原因:" + message);
this.userId = userId;
this.prizeId = prizeId;
this.dispatchStage = stage;
}
// getter 省略
}
二、在派发关键节点主动抛出
不要等到整个方法执行完才汇总错误;而是在库存扣减失败、短信发送超时、账户加积分异常等具体环节,立刻抛出带上下文的异常:
- 库存不足 → 抛
new PrizeDispatchException(userId, prizeId, "inventory_deduct", "库存已为0") - 短信网关返回 503 → 抛
new PrizeDispatchException(userId, prizeId, "sms_notify", "短信服务暂时不可用") - 数据库唯一键冲突(重复发放)→ 抛
new PrizeDispatchException(userId, prizeId, "db_insert", "该用户已领取过此奖品")
三、统一捕获与分级处理
在服务入口(如 Spring 的 @ControllerAdvice)中捕获该异常,做三件事:
- 记录结构化日志:输出 userId、prizeId、stage、堆栈,便于 ELK 检索
- 返回友好提示:对前端返回 {"code":5002, "msg":"奖品发放中遇到问题,请稍后重试"},不暴露技术细节
- 触发异步补偿:将失败事件发到消息队列(如 Kafka),由后台任务重试或人工介入
四、避免常见误区
- 不要用
Exception或IllegalArgumentException替代——语义模糊,后续无法针对性拦截 - 不要只抛异常不传参数——丢失 userId/prizeId,查问题要翻十几张表关联
- 不要在 catch 块里吞掉异常或只打印 e.printStackTrace()——等于放弃监控和告警
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











