说明:

(1)本篇博客的内容:开发【前台:创建订单】接口;

(2)本篇博客的内容比较多,主要需要注意以下几点:

          ● 要明确【创建订单】的思路和流程;

          ● 为了实现同一个业务,具体的编码逻辑可能存在差异;但随着自己编程能力的提升、开发技巧的积累;应该能编写出越来越好的代码;

          ● 涉及到了枚举类的使用;

          ● Spring Boot手动控制事务;

          ● 关于本篇博客中的几个比较中的点,自己单独写了几篇附加博客,来说明,可以去参考;

目录

一:【前提:创建订单】:分析;

1.【前台:创建订单】,在整个【订单模块】中的位置;

2.【前台:创建订单】,思路分析;

二:正式开发;

1.创建【订单模块】对应的OrderController;编写前台创建订单的方法:createOrder()方法;

(1)请求方式,url要符合接口文档要求;

(2)因为,这个接口有三个参数,虽然不是很多,我们还是创建了一个实体类CreateOrderReq,来帮助承接参数;

(3)使用实体类CreateOrderReq,去承接参数;并开启了Valid参数校验;

(4)Service层创建订单的逻辑方法create()方法,在下一部分介绍;

2.创建OrderService接口,OrderServiceImpl实现类;

3.在OrderServiceImpl实现类编写创建订单的逻辑方法:create()方法;

(1)通过UserFilter获取当前登录用户;

(2)然后,调用【cartService.list(userId);】去查询【当前用户的、购物车中的、商品状态仍然是上架状态的】所有商品数据;

(3)然后,筛选出,当前购物车中,那些被勾选的商品;

(4)如果【购物车中,validSaleStatusAndStock没有被勾选的商品】,就抛出一个异常;

(5)编写一个工具方法validSaleStatusAndStock(),去检查,这些被勾选的商品:是否还存在、是否是上架状态、库存是否足够;

(6)然后,因为我们在创建订单的时候,需要把订单中商品的信息,存储到order_item表中;所以,我们编写方法,通过CartVO,来构建OrderItem;

(7)扣库存;

(8)然后,删除【当前用户的、购物车中的、商品是上架状态的、库存足够的、被勾选的】那些商品;

(9)然后,编写OrderCodeFactory工具类,去生成订单号;

(10.1)创建一个订单Order对象,并将其添加到order表中;

(10.2)其中,使用了枚举类,来管理“订单状态”信息;

(10.3)给剩余的属性赋值,然后插入到order表中;

(11)利用循环,把订单中的每种商品,写到order_item表中;

4.在OrderService接口中,反向生成方法的声明;

5.添加数据库事务;

三:测试;


一:【前提:创建订单】:分析;

1.【前台:创建订单】,在整个【订单模块】中的位置;

2.【前台:创建订单】,思路分析;

(1.1)首先,【前台:创建订单】接口:入参说明;

(1.2)所以,用户id数据、购物车中的商品数据,都需要我们自己去获取;

(2)我们要判断,【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】是否存在,如果存在再看其是否还是上架状态;

(3)还要判断,【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】是否库存足够,以防止超卖;(PS:如果,一切顺利,下单后,还要及时的扣库存)

(4)用户下单时,首先,会删除【当前用户的、购物车中的、这个已经被勾选的、将要被下单的,商品】;也就是,删除cart表中,对应的记录;

(5)然后,我们需要编写逻辑,生成一个订单号;

(6)然后,会创建一个订单;也就是在order表中,新增一个订单记录;

(7)然后,也要利用循环,把订单中的每种商品,写到order_item表中;

(8)因为,【前台:创建订单】过程涉及多个数据库的写操作,所以我们这儿需要手动控制数据库事务;

至于,数据库事务,在【附加:Spring Boot项目,手动控制事务;(包括:总结了到目前为止,事务的所有内容;)】中,我们再次做了总结,如有需要可以去查看;


二:正式开发;

1.创建【订单模块】对应的OrderController;编写前台创建订单的方法:createOrder()方法;

package com.imooc.mall.controller;

import com.imooc.mall.common.ApiRestResponse;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.service.OrderService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

/**
 * 描述:订单Controller
 */
@RestController
public class OrderController {

    @Autowired
    OrderService orderService;

    /**
     * 【前台:创建订单】接口;
     * @param createOrderReq
     * @return
     */
    @ApiOperation("创建订单")
    @PostMapping("/order/create ")
    public ApiRestResponse createOrder(@Valid @RequestBody CreateOrderReq createOrderReq) {
        String orderNum = orderService.create(createOrderReq);
        return ApiRestResponse.success(orderNum);
    }

}

说明:

