feat:RESTful API架构改WebSocket架构-执行结果可以分步显示版本

This commit is contained in:
liailing1026
2026-01-22 17:22:30 +08:00
parent 1c8036adf1
commit 786c674d21
15 changed files with 2591 additions and 308 deletions

View File

@@ -0,0 +1,270 @@
/**
* WebSocket 客户端封装
* 基于 socket.io-client 实现
*/
import { io, Socket } from 'socket.io-client'
interface WebSocketConfig {
url?: string
reconnectionAttempts?: number
reconnectionDelay?: number
timeout?: number
}
interface RequestMessage {
id: string
action: string
data: any
}
interface ResponseMessage {
id: string
status: 'success' | 'error' | 'streaming' | 'complete'
data?: any
error?: string
stage?: string
message?: string
[key: string]: any
}
interface StreamProgressCallback {
(data: any): void
}
type RequestHandler = {
resolve: (value: any) => void
reject: (error: Error) => void
timer?: ReturnType<typeof setTimeout>
onProgress?: StreamProgressCallback
}
class WebSocketClient {
private socket: Socket | null = null
private requestHandlers = new Map<string, RequestHandler>()
private streamHandlers = new Map<string, StreamProgressCallback>()
private config: Required<WebSocketConfig>
private isConnected = false
constructor() {
this.config = {
url: '',
reconnectionAttempts: 5,
reconnectionDelay: 1000,
timeout: 300000, // 5分钟超时
}
}
/**
* 连接到WebSocket服务器
*/
connect(url?: string): Promise<void> {
return new Promise((resolve, reject) => {
const wsUrl = url || this.config.url || window.location.origin
this.socket = io(wsUrl, {
transports: ['websocket', 'polling'],
reconnection: true,
reconnectionAttempts: this.config.reconnectionAttempts,
reconnectionDelay: this.config.reconnectionDelay,
})
this.socket.on('connect', () => {
this.isConnected = true
resolve()
})
this.socket.on('connect_error', (error) => {
reject(error)
})
this.socket.on('disconnect', (reason) => {
this.isConnected = false
})
this.socket.on('connected', (data) => {
// Server connected message
})
// 监听响应消息
this.socket.on('response', (response: ResponseMessage) => {
const { id, status, data, error } = response
const handler = this.requestHandlers.get(id)
if (handler) {
// 清除超时定时器
if (handler.timer) {
clearTimeout(handler.timer)
}
if (status === 'success') {
handler.resolve(data)
} else {
handler.reject(new Error(error || 'Unknown error'))
}
// 删除处理器
this.requestHandlers.delete(id)
}
})
// 监听流式进度消息
this.socket.on('progress', (response: ResponseMessage) => {
const { id, status, data, error } = response
// 首先检查是否有对应的流式处理器
const streamCallback = this.streamHandlers.get(id)
if (streamCallback) {
if (status === 'streaming') {
// 解析 data 字段JSON 字符串)并传递给回调
try {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
streamCallback(parsedData)
} catch (e) {
// Failed to parse progress data
}
} else if (status === 'complete') {
this.streamHandlers.delete(id)
streamCallback({ type: 'complete' })
} else if (status === 'error') {
this.streamHandlers.delete(id)
streamCallback({ type: 'error', error })
}
return
}
// 检查是否有对应的普通请求处理器支持send()方法的进度回调)
const requestHandler = this.requestHandlers.get(id)
if (requestHandler && requestHandler.onProgress) {
// 解析 data 字段并传递给进度回调
try {
const parsedData = typeof data === 'string' ? JSON.parse(data) : data
requestHandler.onProgress(parsedData)
} catch (e) {
// Failed to parse progress data
}
}
})
// 心跳检测
this.socket.on('pong', () => {
// Pong received
})
})
}
/**
* 发送请求(双向通信,支持可选的进度回调)
*/
send(action: string, data: any, timeout?: number, onProgress?: StreamProgressCallback): Promise<any> {
if (!this.socket || !this.isConnected) {
return Promise.reject(new Error('WebSocket未连接'))
}
return new Promise((resolve, reject) => {
const requestId = `${action}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
// 设置超时
const timeoutMs = timeout || this.config.timeout
const timer = setTimeout(() => {
if (this.requestHandlers.has(requestId)) {
this.requestHandlers.delete(requestId)
reject(new Error(`Request timeout: ${action}`))
}
}, timeoutMs)
// 保存处理器(包含可选的进度回调)
this.requestHandlers.set(requestId, { resolve, reject, timer, onProgress })
// 发送消息
this.socket!.emit(action, {
id: requestId,
action,
data,
} as RequestMessage)
})
}
/**
* 订阅流式数据
*/
subscribe(
action: string,
data: any,
onProgress: StreamProgressCallback,
onComplete?: () => void,
onError?: (error: Error) => void,
): void {
if (!this.socket || !this.isConnected) {
onError?.(new Error('WebSocket未连接'))
return
}
const requestId = `${action}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
// 保存流式处理器
const wrappedCallback = (progressData: any) => {
if (progressData?.type === 'complete') {
this.streamHandlers.delete(requestId)
onComplete?.()
} else {
onProgress(progressData)
}
}
this.streamHandlers.set(requestId, wrappedCallback)
// 发送订阅请求
this.socket.emit(action, {
id: requestId,
action,
data,
} as RequestMessage)
}
/**
* 发送心跳
*/
ping(): void {
if (this.socket && this.isConnected) {
this.socket.emit('ping')
}
}
/**
* 断开连接
*/
disconnect(): void {
if (this.socket) {
// 清理所有处理器
this.requestHandlers.forEach((handler) => {
if (handler.timer) {
clearTimeout(handler.timer)
}
})
this.requestHandlers.clear()
this.streamHandlers.clear()
this.socket.disconnect()
this.socket = null
this.isConnected = false
}
}
/**
* 获取连接状态
*/
get connected(): boolean {
return this.isConnected && this.socket?.connected === true
}
/**
* 获取Socket ID
*/
get id(): string | undefined {
return this.socket?.id
}
}
// 导出单例
export default new WebSocketClient()