订单服务业务逻辑及异步消息通知(八)

一、服务提供者product

  • springboot:2.1.8.RELEASE
  • springcloud:Greenwich.SR3
  • 本节主要讲的是后台业务逻辑,springcloud相关知识点请参考下面四篇博客:
  • springcloud核心技术之服务注册与发现(二)
  • springcloud核心技术之应用通信(三)
  • springcloud核心技术之全局配置(四)
  • springcloud核心技术之spring cloud bus(五)

1.1、ProductService新增两个接口

1、ProductService接口
  • 通过一组商品id查询商品列表商品扣库存这两个接口是需要提供给Order服务调用的
/**
 * 商品业务接口:主要关注于具体与商品相关的业务逻辑的编写,忽略数据库操作
 */
public interface ProductService {

    /**
     * 查询所有在架商品
     *
     * @return
     */
    List<ProductCategoryVo> getUpAllProductCategoryVo();

    /**
     * 通过一组商品id查询商品列表
     * @param productIds
     * @return
     */
    List<ProductInfoVo> getListByProductIds(List<String> productIds);

    /**
     * 商品扣库存
     */
    void decreaseStock(List<OrderCar> orderCars);

}
2、ProductService实现上面两个接口
  • 扣库存:扣库存成功以后还要使用rabbitmq将该商品的基本信息(剩余库存量)异步写入productExchange中,方便order端查看
/**
     * 通过一组商品id查询商品列表
     *
     * @param productIds
     * @return
     */
    @Override
    public List<ProductInfoVo> getListByProductIds(List<String> productIds) {
        List<ProductInfo> productInfoList = iProductService.findAllByProductIds(productIds);
        if (productInfoList.size() == 0) {
            throw new MyException(ResultEnum.UP_PRODUCT_LIST_NOT_EXIST);
        }
        List<ProductInfoVo> productInfoVoList = new ArrayList<>();
        for (ProductInfo productInfo : productInfoList) {
            ProductInfoVo productInfoVo = new ProductInfoVo();
            BeanUtils.copyProperties(productInfo, productInfoVo);
            productInfoVoList.add(productInfoVo);
        }
        return productInfoVoList;
    }

    /**
     * 商品扣库存
     */
    @Override
    public void decreaseStock(List<OrderCar> orderCars) {
        //執行扣庫存工作
        List<ProductInfoOutput> productInfoOutputList = getOutputsByDecrease(orderCars);
        //mq发布更新商品最新剩余的库存量
        amqpTemplate.convertAndSend("productExchange", "productInfo", FastJsonUtil.objectToJSON(productInfoOutputList));
    }

    /**
     * 通信服务:获取商品扣库存后的商品信息
     * @param orderCars
     * @return
     */
    @Transactional //事务就是要么全部成功(提交),要么全部失败(回滚),就算成功一部分也要(回滚)
    public List<ProductInfoOutput> getOutputsByDecrease(List<OrderCar> orderCars){
        List<ProductInfoOutput> productInfoOutputs = new ArrayList<>();
        for (OrderCar orderCar : orderCars) {
            //查询商品是否存在
            Optional<ProductInfo> productInfoOptional = productInfoRepository.findById(orderCar.getProductId());
            if (!productInfoOptional.isPresent()) {
                throw new MyException("订单中商品信息不存在");
            }
            ProductInfo productInfo = productInfoOptional.get();
            //检测商品库存是否足够
            int newProductStock = productInfo.getProductStock() - orderCar.getProductQuantity();
            if (newProductStock < 0) {
                throw new MyException(productInfo.getProductName()+"库存不足");
            }
            //将新的库存存入商品表中
            productInfo.setProductStock(newProductStock);
            productInfoRepository.save(productInfo);
            ProductInfoOutput productInfoOutput = new ProductInfoOutput();
            BeanUtils.copyProperties(productInfo,productInfoOutput);
            productInfoOutputs.add(productInfoOutput);
        }
        return  productInfoOutputs;
    }

1.2、编写product服务通信接口

@RestController
@RequestMapping("/productClient")
public class ProductClientController {

    @Autowired
    private ProductService productService;

    @PostMapping("/providerList")
    public List<ProductInfoVo> providerProductList(@RequestBody List<String> productIds) {
        //调用时给一个2s的线程睡眠时间
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return productService.getListByProductIds(productIds);
    }

    @PostMapping("/decreaseStock")
    public void decreaseStock(@RequestBody List<OrderCar> orderCars) {
        productService.decreaseStock(orderCars);
    }
}

二、服务消费者order

2.1、创建订单

1、编写服务通信接口获取product提供的controller方法
/**
 * 订单订阅商品服务通信接口:用于订单模块接受商品模块提供的服务
 */
@FeignClient("product")
public interface Product2OrderFeign {