(1)请求方式,url要符合接口文档要求;

          ● 有关@RequestBody注解,如有需要可以参考【附加:【POST请求:方法参数放在url中和放在body中,有什么区别】;也包括【@Param,@RequestParam,@RequestBody这三个【与接收参数有关】的注解,总结】;】;

……………………………………………………

(2)因为,这个接口有三个参数,虽然不是很多,我们还是创建了一个实体类CreateOrderReq,来帮助承接参数;

          ● 同时,上面使用了@Valid注解,进行了Validation参数校验;

……………………………………………………

(3)使用实体类CreateOrderReq,去承接参数;并开启了Valid参数校验;

……………………………………………………

(4)Service层创建订单的逻辑方法create()方法,在下一部分介绍;

2.创建OrderService接口,OrderServiceImpl实现类;

3.在OrderServiceImpl实现类编写创建订单的逻辑方法:create()方法;

package com.imooc.mall.service.impl;

import com.imooc.mall.common.Constant;
import com.imooc.mall.exception.ImoocMallException;
import com.imooc.mall.exception.ImoocMallExceptionEnum;
import com.imooc.mall.filter.UserFilter;
import com.imooc.mall.model.dao.CartMapper;
import com.imooc.mall.model.dao.OrderItemMapper;
import com.imooc.mall.model.dao.OrderMapper;
import com.imooc.mall.model.dao.ProductMapper;
import com.imooc.mall.model.pojo.Cart;
import com.imooc.mall.model.pojo.Order;
import com.imooc.mall.model.pojo.OrderItem;
import com.imooc.mall.model.pojo.Product;
import com.imooc.mall.model.request.CreateOrderReq;
import com.imooc.mall.model.vo.CartVO;
import com.imooc.mall.service.CartService;
import com.imooc.mall.service.OrderService;
import com.imooc.mall.utils.OrderCodeFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 描述:订单模块的Service实现类
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    CartService cartService;
    @Autowired
    ProductMapper productMapper;
    @Autowired
    CartMapper cartMapper;
    @Autowired
    OrderMapper orderMapper;
    @Autowired
    OrderItemMapper orderItemMapper;


    /**
     * 创建订单
     * @param createOrderReq
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String create(CreateOrderReq createOrderReq) {
        //首先,拿到用户ID;
        Integer userId = UserFilter.currentUser.getId();
        //从购物车中,查询当前用户的、购物车中的、已经被勾选的商品;
        List<CartVO> cartVOList = cartService.list(userId);

        //遍历查到的购物车数据,从中筛选出被勾选的;
        ArrayList<CartVO> cartVOArrayListTemp = new ArrayList<>();
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO =  cartVOList.get(i);
            if (cartVO.getSelected().equals(Constant.CartIsSelected.CHECKED)) {
                cartVOArrayListTemp.add(cartVO);
            }
        }
        cartVOList = cartVOArrayListTemp;
        //如果,购物车中没有已经被勾选的商品:就抛出"购物车勾选的商品为空"异常;
        if (CollectionUtils.isEmpty(cartVOList)) {
            throw new ImoocMallException(ImoocMallExceptionEnum.CART_SELECTED_EMPTY);
        }

        //判断商品是否存在;如果存在,是否是上架状态;商品库存是否足够;
        validSaleStatusAndStock(cartVOList);

        //把【查询购物车表cart表,获得的商品数据】转化为【能够存储到order_item表的、商品数据】
        List<OrderItem> orderItemList = cartVOListToOrderItemList(cartVOList);

        //扣库存;(PS:前面有判断库存的逻辑,程序如果能走到这一步,就说明库存是够的)
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem =  orderItemList.get(i);
            //首先,先拿到原先的product
            Product product = productMapper.selectByPrimaryKey(orderItem.getProductId());
            //然后,计算新的库存;
            int stock = product.getStock() - orderItem.getQuantity();
            if (stock < 0) {//上面已经检查过库存了,这儿又判断,是否是重复工作
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
            }

            product.setStock(product.getStock() - orderItem.getQuantity());
            //然后,去更新库存;也就是扣库存啦;
            productMapper.updateByPrimaryKeySelective(product);

        }
        //把【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】给删除;也就是,删除cart表中,对应的记录;
        cleanCart(cartVOList);

        //编写逻辑,生成一个订单号
        String orderNum = OrderCodeFactory.getOrderCode(Long.valueOf(userId));
        //创建一个订单;
        Order order = new Order();
        order.setOrderNo(orderNum);//设置订单号
        order.setUserId(userId);//设置用户id
        order.setTotalPrice(totalPrice(orderItemList));//设置订单总价
        order.setReceiverName(createOrderReq.getReceiverName());//设置收件人姓名
        order.setReceiverAddress(createOrderReq.getReceiverAddress());//设置收件人地址
        order.setReceiverMobile(createOrderReq.getReceiverMobile());//设置收件人电话
        order.setOrderStatus(Constant.OrderStatusEnum.NOT_PAY.getCode());//设置订单状态
        order.setPostage(0);//运费;我们这儿目前是包邮
        order.setPaymentType(1);//付款方式;我们这儿只有一种1在线支付
        //把这个订单,添加到order表中,新增一个订单记录;
        orderMapper.insertSelective(order);

        //也要利用循环,把订单中的每种商品,写到order_item表中;
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem =  orderItemList.get(i);
            orderItem.setOrderNo(orderNum);//给其赋上订单号
            orderItemMapper.insertSelective(orderItem);
        }

        //返回结果;
        return orderNum;
    }


    /**
     * 工具方法:判断列表中的商品是否存在、是否是上架状态、库存是否足够;
     * 规则:购物车中的、已经被勾选的商品;但凡有一种不符合要求,都不行;
     * @param cartVOArrayList
     */
    private void validSaleStatusAndStock(List<CartVO> cartVOArrayList) {
        //循环遍历、判断:【购物车中的、已经被勾选的、每一种商品】
        for (int i = 0; i < cartVOArrayList.size(); i++) {
            CartVO cartVO =  cartVOArrayList.get(i);
            //根据【从购物车中,查到的商品信息】,去查product表;
            Product product = productMapper.selectByPrimaryKey(cartVO.getProductId());
            //如果没查到(说明,商品不存在),或者,商品不是上架状态:就抛出"商品状态不可售"异常;
            if (product == null || !product.getStatus().equals(Constant.SaleStatus.SALE)) {
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_SALE);
            }
            //判断商品库存,如果库存不足,抛出“商品库存不足异常;
            if (cartVO.getQuantity() > product.getStock()) {
                throw new ImoocMallException(ImoocMallExceptionEnum.NOT_ENOUGH);
            }
        }
    }

    /**
     * 工具方法:把【从cart购物车表中,查到的CartVO】转化为【可以存储到order_item表的,OrderItem】;
     * @param cartVOList
     * @return
     */
    private List<OrderItem> cartVOListToOrderItemList(List<CartVO> cartVOList) {
        List<OrderItem> orderItemList = new ArrayList<>();
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO = cartVOList.get(i);
            OrderItem orderItem = new OrderItem();
            orderItem.setProductId(cartVO.getProductId());
            //下面的,其实是【商品当前的快照信息】
            orderItem.setProductName(cartVO.getProductName());//商品(当前的)名称
            orderItem.setProductImg(cartVO.getProductImage());//商品(当前的)图片
            orderItem.setUnitPrice(cartVO.getPrice());//商品(当前的)单价

            orderItem.setQuantity(cartVO.getQuantity());//该种商品的购买数量
            orderItem.setTotalPrice(cartVO.getTotalPrice());//该种商品的总价

            orderItemList.add(orderItem);
        }
        return orderItemList;
    }

    /**
     * 工具方法:把【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】给删除;也就是,删除cart表中,对应的记录;
     * @param cartVOList
     */
    private void cleanCart(List<CartVO> cartVOList) {
        for (int i = 0; i < cartVOList.size(); i++) {
            CartVO cartVO =  cartVOList.get(i);
            cartMapper.deleteByPrimaryKey(cartVO.getId());
        }
    }

    /**
     * 工具方法:获取当前订单的总价;也就是该订单中,所用种类商品的总价;
     * @param orderItemList
     * @return
     */
    private Integer totalPrice(List<OrderItem> orderItemList) {
        Integer totalPrice = 0;
        for (int i = 0; i < orderItemList.size(); i++) {
            OrderItem orderItem =  orderItemList.get(i);
            totalPrice += orderItem.getTotalPrice();
        }
        return totalPrice;
    }


}

