Skip to content

Commit 2f8ab79

Browse files
committed
eventStream添加优雅结束,内容显示添加打字机效果
1 parent ce6c3e3 commit 2f8ab79

File tree

2 files changed

+76
-26
lines changed

2 files changed

+76
-26
lines changed

springboot-web/src/main/java/com/doodl6/springboot/web/controller/EventStreamController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ public class EventStreamController {
1717
public Flux<String> eventStream() {
1818
// 创建一个 Flux,用于每秒推送一条消息
1919
return Flux.interval(Duration.ofSeconds(1))
20-
.map(sequence -> "服务器时间 " + LocalTime.now() + "\n\n")
20+
.map(sequence -> "服务器时间 " + LocalTime.now())
2121
.take(10)// 限制发送 10 条消息
22+
.concatWith(Flux.just("END")) // 添加一个结束信号
2223
.doOnCancel(() -> System.out.println("客户端连接已断开")); // 客户端断开时触发
2324
}
2425
}

springboot-web/src/main/resources/static/event-stream.html

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,101 @@
33
<head>
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>SSE Example</title>
6+
<title>EventStream Example</title>
77
<script>
88
let eventSource = null; // 用于存储 EventSource 实例
9+
let messageQueue = []; // 消息队列
10+
let isTyping = false; // 当前是否有消息正在输出
11+
let scrollTimeout = null; // 用于节流的定时器
12+
13+
// 节流的 scrollToBottom 函数
14+
function scrollToBottom() {
15+
if (scrollTimeout) return; // 如果已经有一个定时器在运行,则不重复设置
16+
scrollTimeout = setTimeout(() => {
17+
const messageBox = document.getElementById("message-box");
18+
messageBox.scrollTop = messageBox.scrollHeight; // 滚动到底部
19+
scrollTimeout = null; // 清除定时器标记
20+
}, 100); // 节流间隔(毫秒)
21+
}
22+
23+
// 打字机效果函数
24+
function typeText(container, text, delay = 50) {
25+
return new Promise((resolve) => {
26+
let index = 0;
27+
28+
function type() {
29+
if (index < text.length) {
30+
container.textContent += text[index];
31+
index++;
32+
33+
// 滚动到底部,使用节流优化
34+
scrollToBottom();
35+
36+
setTimeout(type, delay);
37+
} else {
38+
resolve();
39+
}
40+
}
41+
42+
type();
43+
});
44+
}
45+
46+
async function processQueue() {
47+
if (isTyping || messageQueue.length === 0) return;
48+
49+
isTyping = true; // 设置正在输出状态
50+
51+
const { container, text } = messageQueue.shift(); // 取出队列中的第一个任务
52+
await typeText(container, text); // 执行打字效果
53+
54+
isTyping = false; // 结束输出状态
55+
await processQueue(); // 继续处理队列中的下一个任务
56+
}
957

1058
function startEventStream() {
11-
// 防止重复连接
1259
if (eventSource) {
13-
logMessage("已经连接到事件流,请勿重复点击!");
60+
processMessage("已经连接到事件流,请勿重复点击!");
1461
return;
1562
}
1663

17-
// 创建 EventSource 实例并连接到后端 SSE 接口
1864
eventSource = new EventSource('/eventStream');
1965

20-
// 监听消息事件
2166
eventSource.onmessage = (event) => {
22-
logMessage(`收到消息: ${event.data}`);
67+
if (event.data === "END") {
68+
processMessage("服务器已发送所有消息,流结束。");
69+
stopEventStream(); // 手动停止事件流
70+
} else {
71+
processMessage(`收到消息: ${event.data}`);
72+
}
2373
};
2474

25-
// 监听连接打开事件
2675
eventSource.onopen = () => {
27-
logMessage("连接已建立,开始接收数据...");
76+
processMessage("连接已建立,开始接收数据...");
2877
};
2978

30-
// 监听错误事件
3179
eventSource.onerror = () => {
32-
logMessage("发生错误或连接已关闭!");
33-
stopEventStream(); // 停止事件流
80+
processMessage("发生错误或连接已关闭!");
81+
stopEventStream();
3482
};
3583
}
3684

3785
function stopEventStream() {
3886
if (eventSource) {
39-
eventSource.close(); // 关闭连接
87+
eventSource.close();
4088
eventSource = null;
41-
logMessage("事件流已停止。");
89+
processMessage("事件流已停止。");
4290
}
4391
}
4492

45-
function logMessage(message) {
46-
const logBox = document.getElementById("log-box");
93+
function processMessage(message) {
94+
const messageBox = document.getElementById("message-box");
4795
const messageElement = document.createElement("div");
48-
messageElement.textContent = message;
49-
logBox.appendChild(messageElement);
50-
logBox.scrollTop = logBox.scrollHeight; // 自动滚动到底部
96+
messageBox.appendChild(messageElement);
97+
98+
// 将任务添加到队列中
99+
messageQueue.push({ container: messageElement, text: message });
100+
processQueue();
51101
}
52102
</script>
53103
<style>
@@ -56,13 +106,14 @@
56106
margin: 20px;
57107
}
58108

59-
#log-box {
60-
width: 100%;
109+
#message-box {
110+
width: 95%;
61111
height: 300px;
62112
border: 1px solid #ccc;
63113
padding: 10px;
64-
overflow-y: scroll;
114+
overflow-y: auto;
65115
background-color: #f9f9f9;
116+
margin-top: 10px;
66117
}
67118

68119
.button-container {
@@ -87,14 +138,12 @@
87138
</style>
88139
</head>
89140
<body>
90-
<h1>Server-Sent Events (SSE) 示例</h1>
141+
<h1>EventStream 示例</h1>
91142
<p>点击“开始”按钮连接到后端事件流接口,并查看接收到的消息:</p>
92-
93-
<div id="log-box"></div>
94-
95143
<div class="button-container">
96144
<button onclick="startEventStream()">开始接收</button>
97145
<button onclick="stopEventStream()">停止接收</button>
98146
</div>
147+
<div id="message-box"></div>
99148
</body>
100149
</html>

0 commit comments

Comments
 (0)