@llryiop/avatar-boot-cli 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/exam-question-generate-api.md +163 -0
- package/package.json +1 -1
- package/src/prompts.js +3 -3
- package/src/transform.js +1 -1
- package/templates/.claude/skills/avatar-boot-starter-feign/README.md +243 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/SKILL.md +47 -219
- package/templates/.claude/skills/avatar-boot-starter-feign/references//345/212/237/350/203/275/350/257/246/350/247/243.md +65 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/references//345/277/253/351/200/237/346/216/245/345/205/245/346/214/207/345/215/227.md +75 -0
- package/templates/.claude/skills/avatar-boot-starter-feign/references//351/205/215/347/275/256/345/217/202/350/200/203.md +70 -0
- package/templates/.claude/skills/avatar-boot-starter-job/README.md +437 -0
- package/templates/.claude/skills/avatar-boot-starter-job/SKILL.md +35 -414
- package/templates/.claude/skills/avatar-boot-starter-job/references//345/270/270/350/247/201/351/227/256/351/242/230.md +55 -0
- package/templates/.claude/skills/avatar-boot-starter-job/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +124 -0
- package/templates/.claude/skills/avatar-boot-starter-job/references//347/233/221/346/216/247/346/214/207/346/240/207.md +72 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/README.md +580 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/SKILL.md +36 -560
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//346/234/200/344/275/263/345/256/236/350/267/265.md +43 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//346/240/270/345/277/203/345/212/237/350/203/275.md +117 -0
- package/templates/.claude/skills/avatar-boot-starter-kafka/references//351/205/215/347/275/256/345/217/202/350/200/203.md +54 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/README.md +572 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/SKILL.md +40 -550
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//345/256/236/344/275/223/344/270/216/345/212/237/350/203/275.md +96 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/346/225/260/346/215/256/346/272/220.md +91 -0
- package/templates/.claude/skills/avatar-boot-starter-mysql/references//351/253/230/347/272/247/347/211/271/346/200/247/344/270/216/351/205/215/347/275/256.md +59 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/README.md +901 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/SKILL.md +40 -879
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//345/212/237/350/203/275/344/275/277/347/224/250.md +134 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +96 -0
- package/templates/.claude/skills/avatar-boot-starter-nacos/references//346/225/205/351/232/234/346/216/222/346/237/245.md +64 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/README.md +594 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/SKILL.md +52 -570
- package/templates/.claude/skills/avatar-boot-starter-oss/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +77 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/references//346/240/270/345/277/203/345/212/237/350/203/275.md +94 -0
- package/templates/.claude/skills/avatar-boot-starter-oss/references//350/247/204/350/214/203/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +61 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/README.md +586 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/SKILL.md +42 -566
- package/templates/.claude/skills/avatar-boot-starter-redis/references//345/277/253/351/200/237/346/216/245/345/205/245/344/270/216/351/205/215/347/275/256.md +78 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/references//346/225/260/346/215/256/346/223/215/344/275/234.md +111 -0
- package/templates/.claude/skills/avatar-boot-starter-redis/references//351/253/230/347/272/247/345/212/237/350/203/275.md +90 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/README.md +662 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/SKILL.md +48 -640
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//346/240/270/345/277/203/345/212/237/350/203/275.md +101 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//351/205/215/347/275/256/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.md +44 -0
- package/templates/.claude/skills/avatar-boot-starter-rocketmq/references//351/253/230/347/272/247/347/211/271/346/200/247.md +71 -0
- package/templates/.claude/skills/avatar-boot-starter-web/README.md +1007 -0
- package/templates/.claude/skills/avatar-boot-starter-web/SKILL.md +150 -1003
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-LogInfo/346/263/250/350/247/243.md +75 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/345/205/250/345/261/200/345/274/202/345/270/270/345/244/204/347/220/206.md +90 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/346/214/207/346/240/207/347/233/221/346/216/247.md +74 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/346/227/245/345/277/227/344/275/223/347/263/273.md +73 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/212/237/350/203/275-/350/257/267/346/261/202/344/270/212/344/270/213/346/226/207.md +77 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//345/277/253/351/200/237/346/216/245/345/205/245/346/214/207/345/215/227.md +52 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//346/263/250/346/204/217/344/272/213/351/241/271.md +68 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//350/207/252/345/256/232/344/271/211/346/211/251/345/261/225/346/214/207/345/215/227.md +107 -0
- package/templates/.claude/skills/avatar-boot-starter-web/references//351/205/215/347/275/256/345/217/202/350/200/203.md +107 -0
- package/templates/.claude/skills/crud-generator/SKILL.md +133 -64
- package/templates/.claude/skills/database-design/README.md +207 -0
- package/templates/.claude/skills/database-design/SKILL.md +469 -82
- package/templates/.claude/skills/database-design/references//345/221/275/345/220/215/350/247/204/350/214/203.md +232 -0
- package/templates/.claude/skills/database-design/references//345/255/227/346/256/265/347/261/273/345/236/213/350/247/204/350/214/203.md +400 -0
- package/templates/.claude/skills/database-design/references//347/264/242/345/274/225/350/247/204/350/214/203.md +506 -0
- package/templates/avatar-scaffold-api/pom.xml +0 -5
- package/templates/avatar-scaffold-service/pom.xml +25 -87
- package/templates/avatar-scaffold-service/src/main/resources/application-dev.yaml +3 -5
- package/templates/avatar-scaffold-service/src/main/resources/application-local.yaml +2 -2
- package/templates/pom.xml +9 -18
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: avatar-boot-starter-rocketmq
|
|
3
|
+
description: 当涉及 RocketMQ、消息队列、延迟消息、事务消息、顺序消息 相关功能时使用此技能 - 为 Spring Boot 3.5.3 应用提供基于 rocketmq-spring-boot-starter 2.3.1 的消息队列能力,包含生产者/消费者自动配置、延迟消息、事务消息、顺序消费。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Avatar Boot 的 RocketMQ 消息队列集成模块,基于 rocketmq-spring-boot-starter 2.3.1 提供开箱即用的消息生产和消费能力。
|
|
7
|
+
|
|
8
|
+
## 功能特性
|
|
9
|
+
|
|
10
|
+
- ✅ **自动配置** - 基于 Spring Boot 自动配置机制,自动装配 RocketMQTemplate
|
|
11
|
+
- ✅ **注解驱动** - 使用 @RocketMQMessageListener 注解快速定义消费者
|
|
12
|
+
- ✅ **延迟消息** - 支持 18 个等级的延迟消息投递
|
|
13
|
+
- ✅ **事务消息** - 支持分布式事务消息,保证本地事务与消息发送的一致性
|
|
14
|
+
- ✅ **顺序消息** - 支持分区有序和全局有序消息消费
|
|
15
|
+
- ✅ **Tag 过滤** - 支持基于 Tag 的消息过滤,灵活路由消息
|
|
16
|
+
- ✅ **重试机制** - 内置消费重试策略,支持自定义重试次数
|
|
17
|
+
|
|
18
|
+
## 快速开始
|
|
19
|
+
|
|
20
|
+
### 1. 添加依赖
|
|
21
|
+
|
|
22
|
+
在项目的 `pom.xml` 中添加依赖:
|
|
23
|
+
|
|
24
|
+
```xml
|
|
25
|
+
<dependency>
|
|
26
|
+
<groupId>com.iflytek.avatar.boot</groupId>
|
|
27
|
+
<artifactId>avatar-boot-starter-rocketmq</artifactId>
|
|
28
|
+
</dependency>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 版本由 Avatar Boot BOM 统一管理,无需指定 version。内置 rocketmq-spring-boot-starter 2.3.1。
|
|
32
|
+
|
|
33
|
+
### 2. 配置文件
|
|
34
|
+
|
|
35
|
+
在 `application.yml` 中添加 RocketMQ 配置:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
rocketmq:
|
|
39
|
+
name-server: localhost:9876 # NameServer 地址
|
|
40
|
+
producer:
|
|
41
|
+
group: ${spring.application.name}-producer # 生产者组名
|
|
42
|
+
send-message-timeout: 3000 # 发送超时时间(毫秒)
|
|
43
|
+
retry-times-when-send-failed: 2 # 同步发送失败重试次数
|
|
44
|
+
retry-times-when-send-async-failed: 2 # 异步发送失败重试次数
|
|
45
|
+
max-message-size: 4194304 # 最大消息大小(4MB)
|
|
46
|
+
compress-message-body-threshold: 4096 # 消息压缩阈值(4KB)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. 发送第一条消息
|
|
50
|
+
|
|
51
|
+
```java
|
|
52
|
+
package com.example.producer;
|
|
53
|
+
|
|
54
|
+
import lombok.RequiredArgsConstructor;
|
|
55
|
+
import lombok.extern.slf4j.Slf4j;
|
|
56
|
+
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
57
|
+
import org.springframework.stereotype.Service;
|
|
58
|
+
|
|
59
|
+
@Slf4j
|
|
60
|
+
@Service
|
|
61
|
+
@RequiredArgsConstructor
|
|
62
|
+
public class SimpleProducer {
|
|
63
|
+
|
|
64
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
65
|
+
|
|
66
|
+
public void sendMessage(String topic, String message) {
|
|
67
|
+
rocketMQTemplate.convertAndSend(topic, message);
|
|
68
|
+
log.info("消息发送成功: topic={}, message={}", topic, message);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 4. 消费第一条消息
|
|
74
|
+
|
|
75
|
+
```java
|
|
76
|
+
package com.example.consumer;
|
|
77
|
+
|
|
78
|
+
import lombok.extern.slf4j.Slf4j;
|
|
79
|
+
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
|
80
|
+
import org.apache.rocketmq.spring.core.RocketMQListener;
|
|
81
|
+
import org.springframework.stereotype.Component;
|
|
82
|
+
|
|
83
|
+
@Slf4j
|
|
84
|
+
@Component
|
|
85
|
+
@RocketMQMessageListener(
|
|
86
|
+
topic = "test-topic",
|
|
87
|
+
consumerGroup = "test-consumer-group"
|
|
88
|
+
)
|
|
89
|
+
public class SimpleConsumer implements RocketMQListener<String> {
|
|
90
|
+
|
|
91
|
+
@Override
|
|
92
|
+
public void onMessage(String message) {
|
|
93
|
+
log.info("收到消息: {}", message);
|
|
94
|
+
// 处理业务逻辑
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 生产者详解
|
|
100
|
+
|
|
101
|
+
### 同步发送
|
|
102
|
+
|
|
103
|
+
```java
|
|
104
|
+
@Service
|
|
105
|
+
@RequiredArgsConstructor
|
|
106
|
+
public class OrderProducer {
|
|
107
|
+
|
|
108
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 同步发送(等待 Broker 确认)
|
|
112
|
+
*/
|
|
113
|
+
public void syncSend(OrderEvent event) {
|
|
114
|
+
SendResult result = rocketMQTemplate.syncSend("order-topic", event);
|
|
115
|
+
log.info("同步发送结果: msgId={}, status={}", result.getMsgId(), result.getSendStatus());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 同步发送(带 Tag)
|
|
120
|
+
*/
|
|
121
|
+
public void syncSendWithTag(OrderEvent event, String tag) {
|
|
122
|
+
// destination 格式: topic:tag
|
|
123
|
+
SendResult result = rocketMQTemplate.syncSend("order-topic:" + tag, event);
|
|
124
|
+
log.info("发送结果: msgId={}, tag={}", result.getMsgId(), tag);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 同步发送(带超时和延迟级别)
|
|
129
|
+
*/
|
|
130
|
+
public void syncSendWithTimeout(OrderEvent event) {
|
|
131
|
+
SendResult result = rocketMQTemplate.syncSend("order-topic",
|
|
132
|
+
MessageBuilder.withPayload(event).build(),
|
|
133
|
+
3000); // 超时 3 秒
|
|
134
|
+
log.info("发送结果: {}", result);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 异步发送
|
|
140
|
+
|
|
141
|
+
```java
|
|
142
|
+
@Service
|
|
143
|
+
@RequiredArgsConstructor
|
|
144
|
+
public class AsyncProducer {
|
|
145
|
+
|
|
146
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 异步发送(不阻塞当前线程)
|
|
150
|
+
*/
|
|
151
|
+
public void asyncSend(OrderEvent event) {
|
|
152
|
+
rocketMQTemplate.asyncSend("order-topic", event, new SendCallback() {
|
|
153
|
+
@Override
|
|
154
|
+
public void onSuccess(SendResult sendResult) {
|
|
155
|
+
log.info("异步发送成功: msgId={}", sendResult.getMsgId());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Override
|
|
159
|
+
public void onException(Throwable e) {
|
|
160
|
+
log.error("异步发送失败: event={}", event, e);
|
|
161
|
+
// 补偿处理:入库重试、告警
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 单向发送(不需要确认,最快但不保证送达)
|
|
168
|
+
*/
|
|
169
|
+
public void onewaySend(LogEvent event) {
|
|
170
|
+
rocketMQTemplate.sendOneWay("log-topic", event);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 批量发送
|
|
176
|
+
|
|
177
|
+
```java
|
|
178
|
+
@Service
|
|
179
|
+
@RequiredArgsConstructor
|
|
180
|
+
public class BatchProducer {
|
|
181
|
+
|
|
182
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 批量发送消息
|
|
186
|
+
*/
|
|
187
|
+
public void batchSend(List<OrderEvent> events) {
|
|
188
|
+
List<Message<OrderEvent>> messages = events.stream()
|
|
189
|
+
.map(event -> MessageBuilder.withPayload(event).build())
|
|
190
|
+
.collect(Collectors.toList());
|
|
191
|
+
|
|
192
|
+
SendResult result = rocketMQTemplate.syncSend("order-topic", messages, 5000);
|
|
193
|
+
log.info("批量发送结果: {}", result);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 消费者详解
|
|
199
|
+
|
|
200
|
+
### 基本消费
|
|
201
|
+
|
|
202
|
+
```java
|
|
203
|
+
@Slf4j
|
|
204
|
+
@Component
|
|
205
|
+
@RocketMQMessageListener(
|
|
206
|
+
topic = "order-topic",
|
|
207
|
+
consumerGroup = "order-consumer-group",
|
|
208
|
+
consumeMode = ConsumeMode.CONCURRENTLY, // 并发消费(默认)
|
|
209
|
+
messageModel = MessageModel.CLUSTERING, // 集群模式(默认)
|
|
210
|
+
consumeThreadNumber = 20 // 消费线程数
|
|
211
|
+
)
|
|
212
|
+
public class OrderConsumer implements RocketMQListener<OrderEvent> {
|
|
213
|
+
|
|
214
|
+
@Override
|
|
215
|
+
public void onMessage(OrderEvent event) {
|
|
216
|
+
log.info("收到订单事件: orderNo={}", event.getOrderNo());
|
|
217
|
+
// 处理业务逻辑
|
|
218
|
+
// 抛出异常会触发重试
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 带 Tag 过滤的消费
|
|
224
|
+
|
|
225
|
+
```java
|
|
226
|
+
@Slf4j
|
|
227
|
+
@Component
|
|
228
|
+
@RocketMQMessageListener(
|
|
229
|
+
topic = "order-topic",
|
|
230
|
+
consumerGroup = "order-create-group",
|
|
231
|
+
selectorType = SelectorType.TAG,
|
|
232
|
+
selectorExpression = "CREATE || UPDATE" // 只消费 CREATE 和 UPDATE 标签的消息
|
|
233
|
+
)
|
|
234
|
+
public class OrderCreateConsumer implements RocketMQListener<OrderEvent> {
|
|
235
|
+
|
|
236
|
+
@Override
|
|
237
|
+
public void onMessage(OrderEvent event) {
|
|
238
|
+
log.info("收到订单创建/更新事件: {}", event);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 消费原始 MessageExt
|
|
244
|
+
|
|
245
|
+
```java
|
|
246
|
+
@Slf4j
|
|
247
|
+
@Component
|
|
248
|
+
@RocketMQMessageListener(
|
|
249
|
+
topic = "order-topic",
|
|
250
|
+
consumerGroup = "order-ext-group"
|
|
251
|
+
)
|
|
252
|
+
public class OrderExtConsumer implements RocketMQListener<MessageExt> {
|
|
253
|
+
|
|
254
|
+
@Override
|
|
255
|
+
public void onMessage(MessageExt messageExt) {
|
|
256
|
+
String msgId = messageExt.getMsgId();
|
|
257
|
+
int reconsumeTimes = messageExt.getReconsumeTimes();
|
|
258
|
+
String body = new String(messageExt.getBody(), StandardCharsets.UTF_8);
|
|
259
|
+
|
|
260
|
+
log.info("收到消息: msgId={}, 重试次数={}, body={}", msgId, reconsumeTimes, body);
|
|
261
|
+
|
|
262
|
+
if (reconsumeTimes >= 3) {
|
|
263
|
+
// 超过重试次数,记录到数据库人工处理
|
|
264
|
+
log.error("消息重试超过 3 次,进入人工处理: msgId={}", msgId);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 处理业务逻辑
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## 延迟消息
|
|
274
|
+
|
|
275
|
+
RocketMQ 支持 18 个延迟级别:
|
|
276
|
+
|
|
277
|
+
| 级别 | 延迟时间 | 级别 | 延迟时间 |
|
|
278
|
+
|------|---------|------|---------|
|
|
279
|
+
| 1 | 1s | 10 | 6min |
|
|
280
|
+
| 2 | 5s | 11 | 7min |
|
|
281
|
+
| 3 | 10s | 12 | 8min |
|
|
282
|
+
| 4 | 30s | 13 | 9min |
|
|
283
|
+
| 5 | 1min | 14 | 10min |
|
|
284
|
+
| 6 | 2min | 15 | 20min |
|
|
285
|
+
| 7 | 3min | 16 | 30min |
|
|
286
|
+
| 8 | 4min | 17 | 1h |
|
|
287
|
+
| 9 | 5min | 18 | 2h |
|
|
288
|
+
|
|
289
|
+
### 发送延迟消息
|
|
290
|
+
|
|
291
|
+
```java
|
|
292
|
+
@Service
|
|
293
|
+
@RequiredArgsConstructor
|
|
294
|
+
public class DelayMessageProducer {
|
|
295
|
+
|
|
296
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* 发送延迟消息
|
|
300
|
+
* @param delayLevel 延迟级别(1-18)
|
|
301
|
+
*/
|
|
302
|
+
public void sendDelayMessage(String topic, Object payload, int delayLevel) {
|
|
303
|
+
Message<?> message = MessageBuilder.withPayload(payload).build();
|
|
304
|
+
// syncSend(destination, message, timeout, delayLevel)
|
|
305
|
+
SendResult result = rocketMQTemplate.syncSend(topic, message, 3000, delayLevel);
|
|
306
|
+
log.info("延迟消息发送成功: msgId={}, delayLevel={}", result.getMsgId(), delayLevel);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* 订单超时取消:30 分钟后检查(延迟级别 16)
|
|
311
|
+
*/
|
|
312
|
+
public void sendOrderTimeoutCheck(String orderNo) {
|
|
313
|
+
OrderTimeoutEvent event = new OrderTimeoutEvent(orderNo, LocalDateTime.now());
|
|
314
|
+
Message<?> message = MessageBuilder.withPayload(event).build();
|
|
315
|
+
rocketMQTemplate.syncSend("order-timeout-topic", message, 3000, 16);
|
|
316
|
+
log.info("订单超时检查消息已发送: orderNo={}, 30 分钟后触发", orderNo);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 消费延迟消息
|
|
322
|
+
|
|
323
|
+
```java
|
|
324
|
+
@Slf4j
|
|
325
|
+
@Component
|
|
326
|
+
@RocketMQMessageListener(
|
|
327
|
+
topic = "order-timeout-topic",
|
|
328
|
+
consumerGroup = "order-timeout-group"
|
|
329
|
+
)
|
|
330
|
+
public class OrderTimeoutConsumer implements RocketMQListener<OrderTimeoutEvent> {
|
|
331
|
+
|
|
332
|
+
@Autowired
|
|
333
|
+
private OrderService orderService;
|
|
334
|
+
|
|
335
|
+
@Override
|
|
336
|
+
public void onMessage(OrderTimeoutEvent event) {
|
|
337
|
+
log.info("收到订单超时检查: orderNo={}", event.getOrderNo());
|
|
338
|
+
// 检查订单是否已支付,未支付则取消
|
|
339
|
+
orderService.cancelIfUnpaid(event.getOrderNo());
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## 事务消息
|
|
345
|
+
|
|
346
|
+
### 事务消息流程
|
|
347
|
+
|
|
348
|
+
1. 生产者发送半事务消息(Half Message)到 Broker
|
|
349
|
+
2. Broker 存储成功后回调本地事务执行
|
|
350
|
+
3. 本地事务执行成功,提交消息;失败,回滚消息
|
|
351
|
+
4. Broker 定期回查未确认的事务消息状态
|
|
352
|
+
|
|
353
|
+
### 发送事务消息
|
|
354
|
+
|
|
355
|
+
```java
|
|
356
|
+
@Service
|
|
357
|
+
@RequiredArgsConstructor
|
|
358
|
+
public class TransactionalProducer {
|
|
359
|
+
|
|
360
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* 发送事务消息
|
|
364
|
+
*/
|
|
365
|
+
public void sendTransactionalMessage(OrderEvent event) {
|
|
366
|
+
Message<OrderEvent> message = MessageBuilder
|
|
367
|
+
.withPayload(event)
|
|
368
|
+
.setHeader("orderNo", event.getOrderNo())
|
|
369
|
+
.build();
|
|
370
|
+
|
|
371
|
+
// 发送事务消息
|
|
372
|
+
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
|
|
373
|
+
"order-tx-topic", message, event);
|
|
374
|
+
|
|
375
|
+
log.info("事务消息发送结果: msgId={}, localTxState={}",
|
|
376
|
+
result.getMsgId(), result.getLocalTransactionState());
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 实现本地事务监听器
|
|
382
|
+
|
|
383
|
+
```java
|
|
384
|
+
package com.example.listener;
|
|
385
|
+
|
|
386
|
+
import com.example.event.OrderEvent;
|
|
387
|
+
import com.example.service.OrderService;
|
|
388
|
+
import lombok.RequiredArgsConstructor;
|
|
389
|
+
import lombok.extern.slf4j.Slf4j;
|
|
390
|
+
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
|
|
391
|
+
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
|
|
392
|
+
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
|
|
393
|
+
import org.springframework.messaging.Message;
|
|
394
|
+
import org.springframework.stereotype.Component;
|
|
395
|
+
|
|
396
|
+
@Slf4j
|
|
397
|
+
@Component
|
|
398
|
+
@RocketMQTransactionListener
|
|
399
|
+
@RequiredArgsConstructor
|
|
400
|
+
public class OrderTransactionListener implements RocketMQLocalTransactionListener {
|
|
401
|
+
|
|
402
|
+
private final OrderService orderService;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 执行本地事务
|
|
406
|
+
* 半事务消息发送成功后回调
|
|
407
|
+
*/
|
|
408
|
+
@Override
|
|
409
|
+
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
|
|
410
|
+
OrderEvent event = (OrderEvent) arg;
|
|
411
|
+
try {
|
|
412
|
+
log.info("执行本地事务: orderNo={}", event.getOrderNo());
|
|
413
|
+
// 执行本地数据库操作
|
|
414
|
+
orderService.createOrderLocal(event);
|
|
415
|
+
return RocketMQLocalTransactionState.COMMIT; // 提交消息
|
|
416
|
+
} catch (Exception e) {
|
|
417
|
+
log.error("本地事务执行失败: orderNo={}", event.getOrderNo(), e);
|
|
418
|
+
return RocketMQLocalTransactionState.ROLLBACK; // 回滚消息
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 事务回查
|
|
424
|
+
* Broker 未收到确认时定期回查
|
|
425
|
+
*/
|
|
426
|
+
@Override
|
|
427
|
+
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
|
|
428
|
+
String orderNo = (String) msg.getHeaders().get("orderNo");
|
|
429
|
+
log.info("事务回查: orderNo={}", orderNo);
|
|
430
|
+
|
|
431
|
+
// 查询本地事务是否已执行
|
|
432
|
+
boolean exists = orderService.existsByOrderNo(orderNo);
|
|
433
|
+
if (exists) {
|
|
434
|
+
return RocketMQLocalTransactionState.COMMIT;
|
|
435
|
+
}
|
|
436
|
+
return RocketMQLocalTransactionState.UNKNOWN; // 继续回查
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## 顺序消息
|
|
442
|
+
|
|
443
|
+
### 发送顺序消息
|
|
444
|
+
|
|
445
|
+
```java
|
|
446
|
+
@Service
|
|
447
|
+
@RequiredArgsConstructor
|
|
448
|
+
public class OrderlyProducer {
|
|
449
|
+
|
|
450
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* 发送顺序消息
|
|
454
|
+
* 相同 hashKey 的消息发送到同一队列,保证顺序
|
|
455
|
+
*/
|
|
456
|
+
public void sendOrderly(String topic, Object payload, String hashKey) {
|
|
457
|
+
SendResult result = rocketMQTemplate.syncSendOrderly(topic, payload, hashKey);
|
|
458
|
+
log.info("顺序消息发送成功: msgId={}, queue={}", result.getMsgId(),
|
|
459
|
+
result.getMessageQueue().getQueueId());
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* 订单状态变更:同一订单的状态变更消息保证顺序
|
|
464
|
+
*/
|
|
465
|
+
public void sendOrderStatusChange(String orderNo, String status) {
|
|
466
|
+
OrderStatusEvent event = new OrderStatusEvent(orderNo, status, LocalDateTime.now());
|
|
467
|
+
// 使用 orderNo 作为 hashKey,保证同一订单消息有序
|
|
468
|
+
rocketMQTemplate.syncSendOrderly("order-status-topic", event, orderNo);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 顺序消费
|
|
474
|
+
|
|
475
|
+
```java
|
|
476
|
+
@Slf4j
|
|
477
|
+
@Component
|
|
478
|
+
@RocketMQMessageListener(
|
|
479
|
+
topic = "order-status-topic",
|
|
480
|
+
consumerGroup = "order-status-group",
|
|
481
|
+
consumeMode = ConsumeMode.ORDERLY // 顺序消费模式
|
|
482
|
+
)
|
|
483
|
+
public class OrderStatusConsumer implements RocketMQListener<OrderStatusEvent> {
|
|
484
|
+
|
|
485
|
+
@Override
|
|
486
|
+
public void onMessage(OrderStatusEvent event) {
|
|
487
|
+
log.info("顺序收到订单状态变更: orderNo={}, status={}",
|
|
488
|
+
event.getOrderNo(), event.getStatus());
|
|
489
|
+
// 按顺序处理订单状态变更
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Tag 消息过滤
|
|
495
|
+
|
|
496
|
+
### 发送带 Tag 的消息
|
|
497
|
+
|
|
498
|
+
```java
|
|
499
|
+
@Service
|
|
500
|
+
@RequiredArgsConstructor
|
|
501
|
+
public class TagProducer {
|
|
502
|
+
|
|
503
|
+
private final RocketMQTemplate rocketMQTemplate;
|
|
504
|
+
|
|
505
|
+
public void sendWithTag(OrderEvent event, OrderAction action) {
|
|
506
|
+
// destination 格式: topic:tag
|
|
507
|
+
String destination = "order-topic:" + action.name();
|
|
508
|
+
rocketMQTemplate.syncSend(destination, event);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
public enum OrderAction {
|
|
513
|
+
CREATE, UPDATE, CANCEL, COMPLETE
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 按 Tag 过滤消费
|
|
518
|
+
|
|
519
|
+
```java
|
|
520
|
+
// 只消费创建和取消的订单消息
|
|
521
|
+
@RocketMQMessageListener(
|
|
522
|
+
topic = "order-topic",
|
|
523
|
+
consumerGroup = "order-alert-group",
|
|
524
|
+
selectorType = SelectorType.TAG,
|
|
525
|
+
selectorExpression = "CREATE || CANCEL"
|
|
526
|
+
)
|
|
527
|
+
public class OrderAlertConsumer implements RocketMQListener<OrderEvent> {
|
|
528
|
+
@Override
|
|
529
|
+
public void onMessage(OrderEvent event) {
|
|
530
|
+
// 仅处理 CREATE 和 CANCEL 标签的消息
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## 重试策略配置
|
|
536
|
+
|
|
537
|
+
### 消费重试
|
|
538
|
+
|
|
539
|
+
RocketMQ 默认消费失败重试 16 次,重试间隔逐步增大:
|
|
540
|
+
|
|
541
|
+
| 重试次数 | 间隔时间 | 重试次数 | 间隔时间 |
|
|
542
|
+
|---------|---------|---------|---------|
|
|
543
|
+
| 1 | 10s | 9 | 7min |
|
|
544
|
+
| 2 | 30s | 10 | 8min |
|
|
545
|
+
| 3 | 1min | 11 | 9min |
|
|
546
|
+
| 4 | 2min | 12 | 10min |
|
|
547
|
+
| 5 | 3min | 13 | 20min |
|
|
548
|
+
| 6 | 4min | 14 | 30min |
|
|
549
|
+
| 7 | 5min | 15 | 1h |
|
|
550
|
+
| 8 | 6min | 16 | 2h |
|
|
551
|
+
|
|
552
|
+
### 自定义重试次数
|
|
553
|
+
|
|
554
|
+
```java
|
|
555
|
+
@RocketMQMessageListener(
|
|
556
|
+
topic = "order-topic",
|
|
557
|
+
consumerGroup = "order-retry-group",
|
|
558
|
+
maxReconsumeTimes = 5 // 最大重试 5 次
|
|
559
|
+
)
|
|
560
|
+
public class RetryConsumer implements RocketMQListener<MessageExt> {
|
|
561
|
+
|
|
562
|
+
@Override
|
|
563
|
+
public void onMessage(MessageExt msg) {
|
|
564
|
+
if (msg.getReconsumeTimes() >= 3) {
|
|
565
|
+
log.warn("消息重试 {} 次,进入补偿流程: msgId={}", msg.getReconsumeTimes(), msg.getMsgId());
|
|
566
|
+
// 入库人工处理
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// 正常业务处理,抛出异常会自动重试
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
## 最佳实践
|
|
575
|
+
|
|
576
|
+
### 1. 生产者
|
|
577
|
+
|
|
578
|
+
- **设置合理的超时时间**:`send-message-timeout` 建议 3-5 秒
|
|
579
|
+
- **启用重试**:同步和异步发送各设置 2-3 次重试
|
|
580
|
+
- **使用 Tag 分类消息**:便于消费端按需过滤
|
|
581
|
+
- **消息体控制大小**:单条消息不超过 4MB,大数据存 OSS 传引用
|
|
582
|
+
|
|
583
|
+
### 2. 消费者
|
|
584
|
+
|
|
585
|
+
- **幂等消费**:根据业务唯一标识(如订单号)去重
|
|
586
|
+
- **异常处理**:合理使用重试机制,超过重试次数入库人工处理
|
|
587
|
+
- **消费线程数**:`consumeThreadNumber` 根据业务类型设置,IO 密集型可设大
|
|
588
|
+
- **避免长时间消费**:单条消息处理时间建议不超过 15 秒
|
|
589
|
+
|
|
590
|
+
### 3. Topic 设计
|
|
591
|
+
|
|
592
|
+
- **按业务域划分 Topic**:如 order-topic、payment-topic、user-topic
|
|
593
|
+
- **使用 Tag 区分操作**:如 CREATE、UPDATE、DELETE
|
|
594
|
+
- **Topic 命名规范**:`{应用名}-{业务域}-topic`
|
|
595
|
+
|
|
596
|
+
### 4. 事务消息
|
|
597
|
+
|
|
598
|
+
- **本地事务要快**:避免在 executeLocalTransaction 中执行耗时操作
|
|
599
|
+
- **回查逻辑要幂等**:checkLocalTransaction 可能被多次调用
|
|
600
|
+
- **记录事务状态**:本地事务执行后记录状态,便于回查
|
|
601
|
+
|
|
602
|
+
## 常见问题
|
|
603
|
+
|
|
604
|
+
### 1. 消息发送超时
|
|
605
|
+
|
|
606
|
+
**原因**:NameServer 或 Broker 不可达
|
|
607
|
+
|
|
608
|
+
**解决**:
|
|
609
|
+
- 检查 `rocketmq.name-server` 配置是否正确
|
|
610
|
+
- 检查网络连通性:`telnet nameserver-host 9876`
|
|
611
|
+
- 增大 `send-message-timeout` 配置
|
|
612
|
+
- 检查 Broker 是否正常运行
|
|
613
|
+
|
|
614
|
+
### 2. 消费者无法消费消息
|
|
615
|
+
|
|
616
|
+
**原因**:消费者组名冲突或 Topic 不存在
|
|
617
|
+
|
|
618
|
+
**解决**:
|
|
619
|
+
- 检查 `consumerGroup` 是否唯一(不同 Topic 不能使用相同消费者组)
|
|
620
|
+
- 检查 Topic 是否已创建
|
|
621
|
+
- 检查 selectorExpression(Tag 过滤表达式)是否正确
|
|
622
|
+
- 查看消费者日志是否有注册成功的信息
|
|
623
|
+
|
|
624
|
+
### 3. 消息堆积
|
|
625
|
+
|
|
626
|
+
**原因**:消费速度跟不上生产速度
|
|
627
|
+
|
|
628
|
+
**解决**:
|
|
629
|
+
- 增大 `consumeThreadNumber`(消费线程数)
|
|
630
|
+
- 优化消费逻辑,减少单条消息处理时间
|
|
631
|
+
- 临时扩容消费者实例
|
|
632
|
+
- 检查是否有消费异常导致重试堆积
|
|
633
|
+
|
|
634
|
+
### 4. 事务消息一直 UNKNOWN
|
|
635
|
+
|
|
636
|
+
**原因**:`checkLocalTransaction` 无法确认事务状态
|
|
637
|
+
|
|
638
|
+
**解决**:
|
|
639
|
+
- 确保本地事务执行后有持久化状态可查
|
|
640
|
+
- 检查回查逻辑是否正确查询了本地事务状态
|
|
641
|
+
- 设置合理的事务回查间隔和次数
|
|
642
|
+
|
|
643
|
+
### 5. 顺序消息乱序
|
|
644
|
+
|
|
645
|
+
**原因**:消费端并发消费或消费失败重试
|
|
646
|
+
|
|
647
|
+
**解决**:
|
|
648
|
+
- 确保 `consumeMode = ConsumeMode.ORDERLY`
|
|
649
|
+
- 避免消费异常导致的重试打乱顺序
|
|
650
|
+
- 确保使用正确的 hashKey 发送到同一队列
|
|
651
|
+
|
|
652
|
+
## 依赖版本
|
|
653
|
+
|
|
654
|
+
- rocketmq-spring-boot-starter: 2.3.1
|
|
655
|
+
- Spring Boot: 3.5.3
|
|
656
|
+
- Java: 21
|
|
657
|
+
|
|
658
|
+
## 参考文档
|
|
659
|
+
|
|
660
|
+
- [RocketMQ Spring 官方文档](https://github.com/apache/rocketmq-spring)
|
|
661
|
+
- [Apache RocketMQ 官方文档](https://rocketmq.apache.org/docs/)
|
|
662
|
+
- [RocketMQ 最佳实践](https://rocketmq.apache.org/docs/bestPractice/01bestpractice/)
|