通过 Spring Cloud Stream 访问 RabbitMQ

Spring Cloud Stream 对 RabbitMQ 的支持

依赖

  • 引入 Spring Cloud - spring-cloud-starter-stream-rabbit 依赖
  • 底层使用了 Spring Boot - spring-boot-starter-amqp

配置

  • spring.cloud.stream.rabbit.binder.*
    binder 抽象的配置
  • spring.cloud.stream.rabbit.bindings.<channelName>.consumer.*
    binder 的配置 需要指定绑定的q的名字
  • spring.rabbitmq.*
    与rabbitmq服务相关的配置

通过 Docker 启动 RabbitMQ

官方指引

  • https://hub.docker.com/_/rabbitmq

获取镜像

  • docker pull rabbitmq
  • docker pull rabbitmq:3.7-management
    一般 拉 docker pull rabbitmq:3.7-management 其中包括 rabbitmq 还有它相关的管理界面

运行 RabbitMQ 镜像

  • docker run --name rabbitmq -d -p 5672:5672 -p 15672:15672
    -e RABBITMQ_DEFAULT_USER=spring -e RABBITMQ_DEFAULT_PASS=spring
    rabbitmq:3.7-management
    5672:RabbitMQ 的服务端口
    15672:RabbitMQ 的管理界面
    用户名和密码指定为spring

实例代码

由两部分组成:rabbitmq-barista-service 和 rabbitmq-waiter-service
rabbitmq-barista-service
在这里插入图片描述

@Component
@Slf4j
@Transactional
public class OrderListener {
    @Autowired
    private CoffeeOrderRepository orderRepository;
    @Autowired
    @Qualifier(Waiter.FINISHED_ORDERS)//注入这个Channel
    private MessageChannel finishedOrdersMessageChannel;
    @Value("${order.barista-prefix}${random.uuid}")
    private String barista;

    @StreamListener(Waiter.NEW_ORDERS)
    public void processNewOrder(Long id) {//收到消息之后 从数据库取出一个coffeeorder
        CoffeeOrder o = orderRepository.getOne(id);
        if (o == null) {
            log.warn("Order id {} is NOT valid.", id);//打印一个错误日志
            return;
        }
        log.info("Receive a new Order {}. Waiter: {}. Customer: {}",
                id, o.getWaiter(), o.getCustomer());//打印一个接收日志
        o.setState(OrderState.BREWED);//更改状态
        o.setBarista(barista);//设置barista
        orderRepository.save(o);//保存
        log.info("Order {} is READY.", id);
        finishedOrdersMessageChannel.send(MessageBuilder.withPayload(id).build());//发送出去
    }

}
public interface Waiter {
    String NEW_ORDERS = "newOrders";
    String FINISHED_ORDERS = "finishedOrders";

    @Input(NEW_ORDERS)
    //监听
    SubscribableChannel newOrders();

    @Output(FINISHED_ORDERS)
    //发送
    MessageChannel finishedOrders();
}
@Entity
@Table(name = "T_ORDER")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CoffeeOrder {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String customer;
    private String waiter;
    private String barista;
    @Enumerated
    @Column(nullable = false)
    private OrderState state;
    @Column(updatable = false)
    @CreationTimestamp
    private Date createTime;
    @UpdateTimestamp
    private Date updateTime;
}
public enum OrderState {
    INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
}
public interface CoffeeOrderRepository extends JpaRepository<CoffeeOrder, Long> {
}

@EnableJpaRepositories
@SpringBootApplication
@EnableBinding(Waiter.class)
public class BaristaServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(BaristaServiceApplication.class, args);
	}

}

application.properties

spring.application.name=barista-service

order.barista-prefix=springbucks-

server.port=0

spring.output.ansi.enabled=ALWAYS
#NEVER:禁用ANSI-colored输出(默认项)
#DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项)
#ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

spring.datasource.url=jdbc:mysql://localhost/springbucks
spring.datasource.username=root
spring.datasource.password=123456

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=spring
spring.rabbitmq.password=spring