说明:

(1)通过UserFilter获取当前登录用户;

……………………………………………………

(2)然后,调用【cartService.list(userId);】去查询【当前用户的、购物车中的、商品状态仍然是上架状态的】所有商品数据;

          ● 这个方法,是在开发购物车模块的【购物车列表】接口时开发的,其作用是:查询【当前用户的、购物车中的、商品状态仍然是上架状态的】购物车信息;

          ● 如有需要,可以快速参考【Spring Boot电商项目45:购物车模块三:【购物车列表】接口;】;

          ● 只是需要注意,这个方法的返回结果CartVO是经过处理、组合的,其中属性还是比较丰富的;

……………………………………………………

(3)然后,筛选出,当前购物车中,那些被勾选的商品;

……………………………………………………

(4)如果【购物车中,validSaleStatusAndStock没有被勾选的商品】,就抛出一个异常;

……………………………………………………

(5)编写一个工具方法validSaleStatusAndStock(),去检查,这些被勾选的商品:是否还存在、是否是上架状态、库存是否足够;

(PS:其实,但就这儿的情况来说,上面(2)中的方法,查出来的就已经是:商品存在、商品是上架状态的了;;;;所以,但就这儿的情况来说,validSaleStatusAndStock()方法做了点重复工作)

……………………………………………………

