先来分析一下新增订单的业务逻辑
用户在选中购物车中的商品后,点击添加订单
我们要收集订单信息(sku商品信息、价格信息、优惠和运费信息等)后才能执行生成订单操作。
具体步骤如下:
首先将用户选中的sku库存减少对应的数量
用户购物车要删除对应的商品
创建一个订单,即oms_order表执行新增
我们还要讲订单中的每种商品和订单关系添加在oms_order_item表中
参考下图:
确定要使用的技术
除了之前一直使用的Nacos/Dubbo之外,创建订单的业务在减少库存时,是Dubbo调用的pms中的sku表,这就涉及到了分布式事务Seata。
注意
删除购物车、新增订单和新增订单项是order模块的功能;减少库存的功能是product模块。
一、开发删除选中的购物车商品的功能
要删除的购物车商品通过用户id和skuId来指定
则在OmsCartMapper编写
// 根据用户id和SkuId删除商品
int deleteCartByUserIdAndSkuId(OmsCart omsCart);
OmsCartMapper.xml
<!-- 根据用户id和SkuId删除商品 -->
<delete id="deleteCartByUserIdAndSkuId">
delete from
oms_cart
where
user_id=#{userId}
and
sku_id=#{skuId}
</delete>
删除购物车商品的功能是为生成订单准备,所以只需要开发出业务逻辑层即可,不需要控制层的代码
OmsCartServiceImpl实现方法
@Override
public void removeUserCarts(OmsCart omsCart) {
// 直接调用删除购物车的方法即可
// 即使删除失败也不抛出异常因为用户在使用"立即购买"功能时,购物车里是没有这个商品的
omsCartMapper.deleteCartByUserIdAndSkuId(omsCart);
}
二、编写新增order_item的持久层
新增order_item表,要包含订单号、商品id和相关信息
mapper下创建OmsOrderItemMapper
@Repository
public interface OmsOrderItemMapper {
// 新增订单项(order_item)的方法
// 一个订单可能包含多件商品,如果每件商品都单独新增到数据库,会造成连库次数多,效率低
// 我们采用一次连库增加多条订单项的方式,提升连接\操作数据库的效率
// 所以参数就是一个List<OmsOrderItem>类型了
int insertOrderItemList(List<OmsOrderItem> omsOrderItems);
}
OmsOrderItemMapper.xml文件添加内容
<!-- 新增订单项(order_item)的方法 -->
<insert id="insertOrderItemList">
insert into oms_order_item(
id,
order_id,
sku_id,
title,
bar_code,
data,
main_picture,
price,
quantity
) values
<foreach collection="list" item="orderItem" separator=",">
(
#{orderItem.id},
#{orderItem.orderId},
#{orderItem.skuId},
#{orderItem.title},
#{orderItem.barCode},
#{orderItem.data},
#{orderItem.mainPicture},
#{orderItem.price},
#{orderItem.quantity}
)
</foreach>
</insert>
三、编写新增order的持久层
mapper包下再创建OmsOrderMapper
@Repository
public interface OmsOrderMapper {
// 新增订单的方法
int insertOrder(OmsOrder omsOrder);
}
OmsOrderMapper.xml中添加方法
<!-- 新增订单的mapper -->
<insert id="insertOrder" >
insert into oms_order(
id,
sn,
user_id,
contact_name,
mobile_phone,
telephone,
province_code,
province_name,
city_code,
city_name,
district_code,
district_name,
street_code,
street_name,
detailed_address,
tag,
payment_type,
state,
reward_point,
amount_of_original_price,
amount_of_freight,
amount_of_discount,
amount_of_actual_pay,
gmt_pay,
gmt_order,
gmt_create,
gmt_modified
) values (
#{id},
#{sn},
#{userId},
#{contactName},
#{mobilePhone},
#{telephone},
#{provinceCode},
#{provinceName},
#{cityCode},
#{cityName},
#{districtCode},
#{districtName},
#{streetCode},
#{streetName},
#{detailedAddress},
#{tag},
#{paymentType},
#{state},
#{rewardPoint},
#{amountOfOriginalPrice},
#{amountOfFreight},
#{amountOfDiscount},
#{amountOfActualPay},
#{gmtPay},
#{gmtOrder},
#{gmtCreate},
#{gmtModified}
)
</insert>
四、开发新增订单的业务逻辑层
新增订单的业务是比较复杂的,可以将整个业务分成三大部分:
第一部分: 信息的收集[主要是参数类型数据的完整性验证、计算以及转换]
第二部分: 数据库操作[减少库存,删除购物车,新增订单和新增订单项]
第三部分: 收集需要的返回值[新增订单成功后,需要返回给前端一些信息,例如订单号,实际支付的金额等]
创建OmsOrderServiceImpl类,代码如下:
// 后面的秒杀业务需要调用这个生成订单的方法,所以需要支持dubbo调用
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService{
@DubboReference
private IForOrderSkuService dubboSkuService;
@Autowired
private IOmsCartService omsCartService;
@Autowired
private OmsOrderMapper omsOrderMapper;
@Autowired
private OmsOrderItemMapper omsOrderItemMapper;
// 新增订单的方法
// 这个方法dubbo调用了Product模块的方法,操作了数据库,有分布式的事务需求
// 所以要使用注解激活Seata分布式事务的功能
@GlobalTransactional
@Override
public OrderAddVO addOrder(OrderAddDTO orderAddDTO){
// 第一部分:收集信息,准备数据
// 先实例化OmsOrder对象
OmsOrder order = new OmsOrder();
// 当前方法参数orderAddDTO有很多order需要的同名属性,直接赋值即可
BeanUtils.copyProperties(orderAddDTO,order);
// orderAddDTO中属性比OmsOrder要少,缺少的属性要我们自己赋值或生成
// 可以编写一个专门的方法,来进行数据的收集
loadOrder(order);
// 到此为止,order的普通属性全部赋值完毕
// 下面要将参数orderAddDTO中包含的订单项(orderItem集合)信息赋值
// 首先取出这个集合,也就是当前订单中包含的所有商品的集合
List<OrderItemAddDTO> itemAddDTOs=orderAddDTO.getOrderItems();
if(itemAddDTOs==null || itemAddDTOs.isEmpty()){
// 如果当前订单中没有商品,就无法继续生成订单了
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"订单中必须至少包含一件商品");
}
// 我们最终的目标是将当前订单中包含的订单项新增到数据库
// 当前集合泛型是OrderItemAddDTO,我们编写的新增到数据库的方法泛型是OmsOrderItem
// 所以我们要编写代码将上集合转换为List<OmsOrderItem>集合
List<OmsOrderItem> omsOrderItems=new ArrayList<>();
// 遍历OrderItemAddDTO集合
for(OrderItemAddDTO addDTO : itemAddDTOs ){
// 先实例化一个OmsOrderItem对象,以备赋值使用
OmsOrderItem orderItem=new OmsOrderItem();
// 将同名属性赋值到orderItem对象中
BeanUtils.copyProperties(addDTO,orderItem);
// 将addDTO对象中没有的id和orderId属性赋值
// 赋值Id
Long itemId=IdGeneratorUtils.getDistributeId("order_item");
orderItem.setId(itemId);
// 赋值orderId
orderItem.setOrderId(order.getId());
// 将赋好值的对象添加到omsOrderItems集合中
omsOrderItems.add(orderItem);
// 第二部分:执行操作数据库的指令
// 1.减少库存
// 当前循环是订单中的一件商品,我们可以在此处对这个商品进行库存的减少
// 当前对象属性中是包含skuId和要购买的商品数量的,所以可以执行库存的修改
// 先获取skuId
Long skuId=orderItem.getSkuId();
// 修改库存是Dubbo调用的
int rows=dubboSkuService.reduceStockNum(
skuId,orderItem.getQuantity());
// 判断rows(数据库受影响的行数)的值
if(rows==0){
log.warn("商品库存不足,skuId:{}",skuId);
// 库存不足不能继续生成订单,抛出异常,终止事务进行回滚
throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
"库存不足!");
}
// 2.删除勾选的购物车商品
OmsCart omsCart=new OmsCart();
omsCart.setUserId(order.getUserId());
omsCart.setSkuId(skuId);
// 执行删除的方法
omsCartService.removeUserCarts(omsCart);
}
// 3.执行新增订单
// omsOrderMapper直接调用新增订单的方法即可
omsOrderMapper.insertOrder(order);
// 4.新增订单中所有商品的订单项信息
// omsOrderItemMapper中编写了批量新增订单项的功能
omsOrderItemMapper.insertOrderItemList(omsOrderItems);
// 第三部分:准备返回值,返回给前端
// 当前业务逻辑层方法返回值为OrderAddVO
// 我们需要做的就是实例化这个对象,给它赋值并返回
OrderAddVO addVO=new OrderAddVO();
// 给addVO各属性赋值
addVO.setId(order.getId());
addVO.setSn(order.getSn());
addVO.setCreateTime(order.getGmtOrder());
addVO.setPayAmount(order.getAmountOfActualPay());
// 最后千万别忘了返回addVO!!!!!
return addVO;
}
// 为Order对象补全属性值的方法
private void loadOrder(OmsOrder order){
// 本方法针对order对象没有被赋值的属性,进行生成或手动赋值
// 给id赋值,订单业务不使用数据库自增列做id,而使用Leaf分布式序列生成系统
Long id = IdGeneratorUtils.getDistributeId("order");
order.setId(id);
// 生成订单号,直接使用UUID即可
order.setSn(UUID.randomUUID().toString());
// 赋值UserId
// 以后秒杀业务调用这个方法时,userId属性是会被赋值的
// 所以这里要判断一下userId是否已经有值,没有值再赋值
if (order.getUserId() == null) {
// 从SpringSecurity上下文中获得当前登录用户id
order.setUserId(getUserId());
}
// 为订单状态赋值
// 订单状态如果为null ,将其设默认值0,表示未支付
if (order.getState() == null){
order.setState(0);
}
// 为了保证下单时间和数据创建时间和最后修改时间一致
// 我们给他们赋相同的值
LocalDateTime now=LocalDateTime.now();
order.setGmtOrder(now);
order.setGmtCreate(now);
order.setGmtModified(now);
// 验算实际支付金额
// 计算公式: 实际支付金额=原价-优惠+运费
// 数据类型使用BigDecimal,防止浮点偏移,还有更大的取值范围
BigDecimal price=order.getAmountOfOriginalPrice();
BigDecimal freight=order.getAmountOfFreight();
BigDecimal discount=order.getAmountOfDiscount();
BigDecimal actualPay=price.subtract(discount).add(freight);
// 最后将计算完成的实际支付金额赋值给order
order.setAmountOfActualPay(actualPay);
}
@Override
public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
}
@Override
public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
return null;
}
@Override
public OrderDetailVO getOrderDetail(Long id) {
return null;
}
public CsmallAuthenticationInfo getUserInfo(){
// 编写SpringSecurity上下文中获得用户信息的代码
UsernamePasswordAuthenticationToken authenticationToken=
(UsernamePasswordAuthenticationToken)
SecurityContextHolder.getContext().getAuthentication();
// 为了逻辑严谨性,判断一下SpringSecurity上下文中的信息是不是null
if(authenticationToken == null){
throw new CoolSharkServiceException(
ResponseCode.UNAUTHORIZED,"您没有登录!");
}
// 确定authenticationToken不为null
// 就可以从中获得用户信息了
CsmallAuthenticationInfo csmallAuthenticationInfo=
(CsmallAuthenticationInfo) authenticationToken.getCredentials();
// 别忘了返回
return csmallAuthenticationInfo;
}
// 业务逻辑层中的方法实际上都只需要用户的id即可
// 我们可以再编写一个方法,从用户对象中获得id
public Long getUserId(){
return getUserInfo().getId();
}
}
五、开发新增订单的控制层
新建OmsOrderController
@RestController
@RequestMapping("/oms/order")
@Api(tags = "订单管理模块")
public class OmsOrderController {
@Autowired
private IOmsOrderService omsOrderService;
@PostMapping("/add")
@ApiOperation("执行新增订单的方法")
@PreAuthorize("hasRole('user')")
public JsonResult<OrderAddVO> addOrder(@Validated OrderAddDTO orderAddDTO){
OrderAddVO orderAddVO=omsOrderService.addOrder(orderAddDTO);
return JsonResult.ok(orderAddVO);
}
}
启动Nacos\seata
依次启动服务Leaf\product\\[passport]\order
knife4j测试新增