大纲
1.取消订单的流程回顾
2.拦截履约、取消订单与释放资产的强一致问题
3.取消订单与拦截履约的AT分布式事务
4.释放资产的RocketMQ事务消息原理
5.释放资产的设计:异步化 + 扩展性 + 消息不丢失
6.消费释放资产消息后进行多路推送MQ消息的设计
7.释放具体资产时的幂等性保障
8.双异步退款链路强一致方案
9.发起售后退货链路数据一致方案
10.审核售后退货链路数据一致方案
11.拣货出库和缺品退款的业务场景
12.缺品退款一致性事务代码流程
13.进行代码重构的要点与示例
14.自动关单功能业务流程梳理
15.XXL-Job分布式调度系统架构原理分析
16.基于XXL-Job分布式调度的定时关单功能实现
17.订单系统与XXL-Job配合运行原理
18.根据XXL-Job源码来验证关单分布式调度原理
10.审核售后退货链路数据一致方案
一.更新售后数据 + 发送退款消息到MQ也使用了MQ的事务消息来保证数据一致性
二.增加消费释放资产消息的消费者,多路推送释放具体资源的MQ消息来提高扩展性
三.利用分布式锁 + 状态前置检查确保消息消费幂等
四.进行退款没有采用双异步,因为此时退款不需要更新订单状态。即没有发送准备退款消息到MQ,而是只发送实际退款消息到MQ
typescript
@RestController
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
@Autowired
private RedisLock redisLock;
//客服售后审核
@PostMapping("/audit")
public JsonResult<Boolean> audit(@RequestBody CustomerReviewReturnGoodsRequest customerReviewReturnGoodsRequest) {
Long afterSaleId = customerReviewReturnGoodsRequest.getAfterSaleId();
//分布式锁
String key = RedisLockKeyConstants.REFUND_KEY + afterSaleId;
boolean lock = redisLock.tryLock(key);
if (!lock) {
throw new CustomerBizException(CustomerErrorCodeEnum.CUSTOMER_AUDIT_CANNOT_REPEAT);
}
try {
//客服审核
return customerService.customerAudit(customerReviewReturnGoodsRequest);
} catch (Exception e) {
log.error("system error", e);
return JsonResult.buildError(e.getMessage());
} finally {
redisLock.unlock(key);
}
}
}
@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private AfterSaleRemote afterSaleRemote;
...
//客服审核
@Override
public JsonResult<Boolean> customerAudit(CustomerReviewReturnGoodsRequest customerReviewReturnGoodsRequest) {
return afterSaleRemote.receiveCustomerAuditResult(customerReviewReturnGoodsRequest);
}
...
}
@Component
public class AfterSaleRemote {
@DubboReference(version = "1.0.0")
private AfterSaleApi afterSaleApi;
//接收客服的审核结果
public JsonResult<Boolean> receiveCustomerAuditResult(CustomerReviewReturnGoodsRequest customerReviewReturnGoodsRequest) {
JsonResult<Boolean> jsonResult = afterSaleApi.receiveCustomerAuditResult(customerReviewReturnGoodsRequest);
if (!jsonResult.getSuccess()) {
throw new CustomerBizException(jsonResult.getErrorCode(), jsonResult.getErrorMessage());
}
return jsonResult;
}
...
}
@DubboService(version = "1.0.0", interfaceClass = AfterSaleApi.class, retries = 0)
public class AfterSaleApiImpl implements AfterSaleApi {
...
@Override
public JsonResult<Boolean> receiveCustomerAuditResult(CustomerReviewReturnGoodsRequest customerReviewReturnGoodsRequest) {
//1.组装接收客服审核结果的数据
CustomerAuditAssembleRequest customerAuditAssembleResult = buildCustomerAuditAssembleData(customerReviewReturnGoodsRequest);
//2.客服审核拒绝
if (CustomerAuditResult.REJECT.getCode().equals(customerAuditAssembleResult.getReviewReasonCode())) {
//更新 审核拒绝 售后信息
orderAfterSaleService.receiveCustomerAuditReject(customerAuditAssembleResult);
return JsonResult.buildSuccess(true);
}
//3.客服审核通过
if (CustomerAuditResult.ACCEPT.getCode().equals(customerAuditAssembleResult.getReviewReasonCode())) {
String orderId = customerAuditAssembleResult.getOrderId();
Long afterSaleId = customerAuditAssembleResult.getAfterSaleId();
AfterSaleItemDO afterSaleItemDO = afterSaleItemDAO.getOrderIdAndAfterSaleId(orderId, afterSaleId);
if (afterSaleItemDO == null) {
throw new OrderBizException(OrderErrorCodeEnum.AFTER_SALE_ITEM_CANNOT_NULL);
}
//4.组装释放库存参数
AuditPassReleaseAssetsRequest auditPassReleaseAssetsRequest = buildAuditPassReleaseAssets(afterSaleItemDO, customerAuditAssembleResult, orderId);
//5.组装事务MQ消息
TransactionMQProducer producer = defaultProducer.getProducer();
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
//更新 审核通过 售后信息
orderAfterSaleService.receiveCustomerAuditAccept(customerAuditAssembleResult);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
log.error("system error", e);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer customerAuditAfterSaleStatus = orderAfterSaleService.findCustomerAuditAfterSaleStatus(customerAuditAssembleResult.getAfterSaleId());
if (AfterSaleStatusEnum.REVIEW_PASS.getCode().equals(customerAuditAfterSaleStatus)) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
});
try {
Message message = new Message(RocketMqConstant.CUSTOMER_AUDIT_PASS_RELEASE_ASSETS_TOPIC, JSONObject.toJSONString(auditPassReleaseAssetsRequest).getBytes(StandardCharsets.UTF_8));
//6.发送事务MQ消息 客服审核通过后释放权益资产
TransactionSendResult result = producer.sendMessageInTransaction(message, auditPassReleaseAssetsRequest);
if (!result.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)) {
throw new OrderBizException(OrderErrorCodeEnum.SEND_AUDIT_PASS_RELEASE_ASSETS_FAILED);
}
return JsonResult.buildSuccess(true);
} catch (Exception e) {
throw new OrderBizException(OrderErrorCodeEnum.SEND_TRANSACTION_MQ_FAILED);
}
}
return JsonResult.buildSuccess(true);
}
...
}
@Configuration
public class ConsumerConfig {
@Autowired
private RocketMQProperties rocketMQProperties;
...
//释放资产消息消费者
@Bean("auditPassReleaseAssetsConsumer")
public DefaultMQPushConsumer auditPassReleaseAssetsConsumer(AuditPassReleaseAssetsListener auditPassReleaseAssetsListener) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CUSTOMER_AUDIT_PASS_RELEASE_ASSETS_CONSUMER_GROUP);
consumer.setNamesrvAddr(rocketMQProperties.getNameServer());
consumer.subscribe(CUSTOMER_AUDIT_PASS_RELEASE_ASSETS_TOPIC, "*");
consumer.registerMessageListener(auditPassReleaseAssetsListener);
consumer.start();
return consumer;
}
...
}
@Component
public class AuditPassReleaseAssetsListener implements MessageListenerConcurrently {
@Autowired
private DefaultProducer defaultProducer;
@Autowired
private OrderConverter orderConverter;
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
try {
for (MessageExt messageExt : list) {
//1.消费到释放资产message
String message = new String(messageExt.getBody());
log.info("AuditPassReleaseAssetsListener message:{}", message);
AuditPassReleaseAssetsRequest auditPassReleaseAssetsRequest = JSONObject.parseObject(message, AuditPassReleaseAssetsRequest.class);
//2.发送释放库存MQ
ReleaseProductStockDTO releaseProductStockDTO = auditPassReleaseAssetsRequest.getReleaseProductStockDTO();
ReleaseProductStockRequest releaseProductStockRequest = buildReleaseProductStock(releaseProductStockDTO);
defaultProducer.sendMessage(RocketMqConstant.CANCEL_RELEASE_INVENTORY_TOPIC,
JSONObject.toJSONString(releaseProductStockRequest), "客服审核通过释放库存", null, null);
//3.发送实际退款
ActualRefundMessage actualRefundMessage = auditPassReleaseAssetsRequest.getActualRefundMessage();
defaultProducer.sendMessage(RocketMqConstant.ACTUAL_REFUND_TOPIC,
JSONObject.toJSONString(actualRefundMessage), "客服审核通过实际退款", null, null);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
log.error("consumer error", e);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
...
}
11.拣货出库和缺品退款的业务场景
12.缺品退款一致性事务代码流程
scss
@RestController
@RequestMapping("/afterSale")
public class AfterSaleController {
...
//缺品请求
@PostMapping("/lackItem")
public JsonResult<LackDTO> lackItem(@RequestBody LackRequest request) {
JsonResult<LackDTO> result = afterSaleApi.lackItem(request);
return result;
}
...
}
@DubboService(version = "1.0.0", interfaceClass = AfterSaleApi.class, retries = 0)
public class AfterSaleApiImpl implements AfterSaleApi {
@Autowired
private OrderLackService orderLackItemService;
...
@Override
public JsonResult<LackDTO> lackItem(LackRequest request) {
log.info("request={}", JSONObject.toJSONString(request));
try {
//1.参数基本校验
ParamCheckUtil.checkStringNonEmpty(request.getOrderId(), OrderErrorCodeEnum.ORDER_ID_IS_NULL);
ParamCheckUtil.checkCollectionNonEmpty(request.getLackItems(), OrderErrorCodeEnum.LACK_ITEM_IS_NULL);
//2.加锁防并发
String lockKey = RedisLockKeyConstants.LACK_REQUEST_KEY + request.getOrderId();
if (!redisLock.tryLock(lockKey)) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_NOT_ALLOW_TO_LACK);
}
//3.参数校验
CheckLackDTO checkResult = orderLackItemService.checkRequest(request);
try {
//4.缺品处理
return JsonResult.buildSuccess(orderLackItemService.executeLackRequest(request, checkResult));
} finally {
redisLock.unlock(lockKey);
}
} catch (OrderBizException e) {
log.error("biz error", e);
return JsonResult.buildError(e.getErrorCode(), e.getErrorMsg());
} catch (Exception e) {
log.error("error", e);
return JsonResult.buildError(e.getMessage());
}
}
...
}
@Service
public class OrderLackServiceImpl implements OrderLackService {
...
@Override
public CheckLackDTO checkRequest(LackRequest request) throws OrderBizException {
//1.查询订单
OrderInfoDO order = orderInfoDAO.getByOrderId(request.getOrderId());
ParamCheckUtil.checkObjectNonNull(order, OrderErrorCodeEnum.ORDER_NOT_FOUND);
//2.校验订单是否可以发起缺品
//可以发起缺品的前置条件:
//(1)订单的状态为:已出库
//(2)订单未发起过缺品
//解释一下为啥是"已库存"状态才能发起缺品:
//缺品的业务逻辑是这样的,当订单支付后,进入履约流程,仓库人员捡货当时候发现现有商品无法满足下单所需,即"缺品"了
//仓库人员首先会将通知订单系统将订单的状态变为"已出库",然后会再来调用这个缺品的接口
if (!OrderStatusEnum.canLack().contains(order.getOrderStatus()) || isOrderLacked(order)) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_NOT_ALLOW_TO_LACK);
}
//3.查询订单item
List<OrderItemDO> orderItems = orderItemDAO.listByOrderId(request.getOrderId());
//4.校验具体的缺品项
List<LackItemDTO> lackItems = new ArrayList<>();
for (LackItemRequest itemRequest : request.getLackItems()) {
lackItems.add(checkLackItem(order, orderItems, itemRequest));
}
//5.构造返参
return new CheckLackDTO(order, lackItems);
}
...
@Transactional(rollbackFor = Exception.class)
@Override
public LackDTO executeLackRequest(LackRequest request, CheckLackDTO checkLackItemDTO) throws Exception {
OrderInfoDO order = checkLackItemDTO.getOrder();
List<LackItemDTO> lackItems = checkLackItemDTO.getLackItems();
//1.生成缺品售后单
AfterSaleInfoDO lackAfterSaleOrder = buildLackAfterSaleInfo(order);
//2.生成缺品售后单item
List<AfterSaleItemDO> afterSaleItems = new ArrayList<>();
lackItems.forEach(item -> {
afterSaleItems.add(buildLackAfterSaleItem(order, lackAfterSaleOrder, item));
});
//3.计算订单缺品退款总金额
Integer lackApplyRefundAmount = afterSaleAmountService.calculateOrderLackApplyRefundAmount(afterSaleItems);
Integer lackRealRefundAmount = afterSaleAmountService.calculateOrderLackRealRefundAmount(afterSaleItems);
lackAfterSaleOrder.setApplyRefundAmount(lackApplyRefundAmount);
lackAfterSaleOrder.setRealRefundAmount(lackRealRefundAmount);
//4.构造售后退款单
AfterSaleRefundDO afterSaleRefund = buildLackAfterSaleRefundDO(order, lackAfterSaleOrder);
//5.构造订单缺品扩展信息
OrderExtJsonDTO lackExtJson = buildOrderLackExtJson(request, order, lackAfterSaleOrder);
//6.构造订单缺品信息
OrderLackInfo orderLackInfo = OrderLackInfo.builder()
.lackAfterSaleOrder(lackAfterSaleOrder)
.afterSaleItems(afterSaleItems)
.afterSaleRefund(afterSaleRefund)
.lackExtJson(lackExtJson)
.orderId(order.getOrderId())
.build();
//7.通过缺品事务消息保存缺品数据
TransactionMQProducer producer = defaultProducer.getProducer();
producer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
//保存缺品数据
orderLackProcessor.saveLackInfo(orderLackInfo);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (BaseBizException e) {
throw e;
} catch (Exception e) {
log.error("system error", e);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
//检查缺品售后单是否已经创建
Long afterSaleId = lackAfterSaleOrder.getAfterSaleId();
AfterSaleInfoDO afterSaleInfoDO = afterSaleInfoDAO.getOneByAfterSaleId(afterSaleId);
if (afterSaleInfoDO != null) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
});
//8.发送缺品退款的消息
ActualRefundMessage actualRefundMessage = new ActualRefundMessage();
actualRefundMessage.setAfterSaleRefundId(afterSaleRefund.getId());
actualRefundMessage.setOrderId(order.getOrderId());
actualRefundMessage.setAfterSaleId(lackAfterSaleOrder.getAfterSaleId());
String topic = ACTUAL_REFUND_TOPIC;
byte[] body = JSON.toJSONString(actualRefundMessage).getBytes(StandardCharsets.UTF_8);
Message mq = new Message(topic, null, order.getOrderId(), body);
producer.sendMessageInTransaction(mq, order);
return new LackDTO(order.getOrderId(), lackAfterSaleOrder.getAfterSaleId());
}
}
13.进行代码重构的要点与示例
(1)高并发场景下for循环写数据库问题
for循环读写优化成批量读写。
(2)高并发for循环RPC调用与服务雪崩问题
for循环RPC调用优化成合并调用。
(3)代码重构之冗长方法抽取分离
按业务逻辑划分,提取业务处理方法。
(4)代码重构之分布式锁与幂等检查强关联
加分布式锁后,紧接着应该就是数据的状态检查。
(5)支付回调的整洁代码重构
一.重构前的代码方法过长 + 逻辑不清晰
二.重构后的代码业务清晰 + 代码简洁
重构五步曲:提取参数、入参检查、加分布式锁、幂等检查、提取业务处理方法。
一.重构前的代码方法过长 + 逻辑不清晰
scss
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderManager orderManager;
...
//支付回调
//支付回调有2把分布式锁的原因说明:同一笔订单在同一时间只能支付or取消
//不可以同时对一笔订单,既发起支付,又发起取消
@Override
public void payCallback(PayCallbackRequest payCallbackRequest) {
log.info(LoggerFormat.build().remark("payCallback->request").data("request", payCallbackRequest).finish());
//入参检查
checkPayCallbackRequestParam(payCallbackRequest);
String orderId = payCallbackRequest.getOrderId();
Integer payAmount = payCallbackRequest.getPayAmount();
Integer payType = payCallbackRequest.getPayType();
List<String> redisKeyList = Lists.newArrayList();
//加支付分布式锁避免支付系统并发回调
String orderPayKey = RedisLockKeyConstants.ORDER_PAY_KEY + orderId;
//加取消订单分布式锁避免支付和取消订单同时操作同一笔订单
String cancelOrderKey = RedisLockKeyConstants.CANCEL_KEY + orderId;
redisKeyList.add(orderPayKey);
redisKeyList.add(cancelOrderKey);
boolean lock = redisLock.multiLock(redisKeyList);
if (!lock) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_CALLBACK_ERROR);
}
try {
//从数据库中查询出当前订单信息
OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
OrderPaymentDetailDO orderPaymentDetailDO = orderPaymentDetailDAO.getPaymentDetailByOrderId(orderId);
//校验参数
if (orderInfoDO == null || orderPaymentDetailDO == null) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_INFO_IS_NULL);
}
if (!payAmount.equals(orderInfoDO.getPayAmount())) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CALLBACK_PAY_AMOUNT_ERROR);
}
//异常场景判断
Integer orderStatus = orderInfoDO.getOrderStatus();
if (OrderStatusEnum.CREATED.getCode().equals(orderStatus)) {
//如果订单状态是 "已创建",直接更新订单状态为已支付,并发送事务消息
TransactionMQProducer transactionMQProducer = defaultProducer.getProducer();
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
orderManager.updateOrderStatusPaid(payCallbackRequest, orderInfoDO, orderPaymentDetailDO);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (BaseBizException e) {
throw e;
} catch (Exception e) {
log.error("system error", e);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
//检查订单是否是已支付
OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
if (orderInfoDO != null && OrderStatusEnum.PAID.getCode().equals(orderInfoDO.getOrderStatus())) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
});
//发送 "订单已完成支付" 消息
sendPaidOrderSuccessMessage(transactionMQProducer, orderInfoDO);
} else {
//如果订单状态不是 "已创建"
if (OrderStatusEnum.CANCELED.getCode().equals(orderStatus)) {
//如果订单那状态是取消状态
Integer payStatus = orderPaymentDetailDO.getPayStatus();
if (PayStatusEnum.UNPAID.getCode().equals(payStatus)) {
//调用退款
executeOrderRefund(orderInfoDO, orderPaymentDetailDO);
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_ERROR);
} else if (PayStatusEnum.PAID.getCode().equals(payStatus)) {
if (payType.equals(orderPaymentDetailDO.getPayType())) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_SAME_ERROR);
} else {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_NO_SAME_ERROR);
}
}
} else {
//如果订单状态不是取消状态
if (PayStatusEnum.PAID.getCode().equals(orderPaymentDetailDO.getPayStatus())) {
if (payType.equals(orderPaymentDetailDO.getPayType())) {
return;
}
//调用退款
executeOrderRefund(orderInfoDO, orderPaymentDetailDO);
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_REPEAT_ERROR);
}
}
}
log.info(LoggerFormat.build().remark("payCallback->response").finish());
} catch (Exception e) {
log.error("payCallback error", e);
throw new OrderBizException(e.getMessage());
} finally {
//释放分布式锁
redisLock.unMultiLock(redisKeyList);
}
}
...
}
@Service
public class OrderManagerImpl implements OrderManager {
...
//支付回调更新订单状态
@Transactional(rollbackFor = Exception.class)
@Override
public void updateOrderStatusPaid(PayCallbackRequest payCallbackRequest, OrderInfoDO orderInfoDO, OrderPaymentDetailDO orderPaymentDetailDO) {
//主单信息
String orderId = payCallbackRequest.getOrderId();
Integer preOrderStatus = orderInfoDO.getOrderStatus();
orderInfoDO.setOrderStatus(OrderStatusEnum.PAID.getCode());
orderInfoDAO.updateById(orderInfoDO);
//主单支付信息
orderPaymentDetailDO.setPayStatus(PayStatusEnum.PAID.getCode());
orderPaymentDetailDAO.updateById(orderPaymentDetailDO);
//新增订单状态变更日志
OrderOperateLogDO orderOperateLogDO = new OrderOperateLogDO();
orderOperateLogDO.setOrderId(orderId);
orderOperateLogDO.setOperateType(OrderOperateTypeEnum.PAID_ORDER.getCode());
orderOperateLogDO.setPreStatus(preOrderStatus);
orderOperateLogDO.setCurrentStatus(orderInfoDO.getOrderStatus());
orderOperateLogDO.setRemark("订单支付回调操作" + orderOperateLogDO.getPreStatus() + "-" + orderOperateLogDO.getCurrentStatus());
orderOperateLogDAO.save(orderOperateLogDO);
//判断是否存在子订单
List<OrderInfoDO> subOrderInfoDOList = orderInfoDAO.listByParentOrderId(orderId);
if (subOrderInfoDOList != null && !subOrderInfoDOList.isEmpty()) {
//先将主订单状态设置为无效订单
Integer newPreOrderStatus = orderInfoDO.getOrderStatus();
orderInfoDO.setOrderStatus(OrderStatusEnum.INVALID.getCode());
orderInfoDAO.updateById(orderInfoDO);
//新增订单状态变更日志
OrderOperateLogDO newOrderOperateLogDO = new OrderOperateLogDO();
newOrderOperateLogDO.setOrderId(orderId);
newOrderOperateLogDO.setOperateType(OrderOperateTypeEnum.PAID_ORDER.getCode());
newOrderOperateLogDO.setPreStatus(newPreOrderStatus);
newOrderOperateLogDO.setCurrentStatus(OrderStatusEnum.INVALID.getCode());
orderOperateLogDO.setRemark("订单支付回调操作,主订单状态变更" + newOrderOperateLogDO.getPreStatus() + "-" + newOrderOperateLogDO.getCurrentStatus());
orderOperateLogDAO.save(newOrderOperateLogDO);
//再更新子订单的状态
List<OrderInfoDO> tempSubOrderInfoDOList = new ArrayList<>();
List<String> tempSubOrderIdList = new ArrayList<>();
List<OrderOperateLogDO> tempSubOrderOperateLogDOList = new ArrayList<>();
for (OrderInfoDO subOrderInfo : subOrderInfoDOList) {
Integer subPreOrderStatus = subOrderInfo.getOrderStatus();
subOrderInfo.setOrderStatus(OrderStatusEnum.PAID.getCode());
tempSubOrderInfoDOList.add(subOrderInfo);
//子订单的支付明细
String subOrderId = subOrderInfo.getOrderId();
tempSubOrderIdList.add(subOrderId);
//订单状态变更日志
OrderOperateLogDO subOrderOperateLogDO = new OrderOperateLogDO();
subOrderOperateLogDO.setOrderId(subOrderId);
subOrderOperateLogDO.setOperateType(OrderOperateTypeEnum.PAID_ORDER.getCode());
subOrderOperateLogDO.setPreStatus(subPreOrderStatus);
subOrderOperateLogDO.setCurrentStatus(OrderStatusEnum.PAID.getCode());
orderOperateLogDO.setRemark("订单支付回调操作,子订单状态变更" + subOrderOperateLogDO.getPreStatus() + "-" + subOrderOperateLogDO.getCurrentStatus());
tempSubOrderOperateLogDOList.add(subOrderOperateLogDO);
}
//更新子订单
if (!tempSubOrderInfoDOList.isEmpty()) {
orderInfoDAO.updateBatchById(tempSubOrderInfoDOList);
}
//更新子订单的支付明细
if (!tempSubOrderIdList.isEmpty()) {
OrderPaymentDetailDO subOrderPaymentDetailDO = new OrderPaymentDetailDO();
subOrderPaymentDetailDO.setPayStatus(PayStatusEnum.PAID.getCode());
orderPaymentDetailDAO.updateBatchByOrderIds(subOrderPaymentDetailDO, tempSubOrderIdList);
}
//新增订单状态变更日志
if (!tempSubOrderOperateLogDOList.isEmpty()) {
orderOperateLogDAO.saveBatch(tempSubOrderOperateLogDOList);
}
}
}
...
}
二.重构后的代码业务清晰 + 代码简洁
OrderServiceImpl的重构:
scss
@Service
public class OrderServiceImpl implements OrderService {
...
//支付回调
//支付回调有2把分布式锁的原因说明:同一笔订单在同一时间只能支付or取消
//不可以同时对一笔订单,既发起支付,又发起取消
@Override
public void payCallback(PayCallbackRequest payCallbackRequest) {
log.info(LoggerFormat.build().remark("payCallback->request").data("request", payCallbackRequest).finish());
//提取请求参数中的数据
String orderId = payCallbackRequest.getOrderId();
Integer payType = payCallbackRequest.getPayType();
//从数据库中查询出当前订单信息
OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(orderId);
OrderPaymentDetailDO orderPaymentDetailDO = orderPaymentDetailDAO.getPaymentDetailByOrderId(orderId);
//入参检查
checkPayCallbackRequestParam(payCallbackRequest, orderInfoDO, orderPaymentDetailDO);
//为支付回调操作进行多重分布式锁加锁
List<String> redisKeyList = Lists.newArrayList();
payCallbackMultiLock(redisKeyList, orderId);
try {
Integer orderStatus = orderInfoDO.getOrderStatus();
Integer payStatus = orderPaymentDetailDO.getPayStatus();
//幂等性检查
if (!OrderStatusEnum.CREATED.getCode().equals(orderStatus)) {
//异常场景处理
payCallbackFailure(orderStatus, payStatus, payType, orderPaymentDetailDO, orderInfoDO);
return;
}
//执行正式的订单支付回调处理
doPayCallback(orderInfoDO);
log.info(LoggerFormat.build().remark("payCallback->response").finish());
} catch (Exception e) {
log.error("payCallback error", e);
throw new OrderBizException(e.getMessage());
} finally {
//释放分布式锁
redisLock.unMultiLock(redisKeyList);
}
}
//检查订单支付回调接口入参
private void checkPayCallbackRequestParam(PayCallbackRequest payCallbackRequest, OrderInfoDO orderInfoDO, OrderPaymentDetailDO orderPaymentDetailDO) {
ParamCheckUtil.checkObjectNonNull(payCallbackRequest);
//订单号
String orderId = payCallbackRequest.getOrderId();
ParamCheckUtil.checkStringNonEmpty(orderId);
//支付金额
Integer payAmount = payCallbackRequest.getPayAmount();
ParamCheckUtil.checkObjectNonNull(payAmount);
//支付系统交易流水号
String outTradeNo = payCallbackRequest.getOutTradeNo();
ParamCheckUtil.checkStringNonEmpty(outTradeNo);
//支付类型
Integer payType = payCallbackRequest.getPayType();
ParamCheckUtil.checkObjectNonNull(payType);
if (PayTypeEnum.getByCode(payType) == null) {
throw new OrderBizException(OrderErrorCodeEnum.PAY_TYPE_PARAM_ERROR);
}
//商户ID
String merchantId = payCallbackRequest.getMerchantId();
ParamCheckUtil.checkStringNonEmpty(merchantId);
//校验参数
if (orderInfoDO == null || orderPaymentDetailDO == null) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_INFO_IS_NULL);
}
if (!payAmount.equals(orderInfoDO.getPayAmount())) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CALLBACK_PAY_AMOUNT_ERROR);
}
}
//支付回调加分布式锁
private void payCallbackMultiLock(List<String> redisKeyList, String orderId) {
//加支付分布式锁避免支付系统并发回调
String orderPayKey = RedisLockKeyConstants.ORDER_PAY_KEY + orderId;
//加取消订单分布式锁避免支付和取消订单同时操作同一笔订单
String cancelOrderKey = RedisLockKeyConstants.CANCEL_KEY + orderId;
redisKeyList.add(orderPayKey);
redisKeyList.add(cancelOrderKey);
boolean lock = redisLock.multiLock(redisKeyList);
if (!lock) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_CALLBACK_ERROR);
}
}
//支付回调异常的时候处理逻辑
public void payCallbackFailure(Integer orderStatus, Integer payStatus, Integer payType, OrderPaymentDetailDO orderPaymentDetailDO, OrderInfoDO orderInfoDO) {
//如果订单那状态是取消状态
//可能是支付回调前就取消了订单,也有可能支付回调成功后取消了订单
if (OrderStatusEnum.CANCELED.getCode().equals(orderStatus)) {
//此时如果订单的支付状态是未支付的话
//说明用户在取消订单的时候,支付系统还没有完成回调,而支付系统又已经扣了用户的钱,所以要调用一下退款
if (PayStatusEnum.UNPAID.getCode().equals(payStatus)) {
//调用退款
executeOrderRefund(orderInfoDO, orderPaymentDetailDO);
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_ERROR);
}
//此时如果订单的支付状态是已支付的话
//说明用户在取消订单的时候,订单已经不是"已创建"状态了
if (PayStatusEnum.PAID.getCode().equals(payStatus)) {
if (payType.equals(orderPaymentDetailDO.getPayType())) {
//非"已创建"状态订单的取消操作本身就会进行退款的
//所以如果是同种支付方式,说明用户并没有进行多次支付,是不需要调用退款接口
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_SAME_ERROR);
} else {
//而非同种支付方式的话,说明用户还是更换了不同支付方式进行了多次扣款,所以需要调用一下退款接口
//调用退款
executeOrderRefund(orderInfoDO, orderPaymentDetailDO);
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_PAY_TYPE_NO_SAME_ERROR);
}
}
} else {
//如果订单状态不是取消状态(那么就是已履约、已出库、配送中等状态)
if (PayStatusEnum.PAID.getCode().equals(payStatus)) {
//如果是同种支付方式回调,说明用户是并没有发起重复付款的,只是支付系统多触发了一次回调
//这里做好冥等判断,直接return即可,不需要调用退款接口
if (payType.equals(orderPaymentDetailDO.getPayType())) {
return;
}
//如果是非同种支付方式,说明用户更换了不同的支付方式发起了重复付款,所以要调用一下退款接口
//调用退款
executeOrderRefund(orderInfoDO, orderPaymentDetailDO);
throw new OrderBizException(OrderErrorCodeEnum.ORDER_CANCEL_PAY_CALLBACK_REPEAT_ERROR);
}
}
}
//支付回调成功的时候处理逻辑
private void doPayCallback(OrderInfoDO orderInfoDO) throws MQClientException {
//如果订单状态是 "已创建",直接更新订单状态为已支付,并发送事务消息
TransactionMQProducer transactionMQProducer = paidOrderSuccessProducer.getProducer();
setPayCallbackTransactionListener(transactionMQProducer);
sendPayCallbackSuccessMessage(transactionMQProducer, orderInfoDO);
}
//发送支付成功消息时,设置事务消息TransactionListener组件
private void setPayCallbackTransactionListener(TransactionMQProducer transactionMQProducer) {
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
OrderInfoDO orderInfo = (OrderInfoDO) o;
orderManager.updateOrderStatusWhenPayCallback(orderInfo);
return LocalTransactionState.COMMIT_MESSAGE;
} catch (BaseBizException e) {
throw e;
} catch (Exception e) {
log.error("system error", e);
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
PaidOrderSuccessMessage paidOrderSuccessMessage = JSON.parseObject(new String(messageExt.getBody(), StandardCharsets.UTF_8), PaidOrderSuccessMessage.class);
//检查订单是否是已支付
OrderInfoDO orderInfoDO = orderInfoDAO.getByOrderId(paidOrderSuccessMessage.getOrderId());
if (orderInfoDO != null && OrderStatusEnum.PAID.getCode().equals(orderInfoDO.getOrderStatus())) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
});
}
//送订单已完成支付消息,触发订单进行履约
private void sendPayCallbackSuccessMessage(TransactionMQProducer transactionMQProducer, OrderInfoDO orderInfoDO) throws MQClientException {
String orderId = orderInfoDO.getOrderId();
PaidOrderSuccessMessage message = new PaidOrderSuccessMessage();
message.setOrderId(orderId);
log.info(LoggerFormat.build().remark("发送订单已支付消息").data("message", message).finish());
String topic = RocketMqConstant.PAID_ORDER_SUCCESS_TOPIC;
byte[] body = JSON.toJSONString(message).getBytes(StandardCharsets.UTF_8);
Message mq = new MQMessage(topic, null, orderId, body);
TransactionSendResult result = transactionMQProducer.sendMessageInTransaction(mq, orderInfoDO);
if (!result.getSendStatus().equals(SendStatus.SEND_OK)) {
throw new OrderBizException(OrderErrorCodeEnum.ORDER_PAY_CALLBACK_SEND_MQ_ERROR);
}
}
...
}
OrderManagerImpl的重构:
scss
@Service
public class OrderManagerImpl implements OrderManager {
...
//支付回调更新订单状态
@Transactional(rollbackFor = Exception.class)
@Override
public void updateOrderStatusWhenPayCallback(OrderInfoDO orderInfoDO) {
//更新主单的状态
updateMasterOrderStatus(orderInfoDO);
//判断是否存在子订单
String orderId = orderInfoDO.getOrderId();
List<OrderInfoDO> subOrderInfoDOList = orderInfoDAO.listByParentOrderId(orderId);
if (subOrderInfoDOList == null || subOrderInfoDOList.isEmpty()) {
return;
}
//更新子单的状态
updateSubOrderStatus(orderInfoDO, subOrderInfoDOList);
}
//更新主订单状态
private void updateMasterOrderStatus(OrderInfoDO orderInfoDO) {
String orderId = orderInfoDO.getOrderId();
//更新主单订单状态
Integer preOrderStatus = orderInfoDO.getOrderStatus();
Integer currentStatus = OrderStatusEnum.PAID.getCode();
List<String> orderIdList = Collections.singletonList(orderId);
updateOrderStatus(orderIdList, currentStatus);
//更新主单支付状态
updateOrderPayStatus(orderIdList, PayStatusEnum.PAID.getCode());
//新增主单订单状态变更日志
Integer operateType = OrderOperateTypeEnum.PAID_ORDER.getCode();
String remark = "订单支付回调操作" + preOrderStatus + "-" + currentStatus;
saveOrderOperateLog(orderId, operateType, preOrderStatus, currentStatus, remark);
}
//更新子订单状态
private void updateSubOrderStatus(OrderInfoDO orderInfoDO, List<OrderInfoDO> subOrderInfoDOList) {
//如果出现了拆单的话,完成支付之后,主单会被废弃掉,子单,每个子单后续自己去走履约流程就可以了
//业务上的设计,针对主单信息去做一个后续的操作
String orderId = orderInfoDO.getOrderId();
Integer newPreOrderStatus = orderInfoDO.getOrderStatus();
Integer currentOrderStatus = OrderStatusEnum.INVALID.getCode();
//先将主订单状态设置为无效订单,主单直接设置为invalid,废掉主单
List<String> orderIdList = Collections.singletonList(orderId);
updateOrderStatus(orderIdList, currentOrderStatus);
//新增订单状态变更日志
Integer operateType = OrderOperateTypeEnum.PAID_ORDER.getCode();
//把主单的状态从PAYED更新为INVALID
String remark = "订单支付回调操作,主订单状态变更" + newPreOrderStatus + "-" + currentOrderStatus;
saveOrderOperateLog(orderId, operateType, newPreOrderStatus, currentOrderStatus, remark);
//再更新子订单的状态,子单会更新为PAID
Integer subCurrentOrderStatus = OrderStatusEnum.PAID.getCode();
List<String> subOrderIdList = subOrderInfoDOList.stream().map(OrderInfoDO::getOrderId).collect(Collectors.toList());
//更新子订单状态
updateOrderStatus(subOrderIdList, subCurrentOrderStatus);
//更新子订单的支付明细
updateOrderPayStatus(subOrderIdList, PayStatusEnum.PAID.getCode());
//保存子订单操作日志
saveSubOrderOperateLog(subCurrentOrderStatus, subOrderInfoDOList);
}
//更新订单状态
private void updateOrderStatus(List<String> orderIdList, Integer orderStatus) {
OrderInfoDO orderInfoDO = new OrderInfoDO();
orderInfoDO.setOrderStatus(orderStatus);
if (orderIdList.size() == 1) {
orderInfoDAO.updateByOrderId(orderInfoDO, orderIdList.get(0));
} else {
orderInfoDAO.updateBatchByOrderIds(orderInfoDO, orderIdList);
}
}
//更新订单支付状态
private void updateOrderPayStatus(List<String> orderIdList, Integer payStatus) {
OrderPaymentDetailDO orderPaymentDetailDO = new OrderPaymentDetailDO();
orderPaymentDetailDO.setPayStatus(payStatus);
if (orderIdList.size() == 1) {
orderPaymentDetailDAO.updateByOrderId(orderPaymentDetailDO, orderIdList.get(0));
} else {
orderPaymentDetailDAO.updateBatchByOrderIds(orderPaymentDetailDO, orderIdList);
}
}
//保存订单操作日志
private void saveOrderOperateLog(String orderId, Integer operateType, Integer preOrderStatus, Integer currentStatus, String remark) {
OrderOperateLogDO orderOperateLogDO = new OrderOperateLogDO();
orderOperateLogDO.setOrderId(orderId);
orderOperateLogDO.setOperateType(operateType);
orderOperateLogDO.setPreStatus(preOrderStatus);
orderOperateLogDO.setCurrentStatus(currentStatus);
orderOperateLogDO.setRemark(remark);
orderOperateLogDAO.save(orderOperateLogDO);
}
//保存子订单操作日志
private void saveSubOrderOperateLog(Integer subCurrentOrderStatus, List<OrderInfoDO> subOrderInfoDOList) {
List<OrderOperateLogDO> tempSubOrderOperateLogDOList = new ArrayList<>();
for (OrderInfoDO subOrderInfo : subOrderInfoDOList) {
String subOrderId = subOrderInfo.getOrderId();
Integer subPreOrderStatus = subOrderInfo.getOrderStatus();
//订单状态变更日志
OrderOperateLogDO subOrderOperateLogDO = new OrderOperateLogDO();
subOrderOperateLogDO.setOrderId(subOrderId);
subOrderOperateLogDO.setOperateType(OrderOperateTypeEnum.PAID_ORDER.getCode());
subOrderOperateLogDO.setPreStatus(subPreOrderStatus); // CREATED -> PAID
subOrderOperateLogDO.setCurrentStatus(subCurrentOrderStatus);
subOrderOperateLogDO.setRemark("订单支付回调操作,子订单状态变更" + subOrderOperateLogDO.getPreStatus() + "-" + subOrderOperateLogDO.getCurrentStatus());
tempSubOrderOperateLogDOList.add(subOrderOperateLogDO);
}
//新增子订单状态变更日志
if (!tempSubOrderOperateLogDOList.isEmpty()) {
orderOperateLogDAO.saveBatch(tempSubOrderOperateLogDOList);
}
}
...
}
14.自动关单功能业务流程梳理
15.XXL-Job分布式调度系统架构原理分析
arduino
https://www.xuxueli.com/xxl-job/
16.基于XXL-Job分布式调度的定时关单功能实现
scss
//自动取消超时订单任务
@Component
public class AutoCancelExpiredOrderTask {
//订单管理DAO组件
@Autowired
private OrderInfoDAO orderInfoDAO;
@Autowired
private OrderAfterSaleService orderAfterSaleService;
@Autowired
private OrderProperties orderProperties;
//执行任务逻辑
@XxlJob("autoCancelExpiredOrderTask")
public void execute() throws Exception {
int shardIndex = Optional.ofNullable(XxlJobHelper.getShardIndex()).orElse(0);
int totalShardNum = Optional.ofNullable(XxlJobHelper.getShardTotal()).orElse(0);
String param = XxlJobHelper.getJobParam();
//扫描当前时间 - 订单超时时间 -> 前的一小段时间范围(时间范围用配置中心配置)
//比如当前时间11:40,订单超时时间是30分钟,扫描11:09:00 -> 11:10:00这一分钟的未支付订单,
//缺点:有一个订单超过了30 + 1 = 31分钟,都没有被处理(取消),这笔订单就永久待支付
for (OrderInfoDO order : orderInfoDAO.listAllUnPaid()) {
if (totalShardNum <= 0) {
//不进行分片
doExecute(order);
} else {
//分片
int hash = hash(order.getOrderId()) % totalShardNum;
if (hash == shardIndex) {
doExecute(order);
}
}
}
XxlJobHelper.handleSuccess();
}
private void doExecute(OrderInfoDO order) {
if (new Date().getTime() - order.getExpireTime().getTime() >= orderProperties.getExpireTime()) {
//超过30min未支付
CancelOrderRequest request = new CancelOrderRequest();
request.setOrderId(order.getOrderId());
request.setUserId(order.getUserId());
request.setBusinessIdentifier(order.getBusinessIdentifier());
request.setOrderType(order.getOrderType());
request.setCancelType(OrderCancelTypeEnum.TIMEOUT_CANCELED.getCode());
request.setOrderStatus(order.getOrderStatus());
try {
orderAfterSaleService.cancelOrder(request);
} catch (Exception e) {
log.error("AutoCancelExpiredOrderTask execute error:", e);
}
}
}
private int hash(String orderId) {
//解决取模可能为负数的情况
return orderId.hashCode() & Integer.MAX_VALUE;
}
}
17.订单系统与XXL-Job配合运行原理
18.根据XXL-Job源码来验证关单分布式调度原理
java
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appname;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
return xxlJobSpringExecutor;
}
}
public class XxlJobSpringExecutor extends XxlJobExecutor implements ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(XxlJobSpringExecutor.class);
//start
@Override
public void afterSingletonsInstantiated() {
//init JobHandler Repository
/*initJobHandlerRepository(applicationContext);*/
//init JobHandler Repository (for method)
initJobHandlerMethodRepository(applicationContext);
//refresh GlueFactory
GlueFactory.refreshInstance(1);
//super start
try {
super.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
...
}
public class XxlJobExecutor {
private static final Logger logger = LoggerFactory.getLogger(XxlJobExecutor.class);
...
public void start() throws Exception {
//init logpath
XxlJobFileAppender.initLogPath(logPath);
//init invoker, admin-client
initAdminBizList(adminAddresses, accessToken);
//init JobLogFileCleanThread
JobLogFileCleanThread.getInstance().start(logRetentionDays);
//init TriggerCallbackThread
TriggerCallbackThread.getInstance().start();
//init executor-server
initEmbedServer(address, ip, port, appname, accessToken);
}
//初始化Netty客户端,监听9999端口
private void initEmbedServer(String address, String ip, int port, String appname, String accessToken) throws Exception {
//fill ip port
port = port>0?port: NetUtil.findAvailablePort(9999);
ip = (ip!=null&&ip.trim().length()>0)?ip: IpUtil.getIp();
//generate address
if (address==null || address.trim().length()==0) {
String ip_port_address = IpUtil.getIpPort(ip, port); // registry-address:default use address to registry , otherwise use ip:port if address is null
address = "http://{ip_port}/".replace("{ip_port}", ip_port_address);
}
//accessToken
if (accessToken==null || accessToken.trim().length()==0) {
logger.warn(">>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.");
}
//start
embedServer = new EmbedServer();
embedServer.start(address, port, appname, accessToken);
}
...
}
public class EmbedServer {
private static final Logger logger = LoggerFactory.getLogger(EmbedServer.class);
private ExecutorBiz executorBiz;
private Thread thread;
public void start(final String address, final int port, final String appname, final String accessToken) {
executorBiz = new ExecutorBizImpl();
thread = new Thread(new Runnable() {
@Override
public void run() {
//param
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
0,
200,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(2000),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "xxl-rpc, EmbedServer bizThreadPool-" + r.hashCode());
}
},
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
throw new RuntimeException("xxl-job, EmbedServer bizThreadPool is EXHAUSTED!");
}
});
try {
//start server
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS)) // beat 3N, close if idle
.addLast(new HttpServerCodec())
.addLast(new HttpObjectAggregator(5 * 1024 * 1024)) // merge request & reponse to FULL
.addLast(new EmbedHttpServerHandler(executorBiz, accessToken, bizThreadPool));
}
})
.childOption(ChannelOption.SO_KEEPALIVE, true);
//bind
ChannelFuture future = bootstrap.bind(port).sync();
logger.info(">>>>>>>>>>> xxl-job remoting server start success, nettype = {}, port = {}", EmbedServer.class, port);
//注册执行器
startRegistry(appname, address);
//wait util stop
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
...
} finally {
//stop
try {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
});
//daemon, service jvm, user thread leave >>> daemon leave >>> jvm leave
thread.setDaemon(true);
thread.start();
}
...
public static class EmbedHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
...
@Override
protected void channelRead0(final ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
//request parse
//final byte[] requestBytes = ByteBufUtil.getBytes(msg.content()); // byteBuf.toString(io.netty.util.CharsetUtil.UTF_8);
String requestData = msg.content().toString(CharsetUtil.UTF_8);
String uri = msg.uri();
HttpMethod httpMethod = msg.method();
boolean keepAlive = HttpUtil.isKeepAlive(msg);
String accessTokenReq = msg.headers().get(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN);
//invoke
bizThreadPool.execute(new Runnable() {
@Override
public void run() {
//do invoke
Object responseObj = process(httpMethod, uri, requestData, accessTokenReq);
//to json
String responseJson = GsonTool.toJson(responseObj);
//write response
writeResponse(ctx, keepAlive, responseJson);
}
});
}
private Object process(HttpMethod httpMethod, String uri, String requestData, String accessTokenReq) {
//valid
if (HttpMethod.POST != httpMethod) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, HttpMethod not support.");
}
if (uri==null || uri.trim().length()==0) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping empty.");
}
if (accessToken!=null && accessToken.trim().length()>0 && !accessToken.equals(accessTokenReq)) {
return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
//services mapping
try {
if ("/beat".equals(uri)) {
return executorBiz.beat();
} else if ("/idleBeat".equals(uri)) {
IdleBeatParam idleBeatParam = GsonTool.fromJson(requestData, IdleBeatParam.class);
return executorBiz.idleBeat(idleBeatParam);
} else if ("/run".equals(uri)) {
TriggerParam triggerParam = GsonTool.fromJson(requestData, TriggerParam.class);
return executorBiz.run(triggerParam);
} else if ("/kill".equals(uri)) {
KillParam killParam = GsonTool.fromJson(requestData, KillParam.class);
return executorBiz.kill(killParam);
} else if ("/log".equals(uri)) {
LogParam logParam = GsonTool.fromJson(requestData, LogParam.class);
return executorBiz.log(logParam);
} else {
return new ReturnT<String>(ReturnT.FAIL_CODE, "invalid request, uri-mapping("+ uri +") not found.");
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new ReturnT<String>(ReturnT.FAIL_CODE, "request error:" + ThrowableUtil.toString(e));
}
}
...
}
...
}