perf(worker): ⚡ 使用worker优化计时器不准确问题 (#190)
This commit is contained in:
28
README.en.md
28
README.en.md
@@ -53,15 +53,25 @@ HuLa is an instant messaging system developed with Tauri, Vite 5, Vue 3, and Typ
|
||||
|
||||

|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_4.png" alt="img_4.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_5.png" alt="img_5.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_6.png" alt="img_6.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_7.png" alt="img_7.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_8.png" alt="img_8.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||
## Thanks to the following contributors!
|
||||
|
||||
@@ -96,7 +106,9 @@ Downloading the installation package on the web page will indicate that the inst
|
||||
|
||||
#### 1. Open "System Settings" - "Security & Privacy", as shown in the figure, check the box: Allow apps downloaded from "Any Source" to run:
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_9.png" alt="img_9.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||
#### 2. If an error is reported, run the following command in the terminal to resolve the problem:
|
||||
|
||||
@@ -118,6 +130,8 @@ use **pnpm run commit** to invoke the _git commit_ interaction and follow the pr
|
||||
|
||||
**The final interpretation of this disclaimer belongs to the developer**
|
||||
|
||||
## License
|
||||
## HuLa社区讨论群
|
||||
<img src="preview/wx.jpg" width="240" height="280" alt="微信群二维码" style="border-radius: 12px;" />
|
||||
|
||||
## License
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_large)
|
||||
|
||||
28
README.md
28
README.md
@@ -53,15 +53,25 @@ HuLa 是一个基于 Tauri、Vite 5、Vue 3 和 TypeScript 构建的即时通讯
|
||||
|
||||

|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_4.png" alt="img_4.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_5.png" alt="img_5.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_6.png" alt="img_6.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_7.png" alt="img_7.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_8.png" alt="img_8.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||
## 感谢以下贡献者们!
|
||||
|
||||
@@ -96,7 +106,9 @@ pnpm run tauri:build
|
||||
|
||||
#### 1. 打开 “系统设置” - “安全性与隐私”,如图勾选:允许 “任何来源” 下载的 App 运行:
|
||||
|
||||