    /**
     * 商品服务:获取商品信息列表
     * @param productIds
     * @return
     */
    @PostMapping("/productClient/providerList")
    public List<ProductInfoVo> getProducts(List<String> productIds);

    /**
     * 商品服务:扣库存
     * @param orderCars
     */
    @PostMapping("/productClient/decreaseStock")
    public void decreaseProductStock(List<OrderCar> orderCars);

}
2、创建订单接口
/**
 * 订单业务接口
 */
public interface OrderService {

    /**
     * 创建订单
     *
     * @param orderDto
     * @return
     */
    String createOrder(OrderDto orderDto);

}
3、方法实现
   @Autowired
    private Product2OrderFeign product2OrderFeign;

    @Autowired
    private OrderMasterMapper orderMasterMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    /**
     * 创建订单
     * 1、参数校验(判断订单是否有商品)
     * 2、根据id查询相应的商品信息(调用product-server的服务)
     * 3、计算订单总金额
     * 4、扣库存(调用product-server的服务)
     * 5、订单入库:OrderMaster入库,OrderDetail入库
     */
    @Override
    public String createOrder(OrderDto orderDto) {
        if (orderDto.getItems().size() == 0) {
            throw new MyException("创建订单请先选择商品");
        }
        OrderMaster orderMaster = OrderConvert.orderDto2OrderMaster(orderDto);
        String orderId = BaseDataKeyUtils.keyByCurrentTime();
        orderMaster.setOrderId(orderId);
        orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
        orderMaster.setPayStatus(OrderPayStatusEnum.UN_PAID.getCode());
        orderMaster.setDeleteStatus(DeleteStatusEnum.ON_USE.getCode());
        //1、根据商品id查询商品列表
        List<OrderCar> orderCars = orderDto.getItems();
        List<String> productIds = new ArrayList<>();
        for (OrderCar orderCar : orderCars) {
            productIds.add(orderCar.getProductId());
        }
        List<ProductInfoVo> products = product2OrderFeign.getProducts(productIds);
        //2、计算订单总金额
        BigDecimal orderAmount = new BigDecimal(BigInteger.ZERO);
        for (OrderCar orderCar : orderCars) {
            for (ProductInfoVo product : products) {
                if (orderCar.getProductId().equals(product.getProductId())) {
                    orderAmount = orderAmount.add(product.getProductPrice().multiply(new BigDecimal(orderCar.getProductQuantity())));
                    //订单入库:OrderDetail
                    OrderDetail orderDetail = new OrderDetail();
                    BeanUtils.copyProperties(product, orderDetail);
                    orderDetail.setDetailId(BaseDataKeyUtils.keyByCurrentTime());
                    orderDetail.setOrderId(orderId);
                    orderDetail.setDeleteStatus(DeleteStatusEnum.ON_USE.getCode());
                    orderDetail.setProductQuantity(orderCar.getProductQuantity());
                    //todo 可以做成批量插入
                    orderDetailMapper.insertSelective(orderDetail);
                }
            }
        }
        //3、扣库存
        product2OrderFeign.decreaseProductStock(orderCars);
        //4、订单入库:OrderMaster
        orderMaster.setOrderAmount(orderAmount);
        orderMasterMapper.insertSelective(orderMaster);
        return orderMaster.getOrderId();
    }
4、order端接收创建订单之后商品的rabbitmq异步通知
  • 将商品库存打印出来并且写入redis缓存
@Slf4j
@Component
public class ProductInfoReceiver {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private static final String  productStockKey = "商品_%s_%s库存";

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("productQueue"),
            key = "productInfo",
            exchange = @Exchange("productExchange")
    ))
    public void processStockChange(String message){
        List<ProductInfoOutput> productInfoOutputs = FastJsonUtil.toList(message, ProductInfoOutput.class);
        for (ProductInfoOutput productInfoOutput : productInfoOutputs) {
            log.info("商品_{}_{}的库存剩余 = {}",productInfoOutput.getProductName(),productInfoOutput.getProductId(),productInfoOutput.getProductStock());
            //将商品剩余库存存入redis中
            redisTemplate.opsForValue().set(String.format(productStockKey,productInfoOutput.getProductName(),productInfoOutput.getProductId()),
                    String.valueOf(productInfoOutput.getProductStock()));
        }
    }

}

2.2、完结订单

1、接口方法
 /**
     * 完结订单
     * @return
     */
    OrderMasterPojo finishOrder(String orderId);
