电商网站之新增订单

先来分析一下新增订单的业务逻辑

用户在选中购物车中的商品后,点击添加订单

我们要收集订单信息(sku商品信息、价格信息、优惠和运费信息等)后才能执行生成订单操作。

具体步骤如下:

  1. 首先将用户选中的sku库存减少对应的数量

  1. 用户购物车要删除对应的商品

  1. 创建一个订单,即oms_order表执行新增

  1. 我们还要讲订单中的每种商品和订单关系添加在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测试新增

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿码德乌斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值