一、服务提供者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 {
List<ProductCategoryVo> getUpAllProductCategoryVo();
List<ProductInfoVo> getListByProductIds(List<String> productIds);
void decreaseStock(List<OrderCar> orderCars);
}
2、ProductService实现上面两个接口
- 扣库存:扣库存成功以后还要使用rabbitmq将该商品的基本信息(剩余库存量)异步写入productExchange中,方便order端查看
@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);
amqpTemplate.convertAndSend("productExchange", "productInfo", FastJsonUtil.objectToJSON(productInfoOutputList));
}
@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) {
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 {
@PostMapping("/productClient/providerList")
public List<ProductInfoVo> getProducts(List<String> productIds);
@PostMapping("/productClient/decreaseStock")
public void decreaseProductStock(List<OrderCar> orderCars);
}
2、创建订单接口
public interface OrderService {
String createOrder(OrderDto orderDto);
}
3、方法实现
@Autowired
private Product2OrderFeign product2OrderFeign;
@Autowired
private OrderMasterMapper orderMasterMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@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());
List<OrderCar> orderCars = orderDto.getItems();
List<String> productIds = new ArrayList<>();
for (OrderCar orderCar : orderCars) {
productIds.add(orderCar.getProductId());
}
List<ProductInfoVo> products = product2OrderFeign.getProducts(productIds);
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 = new OrderDetail();
BeanUtils.copyProperties(product, orderDetail);
orderDetail.setDetailId(BaseDataKeyUtils.keyByCurrentTime());
orderDetail.setOrderId(orderId);
orderDetail.setDeleteStatus(DeleteStatusEnum.ON_USE.getCode());
orderDetail.setProductQuantity(orderCar.getProductQuantity());
orderDetailMapper.insertSelective(orderDetail);
}
}
}
product2OrderFeign.decreaseProductStock(orderCars);
orderMaster.setOrderAmount(orderAmount);
orderMasterMapper.insertSelective(orderMaster);
return orderMaster.getOrderId();
}
4、order端接收创建订单之后商品的rabbitmq异步通知
@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());
redisTemplate.opsForValue().set(String.format(productStockKey,productInfoOutput.getProductName(),productInfoOutput.getProductId()),
String.valueOf(productInfoOutput.getProductStock()));
}
}
}
2.2、完结订单
1、接口方法
OrderMasterPojo finishOrder(String orderId);
2、实现finishOrder方法
@Override
@Transactional
public OrderMasterPojo finishOrder(String orderId) {
OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
if (orderMaster == null){
throw new MyException(ResultEnum.ORDER_NOT_EXIST);
}
if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
}
if (orderMaster.getPayStatus() != OrderPayStatusEnum.PAID.getCode()){
throw new MyException(ResultEnum.ORDER_PAID_ERROR);
}
orderMaster.setOrderStatus(OrderStatusEnum.FINISH.getCode());
orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
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);
}
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、接口方法
OrderMasterPojo cancelOrder(String orderId);
2、实现cancelOrder方法
@Override
public OrderMasterPojo cancelOrder(String orderId) {
OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
if (orderMaster == null){
throw new MyException(ResultEnum.ORDER_NOT_EXIST);
}
if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
}
if (orderMaster.getPayStatus() == OrderPayStatusEnum.PAID.getCode()){
throw new MyException(ResultEnum.ORDER_PAID_ERROR);
}
orderMaster.setOrderStatus(OrderStatusEnum.CANCEL.getCode());
orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
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);
}
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、支付订单接口方法
OrderMasterPojo paid(String orderId);
2、实现paid方法
@Override
public OrderMasterPojo paid(String orderId) {
OrderMaster orderMaster = orderMasterMapper.selectByPrimaryKey(orderId);
if (orderMaster == null){
throw new MyException(ResultEnum.ORDER_NOT_EXIST);
}
if(orderMaster.getOrderStatus() != OrderStatusEnum.NEW.getCode()){
throw new MyException(ResultEnum.ORDER_STATUS_NOT_NEW);
}
if (orderMaster.getPayStatus() == OrderPayStatusEnum.PAID.getCode()){
throw new MyException(ResultEnum.ORDER_PAID_ERROR);
}
orderMaster.setPayStatus(OrderPayStatusEnum.PAID.getCode());
orderMasterMapper.updateByPrimaryKeySelective(orderMaster);
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);
}
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
requestVolumeThreshold: 10
sleepWindowInMilliseconds: 10000
errorThresholdPercentage: 60
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
getProducts:
execution:
isolation:
thread:
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/