Spring Boot电商项目53:订单模块二:【前台:创建订单】接口;(这个接口比较复杂,内容较多)
说明:(1)一:【前提:创建订单】:分析;1.【前台:创建订单】在整个【订单模块】中的位置;……………………………………………………2.【前台:创建订单】说明;(1.1)首先,【前台:创建订单】接口:入参说明;(1.2)所以,用户id数据、购物车中的商品数据,都需要我们自己去获取;(2)我们要判断,【当前用户的、购物车中已经被勾选的、将要被我们下单的,商品】是否存在,如果存在再看其是否还是上架状态
说明:
(1)本篇博客的内容:开发【前台:创建订单】接口;
(2)本篇博客的内容比较多,主要需要注意以下几点:
● 要明确【创建订单】的思路和流程;
● 为了实现同一个业务,具体的编码逻辑可能存在差异;但随着自己编程能力的提升、开发技巧的积累;应该能编写出越来越好的代码;
● 涉及到了枚举类的使用;
● Spring Boot手动控制事务;
● 关于本篇博客中的几个比较中的点,自己单独写了几篇附加博客,来说明,可以去参考;
目录
1.创建【订单模块】对应的OrderController;编写前台创建订单的方法:createOrder()方法;
(2)因为,这个接口有三个参数,虽然不是很多,我们还是创建了一个实体类CreateOrderReq,来帮助承接参数;
(3)使用实体类CreateOrderReq,去承接参数;并开启了Valid参数校验;
(4)Service层创建订单的逻辑方法create()方法,在下一部分介绍;
2.创建OrderService接口,OrderServiceImpl实现类;
3.在OrderServiceImpl实现类编写创建订单的逻辑方法:create()方法;
(2)然后,调用【cartService.list(userId);】去查询【当前用户的、购物车中的、商品状态仍然是上架状态的】所有商品数据;
(4)如果【购物车中,validSaleStatusAndStock没有被勾选的商品】,就抛出一个异常;
(5)编写一个工具方法validSaleStatusAndStock(),去检查,这些被勾选的商品:是否还存在、是否是上架状态、库存是否足够;
(6)然后,因为我们在创建订单的时候,需要把订单中商品的信息,存储到order_item表中;所以,我们编写方法,通过CartVO,来构建OrderItem;
(8)然后,删除【当前用户的、购物车中的、商品是上架状态的、库存足够的、被勾选的】那些商品;
(9)然后,编写OrderCodeFactory工具类,去生成订单号;
(10.1)创建一个订单Order对象,并将其添加到order表中;
(11)利用循环,把订单中的每种商品,写到order_item表中;
一:【前提:创建订单】:分析;
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)】来设置手动控制事务;
三:测试;
可以看到,该用户的购物车中被勾选的商品,已经被删除了;
更多推荐