訂單初版—1.分布式訂單系統的簡要設計文檔

大綱

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;//擴展信息
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87957.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87957.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87957.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【HarmonyOS】富文本編輯器RichEditor詳解

【HarmonyOS】富文本編輯器RichEditor詳解 一、前言 在信息化高速發展的今天&#xff0c;普通的文本容器&#xff0c;已經不能夠承載用戶豐富的表達欲。富文本展示已經是移動開發中&#xff0c;必備要解決的問題&#xff0c;在鴻蒙中&#xff0c;通過在系統層提供RichEditor控件…

【MySQL進階】在一臺機器上運行多個MySQL實例

目錄 1.使用MySQL Installer安裝MySQL實例 1.1.去官網下載MySQL Installer 1.2.停止mysql服務 1.3.為不同的版本指定不同的安裝目錄 2.配置不同版本的選項文件 2.1.修改數據目錄 2.2.修改基本目錄 2.3.修改端口號 2.4.設置?志?錄 2.5.配置臨時目錄 2.6.修改綁定地…

verilog中timescale指令的使用

1.timescale指令格式timescale <時間單位> / <時間精度>時間單位&#xff1a;它確定了仿真中時間值的基本單位。比如 1ns 就意味著時間值是以納秒為單位來計量的。 時間精度&#xff1a;該參數決定了時間值能夠表示的最小分辨率。例如 1ps 表示時間可以精確到皮秒級…

08_Excel 導入 - 用戶信息批量導入

