说明:

(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 {@AutowiredOrderService 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 {@AutowiredCartService cartService;@AutowiredProductMapper productMapper;@AutowiredCartMapper cartMapper;@AutowiredOrderMapper orderMapper;@AutowiredOrderItemMapper orderItemMapper;/*** 创建订单* @param createOrderReq* @return*/@Transactional(rollbackFor = Exception.class)@Overridepublic 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);//首先,先拿到原先的productProduct 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);//设置用户idorder.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)】来设置手动控制事务;


三:测试;

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

Spring Boot电商项目53:订单模块二:【前台:创建订单】接口;(这个接口比较复杂,内容较多)相关推荐

  1. Spring Boot电商项目57:订单模块六:【前台:生成支付二维码】接口;(支付url的拼凑;利用zxing生成二维码;二维码图片的存储;真实地址与可访问地址的转换;)

    说明: (1)本篇博客主要内容是:开发[前台:生成支付二维码]接口: (2)本篇博客需要注意的点有: ● 支付url的拼凑: ● 利用zxing生成二维码: ● 二维码图片的存储:真实地址与可访问地址 ...

  2. Spring Boot电商项目59:订单模块八:【后台:订单列表】接口;

     说明: (1)本篇博客的主要内容是开发[后台:订单列表]接口: (2)本篇博客没什么难点:只需要注意:[前台:订单列表]接口是查询当前登录用户的订单数据:[后台:订单列表]接口是查询所有用户的订单数 ...

  3. Spring Boot电商项目17:用户模块六:注册接口开发之:使用【GlobalExceptionHandler】来全局统一处理异常;(涉及了@ControllerAdvice等注解)

    说明: (1)为什么写这篇博客?:在[Spring Boot电商项目15:用户模块四:注册接口开发:]中,在Service层中遇到了[用户名重复]的情况,然后Service层把这个情况做成了一个异常, ...

  4. Spring Boot电商项目:概述;

    说明: (1)强调:该Spring Boot电商项目中,会有大量的以前接触过的内容:但是,当我们遇到的时候,也会重新啰嗦.重复解释:以达到该专栏形成一个比较好的闭环的目的: 目录 一:Spring B ...

  5. Spring Boot电商项目

    Spring Boot电商项目 一.概述 1.电商项目整体介绍 (1)前台模块具体分析 (2)后台模块具体分析 (3) 项目演示 2 . 项目开发所需工具准备 二. 数据库设计于项目初始化 1.表设计 ...

  6. Spring Boot电商项目52:订单模块一:订单模块介绍;(模块介绍;效果演示;数据库设计;9个接口介绍;)

    说明: (1)本篇博客的内容:本篇博客,简单梳理下[订单模块]的基本内容: (2)声明:[订单模块]是一个比较复杂的模块,其中涉及了订单的状态的改变和订单的流转:所以,本篇博客,只是简单介绍了[订单模 ...

  7. 一套仿阿里完整版Spring Boot电商项目,前后端分离+权限管理系统

    项目简介 1.项目背景 2. B2B 模式 3. B2C 模式 ==(商城模式)*== 4. C2B 模式 5. C2C 模式 6. O2O 模式 2.商城架构图 2.1.前后端分离 基础版 2.3. ...

  8. java烟草项目功能模块_一个电商项目的功能模块梳理

    电商项目核心玩法:定制商品+供应链(商品辅料库)+网红社区 最近做项目,功能越来越多,想清晰地理解项目,有点累了. 今天抽空,把这个项目的核心功能模块简要总结下,顺便画了个图. 一.全局功能模块图 二 ...

  9. 微信小程序电商项目源代码开放连载二三事

    大家好,我发布的微信小程序电商项目连载视频,正式进入实战化阶段了,为了让大家更好的去学习微信小程序开发,我在gitbub上面,开放了源代码.随着视频的连载更新,我会逐步将源代码进行更新. 源代码发布地 ...

  10. spring boot电商系统前端界面设计与浏览器兼容性研究 毕业设计-附源码231058

    摘  要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于电商系统前端界面设计与浏览器兼容性研究当然也不能排除在外,随着网络技术的不断成熟,带动了电商系统前 ...

最新文章

  1. 经验 | Pytorch还是Tensorflow?英伟达工程师帮你总结了
  2. calibrate_cameras算子说明
  3. Android开发之带你轻松集成友盟统计
  4. 数据结构学习工具总结
  5. Spring注解开发-属性依赖注入
  6. hibernate mysql id 自增长 注解_hibernate Mysql 自增长 注解配置,表无关联的注解方式关联查询...
  7. CodeForces - 1293D Aroma's Search(暴力)
  8. 《Python Cookbook 3rd》笔记(4.5):反向迭代
  9. postgre 生成数据库html文档_还在手动整理数据库文档?试试这个(螺丝钉)数据库文档生成工具...
  10. Vue源码:mustache模板引擎学习
  11. 文件浏览器一定要有个向上级按钮
  12. MPI集群安装、MPI安装
  13. Keil5编译环境搭建流程----STM32和GD32
  14. 计算机处理器性能排名,2020电脑cpu性能排行榜天梯图
  15. 地址转经纬度(百度)function
  16. c语言——输出字母c
  17. 我的世界Scratch版
  18. 有没有一款软件是测试阅读准确率的,当今各一线股票软件系统指标准确率测试一览表...
  19. 怎么调整gif表情包的比例?
  20. 覃超数据结构(二) 栈和队列

热门文章

  1. 怎么获取计算机的最高权限,获取win8 64位旗舰版系统最高权限的方法【图文详解】...
  2. 基于SSM框架的实验室设备管理系统
  3. 定义并测试一个代表员工的Employee类。
  4. 计算机科学理论数学研讨会,2017年奇异摄动理论及其应用学术研讨会会议-上海交通大学数学系.DOC...
  5. 高股息组合与指数基金谁好?
  6. 第二章 存储器层次结构
  7. Unity XCode 拨号和一键加群
  8. 公司-人人网:人人网
  9. 流媒体弱网优化之路(FEC+mediasoup)——mediasoup的Nack优化以及FEC引入
  10. 2020年计算机组装行业,组装电脑已成夕阳产业?DIY装机发展的道路在何方?