spring.cloud.stream.bindings.newOrders.group=barista-service
#监听的newOrders队列  如果启动多个barista-service它的一个消息就会被当中的一个实例收到

rabbitmq-waiter-service
在这里插入图片描述

需要修改的代码

CoffeeOrderController

@RestController
@RequestMapping("/order")
@Slf4j
public class CoffeeOrderController {
    @Autowired
    private CoffeeOrderService orderService;
    @Autowired
    private CoffeeService coffeeService;
    private RateLimiter rateLimiter;

    public CoffeeOrderController(RateLimiterRegistry rateLimiterRegistry) {
        rateLimiter = rateLimiterRegistry.rateLimiter("order");
    }

    @GetMapping("/{id}")
    public CoffeeOrder getOrder(@PathVariable("id") Long id) {
        CoffeeOrder order = null;
        try {
            order = rateLimiter.executeSupplier(() -> orderService.get(id));
            log.info("Get Order: {}", order);
        } catch(RequestNotPermitted e) {
            log.warn("Request Not Permitted! {}", e.getMessage());
        }
        return order;
    }

    @PostMapping(path = "/", consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseStatus(HttpStatus.CREATED)
    @io.github.resilience4j.ratelimiter.annotation.RateLimiter(name = "order")
    public CoffeeOrder create(@RequestBody NewOrderRequest newOrder) {
        log.info("Receive new Order {}", newOrder);
        Coffee[] coffeeList = coffeeService.getCoffeeByName(newOrder.getItems())
                .toArray(new Coffee[] {});
        return orderService.createOrder(newOrder.getCustomer(), coffeeList);
    }

    @PutMapping("/{id}")
    public CoffeeOrder updateState(@PathVariable("id") Long id,
                                   @RequestBody OrderStateRequest orderState) {
        //订单状态的更新
        log.info("Update order state: {}", orderState);
        CoffeeOrder order = orderService.get(id);
        orderService.updateState(order, orderState.getState());
        return order;
    }
}

Barista

public interface Barista {
    String NEW_ORDERS = "newOrders";
    String FINISHED_ORDERS = "finishedOrders";

    @Input
    //订阅的消息
    SubscribableChannel finishedOrders();

    @Output
    //接收的消息
    MessageChannel newOrders();
}

OrderListener

@Component
@Slf4j
public class OrderListener {
    @StreamListener(Barista.FINISHED_ORDERS)//监听FINISHED_ORDERS这个消息
    public void listenFinishedOrders(Long id) {
        log.info("We've finished an order [{}].", id);
    }//是Barista发出的
}

CoffeeOrderService

@Service
@Transactional
@Slf4j
public class CoffeeOrderService implements MeterBinder {
    @Autowired
    private CoffeeOrderRepository orderRepository;
    @Autowired
    private OrderProperties orderProperties;
    @Autowired
    private Barista barista;

    private String waiterId = UUID.randomUUID().toString();

    private Counter orderCounter = null;

    public CoffeeOrder get(Long id) {
        return orderRepository.getOne(id);
    }

    public CoffeeOrder createOrder(String customer, Coffee...coffee) {
        CoffeeOrder order = CoffeeOrder.builder()
                .customer(customer)
                .items(new ArrayList<>(Arrays.asList(coffee)))
                .discount(orderProperties.getDiscount())
                .total(calcTotal(coffee))
                .state(OrderState.INIT)
                .waiter(orderProperties.getWaiterPrefix() + waiterId)
                .build();
        CoffeeOrder saved = orderRepository.save(order);
        log.info("New Order: {}", saved);
        orderCounter.increment();
        return saved;
    }