2、实现finishOrder方法
@Override
    @Transactional
    public OrderMasterPojo finishOrder(String orderId) {
        //1、查询此订单是否存在
        OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
        if (orderMaster == null){
            throw  new MyException(ResultEnum.ORDER_NOT_EXIST);
        }
        //2、查询此订单是否为新订单
        if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
            throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
        }
        //3、查询此订单是否已支付
        if (orderMaster.getPayStatus() != OrderPayStatusEnum.PAID.getCode()){
            //如果当前订单不是已经支付状态,不能够执行完结操作
            throw new MyException(ResultEnum.ORDER_PAID_ERROR);
        }

        //4、修改订单完结状态
        orderMaster.setOrderStatus(OrderStatusEnum.FINISH.getCode());
        orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
        //5、查询订单详情信息
        Example example = new Example(OrderDetail.class);
        example.createCriteria().andEqualTo("orderId",orderMaster.getOrderId());
        List<OrderDetail> orderDetailList = orderDetailMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(orderDetailList)){
            throw new MyException(ResultEnum.ORDER_DETAIL_MSG_ERROR);
        }
        //6、拼接返回消息体
        OrderMasterPojo orderMasterPojo = new OrderMasterPojo();
        List<OrderDetailPojo> orderDetailPojoList = new ArrayList<>();
        for (OrderDetail orderDetail : orderDetailList) {
            OrderDetailPojo orderDetailPojo = new OrderDetailPojo();
            BeanUtils.copyProperties(orderDetail,orderDetailPojo);
            orderDetailPojoList.add(orderDetailPojo);
        }
        BeanUtils.copyProperties(orderMaster,orderMasterPojo);
        orderMasterPojo.setOrderDetailList(orderDetailList);

        return orderMasterPojo;
    }

2.3、取消订单

1、接口方法
 /**
     * 取消订单
     * @param orderId
     * @return
     */
    OrderMasterPojo cancelOrder(String orderId);
2、实现cancelOrder方法
@Override
    public OrderMasterPojo cancelOrder(String orderId) {
        //1、查询此订单是否存在
        OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
        if (orderMaster == null){
            throw  new MyException(ResultEnum.ORDER_NOT_EXIST);
        }
        //2、查询此订单是否为新订单
        if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
            throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
        }
        //3、查询此订单是否已支付
        if (orderMaster.getPayStatus() == OrderPayStatusEnum.PAID.getCode()){
            //如果当前订单是已经支付状态,不能够执行取消操作
            throw new MyException(ResultEnum.ORDER_PAID_ERROR);
        }

        //4、修改订单为已取消状态
        orderMaster.setOrderStatus(OrderStatusEnum.CANCEL.getCode());
        orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
        //5、查询订单详情信息
        Example example = new Example(OrderDetail.class);
        example.createCriteria().andEqualTo("orderId",orderMaster.getOrderId());
        List<OrderDetail> orderDetailList = orderDetailMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(orderDetailList)){
            throw new MyException(ResultEnum.ORDER_DETAIL_MSG_ERROR);
        }
        //6、拼接返回消息体
        OrderMasterPojo orderMasterPojo = new OrderMasterPojo();
        List<OrderDetailPojo> orderDetailPojoList = new ArrayList<>();
        for (OrderDetail orderDetail : orderDetailList) {
            OrderDetailPojo orderDetailPojo = new OrderDetailPojo();
            BeanUtils.copyProperties(orderDetail,orderDetailPojo);
            orderDetailPojoList.add(orderDetailPojo);
        }
        BeanUtils.copyProperties(orderMaster,orderMasterPojo);
        orderMasterPojo.setOrderDetailList(orderDetailList);

        return orderMasterPojo;
    }

2.4、支付订单

1、支付订单接口方法
/**
     * 支付订单
     * @param orderId
     * @return
     */
    OrderMasterPojo paid(String orderId);
2、实现paid方法
 @Override
    public OrderMasterPojo paid(String orderId) {
        //1、查询此订单是否存在
        OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
        if (orderMaster == null){
            throw  new MyException(ResultEnum.ORDER_NOT_EXIST);
        }
        //2、查询此订单是否为新订单
        if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
            throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
        }
        //3、查询此订单是否已支付
        if (orderMaster.getPayStatus() == OrderPayStatusEnum.PAID.getCode()){
            //如果当前订单是已经支付状态,不能够执行支付操作
            throw new MyException(ResultEnum.ORDER_PAID_ERROR);
        }
        //todo 微信支付和支付宝支付的具体逻辑:需要商户账号才行

        //4、修改订单已经支付状态
        orderMaster.setPayStatus(OrderPayStatusEnum.PAID.getCode());
        orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
        //5、查询订单详情信息
        Example example = new Example(OrderDetail.class);
        example.createCriteria().andEqualTo("orderId",orderMaster.getOrderId());
        List<OrderDetail> orderDetailList = orderDetailMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(orderDetailList)){
            throw new MyException(ResultEnum.ORDER_DETAIL_MSG_ERROR);
        }
        //6、拼接返回消息体
        OrderMasterPojo orderMasterPojo = new OrderMasterPojo();
        List<OrderDetailPojo> orderDetailPojoList = new ArrayList<>();
        for (OrderDetail orderDetail : orderDetailList) {
            OrderDetailPojo orderDetailPojo = new OrderDetailPojo();
            BeanUtils.copyProperties(orderDetail,orderDetailPojo);
            orderDetailPojoList.add(orderDetailPojo);
        }
        BeanUtils.copyProperties(orderMaster,orderMasterPojo);
        orderMasterPojo.setOrderDetailList(orderDetailList);

        return orderMasterPojo;
    }
