InvalidOrderStatusException专用于拦截订单状态非法变更,继承RuntimeException并携带orderId、currentStatus、targetStatus,需在service层校验后主动抛出,配合全局异常处理器输出结构化日志并返回友好提示。

直接抛出 InvalidOrderStatusException 前,先明确它该承担什么职责:不是泛泛的“参数错误”,而是专用于拦截订单状态非法变更(比如从“已发货”回退到“待支付”),让业务逻辑更清晰、错误更易定位。
定义异常类:继承 RuntimeException,带状态上下文
推荐继承 RuntimeException,避免强制 try-catch,符合业务异常“不该被底层吞掉”的设计原则。同时携带关键信息:原始状态、目标状态、订单ID(可选)。
- 构造函数至少支持「错误消息 + 当前状态 + 目标状态」三参数
- 提供 getter 方法,方便日志或监控提取状态值
- 不需重写
printStackTrace(),默认行为已足够
public class InvalidOrderStatusException extends RuntimeException {
private final String currentStatus;
private final String targetStatus;
private final String orderId;
public InvalidOrderStatusException(String orderId, String currentStatus, String targetStatus) {
super(String.format("订单[%s]状态非法变更:从'%s'变为'%s'", orderId, currentStatus, targetStatus));
this.orderId = orderId;
this.currentStatus = currentStatus;
this.targetStatus = targetStatus;
}
// getter 省略,按需生成
}
在状态变更逻辑中主动校验并抛出
不要等数据库更新失败再兜底。应在 service 层调用状态变更方法前,用明确规则判断是否允许跳转。例如:
- 定义合法状态流转图(可用枚举 + Map 或硬编码 if-else)
- 检查当前状态是否在允许的前驱状态集合中
- 不满足则立即 new 并 throw 异常,不执行后续更新
public void updateOrderStatus(String orderId, String targetStatus) {
Order order = orderMapper.selectById(orderId);
if (!isValidTransition(order.getStatus(), targetStatus)) {
throw new InvalidOrderStatusException(orderId, order.getStatus(), targetStatus);
}
order.setStatus(targetStatus);
orderMapper.updateById(order);
}
统一异常处理器中记录结构化日志
配合 Spring 的 @ControllerAdvice 捕获该异常,输出含状态字段的日志,便于排查和告警:
立即学习“Java免费学习笔记(深入)”;
- 用 SLF4J 打印 ERROR 级别日志,包含 orderId、currentStatus、targetStatus
- 返回前端友好提示(如“订单状态不可逆,请确认操作”),不暴露内部状态名
- 避免把异常堆栈全量返回给前端,仅保留必要上下文
@ExceptionHandler(InvalidOrderStatusException.class)
public ResponseEntity<ApiResponse> handleInvalidOrderStatus(InvalidOrderStatusException e) {
log.error("订单状态变更拒绝: orderId={}, from={}, to={}",
e.getOrderId(), e.getCurrentStatus(), e.getTargetStatus());
return ResponseEntity.badRequest()
.body(ApiResponse.fail("订单状态变更不合法"));
}
配套建议:用枚举约束状态值,提升类型安全
避免字符串硬编码导致拼写错误。定义 OrderStatus 枚举,并在异常和校验逻辑中统一使用:
- 枚举内预设所有合法状态(如 WAIT_PAY、SHIPPED、COMPLETED)
- 校验方法接收枚举而非 String,编译期即可发现非法字面量
- 异常类中的 status 字段也建议改为枚举类型(若需兼容旧数据,可用 String 构造后转枚举)


















