Skip to content

Commit 0daeef7

Browse files
author
dushixiang
committed
增加【实时拉取消息】功能。
1 parent 665873b commit 0daeef7

File tree

12 files changed

+461
-14
lines changed

12 files changed

+461
-14
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@
8080
<artifactId>kafka-clients</artifactId>
8181
<version>2.8.0</version>
8282
</dependency>
83+
84+
<dependency>
85+
<groupId>org.springframework.boot</groupId>
86+
<artifactId>spring-boot-starter-webflux</artifactId>
87+
</dependency>
8388
</dependencies>
8489

8590
<build>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package cn.typesafe.km.config;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.web.reactive.config.CorsRegistry;
5+
import org.springframework.web.reactive.config.WebFluxConfigurer;
6+
7+
@Configuration
8+
public class WebFluxConfig implements WebFluxConfigurer {
9+
10+
@Override
11+
public void addCorsMappings(CorsRegistry registry) {
12+
registry.addMapping("/**")
13+
.allowedOrigins("*")
14+
.allowCredentials(false)
15+
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
16+
.allowedHeaders("*")
17+
.exposedHeaders("*");
18+
}
19+
}

src/main/java/cn/typesafe/km/controller/TopicController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import cn.typesafe.km.service.MessageService;
66
import cn.typesafe.km.service.TopicService;
77
import cn.typesafe.km.service.dto.*;
8+
import org.springframework.http.codec.ServerSentEvent;
89
import org.springframework.web.bind.annotation.*;
10+
import reactor.core.publisher.Flux;
911

1012
import javax.annotation.Resource;
1113
import java.util.ArrayList;
@@ -96,6 +98,14 @@ public List<ConsumerMessage> data(@PathVariable String topic, @RequestParam Stri
9698
return messageService.data(clusterId, topic, partition, offset, count, keyFilter, valueFilter);
9799
}
98100

101+
@GetMapping("/{topic}/data/live")
102+
public Flux<ServerSentEvent<LiveMessage>> liveData(@PathVariable String topic, @RequestParam String clusterId,
103+
@RequestParam(defaultValue = "0") Integer partition,
104+
String keyFilter,
105+
String valueFilter) {
106+
return messageService.liveData(clusterId, topic, partition, keyFilter, valueFilter);
107+
}
108+
99109
@PostMapping("/{topic}/data")
100110
public long data(@PathVariable String topic, @RequestParam String clusterId, @RequestBody TopicData topicData) {
101111
return messageService.sendData(clusterId, topic, topicData);

src/main/java/cn/typesafe/km/interceptor/AuthInterceptor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
3131
}
3232

