Java微服务实现商城系统架构设计与实现(业务,规划,设计,实现)
csdh11 2024-12-26 12:26 2 浏览
Java微服务实现商城系统架构设计与实现(业务,规划,设计,实现)
目录
- 背景
- 商城整体架构
- 订单系统
- 商品系统
- 促销系统
- 支付系统
- 推荐系统
- 积分系统
- 会员系统
- 总结
1. 背景
随着互联网技术广泛应用,各行各业都依托线上平台进行商务活动。小到个人带货,大到企业商业活动,都少不了需要少不了在线交易。于是,到处可见商城影响,不管是加盟大的电商平台如淘宝、京东、拼多多,或是企业自建商城平台,目的基本都是扩大生意渠道,卖货增加业绩收入。
下面基于我们公司自建商城平台,来谈谈我们商城架构设计方案。
2. 商城整体架构
一般来说,商城系统按单体服务方案去构思的话,按模块划分至少包括:商品、订单、会员、促销、支付、积分、仓储、物流、风控、企业内部erp、财务系统等。
公司原有技术架构是SpringCloud微服务架构 ,所以商城系统也是在这个体系下,同时根据我们公司实际业务特殊和复杂性,对各个模块进行微服务划分,业务领域分成多个业务中台和数据中台。
设计商城整架构如下:
一个完整商城体系结构是比较复杂的,涉及到很多关联系统和子系统模块。
采用分层设计,从系统分层设计角度考虑,可以划分:前端接入层、应用表示层、中台服务、后端服务层、基础服务层。
3. 订单系统
2.1、订单系统边界
在搭建企业订单系统之前,需要先梳理整体业务系统之间的关系和订单系统上下游关系,只有划分清业务系统边界,才能确定订单系统的职责与功能,进而保证各系统之间高效简洁的工作。
对外服务展示,所有给企业外部用户使用的系统都在这一层,包括WEB、普通用户使用的C端,还包括给商户使用的商家后台和在各个销售渠道进行分销的系统,比如与银行信用卡中心合作、微信合作在合作商的平台展示出本产品。这类系统站在与客户接触的最前线,是企业实现商业模式的桥头堡。
中后台系统,每个C端的业务形态都会有一个对应的系统模块,如负责管理平台交易的订单系统,管理优惠信息的促销系统,管理平台所有产品的产商品系统,以及管理所有对外系统显示内容的内容系统(CMS)等。
基础服务系统,随着企业的发展,信息化建设到达一定程度后,企业需要将通用功能服务化、平台化,以保证应用架构的合理性,提升服务效率。这类系统主要给其他应用系统提供基础服务能力支持。
由此可见,订单系统对上接收用户信息,将用户信息转化为产品订单,同时管理并跟踪订单信息和数据,承载了整个交易线的重要对客环节。
对下则衔接产品系统、促销系统、仓储系统、会员系统、积分系统、支付系统等,对整个电商平台起着承上启下的作用。
2.2、订单系统业务架构
订单服务,该模块的主要功能是用户日常使用的服务和页面,主要有订单列表、订单详情、在线下单等,还包括为公共业务模块提供的多维度订单数据服务。
订单逻辑,订单系统的核心,起着至关重要的作用,在订单系统负责管理订单创建、订单支付、订单生产、订单确认、订单完成、取消订单等订单流程。还涉及到复杂的订单状态规则、订单金额计算规则以及增减库存规则等。
底层服务,信息化建设达到一定程度的企业,一般会将公共服务模块化和中台化,比如:产品,会构建对应的产品系统,代码、数据库,接口等相对独立。但是,这也带来了一个问题,比如:订单创建的场景下需要获取的信息分散在各个系统。组装信息的时候会比较麻烦。
如果需要从各个中台系统调用:一是会花费大量时间,二是代码的维护成本非常高。因此,订单系统接入所需的公共服务模块聚合接口,在订单系统即可完成对接公共系统的服务。
2.3、订单核心功能
功能脑图
为了使订单系统能够对订单进行高效、精准的管理和跟踪,订单会储存关于产品、优惠、用户、支付信息等一系列的订单实时数据,来和下游系统,如:促销、仓储、物流进行交互。
以一个通用B2C商城的订单为例,梳理其包含的信息如下:
这里要注意的是订单类型,随着平台业务的不断发展,品类丰富、交易方式丰富后,需要对订单进行多维度的分类管理,同时订单类型利于订单系统的扩展性。每种订单类型将会对应一套流程及一套状态,便于对订单进行分类管理和复用。
2.4、订单正向流程
2.4.1、订单创建
用户下单后,系统需要生成订单,此时需要先获取下单中涉及的商品信息,然后获取该商品所涉及到的优惠信息,如果商品不参与优惠信息,则无此环节。
接着获取该账户的会员权益,这里要注意的是:优惠信息与会员权益的区别,比如:商品满减是优惠信息,会员全场9.8折指的是会员权益,一个是针对商品,另一个是针对账户。其次就是优惠活动的叠加规则和优先级规则等。
增减库存规则是指订单中的商品,何时从库存系统中对相应商品库存进行扣除,目前主流有两种方式:
(1)下单减库存——即用户下单成功时减少库存数量
优势:用户体验友好,系统逻辑简洁。
缺点:会导致恶意下单或下单后却不买,使得真正有需求的用户无法购买,影响真实销量。
解决办法:
设置订单有效时间,若订单创建成功N分钟不付款,则订单取消,库存回滚;
限购,用各种条件来限制买家的购买件数,比如一个账号、一个ip,只能买一件;
风控,从技术角度进行判断,屏蔽恶意账号,禁止恶意账号购买。
(2)付款减库存——即用户支付完成并反馈给平台后再减少库存数量
优势:减少无效订单带来的资源损耗。
缺点:因第三方支付返回结果存在时差,同一时间多个用户同时付款成功,会导致下单数目超过库存,商家库存不足容易引发断货和投诉,成本增加。
解决办法:
付款前再次校验库存,如确认订单要付款时再验证一次,并友好提示用户库存不足;
增加提示信息:在商品详情页,订单步骤页面提示不及时付款,不能保证有库存等。
综上所述,两种方式各有优缺点,因此,需结合实际场景进行考虑,如:秒杀、抢购、促销活动等,可使用下单减库存的方式。而对于产品库存量大,并发流量没有那么强的产品使用付款减库存的方式(秒杀系统设计是在另外文档中专题描述)。
将两种方式带入到销售场景中,关联商品类型、促销类型、供需关系等,灵活使用,以充分发挥计算机系统的优势。
2.4.2、订单支付
用户支付完订单后,需要获取订单的支付信息,包括支付流水号、支付时间等。支付完订单接着就是等商家发货,但在发货过程中,根据平台业务模式的不同,可能会涉及到订单的拆分。
订单拆分一般分两种:
一种是用户挑选的商品来自于不同渠道(自营与商家,商家与商家);
另一种是在SKU层面上拆分订单:不同仓库,不同运输要求的SKU,包裹重量体积限制等因素需要将订单拆分。
订单拆分也是一个相对独立的模块,可以采用子母单的形式,也可以采用服务单和订单形式,总的来说就是1:N关系。
2.4.3、订单生产
订单生产,是指产品从企业到用户这一流程的概述。如电商平台中,商家发货过程已有一个标准化的流程,订单内容会发送到仓库,仓库对商品进行打单、拣货、包装、交接快递进行配送(在我们这里一般是由供应商完成)。
2.4.4、订单确认
收到货后,订单系统需要在快递被签收后提醒用户对商品做评价。这里要注意,确认收到货不代表交易成功,而是售后服务的开始。
2.4.5、订单完成
订单完成是指在收到货X天的状态,此时订单不在售后的支持时间范围内。到此,一个订单的正向流程就算走完了。
2.4、订单逆向流程
上面说到逆向流程是各种修改订单、取消订单、退款、退货等操作,需要梳理清楚这些流程与正向流程的关系,才能理清订单系统完整的订单流程。
2.4.1、订单修改
可梳理订单内信息,根据信息关联程度及业务诉求,设定订单的可修改范围是什么,比如:客户下单后,想修改收货人地址及电话。此时只需对相应数据进行更新即可。
2.4.2、订单取消
用户提交订单后没有进行支付操作,此时用户原则上属于取消订单,因为还未付款,则比较简单,只需要将原本提交订单时扣减的库存补回,促销优惠中使用的优惠券,权益等视平台规则,进行相应补回。
2.4.3、退款
用户支付成功后,客户发出退款的诉求后,需商户进行退款审核,双方达成一致后,系统应以退款单的形式完成退款,关联原订单数据。因商品无变化,所以不许考虑与库存系统的交互,仅需考虑促销系统及支付系统交互即可。
2.4.4、退货
用户支付成功后,客户发出退货的诉求后,需商户进行退款审核,双方达成一致后,需对库存系统进行补回,支付系统、促销系统以退款单形式完成退款。最后,在退款/退货流程中,需结合平台业务场景,考虑优惠分摊的逻辑,在发生退款/退货时,优惠该如何退回的处理规则和流程。
2.5、状态机设计
状态机是管理订单状态逻辑的工具。状态机可归纳为3个要素,即现态、动作、次态。
现态:是指当前所处的状态。
动作:动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。
次态:动作满足后要迁往的新状态,“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
状态机的设计需要结合平台实际业务场景,将状态间的切换细化成了执行了某个动作。
以一个B2C商城的订单系统举例,状态分类如下:
订单系统为了高效的对订单进行跟踪和管理,会对订单流程当中的关键节点,抽象出订单状态。而订单状态从不同用户的角度可分为:系统订单状态、商家订单状态、买家订单状态等。
对于订单系统来说,订单状态细分的颗粒度越细、越明确,订单系统管理的精度和可靠性就越高,比如:在待付款和待发货两个状态中,订单系统后台会细分为:订单超时取消、订单支付失败、订单付款完成等。
因此,订单状态模块中,通常会维护状态映射表,以不同的用户角色对系统订单状态进行重新划分,以满足不同用户的需求。
除此以外,随着业务的不断发展,不同的业务类型,所对应的订单状态都会有所区别。所以,订单系统中一般会储存多套状态机,以满足不同的订单类型来使用。
2.5、关键代码片断
对前商提供API访问接口
@Module("订单模块-C端")
@RestController
@RequestMapping("/customer-api/v1/order")
public class OrderCustomerController {
@Resource
private OrderService orderService;
@Resource
private OrderCreateCmdExe orderCreateCmdExe;
@Resource
private OrderPayCmdExe orderPayCmdExe;
@Resource
private OrderCancelCmdExe orderCancelCmdExe;
@Resource
private RefundApplyCmdExe refundApplyCmdExe;
@Resource
private OrderDeleteCmdExe orderDeleteCmdExe;
/**
* 生成订单
*
* @param cmd 订单内容
* @return 订单信息
*/
@RestApi(name = "生成订单", logabled = true)
@PostMapping("/create")
@CheckResubmit
public DataResponse<OrderCreateCO> create(@RequestBody @Valid OrderCreateCmd cmd) {
OrderCreateCO orderCreateCO = orderCreateCmdExe.create(cmd);
return DataResponse.of(orderCreateCO);
}
/**
* 订单支付
*/
@RestApi(name = "订单支付", logabled = true)
@PostMapping("/pay")
@CheckResubmit
public DataResponse<OrderPaymentInfoCO> pay(@RequestBody @Valid OrderPayCmd cmd, HttpServletRequest request) {
OrderPaymentInfoCO paymentInfoCO = orderPayCmdExe.pay(cmd, request);
return DataResponse.of(paymentInfoCO);
}
/**
* 订单列表(分页)
*
* @param cmd 查询条件
* @return 查询结果
*/
@RestApi(name = "订单列表(分页)", logabled = true)
@PostMapping("/page")
public DataResponse<CustomizePage<OrderPageCO>> page(@RequestBody OrderPageQryCmd cmd) {
OrderQryDTO dto = BeanToolkit.instance().copy(cmd, OrderQryDTO.class);
dto.setPageNo(cmd.getPageNo());
dto.setPageSize(cmd.getPageSize());
dto.setCreatorId(HttpContext.getCustomerId());
dto.setCustomerDeleted(0);
CustomizePage<OrderDTO> orderDTOPage = orderService.customerPage(dto);
CustomizePage<OrderPageCO> result = new CustomizePage<>(orderDTOPage.getCurrent(), orderDTOPage.getSize(), orderDTOPage.getTotal());
result.setRecords(OrderConverter.converter2OrderCO(orderDTOPage.getRecords()));
return DataResponse.of(result);
}
/**
* 订单详情
*
* @param orderId 订单ID
* @return 订单详情
*/
@RestApi(name = "主订单详情", logabled = true)
@GetMapping("/detail/{orderId}")
public DataResponse<OrderPageCO> detail(@PathVariable("orderId") Long orderId) {
OrderDTO orderDTO = orderService.detail(orderId, TerminalEnum.CUSTOMER);
return DataResponse.of(OrderConverter.converter2OrderCO(orderDTO));
}
/**
* 订单状态统计
*/
@RestApi(name = "订单状态统计", logabled = true)
@GetMapping("/stateStatistics")
public DataResponse<OrderStateStatisticsCustomerCO> orderStateStatistics() {
OrderStateStatisticsCustomerCO result = orderService.customerOrderStateStatistics();
return DataResponse.of(result);
}
/**
* 取消订单
*
* @param cmd 取消订单
*/
@RestApi(name = "取消订单", logabled = true)
@PostMapping("/cancel")
public Response cancel(@RequestBody @Valid OrderCancelCmd cmd) {
orderCancelCmdExe.cancel(cmd);
return Response.buildSuccess();
}
/**
* 退/换货申请
*
* @param cmd 申请内容
*/
@RestApi(name = "退/换货申请", logabled = true)
@PostMapping("/refundApply")
public Response refundApply(@RequestBody @Valid RefundApplyCmd cmd) {
refundApplyCmdExe.refundApply(cmd);
return Response.buildSuccess();
}
/**
* 退/换货订单列表(分页)
*
* @param qry 查询条件
* @return 查询结果
*/
@RestApi(name = "退/换货订单列表(分页)", logabled = true)
@PostMapping("/refundApply/page")
public DataResponse<CustomizePage<OrderPageCO>> refundOrderPage(@RequestBody RefundApplyQry qry) {
CustomizePage<OrderDTO> orderDTOPage = orderService.refundOrderPage(qry);
CustomizePage<OrderPageCO> result = new CustomizePage<>(orderDTOPage.getCurrent(), orderDTOPage.getSize(), orderDTOPage.getTotal());
result.setRecords(OrderConverter.converter2OrderCO(orderDTOPage.getRecords()));
return DataResponse.of(result);
}
/**
* 删除订单
*/
@RestApi(name = "删除订单", logabled = true)
@DeleteMapping("/delete")
public Response deleteOrder(@Valid @RequestBody OrderDeleteCmd cmd) {
orderDeleteCmdExe.deleteOrder(cmd);
return Response.buildSuccess();
}
/**
* 获取订单开票信息
*
* @param orderId 主订单ID
* @return 开票信息
*/
@RestApi(name = "获取订单开票信息", logabled = true)
@GetMapping("/orderInvoiceInfo/{orderId}")
public DataResponse<OrderInvoiceInfoCO> getOrderInvoiceInfo(@PathVariable("orderId") Long orderId) {
return DataResponse.of(orderService.getOrderInvoiceInfo(orderId));
}
}
订单创建执行器
@Slf4j
@Component
public class OrderCreateCmdExe {
@Resource
private NumberingGateway numberingGateway;
@Resource
private OrderValidator orderValidator;
@Resource
private GoodsService goodsService;
@Resource
private PointsGateway pointsGateway;
@Resource
private CustomerMemberGateway customerMemberGateway;
@Resource
private OrderAutoCancelPublisher orderAutoCancelPublisher;
@Resource
private OrderDaiZhiFuNoticePublisher orderDaiZhiFuNoticePublisher;
@Resource
private MemberCartGateway memberCartGateway;
/**
* 生成订单
*/
@Transactional(rollbackFor = Exception.class)
public OrderCreateCO create(OrderCreateCmd cmd) {
// 1、查询商品信息
List<String> skuIds = cmd.getGoodsList().stream().map(OrderCreateCmd.GoodsList::getSkuId).collect(Collectors.toList());
Map<String, SpuApiCO> skuMap = goodsService.mapSkuCO(skuIds);
// 2、查询客户积分
BigDecimal customerIntegral = pointsGateway.getCustomerIntegral(HttpContext.getCustomerId());
// 3、查询会员信息
MemberDTO memberDTO = customerMemberGateway.findByCustomerId(HttpContext.getCustomerId());
// 4、检查下单条件
orderValidator.createOrderValidate(cmd, skuMap, customerIntegral, memberDTO);
// 5、 组装订单信息
List<MallOrderE> mallOrderDOS = newMallOrderE(cmd, skuMap, memberDTO.getMemberLevel());
// 6、检查用户积分余额
orderValidator.customerIntegralValidate(mallOrderDOS.get(0), customerIntegral);
// 7、保存订单信息
MallOrderE.queryInstance().saveBatch(mallOrderDOS);
// 8、批量扣除库存,考虑到没有分布式事务,因此最后进行RPC扣库存
List<UpdateInventoryDTO> updateInventoryDTOS = GoodsConverter.convert2UpdateInventoryDTO(cmd.getGoodsList());
goodsService.updateInventory(updateInventoryDTOS);
// 9、开启延迟队列
// 9.1、待支付订单自动取消
OrderEvent orderEvent = new OrderEvent();
orderEvent.setMainOrderId(mallOrderDOS.get(0).getId());
orderEvent.setMainOrderCode(mallOrderDOS.get(0).getCode());
orderAutoCancelPublisher.sendMessage(orderEvent);
// 9.2、待支付订单提醒
orderDaiZhiFuNoticePublisher.sendMessage(orderEvent);
// 10、删除购物车中的商品
if (SubmitPageEnum.orderConfirm.getValue().equals(cmd.getSubmitPage())) {
deleteGoodsCart(cmd.getGoodsList());
}
OrderCreateCO result = new OrderCreateCO();
result.setOrderId(mallOrderDOS.get(0).getId().toString());
result.setOrderNo(mallOrderDOS.get(0).getCode());
return result;
}
/**
* 组装订单信息
* 返回的订单集合中,index=0的为主订单
*/
private List<MallOrderE> newMallOrderE(OrderCreateCmd cmd, Map<String, SpuApiCO> skuMap, Integer memberLevel) {
List<MallOrderE> result = new ArrayList<>();
// 1、打包主订单信息
MallOrderE mallOrderE = BeanToolkit.instance().copy(cmd, MallOrderE.class);
result.add(mallOrderE);
mallOrderE.setId(numberingGateway.getNextId());
String orderCode = numberingGateway.generateOrderCode();
mallOrderE.setCode(orderCode);
mallOrderE.setStatus(OrderStatusEnum.DAI_ZHI_FU.getValue());
// 订单总价格、订单总积分、订单总运费
Long totalPrice = 0L;
Long totalFreight = 0L;
Long totalIntegral = 0L;
// 2、打包子订单信息
Queue<String> subOrderCode = numberingGateway.generateSubOrderCode(orderCode, cmd.getGoodsList().size());
for (OrderCreateCmd.GoodsList goods : cmd.getGoodsList()) {
SpuApiCO spu = skuMap.get(goods.getSkuId());
MallOrderE mallOrderEItem = BeanToolkit.instance().copy(cmd, MallOrderE.class);
mallOrderEItem.setId(numberingGateway.getNextId());
mallOrderEItem.setCode(subOrderCode.poll());
mallOrderEItem.setMainOrderId(mallOrderE.getId());
mallOrderEItem.setMainOrderCode(mallOrderE.getCode());
mallOrderEItem.setStatus(OrderStatusEnum.DAI_ZHI_FU.getValue());
CalcPayPriceDTO calcPayPriceDTO = goodsService.calcPayPrice(spu.getSku(), goods.getCount(), memberLevel, cmd.getReceiverRegion());
mallOrderEItem.setPrice(calcPayPriceDTO.getPayPrice()); // 子订单价格
mallOrderEItem.setIntegral(calcPayPriceDTO.getGoodsTotalIntegral()); // 子订单积分
mallOrderEItem.setFreight(calcPayPriceDTO.getFreight()); // 子订单运费
mallOrderEItem.setSpuId(Long.valueOf(goods.getSpuId()));
mallOrderEItem.setSkuId(Long.valueOf(goods.getSkuId()));
mallOrderEItem.setQuantity(goods.getCount());
mallOrderEItem.setGoodsName(spu.getGoodsName());
mallOrderEItem.setGoodsSupplierId(spu.getSupplierId());
mallOrderEItem.setGoodsType(spu.getGoodsType());
mallOrderEItem.setCreatorId(HttpContext.getCustomerId());
mallOrderEItem.setCreator(HttpContext.getCustomerName());
mallOrderEItem.setCreateDt(LocalDateTime.now());
mallOrderEItem.setLastUpdatorId(HttpContext.getCustomerId());
mallOrderEItem.setLastUpdator(HttpContext.getCustomerName());
mallOrderEItem.setLastUpdateDt(LocalDateTime.now());
result.add(mallOrderEItem);
totalPrice += calcPayPriceDTO.getPayPrice();
totalFreight += calcPayPriceDTO.getFreight();
totalIntegral += calcPayPriceDTO.getGoodsTotalIntegral();
}
mallOrderE.setPrice(totalPrice); // 订单总价
mallOrderE.setIntegral(totalIntegral); // 订单总积分
mallOrderE.setFreight(totalFreight); // 订单总运费
mallOrderE.setCreatorId(HttpContext.getCustomerId());
mallOrderE.setCreator(HttpContext.getCustomerName());
mallOrderE.setCreateDt(LocalDateTime.now());
mallOrderE.setLastUpdatorId(HttpContext.getCustomerId());
mallOrderE.setLastUpdator(HttpContext.getCustomerName());
mallOrderE.setLastUpdateDt(LocalDateTime.now());
return result;
}
/**
* 批量删除购物车数据
*
* @param goodsList 待删除的商品
*/
private void deleteGoodsCart(List<OrderCreateCmd.GoodsList> goodsList) {
for (OrderCreateCmd.GoodsList list : goodsList) {
DeleteGoodsCartCmd deleteGoodsCartCmd = new DeleteGoodsCartCmd();
deleteGoodsCartCmd.setSpuId(Long.valueOf(list.getSpuId()));
deleteGoodsCartCmd.setSkuId(Long.valueOf(list.getSkuId()));
memberCartGateway.deleteGoodsCart(HttpContext.getCustomerId(), deleteGoodsCartCmd);
}
}
}
订单取消执行器
/**
* 订单取消执行器
*
* @author cgli
* @date 2022/12/30 09:19
*/
@Component
public class OrderCancelCmdExe {
@Resource
private OrderValidator orderValidator;
@Resource
private MallOrderGateway orderGateway;
@Resource
private RollbackIntegralExe rollbackIntegralExe;
@Resource
private GoodsService goodsService;
/**
* 取消订单
*/
@Transactional(rollbackFor = Exception.class)
public void cancel(OrderCancelCmd cmd) {
MallOrderE mallOrderE = MallOrderE.queryInstance().getById(cmd.getOrderId());
orderValidator.cancelOrderValidate(mallOrderE);
List<MallOrderE> orderUpdateList = new ArrayList<>();
List<UpdateInventoryDTO> inventoryUpdateList = new ArrayList<>();
//主订单状态置为"已取消"
MallOrderE mainOrderEForUpdate = new MallOrderE();
mainOrderEForUpdate.setId(mallOrderE.getId());
mainOrderEForUpdate.setStatus(OrderStatusEnum.YI_QU_XIAO.getValue());
orderUpdateList.add(mainOrderEForUpdate);
List<OrderDTO> orderDTOS = orderGateway.listByMainOrderId(cmd.getOrderId());
for (OrderDTO orderDTO : orderDTOS) {
//子订单状态置为"已取消"
MallOrderE subOrderEForUpdate = new MallOrderE();
subOrderEForUpdate.setId(orderDTO.getId());
subOrderEForUpdate.setStatus(OrderStatusEnum.YI_QU_XIAO.getValue());
orderUpdateList.add(subOrderEForUpdate);
//回滚库存
UpdateInventoryDTO updateInventoryDTO = new UpdateInventoryDTO(orderDTO.getSkuId().toString(), orderDTO.getQuantity());
inventoryUpdateList.add(updateInventoryDTO);
}
orderGateway.updateBatchById(orderUpdateList);
// 判断是否已经进行了积分支付,需要将积分回滚
rollbackIntegralExe.execute(mallOrderE, "订单取消积分返还");
// 库存回滚
goodsService.updateInventory(inventoryUpdateList);
}
}
订单删除执行器
/**
* 订单删除执行器
*
* @author cgli
* @date 2022/12/30 09:30
*/
@Component
public class OrderDeleteCmdExe {
@Resource
private OrderValidator orderValidator;
/**
* 删除订单
*/
@Transactional(rollbackFor = Exception.class)
public void deleteOrder(OrderDeleteCmd cmd) {
MallOrderE mallOrderE = MallOrderE.queryInstance().getById(cmd.getOrderId());
orderValidator.deleteValidate(mallOrderE);
MallOrderE.queryInstance().customerDeleteByMainOrderId(mallOrderE.getId());
}
}
订单查询服务
@Slf4j
@Service
public class OrderService extends BaseService {
@Autowired
private MallOrderGateway orderGateway;
@Autowired
private MallDeliveryGateway deliveryGateway;
@Autowired
private GoodsService goodsService;
@Autowired
private MallOrderBillGateway mallOrderBillGateway;
@Autowired
private MallInvoiceRecordGateway invoiceRecordGateway;
/**
* 订单列表(分页)
*/
public CustomizePage<OrderDTO> page(OrderQryDTO dto) {
dto.setQuerySubOrder(true);
CustomizePage<OrderDTO> page = orderGateway.page(dto);
Map<String, SupplierDTO> supplierMap = goodsService.mapSupplierCO();
//查询订单物流信息
List<Long> deliveryIds = page.getRecords().stream().map(OrderDTO::getDeliveryId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
Map<Long, MallDeliveryE> deliveryEMap = deliveryGateway.mapById(deliveryIds);
for (OrderDTO orderDTO : page.getRecords()) {
//整合供应商名称
SupplierDTO supplierDTO = supplierMap.get(orderDTO.getGoodsSupplierId());
if (supplierDTO != null) {
orderDTO.setGoodsSupplierName(supplierDTO.getSupplierName());
}
//转译订单状态
orderDTO.setStatusDesc(OrderStatusEnum.value2Desc(TerminalEnum.ADMIN, orderDTO.getStatus()));
// 物流信息
MallDeliveryE mallDeliveryE = deliveryEMap.get(orderDTO.getDeliveryId());
orderDTO.setDeliveryInfo(mallDeliveryE);
// 商品信息
GoodsCO goodsCO = new GoodsCO();
goodsCO.setGoodsName(orderDTO.getGoodsName());
orderDTO.setGoods(goodsCO);
}
return page;
}
/**
* 消费者端分页查询订单信息
*
* @param dto 查询条件
* @return 查询结果
*/
public CustomizePage<OrderDTO> customerPage(OrderQryDTO dto) {
dto.setQueryMainOrder(true);
CustomizePage<OrderDTO> mainOrderPage = orderGateway.page(dto);
if (CollUtil.isEmpty(mainOrderPage.getRecords())) {
return new CustomizePage<>(mainOrderPage.getCurrent(), mainOrderPage.getSize(), mainOrderPage.getTotal());
}
List<Long> mainOrderIds = mainOrderPage.getRecords().stream().map(OrderDTO::getId).collect(Collectors.toList());
//查询子订单信息
OrderQryDTO subOrderQry = new OrderQryDTO();
subOrderQry.setMainOrderIds(mainOrderIds);
List<OrderDTO> subOrderList = orderGateway.list(subOrderQry);
//查询商品信息
List<String> skuIds = subOrderList.stream().map(OrderDTO::getSkuId).map(String::valueOf).collect(Collectors.toList());
Map<String, SpuApiCO> spuMap = goodsService.mapSkuCO(skuIds);
//查询派送信息
List<Long> deliveryIds = subOrderList.stream().map(OrderDTO::getDeliveryId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
Map<Long, MallDeliveryE> deliveryMap = deliveryGateway.mapById(deliveryIds);
//查询支付信息
Map<Long, MallOrderBillE> billMap = mallOrderBillGateway.getBill(mainOrderIds, BillingTypeEnum.PAY, OrderPayWayEnum.INTEGRAL_PAY);
//整合发票信息
Map<Long, MallInvoiceRecordE> orderInvoiceMap = invoiceRecordGateway.getOrderInvoice(mainOrderIds);
//整合子母单、商品信息和物流信息
return OrderConverter.converter2OrderDTO(mainOrderPage, subOrderList, spuMap, deliveryMap, billMap, orderInvoiceMap);
}
/**
* B端订单状态统计
*/
public OrderStateStatisticsCO orderStateStatistics(OrderPageQryCmd cmd) {
return orderGateway.orderStateStatistics(cmd);
}
/**
* C端订单状态统计
*/
public OrderStateStatisticsCustomerCO customerOrderStateStatistics() {
return orderGateway.customerOrderStateStatistics(HttpContext.getCustomerId());
}
/**
* 获取订单详情
*
* @param orderId 订单ID
* @param terminal 终端类型
* @return 订单详情
*/
public OrderDTO detail(Long orderId, TerminalEnum terminal) {
OrderDTO result = null;
// 1、获取订单信息
MallOrderE mallOrderE = MallOrderE.queryInstance().getById(orderId);
if (mallOrderE.isMainOrder()) {
// 2、查看主订单维度的详情
// 2.1、查询子订单信息
List<OrderDTO> orderDTOS = orderGateway.listByMainOrderId(orderId);
// 2.2、查询商品信息
List<String> skuIds = orderDTOS.stream().map(OrderDTO::getSkuId).map(String::valueOf).collect(Collectors.toList());
Map<String, SpuApiCO> spuMap = goodsService.mapSkuCO(skuIds);
result = OrderConverter.converter2OrderDTO(mallOrderE, orderDTOS, spuMap, terminal);
} else {
// 3、查看子单维度的详情
// 3.1、查询母单订单信息
MallOrderE mainOrderInfo = MallOrderE.queryInstance().getById(mallOrderE.getMainOrderId());
// 3.2、查询商品信息
Map<String, SpuApiCO> spuMap = goodsService.mapSkuCO(Collections.singletonList(String.valueOf(mallOrderE.getSkuId())));
// 4、整合订单详情
OrderDTO subOrderDTO = BeanToolkit.instance().copy(mallOrderE, OrderDTO.class);
result = OrderConverter.converter2OrderDTO(mainOrderInfo, Collections.singletonList(subOrderDTO), spuMap, terminal);
}
return result;
}
/**
* 商品销量统计
*/
public List<GoodsSalesCO> goodsSalesStatistics(GoodsSalesStatisticsQryCmd cmd) {
return orderGateway.goodsSalesStatistics(cmd.getSkuIds(), cmd.getCreateDtBegin(), cmd.getCreateDtEnd(), cmd.getLimit());
}
/**
* 退款订单列表
*
* @param qry 查询条件
* @return 查询结果
*/
public CustomizePage<OrderDTO> refundOrderPage(RefundApplyQry qry) {
OrderQryDTO qryDTO = BeanToolkit.instance().copy(qry, OrderQryDTO.class);
qryDTO.setPageNo(qry.getPageNo());
qryDTO.setPageSize(qry.getPageSize());
qryDTO.setStatusList(Arrays.asList(OrderStatusEnum.TUI_HUO_SHEN_QING.getValue(), OrderStatusEnum.TUI_HUO_WAN_CHENG.getValue()));
qryDTO.setQuerySubOrder(true);
qryDTO.setCreatorId(HttpContext.getCustomerId());
CustomizePage<OrderDTO> page = orderGateway.page(qryDTO);
//查询商品信息
List<String> skuIds = page.getRecords().stream().map(OrderDTO::getSkuId).map(String::valueOf).collect(Collectors.toList());
Map<String, SpuApiCO> spuMap = goodsService.mapSkuCO(skuIds);
return OrderConverter.converter2OrderDTO(page, spuMap);
}
/**
* 获取导出数据
*
* @param cmd 查询条件
* @return 查询结果
*/
public List<OrderExcelDTO> export(OrderPageQryCmd cmd) {
int exportCount = 0; //已经处理的数据
OrderQryDTO orderQryDTO = OrderQryDTO.of(cmd);
orderQryDTO.setPageNo(1);
orderQryDTO.setPageSize(200); //分批整合数据
CustomizePage<OrderDTO> orderPage = page(orderQryDTO);
if (CollUtil.isEmpty(orderPage.getRecords())) {
throw new DataNotFoundException();
}
List<OrderExcelDTO> result = new ArrayList<>();
while (CollUtil.isNotEmpty(orderPage.getRecords()) && BizConstant.MAX_EXPORT_SIZE > exportCount) {
List<OrderExcelDTO> orderExcelDTOList = OrderConverter.converter2OrderExcelDTO(orderPage.getRecords());
result.addAll(orderExcelDTOList);
//处理下一页的数据
exportCount += orderQryDTO.getPageSize();
orderQryDTO.setPageNo(orderQryDTO.getPageNo() + 1);
orderPage = page(orderQryDTO);
}
return result;
}
/**
* 获取订单退货申请记录
*
* @param orderId 订单ID
* @return 查询结果
*/
public RefundApplyInfoCO getRefundApplyInfoCO(Long orderId) {
MallRefundApplyE refundApplyE = MallRefundApplyE.queryInstance().getByOrderId(orderId);
if (refundApplyE == null) {
throw new DataNotFoundException();
}
RefundApplyInfoCO result = BeanToolkit.instance().copy(refundApplyE, RefundApplyInfoCO.class);
result.setRefundTypeDesc(OrderRefundTypeEnum.value2Desc(result.getRefundType()));
return result;
}
/**
* 获取订单列表
*
* @param cmd 查询条件
* @return 查询结果
*/
public List<OrderDTO> queryOrderList(OrderQryCmd cmd) {
OrderQryDTO subOrderQry = BeanToolkit.instance().copy(cmd, OrderQryDTO.class);
subOrderQry.setQuerySubOrder(true);
return orderGateway.list(subOrderQry);
}
/**
* 获取SKU产生的最新订单
*
* @param skuIds skuId集合
* @return 订单信息
*/
public List<OrderCO> skuLatestSale(List<Long> skuIds) {
return orderGateway.skuLatestSale(skuIds);
}
/**
* 获取订单开票信息
*/
public OrderInvoiceInfoCO getOrderInvoiceInfo(Long orderId) {
MallOrderE mainOrderE = MallOrderE.queryInstance().getById(orderId);
if (Objects.isNull(mainOrderE)) {
throw new ServiceException("订单不存在");
}
if (!OrderStatusEnum.YI_WAN_CHENG.getValue().equals(mainOrderE.getStatus())) {
throw new ServiceException("未完成的订单无法申请开发票");
}
if (!mainOrderE.moneyPaid()) {
throw new ServiceException("该订单未进行现金支付,无法开具发票");
}
OrderInvoiceInfoCO result = new OrderInvoiceInfoCO();
result.setOrderId(mainOrderE.getId().toString());
result.setOrderCode(mainOrderE.getCode());
//开票金额,该母单的实际支付金额,部分退款则取最终收款金额
long totalRefund = orderGateway.listByMainOrderId(orderId)
.stream()
.filter(orderDTO -> OrderStatusEnum.TUI_HUO_WAN_CHENG.getValue().equals(orderDTO.getStatus()))
.mapToLong(OrderDTO::getPayPrice)
.sum();
long invoicePrice = mainOrderE.getPayPrice() - totalRefund;
result.setInvoicePrice(BigDecimalUtils.penny2yuan(invoicePrice));
return result;
}
}
/**
* 订单领域层服务接口
*
*/
public interface MallOrderGateway {
/**
* 分页查询
*/
CustomizePage<OrderDTO> page(OrderQryDTO dto);
/**
* 订单列表
*/
List<OrderDTO> list(OrderQryDTO dto);
/**
* 根据主订单ID获取自订单集合
*/
List<OrderDTO> listByMainOrderId(Long orderId);
List<OrderDTO> listByMainOrderIdList(List<Long> orderIdList);
/**
* 统计消费者购买的商品数量
*
* @param customerId 消费者ID
* @param skuId skuId
* @return 统计结果
*/
Integer countBySkuId(String customerId, Long skuId);
/**
* 根据ID查询订单信息
*
* @param id 订单ID
* @return 查询结果
*/
MallOrderE getById(Long id);
/**
* 根据子单查询母单数据
*
* @param id 子单ID
* @return 母单数据
*/
MallOrderE getMainOrderBySubId(Long id);
MallOrderE getByCode(String code);
/**
* 根据ID批量查询订单信息
*
* @param ids 订单ID集合
* @return 查询结果
*/
List<MallOrderE> listById(List<Long> ids);
/**
* 批量保存订单信息
*
* @param mallOrderES 待保存内容
* @return 保存结果
*/
Boolean saveBatch(List<MallOrderE> mallOrderES);
/**
* 根据主键更新订单信息
*
* @param mallOrderE 更新内容
* @return 更新结果
*/
Boolean updateById(MallOrderE mallOrderE);
Boolean updateBatchById(List<MallOrderE> orderEList);
/**
* B端订单状态统计
*/
OrderStateStatisticsCO orderStateStatistics(OrderPageQryCmd cmd);
/**
* 消费者订单状态统计
*/
OrderStateStatisticsCustomerCO customerOrderStateStatistics(String customerId);
/**
* 商品销量统计
*
* @param skuIds 商品SKUID
* @return 统计结果
*/
List<GoodsSalesCO> goodsSalesStatistics(List<Long> skuIds, LocalDateTime createDtBegin, LocalDateTime createDtEnd, Integer limit);
/**
* 删除主订单
*
* @param mainOrderId 主订单ID
* @return 删除结果
*/
Boolean customerDeleteByMainOrderId(Long mainOrderId);
/**
* 获取SKU产生的最新订单
*
* @param skuIds skuId集合
* @return 订单信息
*/
List<OrderCO> skuLatestSale(List<Long> skuIds);
/**
* 根据订单ID集合批量删除订单
*
* @param ids 订单ID集合
* @return 删除结果
*/
Boolean deleteBatchById(Collection<Long> ids);
}
相关推荐
- CentOS7.5系统更新yum源的详细过程
-
简介:最近用CentOS7.5系统的yum安装软件老是出错。一查才知道,原来是官方不再支持CentOS7造成的影响,像mirrorlist.centos.org这样的默认镜像源没法用了...
- 免费开源虚拟机 VirtualBox 7.0.4 发布
-
IT之家11月19日消息,甲骨文公司今天正式发布了VirtualBox7.0.4,和上个7.0.2版本相隔1个月时间。新版本添加了对LinuxKernel6.1内核版本的初...
- centos7系统手动安装ceph教程 centos7详细安装步骤
-
CentOS7系统手动安装Ceph教程Ceph是一款强大的开源分布式存储系统,具备高可扩展性、高可用性和高可靠性,适用于大规模存储需求。它为现代应用提供了对象存储、块存储和文件系统等功能,广泛应用...
- Centos7系统环境tomcat安装步骤详细教程
-
在CentOS7系统环境下安装Tomcat,是搭建JavaWeb应用的关键步骤。Tomcat作为Apache软件基金会下的开源项目,广泛应用于开发和生产环境。以下是详细的安装步骤,涵盖系统更新、J...
- centos7 停服后yum源无法使用的解决办法
-
CentOS7已于2024年6月30日正式停服,停服后默认的yum源已全部404无法使用,对于还在使用CentOS7系统的用户来说,无疑是一个很大的影响。...
- CentOS7安装教程,简单几步,小白也能上手装!
-
Linux,全称GNU/Linux,是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的Unix工具软件、应用程...
- CentOS 7 上安装 JDK 8 的步骤: centos7如何安装jdk
-
CentOS7上安装JDK8的步骤:一、下载JDK...
- Centos7安装MySql步骤 centos7离线安装mysql
-
1.1MySQL安装1.1.1下载wget命令yum-yinstallwget...
- 系列教程之 CentOS7.6系统安装 centos7.6怎么安装
-
1、下载操作系统官网:https://www.centos.org/2、安装CentOS7.6操作系统(上)...
- centos 7 wordpress 安装 centos7安装web服务器
-
1,安装remi包wgethttps://mirrors.aliyun.com/remi/enterprise/remi-release-7.rpmyuminstallremi-releas...
- CentOS7下OSSEC(v3.6.0)安装及配置(图文)
-
一、安装前说明OSSEC是一款开源的多平台基于主机的入侵检测系统,可以简称为HIDS。它具备日志分析,文件完整性检查,策略监控,rootkit检测,实时报警以及联动响应等功能。它支持多种操作系统:Li...
- Centos7.6离线安装MySQL5.7.36 centos7.6离线安装A100G
-
7.4.1.检测是否安装Mysql数据库和Mariadb1、检测系统有没有安装mysql数据库和Mariadb,执行命令:...
- centos7 进行系统升级和系统降级 centos7升级到centos8
-
一直以来大家都是只升级系统内核,很少有人会去升级系统版本,今天来给大家演示下怎么对centos7系统进行升级和降级.系统:centos7.x(64位)1.检查系统版本...
- 虚拟机CentOS操作系统中如何安装使用VMtools工具
-
在VMware虚拟机中,完成centos操作系统的安装后。我们需要进行安装VMtools工具,这样不仅便于鼠标与虚拟机之间的无缝切换,同时也可以让CentOS与主机之间进行文件夹的共享访问。1.进入C...
- Vmware15.5虚拟机安装Centos7.6操作系统全过程
-
引言VMwareWorkstation15.5提供了一个强大的平台,让用户能够在单一物理机上模拟出多个独立的计算机环境。CentOS7.6是一个基于RedHatEnterpriseLi...
- 一周热门
-
-
IDC机房服务器托管可提供的服务
-
新版腾讯QQ更新Windows 9.9.7、Mac 6.9.25、Linux 3.2.5版本
-
一文看懂mysql时间函数now()、current_timestamp() 和sysdate()
-
详解PostgreSQL 如何获取当前日期时间
-
一款全能的看图软件,速度快、功能强、免费用
-
PhotoShop通道
-
Boston Dynamics Founder to Attend the 2024 T-EDGE Conference
-
一文读懂关于MySQL Datetime字段允许插入0000-00-00无效日期
-
Serv-u 提权
-
流星蝴蝶剑:76邵氏精华版,强化了流星,消失了蝴蝶
-
- 最近发表
-
- CentOS7.5系统更新yum源的详细过程
- 免费开源虚拟机 VirtualBox 7.0.4 发布
- centos7系统手动安装ceph教程 centos7详细安装步骤
- Centos7系统环境tomcat安装步骤详细教程
- centos7 停服后yum源无法使用的解决办法
- CentOS7安装教程,简单几步,小白也能上手装!
- CentOS 7 上安装 JDK 8 的步骤: centos7如何安装jdk
- Centos7安装MySql步骤 centos7离线安装mysql
- 系列教程之 CentOS7.6系统安装 centos7.6怎么安装
- centos 7 wordpress 安装 centos7安装web服务器
- 标签列表
-
- serv-u 破解版 (19)
- huaweiupdateextractor (27)
- thinkphp6下载 (25)
- mysql 时间索引 (31)
- mydisktest_v298 (34)
- sql 日期比较 (26)
- document.appendchild (35)
- 头像打包下载 (61)
- oppoa5专用解锁工具包 (23)
- acmecadconverter_8.52绿色版 (39)
- oracle timestamp比较大小 (28)
- f12019破解 (20)
- unity shader入门精要pdf (22)
- word文档批量处理大师破解版 (36)
- pk10牛牛 (22)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- 加密与解密第四版pdf (30)
- pcm文件下载 (23)
- jemeter官网 (31)
- parsevideo (33)
- 个人网站源码 (37)
- ckeditor4中文文档 (27)
- exe4j_java_home (30)
- centos7.4下载 (33)