2.5、OrderController
@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/buyer/create")
    public ResultVo createOrder(@RequestBody OrderDto orderDto) {
        ResultVo resultVo = new ResultVo();
        try {
            String orderId = orderService.createOrder(orderDto);
            resultVo = ResultVOUtils.success(orderId);
        } catch (MyException e) {
            resultVo = ResultVOUtils.error(e.getCode(), e.getMessage());
        }
        return resultVo;
    }

    @PostMapping("/seller/finish")
    public ResultVo finishOrder(@RequestParam("orderId") String orderId){
        ResultVo resultVo = new ResultVo();
        try {
            OrderMasterPojo orderMasterPojo = orderService.finishOrder(orderId);
            resultVo = ResultVOUtils.success(orderMasterPojo);
        } catch (MyException e) {
            resultVo = ResultVOUtils.error(e.getCode(), e.getMessage());
        }
        return resultVo;
    }

    @PostMapping("/buyer/cancel")
    public ResultVo cancelOrder(@RequestParam("orderId") String orderId){
        ResultVo resultVo = new ResultVo();
        try {
            OrderMasterPojo orderMasterPojo = orderService.cancelOrder(orderId);
            resultVo = ResultVOUtils.success(orderMasterPojo);
        } catch (MyException e) {
            resultVo = ResultVOUtils.error(e.getCode(), e.getMessage());
        }
        return resultVo;
    }

    @PostMapping("/buyer/paid")
    public ResultVo paid(@RequestParam("orderId") String orderId){
        ResultVo resultVo = new ResultVo();
        try {
            OrderMasterPojo orderMasterPojo = orderService.paid(orderId);
            resultVo = ResultVOUtils.success(orderMasterPojo);
        } catch (MyException e) {
            resultVo = ResultVOUtils.error(e.getCode(), e.getMessage());
        }
        return resultVo;
    }


}

三、Order和Product配置

1.1、本地application配置

1、product服务
server:
  port: 8083
spring:
  application:
    name: product
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: local
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
vcap:
  application:
    instance_index: ${spring.cloud.config.profile}
2、order服务
server:
  port: 8082
spring:
  application:
    name: order
  cloud:
    config:
      discovery:
        enabled: true
        service-id: CONFIG
      profile: dev
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 3
vcap:
  application:
    instance_index: ${spring.cloud.config.profile}
hystrix:
  command:
    default:
      # 断路器相关配置
      circuitBreaker:
        # 是否开启断路器
        enabled: true
        # 滑动窗口的大小,默认为20
        requestVolumeThreshold: 10
        # 过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
        sleepWindowInMilliseconds: 10000
        # 错误率,默认50%
        errorThresholdPercentage: 60
      execution:
        isolation:
          thread:
            # 配置超时时间:单位ms;默认是1000ms
            timeoutInMilliseconds: 3000
    getProducts:
      execution:
        isolation:
          thread:
            # 配置超时时间:单位ms
            timeoutInMilliseconds: 1200
management:
  endpoints:
    web:
      exposure:
        include: 'hystrix.stream'

1.2、git远程配置

1、order服务
spring:
  devtools:
    remote:
      restart:
        enabled: true
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mall-order?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true& serverTimezone=UTC
    username: root
    password: 123456
  jpa:
    show-sql: true

  freemarker:
    cache: false
mybatis:
  mapper-locations: classpath:mappers/*.xml

logging:
  level:
    com:
      mall:
        dao: debug

user:
  name: zhangsan
  age: 250
2、product服务
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
spring:
  devtools:
    remote:
      restart:
        enabled: true

  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mall-product?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true& serverTimezone=UTC
    username: root
    password: 123456
  jpa:
    show-sql: true

  freemarker:
    cache: false
user:
  name: lisi
  age: 260

1.3、config-server配置

1、本地配置
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          uri: https://github.com/achuanxiang/amall-config.git
          search-paths: local
          default-label: master
          username: [email protected]
          password: xxc13762694142
          basedir: E:\study_project\cloud_projects\a-mall\git-config
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
management:
  endpoints:
    web:
      exposure:
        include: "*"
server:
  port: 8081
2、git远程仓库配置
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

你可能感兴趣的:(springcloud)