Files
nl-im-uniapp/src/api/websocket/index.ts
2026-07-08 08:18:02 +08:00

245 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WebSocket 管理器 - UniApp 适配版
*/
import type { ChatMessage } from '@/types/api'
import type { MomentNotifPayload } from '@/types/moment'
export interface WebSocketMessage {
request_type?: string
clientId?: string
data?: ChatMessage | MomentNotifPayload
}
export type MessageHandler = (message: ChatMessage) => void
export type SignalHandler = (message: ChatMessage) => void
export type MomentNotifHandler = (payload: MomentNotifPayload) => void
class WebSocketManager {
private socketTask: UniApp.SocketTask | null = null
private clientId: string | null = null
private userId: string | null = null
private messageHandlers: MessageHandler[] = []
private signalHandlers: SignalHandler[] = []
private momentNotifHandlers: MomentNotifHandler[] = []
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 3000
private recentMomentNotifs: Set<string> = new Set()
private notifCacheTimeout = 5000
/**
* 连接 WebSocket
*/
connect(userId: string): Promise<void> {
return new Promise((resolve, reject) => {
if (this.socketTask) {
resolve()
return
}
this.userId = userId
// TODO: 替换为实际的 WebSocket 地址
const wsUrl = `ws://127.0.0.1:12080/ws?user_id=${userId}`
// const wsUrl = `wss://g-ws.nailaoyun.cn/ws?user_id=${userId}`
this.socketTask = uni.connectSocket({
url: wsUrl,
success: () => {
console.log('WebSocket connecting...')
},
fail: (err) => {
console.error('WebSocket connect failed:', err)
reject(err)
}
})
this.socketTask.onOpen(() => {
console.log('✅ WebSocket connected')
this.reconnectAttempts = 0
resolve()
})
this.socketTask.onMessage((res) => {
try {
const data = (res.data as string).trim()
const lines = data.split('\n').filter((line: string) => line.trim())
for (const line of lines) {
try {
const payload: WebSocketMessage = JSON.parse(line)
// 接收 clientId
if (payload.clientId) {
this.clientId = payload.clientId
}
// 处理接收消息
if (payload.request_type === 'receive_message' && payload.data) {
this.handleMessage(payload.data as ChatMessage)
}
// 处理朋友圈通知
if (payload.request_type === 'moment_notification' && payload.data) {
this.handleMomentNotification(payload.data as MomentNotifPayload)
}
} catch {
// 忽略单行解析错误
}
}
} catch (error) {
console.error('WebSocket message parse error:', error)
}
})
this.socketTask.onError((error) => {
console.error('WebSocket error:', error)
reject(error)
})
this.socketTask.onClose(() => {
console.log('WebSocket closed')
this.socketTask = null
this.attemptReconnect()
})
})
}
/**
* 断开连接
*/
disconnect() {
if (this.socketTask) {
this.socketTask.close({})
this.socketTask = null
}
this.clientId = null
this.userId = null
this.messageHandlers = []
this.signalHandlers = []
this.momentNotifHandlers = []
}
/**
* 获取 ClientID
*/
getClientId(): string | null {
return this.clientId
}
/**
* 添加普通消息处理器
*/
onMessage(handler: MessageHandler) {
if (!this.messageHandlers.includes(handler)) {
this.messageHandlers.push(handler)
}
}
/**
* 移除普通消息处理器
*/
offMessage(handler: MessageHandler) {
const index = this.messageHandlers.indexOf(handler)
if (index > -1) {
this.messageHandlers.splice(index, 1)
}
}
/**
* 添加信令处理器 (WebRTC用)
*/
onSignal(handler: SignalHandler) {
if (!this.signalHandlers.includes(handler)) {
this.signalHandlers.push(handler)
}
}
/**
* 移除信令处理器
*/
offSignal(handler: SignalHandler) {
const index = this.signalHandlers.indexOf(handler)
if (index > -1) {
this.signalHandlers.splice(index, 1)
}
}
/**
* 添加朋友圈通知处理器
*/
onMomentNotification(handler: MomentNotifHandler) {
if (!this.momentNotifHandlers.includes(handler)) {
this.momentNotifHandlers.push(handler)
}
}
/**
* 移除朋友圈通知处理器
*/
offMomentNotification(handler: MomentNotifHandler) {
const index = this.momentNotifHandlers.indexOf(handler)
if (index > -1) {
this.momentNotifHandlers.splice(index, 1)
}
}
/**
* 内部处理朋友圈通知(带去重)
*/
private handleMomentNotification(payload: MomentNotifPayload) {
const notifKey = `${payload.moment_id}_${payload.type}_${payload.from_user?.id || ''}_${payload.comment_id || ''}`
if (this.recentMomentNotifs.has(notifKey)) {
console.log('🔄 跳过重复朋友圈通知:', notifKey)
return
}
this.recentMomentNotifs.add(notifKey)
setTimeout(() => {
this.recentMomentNotifs.delete(notifKey)
}, this.notifCacheTimeout)
this.momentNotifHandlers.forEach((handler) => handler(payload))
}
/**
* 内部处理接收到的消息
*/
private handleMessage(message: ChatMessage) {
// 信令消息message_type = 6- 路由到 signalHandlers
if (message.message_type === 6) {
this.signalHandlers.forEach((handler) => handler(message))
return
}
// 普通消息 - 路由到 messageHandlers
this.messageHandlers.forEach((handler) => handler(message))
}
/**
* 尝试重连
*/
private attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('WebSocket max reconnect attempts reached')
return
}
if (!this.userId) {
return
}
this.reconnectAttempts++
console.log(`WebSocket reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
setTimeout(() => {
if (this.userId) {
this.connect(this.userId).catch(console.error)
}
}, this.reconnectDelay)
}
}
export const wsManager = new WebSocketManager()