3333
String tokenHeader = request.getHeader("X-Auth-Token");
34+
if (!StringUtils.hasText(tokenHeader)) {
35+
tokenHeader = request.getParameter("X-Auth-Token");
36+
}
37+
3438
if (StringUtils.hasText(tokenHeader)) {
3539
User user = tokenManager.getIfPresent(tokenHeader);
3640
if (user != null) {

src/main/java/cn/typesafe/km/service/MessageService.java

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import cn.typesafe.km.entity.Cluster;
44
import cn.typesafe.km.service.dto.ConsumerMessage;
5+
import cn.typesafe.km.service.dto.LiveMessage;
56
import cn.typesafe.km.service.dto.TopicData;
67
import lombok.SneakyThrows;
78
import org.apache.kafka.clients.consumer.ConsumerRecord;
@@ -10,9 +11,11 @@
1011
import org.apache.kafka.clients.producer.ProducerRecord;
1112
import org.apache.kafka.clients.producer.RecordMetadata;
1213
import org.apache.kafka.common.TopicPartition;
14+
import org.springframework.http.codec.ServerSentEvent;
1315
import org.springframework.stereotype.Service;
1416
import org.springframework.util.CollectionUtils;
1517
import org.springframework.util.StringUtils;
18+
import reactor.core.publisher.Flux;
1619

1720
import javax.annotation.Resource;
1821
import java.time.Duration;
@@ -81,7 +84,81 @@ public List<ConsumerMessage> data(String clusterId, String topicName, Integer tP
8184
}
8285

8386
return records
84-
.subList(0, Math.min(count, records.size()))
87+
.subList(0, Math.min(count, records.size()))
88+
.stream()
89+
.map(record -> {
90+
int partition = record.partition();
91+
long timestamp = record.timestamp();
92+
String key = record.key();
93+
String value = record.value();
94+
long offset = record.offset();
95+
96+
ConsumerMessage consumerMessage = new ConsumerMessage();
97+
consumerMessage.setTopic(topicName);
98+
consumerMessage.setOffset(offset);
99+
consumerMessage.setPartition(partition);
100+
consumerMessage.setTimestamp(timestamp);
101+
consumerMessage.setKey(key);
102+
consumerMessage.setValue(value);
103+
104+
return consumerMessage;
105+
}).collect(Collectors.toList());
106+
}
107+
}
108+
109+
@SneakyThrows
110+
public long sendData(String clusterId, String topic, TopicData topicData) {
111+
Cluster cluster = clusterService.findById(clusterId);
112+
KafkaProducer<String, String> kafkaProducer = clusterService.createProducer(cluster.getServers(), cluster.getSecurityProtocol(), cluster.getSaslMechanism(), cluster.getAuthUsername(), cluster.getAuthPassword());
113+
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topic, topicData.getPartition(), topicData.getKey(), topicData.getValue());
114+
RecordMetadata recordMetadata = kafkaProducer.send(producerRecord).get();
115+
return recordMetadata.offset();
116+
}
117+
118+
public Flux<ServerSentEvent<LiveMessage>> liveData(String clusterId, String topicName, Integer tPartition, String keyFilter, String valueFilter) {
119+
120+
KafkaConsumer<String, String> kafkaConsumer = clusterService.createConsumer(clusterId);
121+
TopicPartition topicPartition = new TopicPartition(topicName, tPartition);
122+
List<TopicPartition> topicPartitions = Collections.singletonList(topicPartition);
123+
kafkaConsumer.assign(topicPartitions);
124+
125+
Long endOffset = kafkaConsumer.endOffsets(topicPartitions).get(topicPartition);
126+
kafkaConsumer.seek(topicPartition, endOffset);
127+
128+
return Flux
129+
.interval(Duration.ofSeconds(1))
130+
.doFinally(x -> {
131+
kafkaConsumer.close();
132+
})
133+
.map(sequence -> {
134+
135+
List<ConsumerRecord<String, String>> records = new ArrayList<>();
136+
137+
List<ConsumerRecord<String, String>> polled = kafkaConsumer.poll(Duration.ofMillis(200)).records(topicPartition);
138+
139+
if (!CollectionUtils.isEmpty(polled)) {
140+
141+
for (ConsumerRecord<String, String> consumerRecord : polled) {
142+
if (StringUtils.hasText(keyFilter)) {
143+
String key = consumerRecord.key();
144+
if (StringUtils.hasText(key) && key.toLowerCase().contains(keyFilter.toLowerCase())) {
145+
records.add(consumerRecord);
146+
}
147+
continue;
148+
}
149+
150+
if (StringUtils.hasText(valueFilter)) {
151+
String value = consumerRecord.value();
152+
if (StringUtils.hasText(value) && value.toLowerCase().contains(valueFilter.toLowerCase())) {
153+
records.add(consumerRecord);
154+
}
155+
continue;
156+
}
157+
records.add(consumerRecord);
158+
}
159+
}
160+
161+
List<ConsumerMessage> data = records
85162
.stream()
86163
.map(record -> {
87164
int partition = record.partition();
@@ -100,15 +177,21 @@ public List<ConsumerMessage> data(String clusterId, String topicName, Integer tP
100177

101178
return consumerMessage;
102179
}).collect(Collectors.toList());
103-
}
104-
}
105180

106-
@SneakyThrows
107-
public long sendData(String clusterId, String topic, TopicData topicData) {
108-
Cluster cluster = clusterService.findById(clusterId);
109-
KafkaProducer<String, String> kafkaProducer = clusterService.createProducer(cluster.getServers(), cluster.getSecurityProtocol(), cluster.getSaslMechanism(), cluster.getAuthUsername(), cluster.getAuthPassword());
110-
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topic,topicData.getPartition(), topicData.getKey(), topicData.getValue());
111-
RecordMetadata recordMetadata = kafkaProducer.send(producerRecord).get();
112-
return recordMetadata.offset();
181+
Long currBeginningOffset = kafkaConsumer.beginningOffsets(topicPartitions).get(topicPartition);
182+
Long currEndOffset = kafkaConsumer.endOffsets(topicPartitions).get(topicPartition);
183+
184+
LiveMessage liveMessage = new LiveMessage();
185+
liveMessage.setBeginningOffset(currBeginningOffset);
186+
liveMessage.setEndOffset(currEndOffset);
187+
liveMessage.setPartition(tPartition);
188+
liveMessage.setMessages(data);
189+
190+
return ServerSentEvent.<LiveMessage>builder()
191+
.id(String.valueOf(sequence))
192+
.event("topic-message-event")
193+
.data(liveMessage)
194+
.build();
195+
});
113196
}
114197
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package cn.typesafe.km.service.dto;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
@Data
8+
public class LiveMessage {
9+
private int partition;
10+
private long beginningOffset;
11+
private long endOffset;
12+
private List<ConsumerMessage> messages;
13+
}

src/main/web/src/App.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
GithubOutlined
2626
} from '@ant-design/icons';
2727
import Info from "./components/Info";
28+
import TopicDataLive from "./components/TopicDataLive";
2829

2930
const {Header, Content, Footer} = Layout;
3031

@@ -182,6 +183,7 @@ class App extends Component {
182183
<Route path="/consumer-group-info" component={ConsumerGroupInfo}/>
183184
<Route path="/topic-info" component={TopicInfo}/>
184185
<Route path="/topic-data" component={TopicData}/>
186+
<Route path="/topic-data-live" component={TopicDataLive}/>
185187
<Route path="/info" component={Info}/>
186188
</Content>
187189
</Layout>

src/main/web/src/components/Topic.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,12 @@ class Topic extends Component {
224224
</Button>
225225
</Link>
226226

227+
<Link to={`/topic-data-live?clusterId=${this.state.clusterId}&topic=${record['name']}`}>
228+
<Button key="2" type="link" size='small'>
229+
<FormattedMessage id="consume-message-live" />
230+
</Button>
231+
</Link>
232+
227233
<Button type="link" size='small' onClick={() => {
228234
this.setState({
229235
createPartitionVisible: true,

0 commit comments

Comments
 (0)