|
||||
<div style="padding: 28px; display: inline-block;">
|
||||
<img src="preview/img_9.png" alt="img_9.png" style="border-radius: 8px; display: block;" />
|
||||
</div>
|
||||
|
||||
#### 2. 如果还报错,请在终端执行以下命令解决:
|
||||
|
||||
@@ -118,6 +130,8 @@ sudo xattr -rd com.apple.quarantine 你的安装包路径/HuLa.app
|
||||
|
||||
**本免责声明的最终解释权归开发者所有**
|
||||
|
||||
## HuLa社区讨论群
|
||||
<img src="preview/wx.jpg" width="240" height="280" alt="微信群二维码" style="border-radius: 12px;" />
|
||||
|
||||
## License
|
||||
## 许可证
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FHuLaSpark%2FHuLa?ref=badge_large)
|
||||
|
||||
34
package.json
34
package.json
@@ -62,20 +62,20 @@
|
||||
"@tauri-apps/api": "2.2.0",
|
||||
"@tauri-apps/plugin-autostart": "2.2.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.2.0",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2.2.0",
|
||||
"@tauri-apps/plugin-http": "2.2.0",
|
||||
"@tauri-apps/plugin-notification": "^2.2.0",
|
||||
"@tauri-apps/plugin-os": "2.2.0",
|
||||
"@tauri-apps/plugin-process": "2.2.0",
|
||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||
"@tauri-apps/plugin-sql": "^2.0.1",
|
||||
"@tauri-apps/plugin-updater": "~2",
|
||||
"@tauri-apps/plugin-updater": "~2.3.1",
|
||||
"colorthief": "^2.6.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"dompurify": "^3.2.3",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"hula-emojis": "^1.2.3",
|
||||
"hula-emojis": "^1.2.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"naive-ui": "^2.41.0",
|
||||
@@ -90,24 +90,24 @@
|
||||
"@babel/eslint-parser": "^7.25.9",
|
||||
"@commitlint/cli": "^19.6.0",
|
||||
"@commitlint/config-conventional": "^19.6.0",
|
||||
"@release-it/bumper": "^6.0.1",
|
||||
"@release-it/conventional-changelog": "9.0.4",
|
||||
"@release-it/bumper": "^7.0.0",
|
||||
"@release-it/conventional-changelog": "10.0.0",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@tauri-apps/cli": "2.0.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.14.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||
"@typescript-eslint/parser": "^7.15.0",
|
||||
"@unocss/preset-uno": "^0.65.3",
|
||||
"@unocss/reset": "^0.65.3",
|
||||
"@unocss/transformer-directives": "^0.65.3",
|
||||
"@unocss/transformer-variant-group": "^0.65.3",
|
||||
"@unocss/vite": "^0.65.3",
|
||||
"@unocss/preset-uno": "^65.4.2",
|
||||
"@unocss/reset": "^65.4.2",
|
||||
"@unocss/transformer-directives": "^65.4.2",
|
||||
"@unocss/transformer-variant-group": "^65.4.2",
|
||||
"@unocss/vite": "^65.4.2",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"@vitest/ui": "^2.1.8",
|
||||
"@vitest/coverage-v8": "^3.0.1",
|
||||
"@vitest/ui": "^3.0.1",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"chalk": "^5.3.0",
|
||||
@@ -123,13 +123,13 @@
|
||||
"lint-staged": "^15.2.7",
|
||||
"oxlint": "^0.2.18",
|
||||
"prettier": "^3.3.2",
|
||||
"release-it": "^17.11.0",
|
||||
"release-it": "^18.1.0",
|
||||
"sass": "1.83.0",
|
||||
"typescript": "^5.7.2",
|
||||
"unplugin-auto-import": "^0.18.6",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"unplugin-auto-import": "^19.0.0",
|
||||
"unplugin-vue-components": "^28.0.0",
|
||||
"vite": "6.0.7",
|
||||
"vitest": "^2.1.8",
|
||||
"vitest": "^3.0.1",
|
||||
"vue-tsc": "^2.2.0"
|
||||
},
|
||||
"config": {
|
||||
|
||||
1662
pnpm-lock.yaml
generated
1662
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
preview/wx.jpg
Normal file
BIN
preview/wx.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
@@ -12,12 +12,12 @@ const __dirname = dirname(__filename)
|
||||
* @param {string} description - 检查描述
|
||||
*/
|
||||
async function runScript(scriptPath, description) {
|
||||
const startTime = Date.now()
|
||||
const startTime = performance.now()
|
||||
console.log(chalk.blue(`\n[HuLa ${new Date().toLocaleTimeString()}] 开始${description}...\n`))
|
||||
|
||||
try {
|
||||
execSync(`node ${scriptPath}`, { stdio: 'inherit' })
|
||||
const duration = ((Date.now() - startTime) / 1000).toFixed(2)
|
||||
const duration = ((performance.now() - startTime) / 1000).toFixed(2)
|
||||
console.log(chalk.green(`\n✓ ${description}完成 (${duration}s)\n`))
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -41,7 +41,7 @@ async function main() {
|
||||
}
|
||||
]
|
||||
|
||||
const startTime = Date.now()
|
||||
const startTime = performance.now()
|
||||
|
||||
for (const check of checks) {
|
||||
const success = await runScript(check.script, check.description)
|
||||
@@ -51,7 +51,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
const totalDuration = ((Date.now() - startTime) / 1000).toFixed(2)
|
||||
const totalDuration = ((performance.now() - startTime) / 1000).toFixed(2)
|
||||
console.log(chalk.green(`\n✨ 所有检查通过!总用时:${totalDuration}s\n`))
|
||||
}
|
||||
|
||||
|
||||
33
src-tauri/Cargo.lock
generated
33
src-tauri/Cargo.lock
generated
@@ -558,9 +558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.17.2"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
|
||||
checksum = "5fbd1fe9db3ebf71b89060adaf7b0504c2d6a425cf061313099547e382c2e472"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml 0.8.19",
|
||||
@@ -5166,9 +5166,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.2.0"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e2e3349fbb2be7af9fad1b43d61ac83ba55ab48d47fbe1b2732f0c8211610a9"
|
||||
checksum = "2979ec5ec5a9310b15d1548db3b8de98d8f75abf2b5b00fec9cd5c0553ecc09c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5217,9 +5217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.0.4"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b274ec7239ada504deb615f1c8abd7ba99631e879709e6f10e5d17217058d976"
|
||||
checksum = "8e950124f6779c6cf98e3260c7a6c8488a74aa6350dd54c6950fdaa349bca2df"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5404,9 +5404,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-notification"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46ab803095f14ac6521fdb6477210a49e86fed6623c3c97d8e4b2b35e045e922"
|
||||
checksum = "0f8d3ee5207d3359ca2b714545664f24f70374d795bf91f7c1935a494003a57d"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify-rust",
|
||||
@@ -5472,9 +5472,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f36019ee9832dc99e4450bb55a21cfad8633b19c2c18bd17c7741939b070ede"
|
||||
checksum = "47c387d4d96690131dc46d1d2827df5c222b896a2bfeb15a16267229a55c50b5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5482,7 +5482,7 @@ dependencies = [
|
||||
"thiserror 2.0.9",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"zbus 4.4.0",
|
||||
"zbus 5.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5506,9 +5506,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7351014c140906bcfff59d96e04b1170c8f602557f40eb37f7de356d4e7067b"
|
||||
checksum = "ce2d39224390c41ba544f02b4f1721f42256320b3fb8c371e9425cbddeb4a68c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs 5.0.1",
|
||||
@@ -7244,8 +7244,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb67eadba43784b6fb14857eba0d8fc518686d3ee537066eb6086dc318e2c8a1"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
|
||||
@@ -24,10 +24,10 @@ name = "hula_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.2", features = [] }
|
||||
tauri-build = { version = "2.0.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.2.0", features = [
|
||||
tauri = { version = "2.2.2", features = [
|
||||
"macos-private-api",
|
||||
"tray-icon",
|
||||
"image-png",
|
||||
@@ -44,10 +44,10 @@ tauri-plugin-dialog = "2.2.0"
|
||||
tauri-plugin-upload = "2.2.0"
|
||||
tauri-plugin-global-shortcut = "2.2.0"
|
||||
tauri-plugin-clipboard-manager = "2.2.0"
|
||||
tauri-plugin-updater = "2.3.0"
|
||||
tauri-plugin-updater = "2.3.1"
|
||||
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
|
||||
tauri-plugin-single-instance = "2.2.0"
|
||||
tauri-plugin-notification = "2.2.0"
|
||||
tauri-plugin-single-instance = "2.2.1"
|
||||
tauri-plugin-notification = "2.2.1"
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-autostart = "2.2.0"
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -813,9 +813,6 @@ onUnmounted(() => {
|
||||
}
|
||||
hoverBubble.value.key = -1
|
||||
window.removeEventListener('click', closeMenu, true)
|
||||
|
||||
// 清理所有过期定时器
|
||||
chatStore.clearAllExpirationTimers()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -334,3 +334,11 @@ export const enum TriggerEnum {
|
||||
AI = '/',
|
||||
TOPIC = '#'
|
||||
}
|
||||
|
||||
/** 连接状态枚举 */
|
||||
export enum ConnectionState {
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
DISCONNECTED = 'disconnected',
|
||||
RECONNECTING = 'reconnecting'
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
// 默认导航第一个子路由
|
||||
{
|
||||
path: '',
|
||||
name: 'mobileMessage',
|
||||
redirect: '/mobile/message'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -143,8 +143,6 @@ async function Http<T = any>(
|
||||
async function attemptFetch(currentAttempt: number): Promise<{ data: T; resp: Response } | T> {
|
||||
try {
|
||||
const response = await fetch(url, fetchOptions)
|
||||
console.log(`Attempt ${currentAttempt + 1}: status = ${response.status}`)
|
||||
|
||||
// 若响应不 OK 并且状态码属于需重试列表,则抛出 FetchRetryError
|
||||
if (!response.ok) {
|
||||
const errorType = getErrorType(response.status)
|
||||
@@ -177,7 +175,7 @@ async function Http<T = any>(
|
||||
}
|
||||
return responseData
|
||||
} catch (error) {
|
||||
console.error(`Attempt ${currentAttempt + 1} failed →`, error)
|
||||
console.error(`尝试 ${currentAttempt + 1} 失败的 →`, error)
|
||||
|
||||
// 检查是否仍需重试
|
||||
if (!shouldRetry(currentAttempt, retries, abort)) {
|
||||
|
||||
@@ -6,18 +6,25 @@ import type {
|
||||
OnStatusChangeType
|
||||
} from '@/services/wsType.ts'
|
||||
import type { MessageType, MarkItemType, RevokedMsgType } from '@/services/types'
|
||||
import { OnlineEnum, ChangeTypeEnum, WorkerMsgEnum } from '@/enums'
|
||||
import { worker } from '@/utils/InitWorker.ts'
|
||||
import { OnlineEnum, ChangeTypeEnum, WorkerMsgEnum, ConnectionState } from '@/enums'
|
||||
import { useMitt } from '@/hooks/useMitt.ts'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getEnhancedFingerprint } from '@/services/fingerprint.ts'
|
||||
|
||||
// 创建 webSocket worker
|
||||
const worker: Worker = new Worker(new URL('../workers/webSocket.worker.ts', import.meta.url), {
|
||||
type: 'module'
|
||||
})
|
||||
|
||||
class WS {
|
||||
// 添加消息队列大小限制
|
||||
readonly #MAX_QUEUE_SIZE = 100
|
||||
#tasks: WsReqMsgContentType[] = []
|
||||
// 重连🔐
|
||||
#connectReady = false
|
||||
// TODO: 暂时使用去重复的逻辑,后续优化
|
||||
#processedMsgIds = new Set<number>()
|
||||
// 使用LRU缓存替代简单的Set
|
||||
#processedMsgCache = new Map<number, number>()
|
||||
readonly #MAX_CACHE_SIZE = 1000
|
||||
|
||||
constructor() {
|
||||
this.initConnect()
|
||||
@@ -54,11 +61,24 @@ class WS {
|
||||
this.#onClose()
|
||||
break
|
||||
}
|
||||
case WorkerMsgEnum.WS_ERROR:
|
||||
console.log('无网络连接')
|
||||
case WorkerMsgEnum.WS_ERROR: {
|
||||
console.log('WebSocket错误:', (params.value as { msg: string }).msg)
|
||||
useMitt.emit(WsResponseMessageType.NO_INTERNET, params.value)
|
||||
localStorage.removeItem('wsLogin')
|
||||
// 如果是重连失败,可以提示用户刷新页面
|
||||
if ((params.value as { msg: string }).msg.includes('连接失败次数过多')) {
|
||||
// 可以触发UI提示,让用户刷新页面
|
||||
useMitt.emit('wsReconnectFailed', params.value)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'connectionStateChange': {
|
||||
const { state } = params.value as { state: ConnectionState }
|
||||
// 可以触发事件通知其他组件
|
||||
console.log('连接状态改变', state)
|
||||
useMitt.emit('wsConnectionStateChange', state)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,125 +113,147 @@ class WS {
|
||||
if (this.#connectReady) {
|
||||
this.#send(params)
|
||||
} else {
|
||||
// 放到队列
|
||||
// 队列限制
|
||||
if (this.#tasks.length >= this.#MAX_QUEUE_SIZE) {
|
||||
console.warn('消息队列已满,正在丢弃最旧的消息')
|
||||
this.#tasks.shift()
|
||||
}
|
||||
this.#tasks.push(params)
|
||||
}
|
||||
}
|
||||
|
||||
// 收到消息回调
|
||||
onMessage = async (value: string) => {
|
||||
// FIXME 可能需要 try catch,
|
||||
const params: { type: WsResponseMessageType; data: unknown } = JSON.parse(value)
|
||||
switch (params.type) {
|
||||
// 获取登录二维码
|
||||
case WsResponseMessageType.LOGIN_QR_CODE: {
|
||||
console.log('获取二维码')
|
||||
useMitt.emit(WsResponseMessageType.LOGIN_QR_CODE, params.data as LoginInitResType)
|
||||
break
|
||||
}
|
||||
// 等待授权
|
||||
case WsResponseMessageType.WAITING_AUTHORIZE: {
|
||||
console.log('等待授权')
|
||||
useMitt.emit(WsResponseMessageType.WAITING_AUTHORIZE)
|
||||
break
|
||||
}
|
||||
// 登录成功
|
||||
case WsResponseMessageType.LOGIN_SUCCESS: {
|
||||
console.log('登录成功')
|
||||
useMitt.emit(WsResponseMessageType.LOGIN_SUCCESS, params.data as LoginSuccessResType)
|
||||
break
|
||||
}
|
||||
// 收到消息
|
||||
case WsResponseMessageType.RECEIVE_MESSAGE: {
|
||||
const message = params.data as MessageType
|
||||
// TODO: 暂时使用去重复的逻辑,后续优化
|
||||
if (this.#isMessageProcessed(message.message.id)) {
|
||||
try {
|
||||
const params: { type: WsResponseMessageType; data: unknown } = JSON.parse(value)
|
||||
switch (params.type) {
|
||||
// 获取登录二维码
|
||||
case WsResponseMessageType.LOGIN_QR_CODE: {
|
||||
console.log('获取二维码')
|
||||
useMitt.emit(WsResponseMessageType.LOGIN_QR_CODE, params.data as LoginInitResType)
|
||||
break
|
||||
}
|
||||
useMitt.emit(WsResponseMessageType.RECEIVE_MESSAGE, message)
|
||||
break
|
||||
}
|
||||
// 用户上线
|
||||
case WsResponseMessageType.ONLINE: {
|
||||
console.log('上线')
|
||||
useMitt.emit(WsResponseMessageType.ONLINE, params.data as OnStatusChangeType)
|
||||
break
|
||||
}
|
||||
// 用户下线
|
||||
case WsResponseMessageType.OFFLINE: {
|
||||
console.log('下线')
|
||||
useMitt.emit(WsResponseMessageType.OFFLINE)
|
||||
break
|
||||
}
|
||||
// 用户 token 过期
|
||||
case WsResponseMessageType.TOKEN_EXPIRED: {
|
||||
console.log('token过期')
|
||||
localStorage.removeItem('TOKEN')
|
||||
useMitt.emit(WsResponseMessageType.TOKEN_EXPIRED, params.data as WsTokenExpire)
|
||||
break
|
||||
}
|
||||
// 小黑子的发言在禁用后,要删除他的发言
|
||||
case WsResponseMessageType.INVALID_USER: {
|
||||
console.log('无效用户')
|
||||
useMitt.emit(WsResponseMessageType.INVALID_USER, params.data as { uid: number })
|
||||
break
|
||||
}
|
||||
// 点赞、倒赞消息通知
|
||||
case WsResponseMessageType.MSG_MARK_ITEM: {
|
||||
console.log('点赞')
|
||||
useMitt.emit(WsResponseMessageType.MSG_MARK_ITEM, params.data as { markList: MarkItemType[] })
|
||||
break
|
||||
}
|
||||
// 消息撤回通知
|
||||
case WsResponseMessageType.MSG_RECALL: {
|
||||
console.log('撤回')
|
||||
useMitt.emit(WsResponseMessageType.MSG_RECALL, params.data as { data: RevokedMsgType })
|
||||
break
|
||||
}
|
||||
// 新好友申请
|
||||
case WsResponseMessageType.REQUEST_NEW_FRIEND: {
|
||||
// TODO: 发送申请后其他人没有接收到好友申请请求,后端查看是否有问题
|
||||
console.log('好友申请')
|
||||
useMitt.emit(WsResponseMessageType.REQUEST_NEW_FRIEND, params.data as { uid: number; unreadCount: number })
|
||||
break
|
||||
}
|
||||
// 成员变动
|
||||
case WsResponseMessageType.NEW_FRIEND_SESSION: {
|
||||
console.log('新好友')
|
||||
useMitt.emit(
|
||||
WsResponseMessageType.NEW_FRIEND_SESSION,
|
||||
params.data as {
|
||||
roomId: number
|
||||
uid: number
|
||||
changeType: ChangeTypeEnum
|
||||
activeStatus: OnlineEnum
|
||||
lastOptTime: number
|
||||
// 等待授权
|
||||
case WsResponseMessageType.WAITING_AUTHORIZE: {
|
||||
console.log('等待授权')
|
||||
useMitt.emit(WsResponseMessageType.WAITING_AUTHORIZE)
|
||||
break
|
||||
}
|
||||
// 登录成功
|
||||
case WsResponseMessageType.LOGIN_SUCCESS: {
|
||||
console.log('登录成功')
|
||||
useMitt.emit(WsResponseMessageType.LOGIN_SUCCESS, params.data as LoginSuccessResType)
|
||||
break
|
||||
}
|
||||
// 收到消息
|
||||
case WsResponseMessageType.RECEIVE_MESSAGE: {
|
||||
const message = params.data as MessageType
|
||||
// TODO: 暂时使用去重复的逻辑,后续优化
|
||||
if (this.#isMessageProcessed(message.message.id)) {
|
||||
break
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
console.log('接收到未处理类型的消息:', params)
|
||||
break
|
||||
useMitt.emit(WsResponseMessageType.RECEIVE_MESSAGE, message)
|
||||
break
|
||||
}
|
||||
// 用户上线
|
||||
case WsResponseMessageType.ONLINE: {
|
||||
console.log('上线')
|
||||
useMitt.emit(WsResponseMessageType.ONLINE, params.data as OnStatusChangeType)
|
||||
break
|
||||
}
|
||||
// 用户下线
|
||||
case WsResponseMessageType.OFFLINE: {
|
||||
console.log('下线')
|
||||
useMitt.emit(WsResponseMessageType.OFFLINE)
|
||||
break
|
||||
}
|
||||
// 用户 token 过期
|
||||
case WsResponseMessageType.TOKEN_EXPIRED: {
|
||||
console.log('token过期')
|
||||
localStorage.removeItem('TOKEN')
|
||||
useMitt.emit(WsResponseMessageType.TOKEN_EXPIRED, params.data as WsTokenExpire)
|
||||
break
|
||||
}
|
||||
// 小黑子的发言在禁用后,要删除他的发言
|
||||
case WsResponseMessageType.INVALID_USER: {
|
||||
console.log('无效用户')
|
||||
useMitt.emit(WsResponseMessageType.INVALID_USER, params.data as { uid: number })
|
||||
break
|
||||
}
|
||||
// 点赞、倒赞消息通知
|
||||
case WsResponseMessageType.MSG_MARK_ITEM: {
|
||||
console.log('点赞')
|
||||
useMitt.emit(WsResponseMessageType.MSG_MARK_ITEM, params.data as { markList: MarkItemType[] })
|
||||
break
|
||||
}
|
||||
// 消息撤回通知
|
||||
case WsResponseMessageType.MSG_RECALL: {
|
||||
console.log('撤回')
|
||||
useMitt.emit(WsResponseMessageType.MSG_RECALL, params.data as { data: RevokedMsgType })
|
||||
break
|
||||
}
|
||||
// 新好友申请
|
||||
case WsResponseMessageType.REQUEST_NEW_FRIEND: {
|
||||
// TODO: 发送申请后其他人没有接收到好友申请请求,后端查看是否有问题
|
||||
console.log('好友申请')
|
||||
useMitt.emit(WsResponseMessageType.REQUEST_NEW_FRIEND, params.data as { uid: number; unreadCount: number })
|
||||
break
|
||||
}
|
||||
// 成员变动
|
||||
case WsResponseMessageType.NEW_FRIEND_SESSION: {
|
||||
console.log('新好友')
|
||||
useMitt.emit(
|
||||
WsResponseMessageType.NEW_FRIEND_SESSION,
|
||||
params.data as {
|
||||
roomId: number
|
||||
uid: number
|
||||
changeType: ChangeTypeEnum
|
||||
activeStatus: OnlineEnum
|
||||
lastOptTime: number
|
||||
}
|
||||
)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
console.log('接收到未处理类型的消息:', params)
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error)
|
||||
// 可以添加错误上报逻辑
|
||||
return
|
||||
}
|
||||
}
|
||||
// TODO: 暂时使用去重复的逻辑,后续优化
|
||||
#isMessageProcessed(msgId: number): boolean {
|
||||
if (this.#processedMsgIds.has(msgId)) {
|
||||
const now = Date.now()
|
||||
const lastProcessed = this.#processedMsgCache.get(msgId)
|
||||
|
||||
if (lastProcessed && now - lastProcessed < 5000) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 添加到已处理集合
|
||||
this.#processedMsgIds.add(msgId)
|
||||
// 清理过期缓存
|
||||
if (this.#processedMsgCache.size >= this.#MAX_CACHE_SIZE) {
|
||||
const oldestEntries = Array.from(this.#processedMsgCache.entries())
|
||||
.sort(([, a], [, b]) => a - b)
|
||||
.slice(0, Math.floor(this.#MAX_CACHE_SIZE / 2))
|
||||
|
||||
// 设置5秒后从集合中删除
|
||||
setTimeout(() => {
|
||||
this.#processedMsgIds.delete(msgId)
|
||||
}, 5000)
|
||||
oldestEntries.forEach(([key]) => this.#processedMsgCache.delete(key))
|
||||
}
|
||||
|
||||
this.#processedMsgCache.set(msgId, now)
|
||||
return false
|
||||
}
|
||||
|
||||
destroy() {
|
||||
worker.postMessage(JSON.stringify({ type: 'clearReconnectTimer' }))
|
||||
worker.terminate()
|
||||
this.#tasks = []
|
||||
this.#processedMsgCache.clear()
|
||||
this.#connectReady = false
|
||||
}
|
||||
}
|
||||
|
||||
export default new WS()
|
||||
|
||||
@@ -28,6 +28,14 @@ let isFirstInit = false
|
||||
// 撤回消息的过期时间
|
||||
const RECALL_EXPIRATION_TIME = 2 * 60 * 1000 // 2分钟,单位毫秒
|
||||
|
||||
// 创建src/workers/timer.worker.ts
|
||||
const timerWorker = new Worker(new URL('../workers/timer.worker.ts', import.meta.url))
|
||||
|
||||
// 添加错误处理
|
||||
timerWorker.onerror = (error) => {
|
||||
console.error('[Worker Error]', error)
|
||||
}
|
||||
|
||||
export const useChatStore = defineStore(
|
||||
'chat',
|
||||
() => {
|
||||
@@ -59,7 +67,7 @@ export const useChatStore = defineStore(
|
||||
// 存储撤回的消息内容和时间
|
||||
const recalledMessages = reactive<Map<number, RecalledMessage>>(new Map())
|
||||
// 存储每条撤回消息的过期定时器
|
||||
const expirationTimers = new Map<number, number>()
|
||||
const expirationTimers = new Map<number, boolean>()
|
||||
|
||||
// 当前聊天室的消息Map计算属性
|
||||
const currentMessageMap = computed({
|
||||
@@ -222,9 +230,10 @@ export const useChatStore = defineStore(
|
||||
const getSessionList = async (isFresh = false) => {
|
||||
if (!isFresh && (sessionOptions.isLast || sessionOptions.isLoading)) return
|
||||
sessionOptions.isLoading = true
|
||||
// TODO: 这里先请求100条会话列表,后续优化
|
||||
const response = await apis
|
||||
.getSessionList({
|
||||
pageSize: sessionList.length > pageSize ? sessionList.length : pageSize,
|
||||
pageSize: sessionList.length > 100 ? sessionList.length : 100,
|
||||
cursor: isFresh || !sessionOptions.cursor ? '' : sessionOptions.cursor
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -419,15 +428,15 @@ export const useChatStore = defineStore(
|
||||
recallTime
|
||||
})
|
||||
|
||||
// 为这条消息设置过期定时器 TODO: setTimeout会有精度问题
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
recalledMessages.delete(msgId)
|
||||
expirationTimers.delete(msgId)
|
||||
triggerMessageMapUpdate()
|
||||
}, RECALL_EXPIRATION_TIME)
|
||||
// 使用 Worker 来处理定时器
|
||||
timerWorker.postMessage({
|
||||
type: 'startTimer',
|
||||
msgId,
|
||||
duration: RECALL_EXPIRATION_TIME
|
||||
})
|
||||
|
||||
// 存储定时器ID以便清理
|
||||
expirationTimers.set(msgId, timeoutId)
|
||||
// 记录这个消息ID已经有了定时器
|
||||
expirationTimers.set(msgId, true)
|
||||
|
||||
message.message.type = MsgEnum.RECALL
|
||||
const cacheUser = cachedStore.userCachedList[data.recallUid]
|
||||
@@ -464,14 +473,6 @@ export const useChatStore = defineStore(
|
||||
return recalledMessages.get(msgId)
|
||||
}
|
||||
|
||||
// 清理所有定时器
|
||||
const clearAllExpirationTimers = () => {
|
||||
expirationTimers.forEach((timerId) => {
|
||||
clearTimeout(timerId)
|
||||
})
|
||||
expirationTimers.clear()
|
||||
}
|
||||
|
||||
// 删除消息
|
||||
const deleteMsg = (msgId: number) => {
|
||||
currentMessageMap.value?.delete(msgId)
|
||||
@@ -526,6 +527,39 @@ export const useChatStore = defineStore(
|
||||
sessionList.splice(index, 1)
|
||||
}
|
||||
|
||||
// 监听 Worker 消息
|
||||
timerWorker.onmessage = (e) => {
|
||||
const { type, msgId } = e.data
|
||||
|
||||
if (type === 'timeout') {
|
||||
console.log(`[Timeout] 消息ID: ${msgId} 已过期`)
|
||||
recalledMessages.delete(msgId)
|
||||
expirationTimers.delete(msgId)
|
||||
triggerMessageMapUpdate()
|
||||
} else if (type === 'allTimersCompleted') {
|
||||
// 所有定时器都完成了,可以安全地清理资源
|
||||
clearAllExpirationTimers()
|
||||
terminateWorker()
|
||||
}
|
||||
}
|
||||
|
||||
// 终止 worker
|
||||
const terminateWorker = () => {
|
||||
timerWorker.terminate()
|
||||
}
|
||||
|
||||
// 清理所有定时器
|
||||
const clearAllExpirationTimers = () => {
|
||||
expirationTimers.forEach((_, msgId) => {
|
||||
// 通知 worker 停止对应的定时器
|
||||
timerWorker.postMessage({
|
||||
type: 'clearTimer',
|
||||
msgId
|
||||
})
|
||||
})
|
||||
expirationTimers.clear()
|
||||
}
|
||||
|
||||
return {
|
||||
getMsgIndex,
|
||||
chatMessageList,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const worker: Worker = new Worker(new URL('./Worker.ts', import.meta.url), {
|
||||
type: 'module'
|
||||
})
|
||||
@@ -71,7 +71,7 @@
|
||||
class="item-box w-full h-75px mb-5px"
|
||||
v-for="item in groupChatList"
|
||||
:key="item.roomId">
|
||||
<n-flex v-slide align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
|
||||
<n-flex align="center" :size="10" class="h-75px pl-6px pr-8px flex-1 truncate">
|
||||
<n-avatar
|
||||
round
|
||||
bordered
|
||||
|
||||
111
src/workers/timer.worker.ts
Normal file
111
src/workers/timer.worker.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
// 修改类型定义以支持字符串和数字类型的key
|
||||
type TimerId = number | string
|
||||
type TimerInfo = {
|
||||
timerId: NodeJS.Timeout
|
||||
debugId: NodeJS.Timeout | null
|
||||
}
|
||||
|
||||
// 存储定时器ID和调试定时器ID
|
||||
const timerIds = new Map<TimerId, TimerInfo>()
|
||||
|
||||
// 添加一个计数器来跟踪活动的定时器数量
|
||||
let activeTimers = 0
|
||||
|
||||
// 检查并通知所有定时器是否完成
|
||||
const checkAllTimersCompleted = () => {
|
||||
if (activeTimers === 0) {
|
||||
self.postMessage({ type: 'allTimersCompleted' })
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试信息打印函数
|
||||
const logDebugInfo = (msgId: number, remainingTime: number) => {
|
||||
console.log(`[Worker Debug] 消息ID: ${msgId}, 剩余时间: ${(remainingTime / 1000).toFixed(1)}秒`)
|
||||
self.postMessage({
|
||||
type: 'debug',
|
||||
msgId,
|
||||
remainingTime,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
self.onmessage = (e) => {
|
||||
const { type, msgId, duration, reconnectCount } = e.data
|
||||
|
||||
switch (type) {
|
||||
case 'startReconnectTimer': {
|
||||
const timerId = setTimeout(() => {
|
||||
self.postMessage({
|
||||
type: 'reconnectTimeout',
|
||||
value: { reconnectCount }
|
||||
})
|
||||
}, e.data.value.delay)
|
||||
|
||||
// 现在可以使用字符串作为key了
|
||||
timerIds.set('reconnect', { timerId, debugId: null })
|
||||
break
|
||||
}
|
||||
|
||||
case 'clearReconnectTimer': {
|
||||
if (timerIds.has('reconnect')) {
|
||||
const { timerId } = timerIds.get('reconnect')!
|
||||
clearTimeout(timerId)
|
||||
timerIds.delete('reconnect')
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'startTimer': {
|
||||
activeTimers++
|
||||
// 使用数字类型的msgId
|
||||
if (timerIds.has(msgId)) {
|
||||
const { timerId, debugId } = timerIds.get(msgId)!
|
||||
clearTimeout(timerId)
|
||||
if (debugId) clearInterval(debugId)
|
||||
timerIds.delete(msgId)
|
||||
}
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
// 立即打印一次初始状态
|
||||
logDebugInfo(msgId, duration)
|
||||
|
||||
// 创建定时打印的间隔器,每1000ms打印一次
|
||||
const debugId = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime
|
||||
const remaining = duration - elapsed
|
||||
if (remaining > 0) {
|
||||
logDebugInfo(msgId, remaining)
|
||||
} else {
|
||||
clearInterval(debugId)
|
||||
}
|
||||
}, 1000) // 每秒打印一次
|
||||
|
||||
const timerId = setTimeout(() => {
|
||||
clearInterval(debugId)
|
||||
console.log('[Worker] 定时器到期:', msgId)
|
||||
self.postMessage({ type: 'timeout', msgId })
|
||||
timerIds.delete(msgId)
|
||||
|
||||
activeTimers--
|
||||
checkAllTimersCompleted()
|
||||
}, duration)
|
||||
|
||||
timerIds.set(msgId, { timerId, debugId })
|
||||
break
|
||||
}
|
||||
|
||||
case 'clearTimer': {
|
||||
console.log('[Worker] 清理定时器:', msgId)
|
||||
if (timerIds.has(msgId)) {
|
||||
const { timerId, debugId } = timerIds.get(msgId)!
|
||||
clearTimeout(timerId)
|
||||
if (debugId) clearInterval(debugId)
|
||||
timerIds.delete(msgId)
|
||||
activeTimers--
|
||||
checkAllTimersCompleted()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 发消息给主进程
|
||||
import { WorkerMsgEnum } from '@/enums'
|
||||
import { ConnectionState, WorkerMsgEnum } from '@/enums'
|
||||
|
||||
const postMsg = ({ type, value }: { type: string; value?: object }) => {
|
||||
self.postMessage(JSON.stringify({ type, value }))
|
||||
@@ -13,11 +13,8 @@ let heartTimer: number | null = null
|
||||
// 重连次数上限
|
||||
const reconnectCountMax = 5
|
||||
let reconnectCount = 0
|
||||
// 重连 timer
|
||||
let timer: null | number = null
|
||||
// 重连🔐
|
||||
let lockReconnect = false
|
||||
// 重连🔐
|
||||
let token: null | string = null
|
||||
|
||||
let clientId: null | string = null
|
||||
@@ -27,14 +24,30 @@ const connectionSend = (value: object) => {
|
||||
connection?.send(JSON.stringify(value))
|
||||
}
|
||||
|
||||
// 添加心跳超时检测
|
||||
let heartbeatTimeout: number | null = null
|
||||
const HEARTBEAT_TIMEOUT = 15000 // 15秒超时
|
||||
|
||||
// 发送心跳 10s 内发送
|
||||
const sendHeartPack = () => {
|
||||
// 10s 检测心跳
|
||||
heartTimer = setInterval(() => {
|
||||
// 心跳消息类型 2
|
||||
connectionSend({ type: 2 })
|
||||
|
||||
// 清除之前的超时计时器
|
||||
if (heartbeatTimeout) {
|
||||
clearTimeout(heartbeatTimeout)
|
||||
}
|
||||
|
||||
// 设置新的超时计时器
|
||||
heartbeatTimeout = setTimeout(() => {
|
||||
console.log('心跳超时,重连...')
|
||||
connection.close()
|
||||
}, HEARTBEAT_TIMEOUT) as any
|
||||
}, 9900) as any
|
||||
}
|
||||
|
||||
// 清除❤️跳 timer
|
||||
const clearHeartPackTimer = () => {
|
||||
if (heartTimer) {
|
||||
@@ -43,33 +56,38 @@ const clearHeartPackTimer = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const getBackoffDelay = (retryCount: number) => {
|
||||
const baseDelay = 1000 // 基础延迟1秒
|
||||
const maxDelay = 30000 // 最大延迟30秒
|
||||
const delay = Math.min(baseDelay * Math.pow(2, retryCount), maxDelay)
|
||||
return delay + Math.random() * 1000 // 添加随机抖动
|
||||
}
|
||||
|
||||
const onCloseHandler = () => {
|
||||
clearHeartPackTimer()
|
||||
// 已经在连接中就不重连了
|
||||
if (lockReconnect) return
|
||||
|
||||
// 标识重连中
|
||||
lockReconnect = true
|
||||
|
||||
// 清除 timer,避免任务堆积。
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
// 达到重连次数上限
|
||||
// 添加重连次数限制
|
||||
if (reconnectCount >= reconnectCountMax) {
|
||||
reconnectCount = 0
|
||||
postMsg({ type: WorkerMsgEnum.WS_ERROR, value: { msg: '连接失败,请检查网络或联系管理员' } })
|
||||
console.log('达到最大重连次数,停止重连')
|
||||
postMsg({
|
||||
type: WorkerMsgEnum.WS_ERROR,
|
||||
value: { msg: '连接失败次数过多,请刷新页面重试' }
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 断线重连
|
||||
timer = setTimeout(async () => {
|
||||
initConnection()
|
||||
reconnectCount++
|
||||
// 标识已经开启重连任务
|
||||
lockReconnect = false
|
||||
}, 2000) as any
|
||||
updateConnectionState(ConnectionState.RECONNECTING)
|
||||
lockReconnect = true
|
||||
|
||||
// 使用 timer worker 替代 setTimeout
|
||||
postMsg({
|
||||
type: 'startReconnectTimer',
|
||||
value: {
|
||||
delay: getBackoffDelay(reconnectCount),
|
||||
reconnectCount
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ws 连接 error
|
||||
@@ -83,14 +101,15 @@ const onConnectError = () => {
|
||||
}
|
||||
// ws 连接 close
|
||||
const onConnectClose = () => {
|
||||
updateConnectionState(ConnectionState.DISCONNECTED)
|
||||
onCloseHandler()
|
||||
token = null
|
||||
postMsg({ type: WorkerMsgEnum.CLOSE })
|
||||
}
|
||||
// ws 连接成功
|
||||
const onConnectOpen = () => {
|
||||
updateConnectionState(ConnectionState.CONNECTED)
|
||||
postMsg({ type: WorkerMsgEnum.OPEN })
|
||||
// 心跳❤️检测
|
||||
sendHeartPack()
|
||||
}
|
||||
// ws 连接 接收到消息
|
||||
@@ -98,6 +117,7 @@ const onConnectMsg = (e: any) => postMsg({ type: WorkerMsgEnum.MESSAGE, value: e
|
||||
|
||||
// 初始化 ws 连接
|
||||
const initConnection = () => {
|
||||
updateConnectionState(ConnectionState.CONNECTING)
|
||||
connection?.removeEventListener('message', onConnectMsg)
|
||||
connection?.removeEventListener('open', onConnectOpen)
|
||||
connection?.removeEventListener('close', onConnectClose)
|
||||
@@ -119,6 +139,14 @@ const initConnection = () => {
|
||||
connection.addEventListener('error', onConnectError)
|
||||
}
|
||||
|
||||
let connectionState = ConnectionState.DISCONNECTED
|
||||
|
||||
// 更新连接状态
|
||||
const updateConnectionState = (newState: ConnectionState) => {
|
||||
connectionState = newState
|
||||
postMsg({ type: 'connectionStateChange', value: { state: connectionState } })
|
||||
}
|
||||
|
||||
self.onmessage = (e: MessageEvent<string>) => {
|
||||
console.log(e.data)
|
||||
const { type, value } = JSON.parse(e.data)
|
||||
@@ -135,5 +163,20 @@ self.onmessage = (e: MessageEvent<string>) => {
|
||||
connectionSend(value)
|
||||
break
|
||||
}
|
||||
case 'reconnectTimeout': {
|
||||
reconnectCount = value.reconnectCount + 1
|
||||
// 如果没有超过最大重连次数才继续重连
|
||||
if (reconnectCount < reconnectCountMax) {
|
||||
initConnection()
|
||||
lockReconnect = false
|
||||
} else {
|
||||
console.log('达到最大重连次数,停止重连')
|
||||
postMsg({
|
||||
type: WorkerMsgEnum.WS_ERROR,
|
||||
value: { msg: '连接失败次数过多,请刷新页面重试' }
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user