Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
Pangea-Agent
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Gavin-Group
Pangea-Agent
Commits
4eed88c9
Commit
4eed88c9
authored
Dec 17, 2025
by
ligaowei
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修复WebSocket消息处理中的类型错误问题:rawData.trim is not a function
parent
19aa1f99
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
400 additions
and
0 deletions
+400
-0
WEBSOCKET_MESSAGE_PROCESSING_FIX.md
WEBSOCKET_MESSAGE_PROCESSING_FIX.md
+89
-0
websocketService.ts
frontend/src/services/websocketService.ts
+311
-0
No files found.
WEBSOCKET_MESSAGE_PROCESSING_FIX.md
0 → 100644
View file @
4eed88c9
# WebSocket消息处理修复说明
## 问题描述
在前端WebSocket服务中,处理文本消息时出现了"rawData.trim is not a function"的错误。错误发生在以下代码段:
```
typescript
const
rawData
=
event
.
data
as
string
;
// ...
if
(
!
rawData
||
rawData
.
trim
().
length
===
0
)
{
// ...
}
```
## 问题原因
1.
**类型假设错误**
:代码假设
`event.data`
总是字符串类型,但实际上WebSocket消息可以是多种类型:
-
`string`
(文本消息)
-
`ArrayBuffer`
(二进制消息)
-
`Blob`
(二进制大对象)
2.
**类型转换不当**
:直接使用
`as string`
进行类型断言,而不是进行类型检查和适当转换。
3.
**方法调用错误**
:当
`event.data`
实际上是
`ArrayBuffer`
或其他非字符串类型时,调用
`.trim()`
方法会导致运行时错误。
## 解决方案
修改WebSocket消息处理逻辑,增加类型检查和适当的类型转换:
```
typescript
// 文本消息处理(兼容旧协议或错误消息)
const
rawData
=
event
.
data
;
// 确保rawData是字符串类型
let
rawString
:
string
;
if
(
typeof
rawData
===
'string'
)
{
rawString
=
rawData
;
}
else
{
// 如果不是字符串,尝试转换为字符串
rawString
=
String
(
rawData
);
}
const
dataLength
=
rawString
?
rawString
.
length
:
0
;
addLog
(
`接收到文本消息,长度:
${
dataLength
}
字符(已弃用,应使用二进制协议)`
,
'debug'
);
if
(
!
rawString
||
rawString
.
trim
().
length
===
0
)
{
addLog
(
'接收到空的WebSocket消息,跳过处理'
,
'warn'
);
return
;
}
// 尝试解析为JSON(仅用于兼容旧数据)
try
{
const
parsedData
=
JSON
.
parse
(
rawString
);
addLog
(
'文本消息JSON解析成功,数据类型: '
+
parsedData
.
type
,
'info'
);
if
(
this
.
options
.
onMessage
)
{
this
.
options
.
onMessage
(
parsedData
);
}
}
catch
(
parseError
)
{
const
errorMsg
=
(
parseError
as
Error
).
message
;
addLog
(
'文本消息JSON解析失败: '
+
errorMsg
+
'(建议检查是否应使用二进制协议)'
,
'warn'
);
}
```
## 修复要点
1.
**类型安全检查**
:使用
`typeof rawData === 'string'`
检查实际类型
2.
**安全类型转换**
:对于非字符串类型,使用
`String(rawData)`
进行转换
3.
**保持功能一致**
:确保修复后的行为与原逻辑一致
4.
**增强健壮性**
:避免因类型不匹配导致的运行时错误
## 测试验证
修复后应验证以下场景:
1.
正常文本消息处理
2.
空消息处理
3.
二进制消息处理(应走二进制分支)
4.
其他类型消息的安全处理
## 相关文件
-
`frontend/src/services/websocketService.ts`
:主要修复文件
-
`frontend/src/services/binaryMessageHandler.ts`
:二进制消息处理相关文件
## 后续建议
1.
强化类型检查,在处理WebSocket消息时明确区分文本和二进制消息
2.
增加更详细的日志记录,便于调试不同类型的WebSocket消息
3.
考虑逐步淘汰文本协议,全面转向更高效的二进制协议
\ No newline at end of file
frontend/src/services/websocketService.ts
0 → 100644
View file @
4eed88c9
// WebSocket服务
import
{
addLog
}
from
'@/utils/logUtils'
;
import
{
BinaryFragmentBuffer
,
handleBinaryMessage
}
from
'./binaryMessageHandler'
;
interface
WebSocketServiceOptions
{
onMessage
?:
(
data
:
any
)
=>
void
;
onOpen
?:
()
=>
void
;
onClose
?:
(
event
:
CloseEvent
)
=>
void
;
onError
?:
(
error
:
any
)
=>
void
;
}
export
class
WebSocketService
{
private
ws
:
WebSocket
|
null
=
null
;
private
url
:
string
=
''
;
private
reconnectAttempts
:
number
=
0
;
private
maxReconnectAttempts
:
number
=
5
;
private
reconnectDelay
:
number
=
3000
;
private
connectionTimeout
:
number
|
null
=
null
;
private
options
:
WebSocketServiceOptions
;
private
binaryFragmentBuffer
:
BinaryFragmentBuffer
=
new
BinaryFragmentBuffer
();
constructor
(
options
:
WebSocketServiceOptions
=
{})
{
this
.
options
=
options
;
}
connect
(
url
:
string
)
{
// 避免重复连接
if
(
this
.
ws
&&
this
.
ws
.
readyState
===
WebSocket
.
OPEN
)
{
addLog
(
'WebSocket连接已存在且处于打开状态'
,
'info'
);
return
;
}
// 如果已有连接但处于其他状态,先关闭它
if
(
this
.
ws
)
{
try
{
addLog
(
`WebSocket当前状态:
${
this
.
ws
.
readyState
}
`
,
'info'
);
this
.
ws
.
close
();
}
catch
(
e
)
{
addLog
(
'关闭旧WebSocket连接时出错: '
+
(
e
as
Error
).
message
,
'error'
);
}
this
.
ws
=
null
;
}
this
.
url
=
url
;
addLog
(
'正在连接WebSocket: '
+
this
.
url
,
'info'
);
try
{
const
ws
=
new
WebSocket
(
this
.
url
);
this
.
ws
=
ws
;
// 设置连接超时
this
.
connectionTimeout
=
window
.
setTimeout
(()
=>
{
if
(
ws
.
readyState
===
WebSocket
.
CONNECTING
)
{
addLog
(
'WebSocket连接超时(30秒未建立连接)。可能的原因:'
,
'error'
);
addLog
(
'1. 后端服务未启动或无法访问 (ws://localhost:8080/ws/dom-sync)'
,
'warn'
);
addLog
(
'2. 防火墙或网络配置阻止WebSocket连接'
,
'warn'
);
addLog
(
'3. 反向代理或负载均衡器未正确配置WebSocket支持'
,
'warn'
);
addLog
(
'4. 握手拦截器验证失败(如JWT Token验证失败)'
,
'warn'
);
try
{
ws
.
close
();
}
catch
(
closeError
)
{
addLog
(
'关闭WebSocket连接时出错: '
+
(
closeError
as
Error
).
message
,
'error'
);
}
// 尝试重连
if
(
this
.
reconnectAttempts
<
this
.
maxReconnectAttempts
)
{
this
.
reconnectAttempts
++
;
const
delay
=
Math
.
min
(
this
.
reconnectDelay
*
Math
.
pow
(
2
,
this
.
reconnectAttempts
-
1
),
30000
);
addLog
(
`WebSocket连接超时,
${
delay
}
ms后进行第
${
this
.
reconnectAttempts
}
次重连...`
,
'warn'
);
setTimeout
(()
=>
this
.
connect
(
this
.
url
),
delay
);
}
else
{
addLog
(
'WebSocket连接超时,已达到最大重连次数,停止重连'
,
'error'
);
addLog
(
'请检查后端服务状态或查看后端日志了解更多信息'
,
'info'
);
}
}
},
30000
);
// 增加到30秒超时,给连接更多时间
// 连接打开事件
ws
.
onopen
=
()
=>
{
if
(
this
.
connectionTimeout
)
{
clearTimeout
(
this
.
connectionTimeout
);
this
.
connectionTimeout
=
null
;
}
this
.
reconnectAttempts
=
0
;
addLog
(
'WebSocket连接已建立,使用二进制协议'
,
'info'
);
// 启动定期清理过期消息缓存
if
(
this
.
cleanupInterval
)
{
clearInterval
(
this
.
cleanupInterval
);
}
this
.
cleanupInterval
=
window
.
setInterval
(()
=>
{
this
.
binaryFragmentBuffer
.
cleanupExpired
();
},
30000
);
// 每30秒检查一次
if
(
this
.
options
.
onOpen
)
{
this
.
options
.
onOpen
();
}
};
// 接收消息事件
ws
.
onmessage
=
(
event
)
=>
{
try
{
// 检查消息类型:二进制或文本
if
(
event
.
data
instanceof
ArrayBuffer
)
{
// 二进制消息处理(推荐使用)
addLog
(
'接收到二进制消息,大小: '
+
(
event
.
data
as
ArrayBuffer
).
byteLength
+
' 字节'
,
'debug'
);
const
success
=
handleBinaryMessage
(
event
.
data
as
ArrayBuffer
,
this
.
binaryFragmentBuffer
,
(
decodedData
:
any
,
encoding
:
number
)
=>
{
// 消息完整接收并处理成功
if
(
this
.
options
.
onMessage
)
{
this
.
options
.
onMessage
(
decodedData
);
}
}
);
if
(
!
success
)
{
addLog
(
'二进制消息处理失败'
,
'warn'
);
}
}
else
{
// 文本消息处理(兼容旧协议或错误消息)
const
rawData
=
event
.
data
;
// 确保rawData是字符串类型
let
rawString
:
string
;
if
(
typeof
rawData
===
'string'
)
{
rawString
=
rawData
;
}
else
{
// 如果不是字符串,尝试转换为字符串
rawString
=
String
(
rawData
);
}
const
dataLength
=
rawString
?
rawString
.
length
:
0
;
addLog
(
`接收到文本消息,长度:
${
dataLength
}
字符(已弃用,应使用二进制协议)`
,
'debug'
);
if
(
!
rawString
||
rawString
.
trim
().
length
===
0
)
{
addLog
(
'接收到空的WebSocket消息,跳过处理'
,
'warn'
);
return
;
}
// 尝试解析为JSON(仅用于兼容旧数据)
try
{
const
parsedData
=
JSON
.
parse
(
rawString
);
addLog
(
'文本消息JSON解析成功,数据类型: '
+
parsedData
.
type
,
'info'
);
if
(
this
.
options
.
onMessage
)
{
this
.
options
.
onMessage
(
parsedData
);
}
}
catch
(
parseError
)
{
const
errorMsg
=
(
parseError
as
Error
).
message
;
addLog
(
'文本消息JSON解析失败: '
+
errorMsg
+
'(建议检查是否应使用二进制协议)'
,
'warn'
);
}
}
}
catch
(
e
)
{
addLog
(
'处理WebSocket消息时发生异常:'
+
(
e
as
Error
).
message
,
'error'
);
addLog
(
'错误堆栈:'
+
(
e
as
Error
).
stack
,
'debug'
);
}
};
// 连接关闭事件
ws
.
onclose
=
(
event
)
=>
{
if
(
this
.
connectionTimeout
)
{
clearTimeout
(
this
.
connectionTimeout
);
this
.
connectionTimeout
=
null
;
}
addLog
(
`WebSocket连接已关闭,代码:
${
event
.
code
}
, 原因:
${
event
.
reason
}
`
,
'info'
);
// 检查关闭原因
if
(
event
.
code
===
1006
)
{
addLog
(
'WebSocket连接异常关闭(代码1006),可能原因:'
,
'warn'
);
addLog
(
'1. 网络中断或连接超时'
,
'info'
);
addLog
(
'2. 服务器主动断开连接'
,
'info'
);
addLog
(
'3. 防火墙、代理或中间件中断连接'
,
'info'
);
}
else
if
(
event
.
code
!==
1000
)
{
addLog
(
`WebSocket异常关闭(代码
${
event
.
code
}
),需要检查服务器日志`
,
'warn'
);
}
// 只有在非正常关闭的情况下才尝试重连
if
(
event
.
code
!==
1000
)
{
// 1000表示正常关闭
if
(
this
.
reconnectAttempts
<
this
.
maxReconnectAttempts
)
{
this
.
reconnectAttempts
++
;
addLog
(
`WebSocket连接已断开,正在尝试第
${
this
.
reconnectAttempts
}
次重连...`
,
'error'
);
// 指数退避重连策略
const
delay
=
Math
.
min
(
this
.
reconnectDelay
*
Math
.
pow
(
2
,
this
.
reconnectAttempts
-
1
),
30000
);
setTimeout
(()
=>
this
.
connect
(
this
.
url
),
delay
);
// 最大延迟30秒
}
else
{
addLog
(
'WebSocket连接已断开,已达到最大重连次数,停止重连'
,
'error'
);
// 重置重连次数,以便用户手动重新连接
this
.
reconnectAttempts
=
0
;
}
}
else
{
addLog
(
'WebSocket连接正常关闭,无需重连'
,
'info'
);
}
if
(
this
.
options
.
onClose
)
{
this
.
options
.
onClose
(
event
);
}
};
// 连接错误事件
ws
.
onerror
=
(
error
)
=>
{
if
(
this
.
connectionTimeout
)
{
clearTimeout
(
this
.
connectionTimeout
);
this
.
connectionTimeout
=
null
;
}
// 检查当前WebSocket状态来判断错误类型
const
readyState
=
ws
.
readyState
;
let
errorDescription
=
''
;
if
(
readyState
===
WebSocket
.
CLOSED
)
{
errorDescription
=
'握手失败:连接已关闭。通常是服务器拒绝连接(可能是认证失败、Token过期或无效)'
;
}
else
if
(
readyState
===
WebSocket
.
CLOSING
)
{
errorDescription
=
'连接正在关闭中'
;
}
else
if
(
readyState
===
WebSocket
.
CONNECTING
)
{
errorDescription
=
'连接超时或连接被中断'
;
}
else
{
errorDescription
=
'未知错误'
;
}
addLog
(
'WebSocket错误 [状态码: '
+
readyState
+
']:'
+
errorDescription
,
'error'
);
addLog
(
'错误详情:'
+
(
error
as
any
).
message
,
'error'
);
addLog
(
'URL: '
+
this
.
url
,
'error'
);
// 检查网络连接状态
if
(
!
navigator
.
onLine
)
{
addLog
(
'网络诊断:网络连接不可用,请检查您的网络连接'
,
'error'
);
}
else
{
addLog
(
'网络诊断:网络连接正常,问题可能在服务器端'
,
'warn'
);
}
// 如果连接失败,尝试重新连接
if
(
this
.
reconnectAttempts
<
this
.
maxReconnectAttempts
)
{
this
.
reconnectAttempts
++
;
const
delay
=
Math
.
min
(
this
.
reconnectDelay
*
Math
.
pow
(
2
,
this
.
reconnectAttempts
-
1
),
30000
);
addLog
(
`WebSocket连接错误,
${
delay
}
ms后进行第
${
this
.
reconnectAttempts
}
次重连...`
,
'warn'
);
setTimeout
(()
=>
this
.
connect
(
this
.
url
),
delay
);
}
else
{
addLog
(
'WebSocket连接错误,已达到最大重连次数('
+
this
.
maxReconnectAttempts
+
'),停止自动重连'
,
'error'
);
addLog
(
'诊断建议:'
,
'info'
);
addLog
(
'1. 检查后端服务是否正常运行(默认: ws://localhost:8080/ws/dom-sync)'
,
'info'
);
addLog
(
'2. 检查JWT Token是否过期(Token有效期: 2小时)'
,
'info'
);
addLog
(
'3. 检查浏览器控制台是否有详细的错误日志'
,
'info'
);
addLog
(
'4. 检查后端是否输出认证拒绝的原因日志'
,
'info'
);
addLog
(
'5. 可尝试手动刷新页面重新连接'
,
'info'
);
// 重置重连次数,以便用户手动重新连接
this
.
reconnectAttempts
=
0
;
}
if
(
this
.
options
.
onError
)
{
this
.
options
.
onError
(
error
);
}
};
}
catch
(
e
)
{
addLog
(
'创建WebSocket连接失败: '
+
(
e
as
Error
).
message
,
'error'
);
addLog
(
'详细信息: '
+
(
e
as
Error
).
stack
,
'debug'
);
addLog
(
'诊断帮助:'
,
'info'
);
addLog
(
'1. 检查URL格式是否正确: '
+
this
.
url
,
'info'
);
addLog
(
'2. WebSocket创建失败可能指示浏览器不支持WebSocket或安全需求不满足'
,
'warn'
);
addLog
(
'3. 检查HTTPS页面是否使用了WSS协议'
,
'warn'
);
// 如果创建连接失败,也尝试重连
if
(
this
.
reconnectAttempts
<
this
.
maxReconnectAttempts
)
{
this
.
reconnectAttempts
++
;
const
delay
=
Math
.
min
(
this
.
reconnectDelay
*
Math
.
pow
(
2
,
this
.
reconnectAttempts
-
1
),
30000
);
addLog
(
`WebSocket连接创建失败,
${
delay
}
ms后进行第
${
this
.
reconnectAttempts
}
次重连...`
,
'warn'
);
setTimeout
(()
=>
this
.
connect
(
this
.
url
),
delay
);
}
else
{
addLog
(
'WebSocket连接创建失败,已达到最大重连次数,停止重连'
,
'error'
);
addLog
(
'提示:请检查网络连接或后端服务是否正常运行'
,
'info'
);
}
}
}
send
(
message
:
string
)
{
if
(
this
.
ws
&&
this
.
ws
.
readyState
===
WebSocket
.
OPEN
)
{
try
{
this
.
ws
.
send
(
message
);
addLog
(
'已发送指令:'
+
message
,
'info'
);
}
catch
(
e
)
{
addLog
(
'发送指令失败:'
+
(
e
as
Error
).
message
,
'error'
);
}
}
else
{
addLog
(
'WebSocket连接已断开,无法发送指令'
,
'error'
);
// 尝试重新连接
this
.
connect
(
this
.
url
);
}
}
close
()
{
if
(
this
.
ws
)
{
try
{
this
.
ws
.
close
();
addLog
(
'WebSocket连接已关闭'
,
'info'
);
}
catch
(
e
)
{
addLog
(
'关闭WebSocket连接时出错: '
+
(
e
as
Error
).
message
,
'error'
);
}
this
.
ws
=
null
;
}
// 清理定时器
if
(
this
.
cleanupInterval
)
{
clearInterval
(
this
.
cleanupInterval
);
this
.
cleanupInterval
=
null
;
}
}
isConnected
():
boolean
{
return
this
.
ws
!==
null
&&
this
.
ws
.
readyState
===
WebSocket
.
OPEN
;
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment