微信点餐系统(六)-买家端订单(上)
章节感悟:
1.进一步的加强了枚举类型的书写规范
2.@DynamicUpdate标签的使用
3.如果从前台接收的数据与后台数据不能保持一致,可以设计DTO包(数据传输对象(DTO)(Data Transfer Object))
4.后台传送到前台的数据可以用VO封装,即viewObject
5.进一步加深了lambda表达式的使用
6.实现一个Page<T>类可以使用PageImpl类来创建
7.实现一个pageable可以用PageRequest.of()来创建
8.自定义异常,继承runtimeException类,然后用枚举来列举异常,直接使用
9.上一章的resultFul风格前后端数据接口
10.设计一个唯一的Id类似于UUID的使用,很简单,但是这里要注意并发的使用
11.转换器converter,和工具类一样
章节自问自答:
买家端订单
DAO层设计
1.写OrderMaster和OrderDetail两个实体类,其中OrderMaster中OrderStatus和PayStatus两个属性设置为0默认,代码如下,已略,因为涉及到订单修改时间的更新,所以添加注解@DynamicUpdate,即在更新时候修改时间数据
package com.xiong.sell.dataobject; import lombok.Data; import org.hibernate.annotations.DynamicUpdate; import javax.persistence.Entity; import javax.persistence.Id; import java.math.BigDecimal; import java.util.Date; /** * @author Xiong YuSong * 2019/1/18 9:48 */ @Entity @Data @DynamicUpdate public class OrderMaster { /** * 订单id. */ @Id private String orderId; /** * 买家名字. */ private String buyerName; /** * 买家手机号. */ private String buyerPhone; /** * 买家地址. */ private String buyerAddress; /** * 买家微信Openid. */ private String buyerOpenid; /** * 订单总金额. */ private BigDecimal orderAmount; /** * 订单状态, 默认为0新下单. */ private Integer orderStatus = OrderStatusEnum.NEW.getCode(); /** * 支付状态, 默认为0未支付. */ private Integer payStatus = PayStatusEnum.WAIT.getCode(); /** * 创建时间. */ private Date createTime; /** * 更新时间. */ private Date updateTime; }
package com.xiong.sell.dataobject; import lombok.Data; import javax.persistence.Entity; import javax.persistence.Id; import java.math.BigDecimal; /** * @author Xiong YuSong * 2019/1/18 9:51 */ @Entity @Data public class OrderDetail { @Id private String detailId; /** * 订单id. */ private String orderId; /** * 商品id. */ private String productId; /** * 商品名称. */ private String productName; /** * 商品单价. */ private BigDecimal productPrice; /** * 商品数量. */ private Integer productQuantity; /** * 商品小图. */ private String productIcon; }
2.写OrderStatusEnum和PayStatusEnum两个枚举类,代码如下,已略
package com.xiong.sell.enums; import lombok.Getter; /** * @author Xiong YuSong * 2019/1/18 9:55 */ @Getter public enum OrderStatusEnum { /** * 新订单 */ NEW(0, "新订单"), /** * 完结 */ FINISHED(1, "完结"), /** * 已取消 */ CANCEL(2, "已取消"), ; private Integer code; private String message; OrderStatusEnum(Integer code, String message) { this.code = code; this.message = message; } }
package com.xiong.sell.enums; import lombok.Getter; /** * @author Xiong YuSong * 2019/1/18 9:53 */ @Getter public enum PayStatusEnum { /** * 等待支付 */ WAIT(0, "等待支付"), /** * 支付成功 */ SUCCESS(1, "支付成功"); private Integer code; private String message; PayStatusEnum(Integer code, String message) { this.code = code; this.message = message; } }
3.编写DAO层的两个类OrderMasterRepository和OrderDetailRepository两个类
package com.xiong.sell.repository; import com.xiong.sell.dataobject.OrderMaster; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; /** * @author Xiong YuSong * 2019/1/18 9:58 */ public interface OrderMasterRepository extends JpaRepository<OrderMaster,String> { /** * 分页显示,通过买家的openid查找他的订单信息 * @param buyerOpenid * @param pageable * @return */ Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable); }
package com.xiong.sell.repository; import com.xiong.sell.dataobject.OrderMaster; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; /** * @author Xiong YuSong * 2019/1/18 9:58 */ public interface OrderMasterRepository extends JpaRepository<OrderMaster,String> { /** * 分页显示,通过买家的openid查找他的订单信息 * @param buyerOpenid * @param pageable * @return */ Page<OrderMaster> findByBuyerOpenid(String buyerOpenid, Pageable pageable); }
4.对持久层两个类进行单元测试
Service层设计
1.创建OrderService接口,并且赋予六个方法,别忘记@Service标签
package com.xiong.sell.service; import com.xiong.sell.dto.OrderDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; /** * @author Xiong YuSong * 2019/1/18 13:15 */ @Service public interface OrderService { /** * 创建订单 * @param orderDTO * @return */ OrderDTO create(OrderDTO orderDTO); /** * 根据订单ID查询订单 * @param orderId * @return */ OrderDTO findOne(String orderId); /** * 通过买家openid查询他的所有订单,分页返回 * @param buyerOpenid * @param pageable * @return */ Page<OrderDTO> findList(String buyerOpenid, Pageable pageable); /** * 取消订单 * @param orderDTO * @return */ OrderDTO cancel(OrderDTO orderDTO); /** * 完结订单 * @param orderDTO * @return */ OrderDTO finish(OrderDTO orderDTO); /** * 支付订单 * @param orderDTO * @return */ OrderDTO paid(OrderDTO orderDTO); }
2.创建OrderDTO这个类(以前的项目中使用的VOviewobject来使用),这个类包含了orderMaster所有数据,还加入了List<OrderDetail>集合,因为是数据涉及到getset方法,所以添加@Data方法
package com.xiong.sell.dto; import com.xiong.sell.dataobject.OrderDetail; import com.xiong.sell.enums.OrderStatusEnum; import com.xiong.sell.enums.PayStatusEnum; import lombok.Data; import javax.persistence.Id; import java.math.BigDecimal; import java.util.Date; import java.util.List; /** * @author Xiong YuSong * 2019/1/18 13:23 */ @Data public class OrderDTO { /** * 订单id. */ private String orderId; /** * 买家名字. */ private String buyerName; /** * 买家手机号. */ private String buyerPhone; /** * 买家地址. */ private String buyerAddress; /** * 买家微信Openid. */ private String buyerOpenid; /** * 订单总金额. */ private BigDecimal orderAmount; /** * 订单状态, 默认为0新下单. */ private Integer orderStatus ; /** * 支付状态, 默认为0未支付. */ private Integer payStatus ; /** * 创建时间. */ private Date createTime; /** * 更新时间. */ private Date updateTime; private List<OrderDetail> orderDetailList; }
根据order_master和order_detail之间一对多的联系,本来应该在OrderMaster实体表中加入一个字段,进行一对多连接或者使用@Transient注解让jpa取消该字段与数据库映射,但是这里设计者为了不要太过混淆,于是创建了dto这个包(数据传输对象(DTO)(Data Transfer Object),是一种设计模式之间传输数据的软件应用系统。数据传输目标往往是数据访问对象从数据库中检索数据。数据传输对象与数据交互对象或数据访问对象之间的差异是一个以不具有任何行为除了存储和检索的数据(访问和存取器))
3.创建OrderServiceImpl类,实现OrderService接口,并且覆写方法,根据创建订单的api,所以我们这里知道了前台传过来的数据格式
创建订单 POST /sell/buyer/order/create 参数 name: "张三" phone: "18868822111" address: "慕课网总部" openid: "ew3euwhd7sjw9diwkq" //用户的微信openid items: [{ productId: "1423113435324", productQuantity: 2 //购买数量 }] 返回 { "code": 0, "msg": "成功", "data": { "orderId": "147283992738221" } }
别忘记注解@Service
package com.xiong.sell.service.impl; import com.xiong.sell.converter.OrderMaster2OrderDTOConverter; import com.xiong.sell.dataobject.OrderDetail; import com.xiong.sell.dataobject.OrderMaster; import com.xiong.sell.dataobject.ProductInfo; import com.xiong.sell.dto.CartDTO; import com.xiong.sell.dto.OrderDTO; import com.xiong.sell.enums.OrderStatusEnum; import com.xiong.sell.enums.PayStatusEnum; import com.xiong.sell.enums.ResultEnum; import com.xiong.sell.exception.SellException; import com.xiong.sell.repository.OrderDetailRepository; import com.xiong.sell.repository.OrderMasterRepository; import com.xiong.sell.service.OrderService; import com.xiong.sell.service.ProductInfoService; import com.xiong.sell.utils.KeyUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.transaction.Transactional; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; /** * @author Xiong YuSong * 2019/1/18 13:36 */ @Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired private ProductInfoService productInfoService; @Autowired private OrderDetailRepository orderDetailRepository; @Autowired private OrderMasterRepository orderMasterRepository; @Override @Transactional(rollbackOn = Exception.class) public OrderDTO create(OrderDTO orderDTO) { BigDecimal orderAmount = new BigDecimal(0); //生成orderID String orderId = KeyUtil.genUniqueKey(); //1.查询商品(这里只有商品编号和数量) for (OrderDetail orderDetail : orderDTO.getOrderDetailList()) { //根据Id查到商品之后 ProductInfo productInfo = productInfoService.findOne(orderDetail.getProductId()); if (productInfo == null) { throw new SellException(ResultEnum.PRODUCT_NOT_EXIST); } //2.计算订单总价 orderAmount = productInfo.getProductPrice() .multiply(new BigDecimal(orderDetail.getProductQuantity())) .add(orderAmount); //将订单详情放入数据库中,这里注意orderDetail里面的数据,时候是完整的,正确的 BeanUtils.copyProperties(productInfo, orderDetail); orderDetail.setDetailId(KeyUtil.genUniqueKey()); orderDetail.setOrderId(orderId); orderDetailRepository.save(orderDetail); } //3.写入订单数据库 OrderMaster orderMaster = new OrderMaster(); orderDTO.setOrderId(orderId); orderDTO.setOrderAmount(orderAmount); BeanUtils.copyProperties(orderDTO, orderMaster); orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode()); orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode()); orderMasterRepository.save(orderMaster); //4.扣库存 List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream().map(e -> new CartDTO(e.getProductId(), e.getProductQuantity())).collect(Collectors.toList()); productInfoService.decreaseStock(cartDTOList); return orderDTO; } @Override public OrderDTO findOne(String orderId) { Optional<OrderMaster> optional = orderMasterRepository.findById(orderId); if (!optional.isPresent()) { throw new SellException(ResultEnum.ORDER_NOT_EXIST); } OrderMaster orderMaster = optional.get(); List<OrderDetail> orderDetailList = orderDetailRepository.findByOrderId(orderId); if (CollectionUtils.isEmpty(orderDetailList)) { throw new SellException(ResultEnum.ORDERDETAIL_NOT_EXIST); } OrderDTO orderDTO = new OrderDTO(); BeanUtils.copyProperties(orderMaster, orderDTO); orderDTO.setOrderDetailList(orderDetailList); return orderDTO; } @Override public Page<OrderDTO> findList(String buyerOpenid, Pageable pageable) { Page<OrderMaster> orderMasterPage = orderMasterRepository.findByBuyerOpenid(buyerOpenid, pageable); List<OrderDTO> orderDTOList = OrderMaster2OrderDTOConverter.convert(orderMasterPage.getContent()); //PageImpl,泛型OrderDTO,需要参数为列表,pageable,以及总数 return new PageImpl<OrderDTO>(orderDTOList, pageable, orderMasterPage.getTotalElements()); } @Override @Transactional(rollbackOn = Exception.class) public OrderDTO cancel(OrderDTO orderDTO) { OrderMaster orderMaster = new OrderMaster(); //判断订单状态 if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) { log.error("【取消订单】订单状态不正确, orderId={}, orderStatus={}", orderDTO.getOrderId(), orderDTO.getOrderStatus()); throw new SellException(ResultEnum.ORDER_STATUS_ERROR); } //修改订单状态 orderDTO.setOrderStatus(OrderStatusEnum.CANCEL.getCode()); BeanUtils.copyProperties(orderDTO, orderMaster); OrderMaster updateResult = orderMasterRepository.save(orderMaster); if (updateResult == null) { log.error("【取消订单】更新失败, orderMaster={}", orderMaster); throw new SellException(ResultEnum.ORDER_UPDATE_FAIL); } //返回库存 if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) { log.error("【取消订单】订单中无商品详情, orderDTO={}", orderDTO); throw new SellException(ResultEnum.ORDER_DETAIL_EMPTY); } List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream() .map(e -> new CartDTO(e.getProductId(), e.getProductQuantity())) .collect(Collectors.toList()); productInfoService.increaseStock(cartDTOList); //如果已支付, 需要退款 if (orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS.getCode())) { //TODO } return orderDTO; } @Override @Transactional(rollbackOn = Exception.class) public OrderDTO finish(OrderDTO orderDTO) { //判断订单状态 if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) { log.error("【完结订单】订单状态不正确, orderId={}, orderStatus={}", orderDTO.getOrderId(), orderDTO.getOrderStatus()); throw new SellException(ResultEnum.ORDER_STATUS_ERROR); } //修改订单状态 orderDTO.setOrderStatus(OrderStatusEnum.FINISHED.getCode()); OrderMaster orderMaster = new OrderMaster(); BeanUtils.copyProperties(orderDTO, orderMaster); OrderMaster updateResult = orderMasterRepository.save(orderMaster); if (updateResult == null) { log.error("【完结订单】更新失败, orderMaster={}", orderMaster); throw new SellException(ResultEnum.ORDER_UPDATE_FAIL); } return orderDTO; } @Override @Transactional(rollbackOn = Exception.class) public OrderDTO paid(OrderDTO orderDTO) { //判断订单状态 if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) { log.error("【订单支付完成】订单状态不正确, orderId={}, orderStatus={}", orderDTO.getOrderId(), orderDTO.getOrderStatus()); throw new SellException(ResultEnum.ORDER_STATUS_ERROR); } //判断支付状态 if (!orderDTO.getPayStatus().equals(PayStatusEnum.WAIT.getCode())) { log.error("【订单支付完成】订单支付状态不正确, orderDTO={}", orderDTO); throw new SellException(ResultEnum.ORDER_PAY_STATUS_ERROR); } //修改支付状态 orderDTO.setPayStatus(PayStatusEnum.SUCCESS.getCode()); OrderMaster orderMaster = new OrderMaster(); BeanUtils.copyProperties(orderDTO, orderMaster); OrderMaster updateResult = orderMasterRepository.save(orderMaster); if (updateResult == null) { log.error("【订单支付完成】更新失败, orderMaster={}", orderMaster); throw new SellException(ResultEnum.ORDER_UPDATE_FAIL); } return orderDTO; } }
4.在OrderServiceImpl类中涉及到了自定义异常,所以我们在这里创建了一个SellException类放入Exception包中
package com.xiong.sell.exception; import com.xiong.sell.enums.ResultEnum; /** * @author Xiong YuSong * 2019/1/18 13:41 */ public class SellException extends RuntimeException { private Integer code; public SellException(ResultEnum resultEnum) { super(resultEnum.getMessage()); this.code = resultEnum.getCode(); } }
5.因为异常有很多类型,所以我们建立一个枚举类ResultEnum,在这个枚举类中放入所有的能使用的异常然后进行调用,枚举类可以访问数据但是不能修改,所以我们使用@Getter标签
package com.xiong.sell.enums; import lombok.Getter; /** * @author Xiong YuSong * 2019/1/18 13:43 */ @Getter public enum ResultEnum { /** * 商品不存在 */ Product_not_exist(10,"商品不存在"), ; private Integer code; private String message; ResultEnum(Integer code, String message) { this.code = code; this.message = message; } }
6.这里涉及到唯一订单ID的设计,所以创建一个工具类KeyUtil放入Utils包下,由于在创建唯一ID的时候一定会涉及到多线程重复的情况,所以要加上synchronized
package com.xiong.sell.utils; import java.util.Random; /** * @author Xiong YuSong * 2019/1/18 14:08 */ public class KeyUtil { /** * 生成唯一的Key * @return */ public static synchronized String genUniqueKey() { Random random = new Random(); Integer a = random.nextInt(900000) + 10000; return System.currentTimeMillis() + String.valueOf(a); } }
7.这里涉及到商品表中的库存的增加减少问题,所以我们返回ProductService中去完成这两个功能,这里添加了几个异常类,后面的异常类全部不说明
@Override @Transactional(rollbackOn = Exception.class) public void increaseStock(List<CartDTO> cartDTOList) { for (CartDTO cartDTO: cartDTOList) { ProductInfo productInfo = findOne(cartDTO.getProductId()); if (productInfo == null) { throw new SellException(ResultEnum.PRODUCT_NOT_EXIST); } Integer result = productInfo.getProductStock() + cartDTO.getProductQuantity(); productInfo.setProductStock(result); productInfoRepository.save(productInfo); } } @Override @Transactional(rollbackOn = Exception.class) public void decreaseStock(List<CartDTO> cartDTOList) { for (CartDTO cartDTO : cartDTOList) { ProductInfo productInfo = findOne(cartDTO.getProductId()); if (productInfo == null) { throw new SellException(ResultEnum.PRODUCT_NOT_EXIST); } Integer result = productInfo.getProductStock() - cartDTO.getProductQuantity(); if (result < 0) { throw new SellException(ResultEnum.PRODUCT_STOCK_ERROR); } productInfo.setProductStock(result); productInfoRepository.save(productInfo); } }
8.这里加减库存涉及到了两个数据,即商品ID以及数量,所以我们可以创建一个新的类来存放这两个数据(CartDTO),别忘记@Data方法
package com.xiong.sell.dto; import lombok.Data; /** * @author Xiong YuSong * 2019/1/18 14:33 */ @Data public class CartDTO { /** * 商品id. */ private String productId; /** * 商品数量. */ private Integer productQuantity; }
9.这里书写了一个转换器OrderMaster2OrderDTOConverter放在Converter包中
package com.xiong.sell.converter; import com.xiong.sell.dataobject.OrderMaster; import com.xiong.sell.dto.OrderDTO; import org.springframework.beans.BeanUtils; import java.util.List; import java.util.stream.Collectors; /** * @author Xiong YuSong * 2019/1/18 15:19 */ public class OrderMaster2OrderDTOConverter { public static OrderDTO convert(OrderMaster orderMaster) { OrderDTO orderDTO = new OrderDTO(); BeanUtils.copyProperties(orderMaster, orderDTO); return orderDTO; } public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) { return orderMasterList.stream().map(e -> convert(e) ).collect(Collectors.toList()); } }