08_Excel 導入 - 用戶信息批量導入 1. VO 類 java復制編輯Data AllArgsConstructor NoArgsConstructor public class UserInfoBatch4ExcelReq {ExcelProperty(value "用戶姓名")Schema(description "用戶姓名")private String userName;ExcelProperty(va…

【深度學習新浪潮】什么是世界模型?

世界模型(World Model)是人工智能領域中一類通過構建環境的抽象表示來理解和預測外部世界的系統。它通過整合多模態數據(如視覺、語言、傳感器信號)形成對環境的動態認知,并支持智能體在復雜場景中進行決策與規劃。以下從核心概念、解決的問題、關鍵研究、技術路線、現狀與…

React + Express 傳輸加密以及不可逆加密

一、傳輸加密這里用 對稱加密模式 ASE實現。React 前端const CryptoJS require("crypto-js");// 示例1&#xff1a;ECB模式&#xff08;無需IV&#xff09; const encryptECB (plainText, key) > {return CryptoJS.AES.encrypt(plainText, key, {mode: CryptoJS…

瀏覽器(Chrome /Edge)高效使用 - 內部命令/快捷鍵/啟動參數

今天在CSDN上傳文件,提交總是提示續傳失敗,重試了五六次才想到獲取是科學上網的問題,這個時候其實只要重啟瀏覽器即可,但如果手動關閉瀏覽器再次打開,瀏覽器不會恢復之前的多開窗口(會恢復最后一個窗口內多開的標簽頁,但不會恢復其他窗口)。想了想記得 Chrome 流行的時…

【PTA數據結構 | C語言版】連續子序列最大和

本專欄持續輸出數據結構題目集&#xff0c;歡迎訂閱。 文章目錄 題目代碼 題目 給定 n 個整數組成的序列 { a1 ,a2 ,?,an }&#xff0c;“連續子序列”被定義為 { ai ,ai1 ,?,aj }&#xff0c;其中 1≤i≤j≤n。“連續子序列最大和”則被定義為所有連續子序列元素的和中最大…

Vrrp配置和原理

Vrrp配置和原理 文章目錄Vrrp配置和原理概述物理與邏輯拓撲重點vrid虛擬路由器虛擬IP地址及虛擬MAC地址超時時間計算-MASTER_DOWNvip 管理員手動指定方法Master路由器Backup路由器PriorityVRRP報文格式VRRP狀態機從Backup到masterVRRP協議狀態二.優先級一樣比較接口IPVRRP優先級…

可編輯59頁PPT | 某大型集團人工智能數字化轉型SAP解決方案

薦言摘要&#xff1a;某大型集團人工智能數字化轉型中&#xff0c;SAP解決方案扮演著智能中樞角色&#xff0c;深度融合AI技術與核心業務場景&#xff0c;破解傳統系統“數據孤島流程僵化”雙重困局。針對集團跨產業、多業態特點&#xff0c;方案以SAP S/4HANA為數據底座&#…

【RK3568 驅動開發:實現一個最基礎的網絡設備】

RK3568 驅動開發&#xff1a;實現一個最基礎的網絡設備一、引言二、編寫網絡設備驅動代碼1. 核心數據結構與接口2. 核心功能實現3. 網絡命名空間管理4.源代碼三、編譯與驗證1.加載模塊2.驗證網絡四、注意事項一、引言 RK3568 作為一款高性能 ARM 架構處理器&#xff0c;廣泛應…

CAIDCP系列對話:AI 驅動安全

數字時代&#xff0c;AI浪潮翻涌&#xff0c;網絡安全攻防戰已悄然升級&#xff1a; 某工業控制系統遭AI驅動勒索攻擊&#xff1a;攻擊者借 AI 精準捕捉異常網絡掃描、遠程 PowerShell 痕跡&#xff0c;瞬間加密文件索要贖金&#xff1b; 另一邊&#xff0c;某大型科技公司用AI…

ARMv8 沒開mmu執行memset引起的非對齊訪問異常

最近在haps上驗證一個新的芯片&#xff0c;記錄一下memset訪問出錯的問題。在沒開mmu和cache的情況下&#xff0c;對全局變量指針進行memset清零操作&#xff0c;發現每次都會出現異常。最后發現是沒開mmu導致出現了數據非對齊訪問導致報錯。排查EC區域發現是0x25&#xff0c;產…

基于LiveKit Go 實現騰訊云實時音視頻功能

詳細的生產部署建議&#xff0c;適用于 LiveKit Go 服務器 Web 客戶端 TURN/HTTPS。 1. 服務器準備 推薦使用云服務器&#xff08;如阿里云、騰訊云、AWS、Azure等&#xff09;&#xff0c;公網IP&#xff0c;帶寬建議≥10Mbps。系統推薦 Ubuntu 20.04/22.04 或 CentOS 7/8&…

三位一體:Ovis-U1如何以30億參數重構多模態AI格局?

1. 時代命題&#xff1a;多模態統一模型的破局之戰當GPT-4o以萬億級參數構建多模態帝國時&#xff0c;中國AI軍團正在書寫另一種答案。Ovis-U1用30億參數證明&#xff1a;參數量并非決定性因素&#xff0c;架構創新與訓練策略的化學反應&#xff0c;同樣能催生出改變游戲規則的…

圖像處理基礎:鏡像、縮放與矯正

在圖像處理中&#xff0c;鏡像、縮放和矯正操作是常見的圖像變換手段。這些操作可以幫助我們對圖像進行調整&#xff0c;以滿足不同的需求。本文將詳細介紹這三種操作的原理和實現方法&#xff0c;并通過代碼示例展示它們的實際應用。一、圖片鏡像旋轉1.1 什么是鏡像旋轉&#…

「Java案例」猜數游戲

案例實現 猜數字游戲 設計一個三位數的猜數游戲,三位數隨機生成。程序提示用戶輸入一個三位的數字,依照以下的規則決定贏取多少獎金:1) 如果用戶輸入的數字和隨機數字完全一致,輸出:“恭喜恭喜!完全猜對了!獲得三個贊!”2) 如果用戶輸入的數字覆蓋了隨機生成的所有數…

創客匠人解析創始人 IP 內卷:知識變現時代的生存邏輯與破局路徑

當知識付費行業進入 “存量競爭” 階段&#xff0c;創始人 IP 的 “內卷” 已非選擇而是必然。創客匠人在服務數萬知識創業者的實踐中發現&#xff0c;那些實現逆勢增長的案例&#xff0c;其核心差異往往在于創始人是否具備 “從幕后走到臺前” 的決心與能力 —— 這種內卷并非…

250705-Debian12-sudo apt update加速+配置RDP遠程桌面環境+設置FRP服務為開機啟動項

A. 實現sudo apt update加速 在 Debian 12 上運行 sudo apt update 很慢的常見原因包括&#xff1a; &#x1f50d; 一、常見原因分析 使用了國外的軟件源 默認 Debian 安裝源多數是國際服務器&#xff0c;國內訪問會非常慢。 DNS 解析慢或失敗 軟件源地址解析時間長&#xf…

數學視頻動畫引擎Python庫 -- Manim Voiceover 語音服務 Speech Services

文中內容僅限技術學習與代碼實踐參考&#xff0c;市場存在不確定性&#xff0c;技術分析需謹慎驗證&#xff0c;不構成任何投資建議。 Manim Voiceover 是一個為 Manim 打造的專注于語音旁白的插件&#xff1a; 直接在 Python 中添加語音旁白&#xff1a; 無需使用視頻編輯器&…