    public boolean updateState(CoffeeOrder order, OrderState state) {
        if (order == null) {
            log.warn("Can not find order.");
            return false;
        }
        if (state.compareTo(order.getState()) <= 0) {
            //判断State  State只能往一个方向去状态的控制  从未支付到支付
            log.warn("Wrong State order: {}, {}", state, order.getState());
            return false;
        }
        order.setState(state);
        orderRepository.save(order);
        log.info("Updated Order: {}", order);
        if (state == OrderState.PAID) {
            // 有返回值,如果要关注发送结果,则判断返回值
            // 一般消息体不会这么简单
            // 如果是支付消息 我们就做一个消息的发送
            barista.newOrders().send(MessageBuilder.withPayload(order.getId()).build());//传入订单的id
        }
        return true;
    }

    @Override
    public void bindTo(MeterRegistry meterRegistry) {
        this.orderCounter = meterRegistry.counter("order.count");
    }

    private Money calcTotal(Coffee...coffee) {
        List<Money> items = Stream.of(coffee).map(c -> c.getPrice())
                .collect(Collectors.toList());
        return Money.total(items).multipliedBy(orderProperties.getDiscount())
                .dividedBy(100, RoundingMode.HALF_UP);
    }
}

WaiterServiceApplication

@SpringBootApplication
@EnableJpaRepositories
@EnableCaching
@EnableDiscoveryClient
@EnableBinding(Barista.class)
//告诉我的应用程序,去EnableBinding哪些接口
public class WaiterServiceApplication implements WebMvcConfigurer {

	public static void main(String[] args) {
		SpringApplication.run(WaiterServiceApplication.class, args);
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(new PerformanceInteceptor())
				.addPathPatterns("/coffee/**").addPathPatterns("/order/**");
	}

	@Bean
	public Hibernate5Module hibernate5Module() {
		return new Hibernate5Module();
	}

	@Bean
	public Jackson2ObjectMapperBuilderCustomizer jacksonBuilderCustomizer() {
		return builder -> {
			builder.indentOutput(true);
			builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
		};
	}
}

application.properties

spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.show_sql=false
spring.jpa.properties.hibernate.format_sql=false

# 运行过一次后,如果不想清空数据库就注释掉下面这行 不管是什么数据库
spring.datasource.initialization-mode=always

management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always

info.app.author=DigitalSonic
info.app.encoding=@project.build.sourceEncoding@

server.port=8080

spring.datasource.url=jdbc:mysql://localhost/springbucks
spring.datasource.username=root
spring.datasource.password=123456

order.discount=95

resilience4j.ratelimiter.limiters.coffee.limit-for-period=5
resilience4j.ratelimiter.limiters.coffee.limit-refresh-period-in-millis=30000
resilience4j.ratelimiter.limiters.coffee.timeout-in-millis=5000
resilience4j.ratelimiter.limiters.coffee.subscribe-for-events=true
resilience4j.ratelimiter.limiters.coffee.register-health-indicator=true

resilience4j.ratelimiter.limiters.order.limit-for-period=3
resilience4j.ratelimiter.limiters.order.limit-refresh-period-in-millis=30000
resilience4j.ratelimiter.limiters.order.timeout-in-millis=1000
resilience4j.ratelimiter.limiters.order.subscribe-for-events=true
resilience4j.ratelimiter.limiters.order.register-health-indicator=true

#配置rabbitmq
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=spring
spring.rabbitmq.password=spring


spring.cloud.stream.bindings.finishedOrders.group=waiter-service
#监听的 finishedOrders 队列 如果启动多个waiter-service它的一个消息就会被当中的一个实例收到

另外 在CoffeeOrder中 增加了private String barista;属性

结果分析

输入 http://localhost:15672/ 进入RabbitMQ
在这里插入图片描述
进入 之后 我们可以看到 finishedOrders 和 newOrders 两个队列
在这里插入图片描述
finishedOrders 绑定了 waiter-service

输入 http://localhost:8080/actuator/bindings
可以 看到 对应的 binding 的信息
在这里插入图片描述
使用 postman 进行测试
创建一个订单 显示 未支付
在这里插入图片描述

进行 支付操作 状态变更完毕
在这里插入图片描述

这个时候 查询订单 会发现 订单已经完成 制作完毕
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值