大纲

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; 无需使用视频编辑器&…