从登录失败到订单校验:盘点5个真实业务场景,手把手教你用BusinessException处理非技术异常
2026/6/8 6:27:23 网站建设 项目流程

从登录失败到订单校验:5个真实场景解锁BusinessException实战技巧

在Java开发中,我们经常遇到两类问题:一类是代码执行时出现的技术异常(如NullPointerException),另一类则是业务规则校验失败(如密码错误、库存不足)。传统处理方式往往将两者混为一谈,导致代码充斥着大量if-else和Result对象返回。今天,我们就通过5个真实业务场景,看看如何用BusinessException让代码更优雅。

1. 用户登录:告别Result对象嵌套

假设我们有一个用户登录功能,传统实现可能是这样的:

public Result<User> login(String username, String password) { User user = userRepository.findByUsername(username); if (user == null) { return Result.error("用户不存在"); } if (!passwordEncoder.matches(password, user.getPassword())) { return Result.error("密码错误"); } if (user.getStatus() == UserStatus.LOCKED) { return Result.error("账户已锁定"); } return Result.success(user); }

这种写法有三个明显问题:

  1. 业务逻辑与技术处理耦合
  2. 方法签名暴露了返回包装类型
  3. 错误处理分散在各个if条件中

使用BusinessException改造后:

public User login(String username, String password) { User user = userRepository.findByUsername(username); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } if (!passwordEncoder.matches(password, user.getPassword())) { throw new BusinessException(ErrorCode.PASSWORD_MISMATCH); } if (user.getStatus() == UserStatus.LOCKED) { throw new BusinessException(ErrorCode.ACCOUNT_LOCKED); } return user; }

关键改进点

  • 方法签名更干净,直接返回User类型
  • 业务规则校验集中抛出异常
  • 错误码和消息通过枚举统一管理

提示:在Controller层可以通过@ExceptionHandler统一捕获BusinessException,转换为对应的HTTP状态码和错误信息。

2. 参数校验:超越JSR-303的灵活控制

JSR-303注解(如@NotBlank)适合简单校验,但遇到复杂业务规则时往往力不从心。比如商品创建接口需要校验:

  • 商品名称不能包含特殊字符
  • 价格必须大于成本价
  • 上架时间不能早于当前时间

传统实现:

public Result createProduct(ProductCreateDTO dto) { if (containsSpecialChars(dto.getName())) { return Result.error("商品名称含非法字符"); } if (dto.getPrice() <= dto.getCostPrice()) { return Result.error("售价必须高于成本价"); } if (dto.getOnlineTime().isBefore(LocalDateTime.now())) { return Result.error("上架时间不能早于当前时间"); } // 保存逻辑... }

BusinessException版本:

public void createProduct(ProductCreateDTO dto) { if (containsSpecialChars(dto.getName())) { throw new BusinessException(ErrorCode.INVALID_PRODUCT_NAME); } if (dto.getPrice() <= dto.getCostPrice()) { throw new BusinessException(ErrorCode.PRICE_TOO_LOW); } if (dto.getOnlineTime().isBefore(LocalDateTime.now())) { throw new BusinessException(ErrorCode.INVALID_ONLINE_TIME); } // 保存逻辑... }

对比优势

校验方式可读性灵活性错误处理
JSR-303统一但不够具体
Result返回分散在各处
BusinessException集中且类型明确

3. 库存管理:原子操作与异常处理

电商系统中,扣减库存需要保证原子性。传统实现可能会这样:

public Result deductStock(Long productId, int quantity) { Product product = productRepository.findById(productId); if (product == null) { return Result.error("商品不存在"); } if (product.getStock() < quantity) { return Result.error("库存不足"); } // 非原子操作存在并发问题 product.setStock(product.getStock() - quantity); productRepository.save(product); return Result.success(); }

更完善的BusinessException版本:

@Transactional public void deductStock(Long productId, int quantity) { int updated = productRepository.reduceStock(productId, quantity); if (updated == 0) { throw new BusinessException(ErrorCode.STOCK_NOT_ENOUGH); } }

对应的Repository方法:

@Modifying @Query("UPDATE Product p SET p.stock = p.stock - :quantity WHERE p.id = :productId AND p.stock >= :quantity") int reduceStock(@Param("productId") Long productId, @Param("quantity") int quantity);

关键设计点

  1. 使用数据库乐观锁保证原子性
  2. 通过update返回影响行数判断是否成功
  3. 失败时抛出业务异常

4. 权限校验:分层拦截与异常统一处理

在订单详情查询接口中,我们需要校验:

  • 订单是否存在
  • 当前用户是否有权限查看

传统嵌套校验:

public Result<Order> getOrderDetail(Long orderId, Long userId) { Order order = orderRepository.findById(orderId); if (order == null) { return Result.error("订单不存在"); } if (!order.getUserId().equals(userId)) { return Result.error("无权查看该订单"); } return Result.success(order); }

使用BusinessException结合Spring AOP的优雅方案:

// 业务方法 public Order getOrderDetail(Long orderId) { return orderRepository.findById(orderId) .orElseThrow(() -> new BusinessException(ErrorCode.ORDER_NOT_FOUND)); } // AOP切面 @Before("@annotation(requirePermission) && args(orderId,..)") public void checkPermission(RequirePermission requirePermission, Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new BusinessException(ErrorCode.ORDER_NOT_FOUND)); if (!order.getUserId().equals(SecurityUtils.getCurrentUserId())) { throw new BusinessException(ErrorCode.PERMISSION_DENIED); } }

架构优势

  • 业务方法只需关注核心逻辑
  • 权限校验通过注解+AOP实现
  • 异常类型可以精确区分"订单不存在"和"权限不足"

5. 状态流转:用异常替代状态判断

考虑订单发货流程,业务规则包括:

  • 只有"待发货"状态的订单可以发货
  • 发货必须填写物流单号
  • 物流单号必须符合格式

传统状态判断:

public Result deliverOrder(Long orderId, String trackingNumber) { Order order = orderRepository.findById(orderId); if (order == null) { return Result.error("订单不存在"); } if (order.getStatus() != OrderStatus.PENDING_SHIPMENT) { return Result.error("订单状态不正确"); } if (!isValidTrackingNumber(trackingNumber)) { return Result.error("物流单号格式错误"); } // 发货逻辑... }

BusinessException实现:

public void deliverOrder(Long orderId, String trackingNumber) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new BusinessException(ErrorCode.ORDER_NOT_FOUND)); if (order.getStatus() != OrderStatus.PENDING_SHIPMENT) { throw new BusinessException(ErrorCode.INVALID_ORDER_STATUS); } if (!isValidTrackingNumber(trackingNumber)) { throw new BusinessException(ErrorCode.INVALID_TRACKING_NUMBER); } // 发货逻辑... }

状态机对比

方法可维护性可扩展性错误定位
if-else返回Result需要查看每个条件
BusinessException异常类型即说明问题
状态模式最高最高需要额外设计成本

在实际项目中,我通常会根据业务复杂度选择方案:简单流程用BusinessException,复杂状态机才引入专门的状态模式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询