关注公众号,回复【1032】领取资料
@GetMapping(value = "/chatTemplate4", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
Flux.fromIterable(...)
.map(String::trim)
.filter(line -> !line.isEmpty())
.concatMap(...)
.takeWhile(data -> isStreaming.get())
private final AtomicBoolean isStreaming = new AtomicBoolean(true);
@PostMapping("/cancel")
public void cancelStream() {
isStreaming.set(false);
}
const eventSource = new EventSource("/api/ai_travel/chatTemplate");
eventSource.onmessage = function(event) { ... }
private final AtomicBoolean isStreaming = new AtomicBoolean(true);
@GetMapping(value = "/chatTemplate", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatTemplate() {
log.info("收到消息");
isStreaming.set(true); // 每次新连接重置状态
return Flux.fromIterable(Arrays.asList(RES_TEMPLATE.split("\n")))
.map(String::trim)
.filter(line -> !line.isEmpty())
.index() // 索引区分首行
.concatMap(tuple -> {
long idx = tuple.getT1();
String line = tuple.getT2();
// 首行不加换行符,后续行开头加换行
Flux<String> startFlux = (idx == 0)
? Flux.empty()
: Flux.just("\n");
// 逐字输出
Flux<String> charFlux = Flux.fromIterable(Arrays.asList(line.split("")))
.delayElements(Duration.ofMillis(50));
return startFlux.concatWith(charFlux);
})
.takeWhile(data -> isStreaming.get())
.concatWithValues("\u0003"); // 结束标识符
}
@PostMapping("/cancel")
public void cancelStream() {
isStreaming.set(false);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>逐行逐字输出</title>
</head>
<body>
<button onclick="send()">发送消息</button>
<button onclick="cancel()">中断回复</button>
<h2>AI 回复:</h2>
<div id="output" style="white-space: pre-wrap;"></div>
<script>
const output = document.getElementById('output');
let eventSource = null;
function cancel() {
fetch('/api/ai_travel/cancel', { method: 'POST' });
if(eventSource){
eventSource.close();
}
}
function send() {
let currentLineElement = null;
const eventSource = new EventSource("/api/ai_travel/chatTemplate");
eventSource.onmessage = function(event) {
const char = event.data;
// 检测结束标识符 (ETX)
if (char === '\u0003') {
eventSource.close(); // 主动关闭连接
return;
}
if (char === '\n') {
currentLineElement = document.createElement("p");
output.appendChild(currentLineElement);
} else {
if (!currentLineElement) {
currentLineElement = document.createElement("p");
output.appendChild(currentLineElement);
}
currentLineElement.textContent += char;
}
};
eventSource.onerror = function() {
eventSource.close(); // 确保出错时关闭连接
};
}
</script>
</body>
</html>