大綱
1.訂單系統核心業務流程
2.Spring Cloud Alibaba在訂單業務中的落地方案
3.面向分布式全鏈路故障設計的高可靠架構方案
4.分布式訂單系統的技術棧與代碼規范
1.訂單系統核心業務流程
(1)生成訂單時序圖
(2)支付訂單流程圖
(3)取消訂單流程圖
這里主要介紹生單和退款兩個核心鏈路:一是訂單正向核心鏈路,二是訂單逆向核心鏈路。
(1)生成訂單時序圖
(2)支付訂單流程圖
(3)取消訂單流程圖
2.Spring Cloud Alibaba在訂單業務中的落地方案
(1)基于Dubbo + Nacos實現全RPC調用鏈路
(2)基于Seata實現正向核心鏈路的數據強一致性
(3)基于RocketMQ延遲消息取消超時未支付訂單
(4)基于Builder和Template模式構建對象和發通知
(5)基于Redisson分布式鎖解決并發預支付問題
(6)訂單履約強一致解決方案
(7)訂單履約冪等性解決方案
(8)基于Seata實現逆向核心鏈路數據強一致性
SCA包含了:Dubbo、Nacos、Sentinel、Seata、RocketMQ等組件,SCA技術棧在訂單業務中的落地方案包括如下:
(1)基于Dubbo + Nacos實現全RPC調用鏈路
(2)基于Seata實現正向核心鏈路數據強一致性
針對生單 -> 優惠券鎖定 -> 庫存鎖定等正向核心鏈路流程,使用Seata的AT模式確保涉及分布式事務的正向核心鏈路的數據強一致性。
(3)基于RocketMQ延遲消息取消超時未支付訂單
通過Redisson分布式鎖 + Elastic-Job實現補償。
(4)基于Builder和Template模式構建對象和發通知
基于Builder模式實現復雜訂單對象的構建、復雜查詢規則對象的構建,基于Template模式實現物流配送的通知。
(5)基于Redisson分布式鎖解決并發預支付問題
(6)訂單履約強一致解決方案
基于RocketMQ的柔性分布式事務解決方案。
(7)訂單履約冪等性解決方案
基于Redisson分布式鎖 + 前置狀態檢查實現冪等。
(8)基于Seata實現逆向核心鏈路數據強一致性
針對取消訂單 -> 取消履約 -> 釋放資產等逆向核心鏈路流程,使用Seata的AT模式確保涉及分布式事務的逆向核心鏈路的數據強一致性。
簡單介紹完SCA在復雜訂單業務中的基礎技術方案,接下來簡單介紹在分布式架構中面向分布式全鏈路故障而設計的高可靠架構方案。
3.面向分布式全鏈路故障設計的高可靠架構方案
(1)訂單系統Dubbo服務高并發壓力優化
(2)訂單系統引入Spring Cloud Alibaba Sentinel
(3)大促活動網關層限流解決方案
(4)訂單系統自適應流控解決方案
(5)訂單集群柔性限流解決方案
(6)訂單核心鏈路雪崩場景保護方案
(7)防惡意刷單自動發現與黑名單方案
(8)庫存系統異構存儲的TCC分布式事務解決方案
(9)倉儲系統老舊代碼的Saga分布式事務解決方案
(10)物流系統多數據庫的XA分布式事務解決方案
(11)Nacos+ZooKeeper雙注冊中心高可用方案
(12)Dubbo+Nacos多機房部署流量Mesh管控方案
分布式系統的技術難點,其實就是分布式系統鏈路長、故障多,如果任何一個環節出了故障就會導致系統出問題。所以要分析全鏈路里會有哪些問題,要用哪些方案來確保系統運行穩定。
分布式訂單系統就采用了如下方案來保證系統穩定:
Dubbo服務高并發壓力生產優化、大促活動網關層限流解決方案、訂單集群柔性限流解決方案、訂單系統自適應流控解決方案、訂單系統核心鏈路雪崩解決方案、防惡意刷單與黑名單解決方案、庫存系統異構存儲架構TCC分布式事務解決方案、倉儲系統老舊代碼Saga分布式事務解決方案、物流系統多數據庫XA分布式事務解決方案、Nacos + ZooKeeper雙注冊中心高可用方案、Dubbo + Nacos多機房部署流量Mesh管控方案。
(1)訂單系統Dubbo服務高并發壓力優化
首先針對訂單系統核心接口逐個進行高并發壓測,然后逐步分析Linux OS、Dubbo線程池、數據庫連接池、數據庫索引和SQL語句、業務邏輯效率等各個環節存在的問題。并得出在機器負載可控情況下,訂單系統可以接受的最大并發壓力。
(2)訂單系統引入Spring Cloud Alibaba Sentinel
此時需要將Sentinel客戶端引入訂單系統工程,同時完成Sentinel Dashboard的搭建。
(3)大促活動網關層限流解決方案
首先演示在大促場景下,瞬時高并發是如何擊垮訂單系統的,同時評估出訂單集群部署下的最大可抗壓力,然后設計網關大促限流方案。可以基于Spring Cloud Gateway實現一個訂單系統前置網關,對訂單系統集群部署做負載均衡 + 部署網關集群。通過在網關引入Sentinel來實現限流,在流量入口處保護訂單系統不被擊垮。
(4)訂單系統自適應流控解決方案
首先演示訂單系統單實例流量超載的問題。然后根據訂單系統部署機器的配置、高壓下的機器負載、業務邏輯的復雜度,基于Sentinel設計訂單系統的自適應流控方案。也就是根據機器負載、響應時間、請求速率,自適應調整機器的流量閾值,從而實現柔性流控效果。最后演示大流量下,流量被網關層限流后穿透到訂單層,各個機器上的自適應流控效果。
(5)訂單集群柔性限流解決方案
首先針對訂單系統各核心接口,評估出集群模式下每個接口的最大負載壓力。然后演示出集群模式下的接口,在訪問超載時引發的問題。接著基于Sentinel設計各個核心接口的柔性限流方案。最后對訂單接口進行超壓力訪問,演示接口級的柔性流控效果。
(6)訂單核心鏈路雪崩場景保護方案
首先演示訂單核心鏈路的服務雪崩場景,單服務崩潰是如何引發服務鏈路全面崩潰的。接著對訂單核心鏈路的各個服務,基于Sentinel設計服務鏈路防雪崩方案,避免核心鏈路任何一個服務崩潰引發的服務鏈路雪崩問題。最后演示單服務崩潰時,整個訂單服務的防雪崩效果。
訂單核心鏈路的各個服務有:訂單服務、庫存服務、營銷服務、倉儲服務、物流服務、風控服務。
(7)防惡意刷單自動發現與黑名單方案
首先演示單用戶惡意刷單行為和效果,接著基于基于Sentinel設計自動識別用戶惡意刷單的方案,將惡意刷單的用戶ID自動加入訪問控制黑名單。從而實現自動化識別 + 防止惡意刷單 + 黑名單控制的機制。
(8)庫存系統異構存儲的TCC分布式事務解決方案
常見的庫存架構是Redis + 數據庫異構存儲架構。由于訂單系統又會強依賴庫存系統,所以庫存系統的異構存儲架構的分布式事務解決方案,通常是基于Seata的TCC模式來實現的。
(9)倉儲系統老舊代碼的Saga分布式事務解決方案
由于倉儲系統的邏輯通常會非常復雜,而且會包含多個服務協同工作,所以對這類系統做分布式事務改造的難度比較大。倉儲系統的多服務鏈路老舊代碼的分布式事務解決方案,通常是基于Seata的Saga模式來實現的。
(10)物流系統多數據庫的XA分布式事務解決方案
(11)Nacos+ZooKeeper雙注冊中心高可用方案
(12)Dubbo+Nacos多機房部署流量Mesh管控方案
4.分布式訂單系統的技術棧與代碼規范
(1)訂單系統的技術棧
(2)各層方法的命名規范
(3)領域模型POJO類命名規范
(4)統一異常處理規范
(5)統一返回結果處理規范
(6)Service層開發規范
(1)訂單系統的技術棧
(2)各層方法的命名規范
開發規范基于阿?巴巴的《Java開發?冊》:
一.總的原則是?動詞做前綴,名詞在后?
二.獲取單個對象時使?get做前綴
三.獲取多個對象時使?list做前綴如listOrders
四.獲取統計值時使?count做前綴
五.插?數據時使?save/insert做前綴
六.刪除數據時使?remove/delete做前綴
七.修改數據時使?update做前綴
(3)領域模型POJO類命名規范
一.數據對象:xxxDO,xxx即為數據表名
二.Controller層返回結果,展示對象?xxxVO
請求?參的命名格式?xxxRequest,查詢條件封裝對象?xxxQuery。
三.Dubbo API層返回結果,返回對象?xxxDTO
請求的命名格式為xxxRequest,查詢條件封裝對象?xxxQuery。
四.業務層內部的數據傳輸對象?般?xxxDTO,不做強制規定。
每個POJO類都會繼承AbstractObject類,方便不同POJO之間的屬性克隆。
//基礎POJO類
//淺克隆:
//復制對象時僅僅復制對象本身,包括基本屬性;
//但該對象的屬性引用其他對象時,該引用對象不會被復制;
//即拷貝出來的對象與被拷貝出來的對象中的屬性引用的對象是同一個;
//深克隆:
//復制對象本身的同時,也復制對象包含的引用指向的對象;
//即修改被克隆對象的任何屬性都不會影響到克隆出來的對象。
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class AbstractObject {//深度克隆//@param targetClazz 目標對象的Class類型//@return 目標對象實例public <T> T clone(Class<T> targetClazz) {try {T target = targetClazz.newInstance();BeanCopierUtil.copyProperties(this, target);return getTarget(target);} catch (Exception e) {throw new RuntimeException("error", e);}}//淺度克隆//@param target 目標對象實例//@return 目標對象實例public <T> T clone(T target) {try {BeanCopierUtil.copyProperties(this, target);return getTarget(target);} catch (Exception e) {throw new RuntimeException("error", e);}}...
}
(4)統一異常處理規范
一.定義?個GlobalExceptionHandler組件
通過對該組件添加@RestControllerAdvice注解,讓該組件成為默認的Controller全局異常處理增強組件。在這個組件中,會分別對系統級別未知系統、客戶端異常、服務端異常都做了統?處理。
//默認的Controller全局異常處理增強組件
@RestControllerAdvice
@Order
public class GlobalExceptionHandler {// =========== 系統級別未知異常 =========@ExceptionHandler(value = Exception.class)public JsonResult<Object> handle(Exception e) {log.error("[ 系統未知錯誤 ]", e);return JsonResult.buildError(CommonErrorCodeEnum.SYSTEM_UNKNOWN_ERROR);}// =========== 客戶端異常 =========//1001 HTTP請求方法類型錯誤@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)public JsonResult<Object> handle(HttpRequestMethodNotSupportedException e) {log.error("[客戶端HTTP請求方法錯誤]", e);return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_HTTP_METHOD_ERROR);}//1002 客戶端請求體參數校驗不通過@ExceptionHandler(value = MethodArgumentNotValidException.class)public JsonResult<Object> handle(MethodArgumentNotValidException e) {log.error("[客戶端請求體參數校驗不通過]", e);String errorMsg = this.handle(e.getBindingResult().getFieldErrors());return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_CHECK_ERROR.getErrorCode(), errorMsg);}private String handle(List<FieldError> fieldErrors) {StringBuilder sb = new StringBuilder();for (FieldError obj : fieldErrors) {sb.append(obj.getField());sb.append("=[");sb.append(obj.getDefaultMessage());sb.append("] ");}return sb.toString();}//1003 客戶端請求體JSON格式錯誤或字段類型不匹配@ExceptionHandler(value = HttpMessageNotReadableException.class)public JsonResult<Object> handle(HttpMessageNotReadableException e) {log.error("[客戶端請求體JSON格式錯誤或字段類型不匹配]", e);return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_BODY_FORMAT_ERROR);}//1004 客戶端URL中的參數類型錯誤@ExceptionHandler(value = BindException.class)public JsonResult<Object> handle(BindException e) {log.error("[客戶端URL中的參數類型錯誤]", e);FieldError fieldError = e.getBindingResult().getFieldError();String errorMsg = null;if (fieldError != null) {errorMsg = fieldError.getDefaultMessage();if (errorMsg != null && errorMsg.contains("java.lang.NumberFormatException")) {errorMsg = fieldError.getField() + "參數類型錯誤";}}if (errorMsg != null && !"".equals(errorMsg)) {return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR.getErrorCode(), errorMsg);}return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_PATH_VARIABLE_ERROR);}//1005 客戶端請求參數校驗不通過@ExceptionHandler(value = ConstraintViolationException.class)public JsonResult<Object> handle(ConstraintViolationException e) {log.error("[客戶端請求參數校驗不通過]", e);Iterator<ConstraintViolation<?>> it = e.getConstraintViolations().iterator();String errorMsg = null;if (it.hasNext()) {errorMsg = it.next().getMessageTemplate();}if (errorMsg != null && !"".equals(errorMsg)) {return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR.getErrorCode(), errorMsg);}return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_CHECK_ERROR);}//1006 客戶端請求缺少必填的參數@ExceptionHandler(value = MissingServletRequestParameterException.class)public JsonResult<Object> handle(MissingServletRequestParameterException e) {log.error("[客戶端請求缺少必填的參數]", e);String errorMsg = null;String parameterName = e.getParameterName();if (!"".equals(parameterName)) {errorMsg = parameterName + "不能為空";}if (errorMsg != null) {return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR.getErrorCode(), errorMsg);}return JsonResult.buildError(CommonErrorCodeEnum.CLIENT_REQUEST_PARAM_REQUIRED_ERROR);}// =========== 服務端異常 =========//2001 業務方法參數檢查不通過@ExceptionHandler(value = IllegalArgumentException.class)public JsonResult<Object> handle(IllegalArgumentException e) {log.error("[業務方法參數檢查不通過]", e);return JsonResult.buildError(CommonErrorCodeEnum.SERVER_ILLEGAL_ARGUMENT_ERROR);}//系統自定義業務異常@ExceptionHandler(value = BaseBizException.class)public JsonResult<Object> handle(BaseBizException e) {log.error("[ 業務異常 ]", e);return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());}
}
二.繼承基礎業務異常類BaseBizException
在業務代碼開發中,可以直接拋出這個異常類,也可以在具體的業務代碼?程新建?個類繼承BaseBizException,然后拋出?定義的異常類。?如在訂單中?,新建?個OrderBizException?定義類,然后繼承BaseBizException,最后在業務代碼中拋出OrderBizException。
//基礎自定義業務異常
public class BaseBizException extends RuntimeException {//默認錯誤碼private static final String DEFAULT_ERROR_CODE = "-1";private String errorCode;private String errorMsg;public BaseBizException(String errorMsg) {super(errorMsg);this.errorCode = DEFAULT_ERROR_CODE;this.errorMsg = errorMsg;}public BaseBizException(String errorCode, String errorMsg) {super(errorMsg);this.errorCode = errorCode;this.errorMsg = errorMsg;}public BaseBizException(BaseErrorCodeEnum baseErrorCodeEnum) {super(baseErrorCodeEnum.getErrorMsg());this.errorCode = baseErrorCodeEnum.getErrorCode();this.errorMsg = baseErrorCodeEnum.getErrorMsg();}public BaseBizException(String errorCode, String errorMsg, Object... arguments) {super(MessageFormat.format(errorMsg, arguments));this.errorCode = errorCode;this.errorMsg = MessageFormat.format(errorMsg, arguments);}public BaseBizException(BaseErrorCodeEnum baseErrorCodeEnum, Object... arguments) {super(MessageFormat.format(baseErrorCodeEnum.getErrorMsg(), arguments));this.errorCode = baseErrorCodeEnum.getErrorCode();this.errorMsg = MessageFormat.format(baseErrorCodeEnum.getErrorMsg(), arguments);}public String getErrorCode() {return errorCode;}public void setErrorCode(String errorCode) {this.errorCode = errorCode;}public String getErrorMsg() {return errorMsg;}public void setErrorMsg(String errorMsg) {this.errorMsg = errorMsg;}
}//訂單中心自定義業務異常類
public class OrderBizException extends BaseBizException {public OrderBizException(String errorMsg) {super(errorMsg);}public OrderBizException(String errorCode, String errorMsg) {super(errorCode, errorMsg);}public OrderBizException(BaseErrorCodeEnum baseErrorCodeEnum) {super(baseErrorCodeEnum);}public OrderBizException(String errorCode, String errorMsg, Object... arguments) {super(errorCode, errorMsg, arguments);}public OrderBizException(BaseErrorCodeEnum baseErrorCodeEnum, Object... arguments) {super(baseErrorCodeEnum, arguments);}
}@Service
public class OrderServiceImpl implements OrderService {...//風控檢查private void checkRisk(CreateOrderRequest createOrderRequest) {//調用風控服務進行風控檢查CheckOrderRiskRequest checkOrderRiskRequest = createOrderRequest.clone(CheckOrderRiskRequest.class);JsonResult<CheckOrderRiskDTO> jsonResult = riskApi.checkOrderRisk(checkOrderRiskRequest);if (!jsonResult.getSuccess()) {throw new OrderBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());}}...
}
三.提供BaseErrorCodeEnum錯誤信息枚舉接?
各個業務服務在?定義錯誤枚舉信息時,會繼承該接口。在拋出業務異常時,統?使?錯誤枚舉信息,可以很好地集中管理錯誤信息并定義錯誤碼。
public enum OrderErrorCodeEnum implements BaseErrorCodeEnum {//正向訂單錯誤碼105開頭USER_ID_IS_NULL("105001", "用戶ID不能為空"),ORDER_NO_TYPE_ERROR("105002", "訂單號類型錯誤"),CREATE_ORDER_REQUEST_ERROR("105003", "生單請求參數錯誤"),ORDER_ID_IS_NULL("105004", "訂單號不能為空"),BUSINESS_IDENTIFIER_IS_NULL("105005", "業務線標識不能為空"),BUSINESS_IDENTIFIER_ERROR("105006", "業務線標識錯誤"),ORDER_TYPE_IS_NULL("105007", "訂單類型不能為空"),ORDER_TYPE_ERROR("105008", "訂單類型錯誤"),SELLER_ID_IS_NULL("105009", "賣家ID不能為空"),USER_ADDRESS_ERROR("105010", "地址信息錯誤"),DELIVERY_TYPE_IS_NULL("105011", "配送類型不能為空"),USER_LOCATION_IS_NULL("105011", "地理位置不能為空"),ORDER_RECEIVER_IS_NULL("105012", "收貨人不能為空"),ORDER_CHANNEL_IS_NULL("105013", "下單渠道信息不能為空"),CLIENT_IP_IS_NULL("105014", "客戶端IP不能為空"),ORDER_ITEM_IS_NULL("105015", "訂單商品信息不能為空"),ORDER_ITEM_PARAM_ERROR("105016", "訂單商品信息錯誤"),ORDER_AMOUNT_IS_NULL("105017", "訂單費用信息不能為空"),ORDER_AMOUNT_TYPE_IS_NULL("105018", "訂單費用類型不能為空"),ORDER_AMOUNT_TYPE_PARAM_ERROR("105019", "訂單費用類型錯誤"),ORDER_ORIGIN_PAY_AMOUNT_IS_NULL("105020", "訂單支付原價不能為空"),ORDER_SHIPPING_AMOUNT_IS_NULL("105021", "訂單運費不能為空"),ORDER_REAL_PAY_AMOUNT_IS_NULL("105022", "訂單實付金額不能為空"),ORDER_DISCOUNT_AMOUNT_IS_NULL("105023", "訂單優惠券抵扣金額不能為空"),ORDER_PAYMENT_IS_NULL("105024", "訂單支付信息不能為空"),PAY_TYPE_PARAM_ERROR("105025", "支付類型錯誤"),ACCOUNT_TYPE_PARAM_ERROR("105026", "賬戶類型錯誤"),PRODUCT_SKU_CODE_ERROR("105027", "商品{0}不存在"),CALCULATE_ORDER_AMOUNT_ERROR("105028", "計算訂單價格失敗"),ORDER_CHECK_REAL_PAY_AMOUNT_FAIL("105029", "訂單驗價失敗"),ORDER_NOT_ALLOW_INFORM_WMS_RESULT("105029", "訂單不允許通知物流配送結果"),ORDER_NOT_ALLOW_TO_DELIVERY("105030", "訂單不允許配送"),ORDER_NOT_ALLOW_TO_SIGN("105031", "訂單不允許簽收"),SKU_CODE_IS_NULL("105032", "skuCode 不能為空"),AFTER_SALE_ID_IS_NULL("105033", "售后ID不能為空"),LACK_ITEM_IS_NULL("105034", "缺品項不能為空"),LACK_NUM_IS_LT_0("105035", "缺品數量不能小于0"),ORDER_NOT_FOUND("105036", "查無此訂單"),LACK_ITEM_NOT_IN_ORDER("105037", "缺品商品并未下單"),LACK_NUM_IS_GE_SKU_ORDER_ITEM_SIZE("105038", "缺品數量不能大于或等于下單商品數量"),ORDER_NOT_ALLOW_TO_LACK("105039", "訂單不允許發起缺品"),REGION_ID_IS_NULL("105040", "區域ID不能為空"),ORDER_PAY_AMOUNT_ERROR("105041", "訂單支付金額錯誤"),ORDER_PRE_PAY_ERROR("105042", "訂單支付發生錯誤"),ORDER_PRE_PAY_EXPIRE_ERROR("105042", "已經超過支付訂單的截止時間"),ORDER_PAY_CALLBACK_ERROR("105043", "訂單支付回調發生錯誤"),ORDER_INFO_IS_NULL("105044", "訂單信息不存在"),ORDER_CALLBACK_PAY_AMOUNT_ERROR("105045", "訂單支付金額錯誤"),ORDER_CANCEL_PAY_CALLBACK_ERROR("105046", "接收到支付回調時,訂單已經取消"),ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_SAME_ERROR("105047", "接收到支付回調的時候訂單已經取消,且支付回調為同種支付方式"),ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_NO_SAME_ERROR("105047", "接收到支付回調的時候訂單已經取消,且支付回調非同種支付方式"),ORDER_CANCEL_PAY_CALLBACK_REPEAT_ERROR("105048", "不同支付方式下的重復支付回調"),ORDER_CANNOT_REMOVE("105046", "訂單不允許刪除"),ORDER_NOT_ALLOW_TO_ADJUST_ADDRESS("105047", "訂單不允許調整配送地址"),ORDER_DELIVERY_NOT_FOUND("105048", "訂單配送記錄不存在"),ORDER_DELIVERY_ADDRESS_HAS_BEEN_ADJUSTED("105049", "訂單配送地址已被調整過"),ORDER_FULFILL_ERROR("105050", "訂單履約失敗"),AFTER_SALE_NOT_FOUND("105051", "查無此售后單"),AFTER_SALE_CANNOT_REVOKE("105052", "售后單無法撤銷"),ORDER_STATUS_ERROR("105049", "訂單狀態異常"),ORDER_PAY_STATUS_IS_PAID("105050", "訂單已經是已完成支付狀態"),RETURN_GOODS_REQUEST_IS_NULL("105051", "手動退貨入參不能為空"),AFTER_SALE_TIME_IS_NULL("105052", "申請售后時間不能為空"),ORDER_CURRENT_TYPE_IS_NULL("105053", "當前訂單狀態不能為空"),SKU_IS_NULL("105054", "sku列表不能為空"),RETURN_GOODS_NUM_IS_NULL("105055", "退貨數量不能為空"),RETURN_GOODS_CODE_IS_NULL("105056", "退貨原因選項不能為空"),AFTER_SALE_TYPE_IS_NULL("105057", "售后類型不能為空"),ORDER_STATUS_CHANGED("105058", "當前訂單狀態已變更,請重新刷新"),ORDER_STATUS_CANCELED("105058", "當前訂單已取消"),ORDER_STATUS_IS_NULL("105059", "當前訂單狀態不能為空"),ORDER_PAY_TRADE_NO("105060", "當前訂單已產生支付流水號,不能取消"),CANCEL_REQUEST_IS_NULL("105061", "取消訂單入參不能為空"),CANCEL_TYPE_IS_NULL("105062", "取消訂單類型不能為空"),CANCEL_ORDER_ID_IS_NULL("105063", "取消訂單ID不能為空"),CANCEL_USER_ID_IS_NULL("105064", "取消訂單用戶ID不能為空"),CANCEL_ORDER_REPEAT("105065", "取消訂單重復"),CANCEL_ORDER_FULFILL_ERROR("105066", "調用履約系統失敗"),PROCESS_REFUND_REPEAT("105067", "處理退款重復"),CALCULATING_REFUND_AMOUNT_FAILED("105071", "取消訂單用戶ID不能為空"),PROCESS_REFUND_FAILED("105072", "退款前準備工作失敗"),SEND_MQ_FAILED("105073", "發送MQ消息失敗"),CONSUME_MQ_FAILED("105074", "消費MQ消息失敗"),ORDER_REFUND_AMOUNT_FAILED("105075", "調用支付退款接口失敗"),PROCESS_PAY_REFUND_CALLBACK_REPEAT("105076", "處理支付退款回調重復"),PROCESS_PAY_REFUND_CALLBACK_FAILED("105077", "處理支付退款回調失敗"),PROCESS_PAY_REFUND_CALLBACK_BATCH_NO_IS_NULL("105078", "處理支付退款回調批次號不能為空"),PROCESS_PAY_REFUND_CALLBACK_STATUS_NO_IS_NUL("105079", "處理支付退款回調退款狀態不能為空"),PROCESS_PAY_REFUND_CALLBACK_FEE_NO_IS_NUL("105080", "處理支付退款回調退款金額不能為空"),PROCESS_PAY_REFUND_CALLBACK_TOTAL_FEE_NO_IS_NUL("105081", "處理支付退款回調退款總額不能為空"),PROCESS_PAY_REFUND_CALLBACK_SIGN_NO_IS_NUL("105082", "處理支付退款回調簽名不能為空"),PROCESS_PAY_REFUND_CALLBACK_TRADE_NO_IS_NUL("105083", "處理支付退款回調交易號不能為空"),PROCESS_AFTER_SALE_RETURN_GOODS("105084", "處理售后退款重復"),PROCESS_PAY_REFUND_CALLBACK_AFTER_SALE_ID_IS_NULL("105085", "處理支付退款回調售后訂單號不能為空"),PROCESS_PAY_REFUND_CALLBACK_AFTER_SALE_REFUND_TIME_IS_NULL("105086", "處理支付退款回調售后退款時間不能為空"),REPEAT_CALLBACK("105087", "重復的退款回調"),REPEAT_CALL("105088", "當前接口被重復調用"),REFUND_MONEY_REPEAT("105089", "執行退款操作重復"),ORDER_CANNOT_REPEAT_OPERATE("105090", "當前訂單不能重復操作"),PROCESS_APPLY_AFTER_SALE_CANNOT_REPEAT("105091", "不能重復發起售后"),CUSTOMER_AUDIT_CANNOT_REPEAT("105092", "不能重復發起客服審核"),AFTER_SALE_ITEM_CANNOT_NULL("105093", "售后商品信息不能為空"),//通用異常COLLECTION_PARAM_CANNOT_BEYOND_MAX_SIZE("108001", "[{0}]大小不能超過{1}"),ENUM_PARAM_MUST_BE_IN_ALLOWABLE_VALUE("108002", "[{0}]的取值必須為{1}"),DELIVERY_TYPE_ERROR("105080", "配送類型錯誤"),;private String errorCode;private String errorMsg;...
}
(5)統一返回結果處理規范
規范一:Web層各個Controller組件可統?使?JsonResult組件作為返回值,JsonResult主要是定義了統?返回給前端的Json格式。
其中,success字段表示請求是否成功,data字段表業務數據。請求成功時才會返回業務數據,請求失敗時data是null。當success字段為false時,表示請求失敗,此時errorCode與errorMessage才會有值,errorCode與errorMessage分別表示錯誤碼與錯誤提示信息。
//統一的Spring mvc響應結果封裝對象
public class JsonResult<T> implements Serializable {private static final long serialVersionUID = 1L;private static final boolean REQUEST_SUCCESS = true;//請求成功private static final boolean REQUEST_FAIL = false;//請求失敗private static final String DEFAULT_ERROR_CODE = "-1";//默認錯誤碼private Boolean success;//請求是否成功private T data;//業務數據private String errorCode;//錯誤碼private String errorMessage;//錯誤提示語public JsonResult() {}public JsonResult(Boolean success, T data, String errorCode, String errorMessage) {this.success = success;this.data = data;this.errorCode = errorCode;this.errorMessage = errorMessage;}//成功,不用返回數據public static <T> JsonResult<T> buildSuccess() {return new JsonResult<>(REQUEST_SUCCESS, null, null, null);}//成功,返回數據public static <T> JsonResult<T> buildSuccess(T data) {return new JsonResult<>(REQUEST_SUCCESS, data, null, null);}//失敗,固定狀態碼public static <T> JsonResult<T> buildError(String errorMsg) {return new JsonResult<>(REQUEST_FAIL, null, DEFAULT_ERROR_CODE, errorMsg);}//失敗,自定義錯誤碼和信息//@param errorCode 錯誤碼//@param errorMsg 錯誤提示public static <T> JsonResult<T> buildError(String errorCode, String errorMsg) {return new JsonResult<>(REQUEST_FAIL, null, errorCode, errorMsg);}//失敗,枚舉類定義錯誤碼和信息public static <T> JsonResult<T> buildError(BaseErrorCodeEnum baseErrorCodeEnum) {return new JsonResult<>(REQUEST_FAIL, null, baseErrorCodeEnum.getErrorCode(), baseErrorCodeEnum.getErrorMsg());}...
}
規范二:Controller中的?法也可以不返回JsonResult組件,?返回原樣的對象,最后會由框架中的GlobalResponseBodyAdvice組件來統?攔截處理。
這個組件通過實現ResponseBodyAdvice接口 + 添加@RestControllerAdvice注解,實現了全局Controller?法的默認的返回結果格式的統?處理。其中,處理邏輯都在beforeBodyWrite()?法中。
//默認的Controller全局響應結果處理增強組件
@RestControllerAdvice
@Order
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {//是否支持advice功能@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {Class<?> declaringClass = returnType.getDeclaringClass();if (declaringClass.equals(ApiResourceController.class) || declaringClass.equals(Swagger2Controller.class)) {return false;}if (declaringClass.equals(BasicErrorController.class)) {return false;}return true;}//處理response的具體業務方法@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (!selectedContentType.equalsTypeAndSubtype(MediaType.APPLICATION_JSON)) {return body;}if (body instanceof JsonResult || body instanceof JsonMap) {return body;} else if (body instanceof String) {try {HttpServletResponse httpServletResponse = ServletUtil.getResponse();if (httpServletResponse != null) {ServletUtil.writeJsonMessage(httpServletResponse, JsonResult.buildSuccess(body));return null;}} catch (Exception e) {log.warn("響應字符串對象給前端異常", e);}return JsonUtil.object2Json(JsonResult.buildSuccess(body));}return JsonResult.buildSuccess(body);}
}
規范三:如果某接?就想返回原樣的對象,不想讓返回結果被JsonResult封裝,那么可以讓Controller?法返回一個JsonMap對象。
GlobalResponseBodyAdvice.beforeBodyWrite()?法會對類型為JsonMap的body進行直接返回。
//自定義Map實現,完全兼容java.util.HashMap
public class JsonMap<K, V> extends HashMap<K, V> {public JsonMap(int initialCapacity, float loadFactor) {super(initialCapacity, loadFactor);}public JsonMap(int initialCapacity) {super(initialCapacity);}public JsonMap() {}public JsonMap(Map<? extends K, ? extends V> m) {super(m);}
}
(6)Service層開發規范
業務異常規范:
一.Service層需要中斷執行的邏輯時,統?拋BaseBizException或其?類業務異常
二.DAO層不要顯式地拋任何業務異常,統?由Service來捕捉并拋異常
三.Controller層不需要顯式通過try catch來捕捉Service層拋出的業務異常,因為會由GlobalExceptionHandler組件來進行統?處理
四.拋業務異常時,建議對異常信息定義?個枚舉值,這個枚舉類要繼承BaseErrorCodeEnum
事務處理規范:
對于?查詢接?,必須在Service實現類的?法上添加如下的@Transactional注解。
@Transactional(rollbackFor = Exception.class)
(7)DAO層開發規范
一.Mybatis Plus的配置使?規范
使?mybatisplus 3.x版本;
mybatisplus相關依賴在demo-eshop-common中都已經添加好了;
mybatisplus插件的配置參考com.demo.eshop.order.config.MybatisPlusConfig組件;
mybatisplus其它配置參考application.yml中mybatis-plus開頭的配置項;
二.訂單中?的MybatisPlusConfig組件示例
//Mybatis Plus配置
@Configuration
public class MybatisPlusConfig {//通用字段自動填充配置@Beanpublic MetaObjectHandler metaObjectHandler() {return new MetaObjectHandler() {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "gmtCreate", Date.class, new Date());this.strictInsertFill(metaObject, "gmtModified", Date.class, new Date());}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "gmtModified", Date.class, new Date());}};}//插件配置@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//分頁插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}
三.application.yml中mybatis-plus開頭的配置項
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: truemapper-locations: classpath:mapper/*.xml// mybatis-plus.configuration.log-impl?于控制臺開啟?志輸出,?般只在開發環境使?;
// mybatis-plus.configuration.map-underscore-to-camel-case表示是否開啟下劃線轉駝峰,默認就是true;
// mybatis-plus.mapper-locations表示掃描mapper接?對應的xml?件的位置,默認也是在src/main/resources下的mapper?錄下的;
// 更多配置,參考:https://mp.baomidou.com/config/#configlocation
四.單表操作規范
?般情況下單表操作?論單條記錄還是批量記錄的增刪改查,全部使?Mybatis Plus框架的IService接口或BaseMapper接口。
只有多表關聯查詢時才需要在mapper.xml?件中編寫SQL腳本,不要讓?個SQL腳本?于多個功能多個場景,特別是對很多字段做<#if>判斷。然后作為可能的查詢條件,應該按不同功能需求定義多個?法。
五.DO通用字段處理規范
每個表都必須有三個必選字段 id、gmt_create、gmt_modified,其中gmt_create、gmt_modified這兩個字段值都是讓Mybatis Plus?動填充的,不需要在業務代碼中顯式對這兩個字段進?操作。
不過除了前?的MybatisPlusConfig中的配置之外,還需要在DO類的這兩個字段上添加相應的注解,?如OrderInfoDO類。
public class BaseEntity extends AbstractObject {//主鍵ID@TableId(value = "id", type = IdType.AUTO)private Long id;//創建時間@TableField(fill = FieldFill.INSERT)private Date gmtCreate;//更新時間@TableField(fill = FieldFill.INSERT_UPDATE)private Date gmtModified;...
}//訂單表
@Data
@TableName("order_info")
public class OrderInfoDO extends BaseEntity implements Serializable {private static final long serialVersionUID = 1L;private Integer businessIdentifier;//接入方業務線標識 1, "自營商城"private String orderId;//訂單編號private String parentOrderId;//父訂單編號private String businessOrderId;//接入方訂單號private Integer orderType;//訂單類型 1:一般訂單 255:其它private Integer orderStatus;//訂單狀態 10:已創建, 30:已履約, 40:出庫, 50:配送中, 60:已簽收, 70:已取消, 100:已拒收, 255:無效訂單private String cancelType;//訂單取消類型private Date cancelTime;//訂單取消時間private String sellerId;//賣家編號private String userId;//買家編號private Integer totalAmount;//交易總金額(以分為單位存儲)private Integer payAmount;//交易支付金額private Integer payType;//交易支付方式private String couponId;//使用的優惠券編號private Date payTime;//支付時間private Date expireTime;//支付訂單截止時間private String userRemark;//用戶備注private Integer deleteStatus;//訂單刪除狀態 0:未刪除 1:已刪除private Integer commentStatus;//訂單評論狀態 0:未發表評論 1:已發表評論private String extJson;//擴展信息
}