章節感悟:
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()); } }