
Java 業務異常體系設計一、核心概念在 Spring Boot 項目中異常分為兩大類類型含義誰關心業務校驗異常用戶輸入不合法、業務規則不滿足前端/用戶系統服務異常代碼邏輯錯誤、外部依賴故障開發/運維兩者的本質區別在于業務異常是預期內的失敗系統異常是預期外的故障。注博客https://blog.csdn.net/badao_liumang_qizhi二、為什么要區分兩種異常如果不區分會出現這些問題// 反面示例全部用 RuntimeExceptionthrownewRuntimeException(手機號格式不正確);// 業務校驗thrownewRuntimeException(Redis連接超時);// 系統故障全局異常處理器無法區分該返回 400 還是 500日志級別不好定——業務校驗 warn 就夠系統異常要 error前端不知道該展示錯誤提示還是系統繁忙請重試監控報警會被大量業務校驗失敗淹沒三、異常類設計3.1 基類/** * 業務異常基類. */publicabstractclassBaseBusinessExceptionextendsRuntimeException{/** 錯誤碼 */privateStringerrorCode;/** 給前端展示的消息 */privateStringdisplayMessage;publicBaseBusinessException(StringerrorCode,StringdisplayMessage){super(displayMessage);this.errorCodeerrorCode;this.displayMessagedisplayMessage;}publicStringgetErrorCode(){returnerrorCode;}publicStringgetDisplayMessage(){returndisplayMessage;}}3.2 業務校驗異常CheckException用戶操作不符合業務規則時拋出。消息是給用戶看的。/** * 業務校驗異常 — 用戶可感知、可處理的錯誤. * * 場景參數校驗失敗、業務規則不滿足、前置條件不具備 * HTTP 狀態碼200業務層面的失敗不是HTTP層面的錯誤 * 日志級別WARN */publicclassCheckExceptionextendsBaseBusinessException{publicCheckException(StringerrorCode){super(errorCode,null);}publicCheckException(StringerrorCode,StringdisplayMessage){super(errorCode,displayMessage);}}3.3 系統服務異常ServerException系統內部出錯或外部依賴不可用時拋出。消息是給開發排查用的。/** * 系統服務異常 — 非預期的系統錯誤. * * 場景外部接口調用失敗、數據不一致、空指針前的主動拋出 * HTTP 狀態碼200統一返回結構通過 successfalse 標記 * 日志級別ERROR */publicclassServerExceptionextendsBaseBusinessException{publicServerException(Stringmessage){super(SYSTEM_ERROR,message);}publicServerException(Stringmessage,Throwablecause){super(SYSTEM_ERROR,message);initCause(cause);}}四、全局異常處理器通過RestControllerAdvice統一攔截異常返回標準化響應RestControllerAdvicepublicclassGlobalExceptionHandler{privatestaticfinalLoggerlogLoggerFactory.getLogger(GlobalExceptionHandler.class);/** * 業務校驗異常 — 返回錯誤提示給前端. */ExceptionHandler(CheckException.class)publicRestControllerResult?handleCheckException(CheckExceptione){log.warn(業務校驗失敗: errorCode{}, message{},e.getErrorCode(),e.getMessage());RestControllerResult?resultnewRestControllerResult();result.setSuccess(false);result.setErrorMsg(resolveMessage(e));result.setErrCode(e.getErrorCode());returnresult;}/** * 系統異常 — 返回通用提示詳細信息記入日志. */ExceptionHandler(ServerException.class)publicRestControllerResult?handleServerException(ServerExceptione){log.error(系統異常: {},e.getMessage(),e);RestControllerResult?resultnewRestControllerResult();result.setSuccess(false);result.setErrorMsg(系統繁忙請稍后重試);result.setErrCode(SYSTEM_ERROR);returnresult;}/** * 兜底 — 未預期的異常. */ExceptionHandler(Exception.class)publicRestControllerResult?handleException(Exceptione){log.error(未知異常,e);RestControllerResult?resultnewRestControllerResult();result.setSuccess(false);result.setErrorMsg(系統繁忙請稍后重試);returnresult;}/** * 解析錯誤消息支持 i18n 資源 key 或直接文本. */privateStringresolveMessage(CheckExceptione){if(e.getDisplayMessage()!null){returne.getDisplayMessage();}// 嘗試從 i18n 資源文件解析 errorCode 對應的文本// 如 xxx.delivery.confirm.install-time-empty → 請選擇安裝時間returnMessageSourceUtil.getMessage(e.getErrorCode());}}五、i18n 國際化消息CheckException 的 errorCode 模式當CheckException只傳 errorCode 時通過資源文件解析對應文案# messages.properties xxx.delivery.confirm.install-time-empty請選擇安裝時間 xxx.delivery.confirm.install-time-too-early安裝時間不能早于當前時間1小時 xxx.check.warehouse.delivery.range.error該倉庫不在配送范圍內 // 使用方式 — 只傳 key throw new CheckException(xxx.delivery.confirm.install-time-empty); // 前端收到: {success:false, errorMsg:請選擇安裝時間}好處錯誤文案統一管理修改不用改代碼支持多語言errorCode 可用于前端精確匹配特定錯誤做差異化處理六、兩種異常的使用場景對比6.1 CheckException 適用場景// 1. 參數校驗if(StringUtils.isEmpty(orderCode)){thrownewCheckException(ORDER_CODE_EMPTY,訂單號不能為空);}// 2. 業務規則校驗if(stockdeliveryQty){thrownewCheckException(STOCK_NOT_ENOUGH,庫存不足當前庫存stock);}// 3. 狀態校驗if(!Objects.equals(order.getStatus(),WAIT_DELIVERY)){thrownewCheckException(ORDER_STATUS_ERROR,當前訂單狀態不允許發貨);}// 4. 用 i18n key 的方式if(installTime.before(DateUtils.addHour(newDate(),1))){thrownewCheckException(stock.delivery.confirm.install-time-too-early);}6.2 ServerException 適用場景// 1. 外部服務調用失敗RestControllerResult?resultorderFeign.getOrderInfo(orderId);if(!Boolean.TRUE.equals(result.getSuccess())){thrownewServerException(查詢訂單失敗orderIdorderId, msgresult.getErrorMsg());}// 2. 數據一致性異常不應該出現的情況WaitDeliveryMastermasterrepository.findById(id);if(masternull){thrownewServerException(xxx主表數據不存在idid);}// 3. 直接拼接錯誤信息本次需求的用法thrownewServerException(goodsNames缺少安裝時間);七、通用示例一個完整的 Service 方法ServicepublicclassOrderServiceImplimplementsOrderService{OverridepublicvoidsubmitOrder(SubmitOrderParamparam){// 1. 參數校驗 → CheckExceptionif(param.getItems()null||param.getItems().isEmpty()){thrownewCheckException(ORDER_ITEMS_EMPTY,請至少選擇一件商品);}// 2. 業務規則校驗 → CheckException (i18n key)if(param.getTotalAmount().compareTo(BigDecimal.ZERO)0){thrownewCheckException(order.submit.amount-invalid);}// 3. 調用外部服務 → ServerExceptionRestControllerResultStockInfostockResultstockFeign.checkStock(param.getItems());if(!Boolean.TRUE.equals(stockResult.getSuccess())){thrownewServerException(xx服務調用失敗: stockResult.getErrorMsg());}// 4. 動態拼接的業務提示 → ServerExceptionListStringnoStockItemsfindNoStockItems(stockResult.getData(),param.getItems());if(!noStockItems.isEmpty()){thrownewServerException(String.join(,,noStockItems) 庫存不足);}// 5. 正常業務邏輯orderRepository.save(buildOrder(param));}}八、總結維度CheckExceptionServerException語義業務規則不滿足系統出了問題消息對象用戶開發者消息內容i18n key 或用戶友好文案帶上下文的技術描述日志級別WARNERROR是否觸發告警一般不是HTTP 狀態碼200 successfalse200 successfalse前端處理展示 errorMsg 給用戶展示系統繁忙