(6)然后,因为我们在创建订单的时候,需要把订单中商品的信息,存储到order_item表中;所以,我们编写方法,通过CartVO,来构建OrderItem;

……………………………………………………

(7)扣库存;

……………………………………………………

(8)然后,删除【当前用户的、购物车中的、商品是上架状态的、库存足够的、被勾选的】那些商品;

 很自然,当我们把购物车中某些商品下单后,这些商品是应该从购物车中删除的;

PS:这儿需要明确的时,我们在下单的过程中,任何地方不符合要求,都会抛出异常;如果能一步一步走到这儿,就说明前面是符合要求的;

……………………………………………………

(9)然后,编写OrderCodeFactory工具类,去生成订单号;

package com.imooc.mall.utils;

import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * 描述:订单号生成类
 */
public class OrderCodeFactory {
    //订单号有多种生成规则,主要是原则就是:防止重复;这儿我们采用【时间+随机数】的方式;

    /**
     * 以一定格式,获取当前时间
     * @return
     */
    private static String getDateTime() {
        DateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        return sdf.format(new Date());
    }

    /**
     * 生成一个5位的随机数;(当然可以生成3、4、6、7、8……位;只是,这儿为了便于管理、程序明确,这儿我们统一生成5位)
     * @param n
     * @return
     */
    private static int getRandom(Long n) {
        Random random = new Random();
        return (int) (random.nextDouble() * (90000)) + 10000;
    }

    /**
     * 生成订单号
     * @param userId
     * @return
     */
    public static String getOrderCode(Long userId) {
        return getDateTime() + getRandom(userId);
    }
}

 

……………………………………………………

(10.1)创建一个订单Order对象,并将其添加到order表中;

……………………………………………………

(10.2)其中,使用了枚举类,来管理“订单状态”信息;

    /**
     * 枚举类,来说明订单状态
     */
    public enum OrderStatusEnum {
        CANCELED(0, "用户已取消"),
        NOT_PAY(10, "未付款"),
        PAID(20,"已付款"),
        DELIVERED(30,"已发货"),
        FINISHED(40,"交易完成");

        private int code;
        private String value;

        OrderStatusEnum(int code, String value) {
            this.code = code;
            this.value = value;
        }

        public static OrderStatusEnum codeOf(int code) {
            for (OrderStatusEnum orderStatusEnum:values() ) {
                if (orderStatusEnum.getCode() == code) {
                    return orderStatusEnum;
                }
            }
            throw new ImoocMallException(ImoocMallExceptionEnum.NO_ENUM);

        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

          ● 在本项目中,多次使用了枚举;可以参考【Spring Boot电商项目13:用户模块二:枚举类;(和【枚举数据类型】相比,【枚举类】可以管理一些复杂数据)】,【Spring Boot电商项目14:用户模块三:API统一返回对象;(其中,涉及了【使用枚举类,来管理接口请求失败时,将要向前端返回的(状态码)和(错误信息)】;)】;

          ● 至于,为什么我们这儿又使用枚举类来定义常量,而不使用内部接口定义常量了;

可以看下,这个回答;(PS:虽然,有点点文不对题,不太能解决自己的疑惑,,,但其内容,还是很有参考价值的)

          ● 这儿,为了能够通过code,获得对应的value信息,我们编写了codeOf()方法;

……………………………………………………

(10.3)给剩余的属性赋值,然后插入到order表中;

……………………………………………………

(11)利用循环,把订单中的每种商品,写到order_item表中;

4.在OrderService接口中,反向生成方法的声明;

5.添加数据库事务;

          ● 很显然,创建订单的时候,这一个业务逻辑,底层需要多次的增删改操作;所以,我们需要去手动控制事务;

          ● 有关,事务及Spring Boot中使用事务,可以参考【附加:Spring Boot项目,手动控制事务;(包括:总结了到目前为止,事务的所有内容;)】;

……………………………………………………

通过【@Transactional(rollbackFor = Exception.class)】来设置手动控制事务;


三:测试;

可以看到,该用户的购物车中被勾选的商品,已经被删除了;

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