perf: 优化SSE连接

This commit is contained in:
jmal 2024-06-05 21:40:18 +08:00
parent 7c40b0dd40
commit 6dc2c40a64
4 changed files with 90 additions and 75 deletions

View File

@ -132,7 +132,7 @@ export default {
// speedSmoothingFactor: 0.1, // speedSmoothingFactor: 0.1,
// progressCallbacksInterval: 500, // progressCallbacksInterval: 500,
maxChunkRetries: 3, // 最大重试次数 maxChunkRetries: 3, // 最大重试次数
simultaneousUploads: 5, // 并发上传数 simultaneousUploads: 2, // 并发上传数
testChunks: true, // 是否开启服务器分片校验 testChunks: true, // 是否开启服务器分片校验
// 服务器分片校验函数,秒传及断点续传基础 // 服务器分片校验函数,秒传及断点续传基础
checkChunkUploadedByResponse: function (chunk, message) { checkChunkUploadedByResponse: function (chunk, message) {
@ -204,7 +204,7 @@ export default {
message(msg) { message(msg) {
switch (msg.event) { switch (msg.event) {
case 'uploaderChunkSize': case 'uploaderChunkSize':
this.onStorageTypeChange(msg.data) this.onStorageTypeChange(msg.data.body)
break break
case 'fileListScrollTop': case 'fileListScrollTop':
this.fileListScrollTop = msg.data this.fileListScrollTop = msg.data

View File

@ -43,7 +43,6 @@ export default {
}, },
watch: { watch: {
data(val) { data(val) {
console.log('data', val)
this.dataList = val this.dataList = val
}, },
message(msg) { message(msg) {

View File

@ -21,7 +21,7 @@ import ResizeMixin from './mixin/ResizeHandler'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import AudioPreview from "@/components/preview/AudioPreview"; import AudioPreview from "@/components/preview/AudioPreview";
import globalUploader from '@/components/SimpleUploader/globalUploader.vue' import globalUploader from '@/components/SimpleUploader/globalUploader.vue'
import { connectToSSE } from '@/sse/SSEClient' import { initiateSSE } from '@/sse/SSEClient'
export default { export default {
name: 'Layout', name: 'Layout',
@ -62,7 +62,7 @@ export default {
if(getToken()){ if(getToken()){
this.isShow = window.pc; this.isShow = window.pc;
// 与服务器建立sse连接 // 与服务器建立sse连接
connectToSSE(this.$store.state.user.name) initiateSSE(this.$store.state.user.name)
if(this.$route.path.startsWith('/setting')){ if(this.$route.path.startsWith('/setting')){
this.isShow = true this.isShow = true
} }

View File

@ -1,92 +1,108 @@
import Bus from "@/assets/js/bus"; import Bus from "@/assets/js/bus";
import _ from "lodash"; import _ from "lodash";
import store from "@/store"; import store from "@/store";
import {generateUUID} from "ant-design-vue/lib/vc-select/util"; import { generateUUID } from "ant-design-vue/lib/vc-select/util";
let eventSource = null; let eventSource = null;
let timer = null; let timer = null;
let lastHeartbeat = new Date().getTime(); let lastHeartbeat = new Date().getTime();
const uuid = generateUUID(); const uuid = generateUUID();
const throttledConnectSSE = _.throttle(username => { const throttledConnectSSE = _.throttle((username) => {
if (typeof (EventSource) !== 'undefined') { if (typeof EventSource !== 'undefined') {
const url = `/api/events?username=${username}&uuid=${uuid}`; connectToSSE(username);
eventSource = new EventSource(url);
eventSource.addEventListener('message', function (event) {
if (event.data === 'h') {
// 心跳包, 超过5s没有收到心跳包, 则关闭连接
lastHeartbeat = new Date().getTime()
} else {
const msg = JSON.parse( event.data);
onMessage(msg)
}
});
eventSource.addEventListener('error', function (event) {
eventSource.close()
connectToSSE(username);
});
// 清除定时器
if (timer) {
clearInterval(timer);
}
// 3s检测一次连接状态
timer = setInterval(() => {
if (eventSource.readyState === 1) {
// 心跳包, 超过5s没有收到心跳包, 则关闭连接
if (new Date().getTime() - lastHeartbeat > 5000) {
eventSource.close()
}
}
if (eventSource.readyState === 2) {
connectToSSE(username);
}
}, 3000);
} else { } else {
console.error('EventSource is not supported by the browser'); console.error('EventSource is not supported by the browser');
} }
}, 3000); }, 3000);
export function connectToSSE(username) { function connectToSSE(username) {
const url = `/api/events?username=${username}&uuid=${uuid}`;
eventSource = new EventSource(url);
eventSource.addEventListener('message', handleMessage);
eventSource.addEventListener('error', handleError.bind(null, username));
startHeartbeatCheck(username);
}
function handleMessage(event) {
if (event.data === 'h') {
lastHeartbeat = new Date().getTime();
} else {
const msg = JSON.parse(event.data);
onMessage(msg);
}
}
function handleError(username) {
eventSource.close();
throttledConnectSSE(username); throttledConnectSSE(username);
} }
function clearExistingTimer() {
if (timer) {
clearInterval(timer);
}
}
function startHeartbeatCheck(username) {
clearExistingTimer();
timer = setInterval(() => {
if (eventSource.readyState === 1 && new Date().getTime() - lastHeartbeat > 5000) {
eventSource.close();
}
if (eventSource.readyState === 2) {
throttledConnectSSE(username);
}
}, 3000);
}
function onMessage(msg) { function onMessage(msg) {
const url = msg.url const { url, body } = msg;
if (url === 'synced') { const eventMapping = {
store.dispatch('updateMessage', {event: 'msg/synced', data: msg}) synced: 'msg/synced',
} else if (url === 'taskProgress') { taskProgress: 'msg/taskProgress',
store.dispatch('updateMessage', {event: 'msg/taskProgress', data: msg}) uploaderChunkSize: 'uploaderChunkSize',
} else if (url === 'uploaderChunkSize') { };
store.dispatch('updateMessage', {event: 'uploaderChunkSize', data: msg.body})
if (eventMapping[url]) {
store.dispatch('updateMessage', { event: eventMapping[url], data: msg });
} else { } else {
store.dispatch('updateMessage', {event: 'msg/file/change', data: msg}) handleFileChangeMessage(msg, url, body);
if ('operationFile' === url) { }
let doc = msg.body }
if (doc.code === 0) {
Bus.notify({ function handleFileChangeMessage(msg, url, body) {
title: `${doc.operation}成功`, store.dispatch('updateMessage', { event: 'msg/file/change', data: msg });
dangerouslyUseHTMLString: true,
message: ` if (url === 'operationFile') {
<div> const { operation, from, to, code, msg: message } = body;
<p>form:</p> const notificationOptions = {
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${doc.from}</pre> title: `${operation} ${code === 0 ? '成功' : '失败'}`,
</div> dangerouslyUseHTMLString: true,
<div> message: code === 0
<p>to:</p> ? `
<pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${doc.to}</pre> <div>
</div>`, <p>from:</p>
type: 'success', <pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${from}</pre>
}); </div>
} else { <div>
store.dispatch('updateMessage', {event: 'msg/file/operation/fault', data: msg}); <p>to:</p>
Bus.notify({ <pre style="word-break: break-all;white-space: pre-wrap;font-size: 12px;">${to}</pre>
title: `${doc.operation}失败`, </div>`
dangerouslyUseHTMLString: true, : `<span style="font-size: 12px;">${message}</span>`,
message: `<span style="font-size: 12px;">${doc.msg}</span>`, type: code === 0 ? 'success' : 'error',
type: 'error' };
});
} Bus.notify(notificationOptions);
if (code !== 0) {
store.dispatch('updateMessage', { event: 'msg/file/operation/fault', data: msg });
} }
} }
} }
export function initiateSSE(username) {
throttledConnectSSE(username);
}