【Bug周刊】Vol.7

前言

最近开发中遇到很多相同的问题,下意识去翻自己的历史记录,但又没能快速定位。我的trilium一直用来记录自己的周报和相关教程,对于常见的bug和修复方案也找不到合适的地方,只能穿插在日报的历史中,随时间沉没。无意间翻到子舒的奇趣周刊,Bug周刊也由此而生。

Boot项目连接多个MQ

问题描述

最近公司因为业务需求,需要系统A向系统B推送对应的补充字段,原系统A在nacos中配置了对应的MQ,并在系统运行时自动初始化。config包中并没有对应的 FactoryBean

yml
1
2
3
4
5
6
7
8
# 系统A common-config.yml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
virtual-host: /
password: guest

解决方案

1️⃣ nacos中配置MQ信息

在对应模块中添加对应的MQ信息,不同的MQ配置对应不同的系统。

yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 系统A business模块 business.yml

# 系统B
rabbitmqB:
host: 127.0.0.2
port: 5672
username: guest
virtual-host: /
password: guest
# 默认MQ配置 系统A
rabbitmqDefault:
host: 127.0.0.1
port: 5672
username: guest
virtual-host: /
password: guest

2️⃣ 在系统A Business模块中添加对应的FactoryBean

因为系统A的Business模块中需要连接两个MQ,所以需要添加两个FactoryBean。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Configuration
public class RabbitMQConfig {

@Value("${rabbitmqDefault.port}")
private int port;

@Value("${rabbitmqDefault.host}")
private String host;

@Value("${rabbitmqDefault.username}")
private String username;

@Value("${rabbitmqDefault.password}")
private String password;

@Value("${rabbitmqDefault.virtual-host}")
private String virtualHost;

@Primary // 声明连接时以default为主,避免启动boot时报错 “我去找一个mq连接,但事实上有两个”
@Bean(name = "defaultConnectionFactory")
public ConnectionFactory defaultConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost(virtualHost); // 如果正式环境指明了MQ的命名空间,就需要说明,未指明则默认 / 根目录
factory.setPublisherConfirms(true); // 启用发布者确认
factory.setPublisherReturns(true); // 启用发布者返回
return factory;
}

@Primary
@Bean(name = "defaultRabbitTemplate")
public RabbitTemplate defaultRabbitTemplate(@Qualifier("defaultConnectionFactory") ConnectionFactory defaultConnectionFactory) {
return new RabbitTemplate(defaultConnectionFactory);
}

@Primary
@Bean(name = "defaultListenerContainerFactory")
public SimpleRabbitListenerContainerFactory defaultListenerContainerFactory(@Qualifier("defaultConnectionFactory") ConnectionFactory defaultConnectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// configurer.configure(factory, defaultConnectionFactory);
factory.setConnectionFactory(defaultConnectionFactory);
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 设置为手动确认, 避免mq交换机 192.168.0.1(xxx) 数字过大炸掉
return factory;
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Configuration
public class RabbitMQBConfig {

@Value("${rabbitmqB.port}")
private int port;

@Value("${rabbitmqB.host}")
private String host;

@Value("${rabbitmqB.username}")
private String username;

@Value("${rabbitmqB.password}")
private String password;

@Value("${rabbitmqB.virtual-host}")
private String virtualHost;

@Bean(name = "BConnectionFactory")
public ConnectionFactory BConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setUsername(username);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setPublisherConfirms(true); // 启用发布者确认
factory.setPublisherReturns(true); // 启用发布者返回
return factory;
}

@Bean(name = "BRabbitTemplate")
public RabbitTemplate BRabbitTemplate(@Qualifier("BConnectionFactory")ConnectionFactory BConnectionFactory) {
return new RabbitTemplate(BConnectionFactory);
}

@Bean(name = "BListenerContainerFactory")
public SimpleRabbitListenerContainerFactory BListenerContainerFactory(@Qualifier("BConnectionFactory")ConnectionFactory BConnectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(BConnectionFactory);
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 设置为手动确认
return factory;
}
}

3️⃣ 声明原系统A的生产者(defaultRabbitTemplate)

java
1
2
3
@Autowired
@Qualifier("defaultRabbitTemplate") // 说明当前使用的生产template 用的是默认的
private RabbitTemplate rabbitTemplate;

4️⃣ 修改原系统A的消费者

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
@Component
@Slf4j
public class RabbitMqListenerConfig {

@RabbitListener(queuesToDeclare = @Queue(RabbitLogConstant.TEST_QUEUE), containerFactory = "defaultListenerContainerFactory") // 用默认消费者完成系统A的日志存储
public void process(BusinessLog businessLog, Message message, Channel channel) throws IOException {
log.info("{}队列收到消息:{}", RabbitLogConstant.TEST_QUEUE, JSONUtil.toJsonStr(businessLog));
try {
// 处理系统A的日志
} catch (Exception e) {
log.error("{}队列消息处理异常,{}", RabbitLogConstant.TEST_QUEUE, e.getMessage());
}
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}

5️⃣ 在系统A Bussiness模块实现类中声明推送至系统B的生产者

java
1
2
3
4
5
@Autowired
@Qualifier("BRabbitTemplate") // 说明当前使用的生产者用的是 系统B的,需要注意,如果同时在一个.java中使用,两者不能重名
private RabbitTemplate rabbitTemplateB;

rabbitTemplateB.convertAndSend(RabbitLogConstant.BUSINESS_QUEUE, JSONUtil.toJsonStr(businessData));

不能仅在系统A的Business模块中声明新的FactoryBean,不声明原默认的FactoryBean,否则新的MQ配置会覆盖系统A的默认MQ配置。

也就是说,原系统A的Business模块在此情况下,所有的生产都会推送到系统B,消费者也监听不到原MQ。B系统又没有对应的消费者,就会导致消息堆积。

Pageable分页size上限

问题描述

在开发信息台账时,导出需要全部的数据,在最初设计接口时,使用Pageable分页,打算导出时直接设置size为极值。

但实际测试发现接口只返回了2000条数据,查看Pageable的源码发现,默认的size上限为2000。

解决方案

在当前模块 config 包中添加对应的配置,重写Pageable的size上限。运行boot时,会自动加载。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.test.services.currentpackage.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class PageConfig extends WebMvcConfigurerAdapter {

private static final int PMP_MAX_PAGE_SIZE = 100000; // 设置为你需要的size上限

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();

resolver.setMaxPageSize(PMP_MAX_PAGE_SIZE);
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
}

结构体收发读取失败

问题描述

接上面MQ的解决方案,A、B系统接口联调时,A系统发送数据,B系统接收数据。但B系统从MQ中读取数据为空,使用的是相同的 Dto。

解决方案

经确认数据无误,于是怀疑是Dto的属性问题,@Data、@Getter、@Setter注解均已添加,但仍未解决。最终添加反序列化操作成功。

java
1
2
rabbitTemplateB.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplateB.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE_DATA_PUSH_B, key, dataPushBDto);