67 KiB
修改记录
2025-08-12
WebSocket心跳任务启动时机修复
问题:心跳任务应该在客户端连接成功后才启动,而不是在创建客户端时就启动
解决方案:
- 修改
create_client方法,移除心跳任务启动逻辑 - 在
connect_client方法中添加心跳任务启动逻辑 - 为WebSocketClient类添加heartbeat_interval属性
文件变更:
- 更新
app/core/websocket/manager.py- 修改心跳任务启动时机 - 更新
app/core/websocket/client.py- 添加heartbeat_interval属性
修改内容:
- create_client方法优化:
async def create_client(self, name: str, url: str, heartbeat_interval: int = 120) -> WebSocketClient:
# ... 创建客户端和Channel ...
# 保存心跳间隔配置,在连接成功后启动
client.heartbeat_interval = heartbeat_interval
logger.info(f"WebSocket管理器创建客户端: {name} -> {url}")
return client
- connect_client方法增强:
async def connect_client(self, name: str) -> bool:
# 连接客户端
success = await client.connect()
# 如果连接成功,启动心跳任务
if success and hasattr(client, 'heartbeat_interval'):
try:
await self._start_heartbeat_task(name, client.heartbeat_interval)
logger.info(f"WebSocket管理器连接成功后启动心跳任务: {name}")
except Exception as e:
logger.error(f"心跳任务启动失败: {name} - {e}")
return success
- WebSocketClient类增强:
def __init__(self, url: str, name: str = "default"):
# ... 其他属性 ...
self.heartbeat_interval: Optional[int] = None # 心跳间隔配置
优化效果:
- ✅ 心跳任务在连接成功后启动,避免无效的心跳
- ✅ 心跳间隔配置保存在客户端对象中
- ✅ 连接失败时不会启动心跳任务
- ✅ 更好的资源管理和错误处理
设计原则:
- 心跳任务只在连接成功时启动
- 心跳间隔配置与客户端绑定
- 连接失败时不会产生无效的心跳任务
- 保持清晰的职责分离
日志系统完全统一 - 移除冗余文件
问题:项目前期不需要向后兼容,log.py 文件造成冗余和混乱
get_enhanced_logger()只是简单包装了get_structured_logger()- 两个文件功能重复,增加维护成本
- 导入混乱,不利于统一管理
解决方案:
- 删除
app/utils/log.py文件 - 将所有使用
get_enhanced_logger()的文件改为直接使用get_structured_logger() - 统一日志使用方式
文件变更:
- 删除
app/utils/log.py- 移除冗余文件 - 更新所有测试文件 - 统一使用结构化日志
- 更新
run.py- 使用结构化日志
修改内容:
# 所有文件统一改为
from app.utils.structured_log import get_structured_logger, LogLevel
logger = get_structured_logger(__name__, LogLevel.DEBUG)
更新的文件列表:
- test_websocket_ssl_fix.py
- test_websocket_ssl.py
- test_websocket_connection.py
- test_websocket_api_logic.py
- test_websocket_api.py
- test_logging_fix.py
- test_logging.py
- test_input_output_logging.py
- test_heartbeat_interval.py
- test_enhanced_logger.py
- test_api_only.py
- run.py
优化效果:
- ✅ 完全统一日志使用方式
- ✅ 简化架构,移除不必要的包装层
- ✅ 提高性能,减少函数调用开销
- ✅ 便于维护,单一日志系统
日志系统冗余优化 - 简化架构
问题:structured_log.py 与 log.py 之间存在功能冗余,增加了维护成本
get_enhanced_logger()只是简单包装了get_structured_logger()- 两个文件都创建了默认日志记录器
- 功能分散,不利于统一管理
解决方案:
- 简化
log.py为纯向后兼容包装器 - 移除
structured_log.py中的默认日志记录器创建 - 明确标记各功能的用途和状态
文件变更:
- 更新
app/utils/log.py- 简化为向后兼容包装器 - 更新
app/utils/structured_log.py- 移除默认日志记录器创建
修改内容:
# log.py 优化
"""
日志模块 - 向后兼容包装器
提供传统日志格式的兼容性支持
"""
def get_enhanced_logger(name: str, level: LogLevel = LogLevel.INFO):
"""获取增强的结构化日志记录器(向后兼容)"""
return get_structured_logger(name, level)
# structured_log.py 优化
# 移除自动创建默认日志记录器
# default_logger = get_structured_logger("TermControlAgent")
优化效果:
- ✅ 简化架构,移除不必要的包装层
- ✅ 提高性能,减少函数调用开销
- ✅ 统一使用方式,便于维护
- ✅ 保持向后兼容性
日志系统统一化 - 全部采用结构化日志
问题:项目中存在两种日志使用方式,不利于统一管理和维护
- 部分文件使用
from app.utils.log import get_enhanced_logger - 部分文件使用
from app.utils.structured_log import get_structured_logger - 测试文件中仍在使用旧的
get_logger()方法
解决方案:
- 统一所有业务代码和核心代码使用
get_structured_logger() - 移除对
get_enhanced_logger()的依赖 - 更新所有测试文件使用新的日志方式
- 保持
log.py中的get_logger()仅用于向后兼容
文件变更:
- 更新所有业务代码文件 - 统一使用
get_structured_logger() - 更新所有核心代码文件 - 统一使用
get_structured_logger() - 更新所有测试文件 - 统一使用
get_enhanced_logger()或get_structured_logger()
修改内容:
# 统一前:
from app.utils.log import get_enhanced_logger, LogLevel
logger = get_enhanced_logger(__name__, LogLevel.DEBUG)
# 统一后:
from app.utils.structured_log import get_structured_logger, LogLevel
logger = get_structured_logger(__name__, LogLevel.DEBUG)
涉及文件:
app/services/auto_discovery_adb_service.pyapp/core/middleware/request.pyapp/core/handlers/exception_handlers.pyapp/core/config/cors.pyapp/core/app/router.pyapp/core/app/factory.py- 所有测试文件
优化效果:
- ✅ 统一日志使用方式,便于维护
- ✅ 减少依赖层级,提高性能
- ✅ 保持向后兼容性
- ✅ 便于统一配置和管理
日志系统TRACE级别问题修复
问题:structured_log.py 中定义了 LogLevel.TRACE,但Python的 logging 模块没有 TRACE 级别,导致 AttributeError: module 'logging' has no attribute 'TRACE'
解决方案:
- 在
structured_log.py中添加自定义TRACE级别到logging模块 - 修复
_log方法中的级别映射,添加默认值处理 - 修复
StructuredLogger初始化中的级别设置问题 - 修复
log.py中的重复日志记录器创建问题
文件变更:
- 更新
app/utils/structured_log.py- 添加TRACE级别支持和错误处理 - 更新
app/utils/log.py- 修复重复日志记录器创建问题
修改内容:
# 添加TRACE级别到logging模块
if not hasattr(logging, 'TRACE'):
logging.TRACE = 5 # 比DEBUG更低
logging.addLevelName(logging.TRACE, 'TRACE')
# 修复_log方法中的级别映射
def _log(self, level: LogLevel, message: str, **kwargs):
"""通用日志方法"""
extra = {
'extra': kwargs,
'level': level.value
}
# 获取对应的logging级别,如果不存在则使用DEBUG
log_level = getattr(logging, level.value, logging.DEBUG)
self.logger.log(log_level, message, extra=extra)
# 修复StructuredLogger初始化
def __init__(self, name: str, level: LogLevel = LogLevel.INFO):
self.logger = logging.getLogger(name)
# 获取对应的logging级别,如果不存在则使用DEBUG
log_level = getattr(logging, level.value, logging.DEBUG)
self.logger.setLevel(log_level)
self.name = name
# 修复log.py中的重复创建问题
try:
default_logger = get_logger("TermControlAgent")
enhanced_logger = get_enhanced_logger("TermControlAgent")
except Exception as e:
# 如果创建失败,创建简单的logger
import logging
default_logger = logging.getLogger("TermControlAgent")
enhanced_logger = None
优化效果:
- ✅ 修复了TRACE级别的
AttributeError问题 - ✅ 添加了自定义TRACE级别支持
- ✅ 增强了错误处理,避免因日志系统问题导致应用崩溃
- ✅ 修复了重复日志记录器创建问题
- ✅ 保持了向后兼容性
验证方法:
# TRACE级别测试
from app.utils.structured_log import get_structured_logger
logger = get_structured_logger('test')
logger.trace('test trace') # 不再报错
# log.py测试
from app.utils.log import get_enhanced_logger
logger = get_enhanced_logger('test')
logger.trace('test trace') # 不再报错
日志系统简化 - 移除模块专用日志文件
日志系统简化 - 移除模块专用日志文件
问题:用户反馈"模块专用日志文件处理器 这样太复杂 问题不好跟踪"
解决方案:简化日志系统,移除模块专用日志文件处理器,只保留三个输出:
- 控制台输出 - 实时查看日志
logs/app.log- 正常日志(DEBUG、INFO、WARNING级别)logs/error.log- 异常日志(ERROR、CRITICAL级别,包含堆栈跟踪)
文件变更:
- 更新
app/utils/structured_log.py- 移除模块专用日志文件处理器
修改内容:
# 移除的代码:
# 模块专用日志文件处理器
module_log_file = f"logs/{self.name}.log"
try:
module_handler = logging.FileHandler(module_log_file, encoding='utf-8')
module_handler.setLevel(logging.DEBUG)
module_handler.setFormatter(StructuredFormatter(include_stack_trace=False))
self.logger.addHandler(module_handler)
except Exception as e:
print(f"警告:无法创建模块日志文件 {module_log_file}: {e}")
优化效果:
- ✅ 简化日志文件管理,只有两个主要日志文件
- ✅ 便于问题跟踪,所有日志集中在一个文件中
- ✅ 减少文件系统开销
- ✅ 保持结构化日志的优势(JSON格式、上下文信息)
- ✅ 异常日志包含完整堆栈跟踪,便于调试
- ✅ 正常日志不包含堆栈跟踪,减少文件大小
日志文件说明:
app.log- 包含所有级别的日志,按时间顺序排列,便于查看完整流程error.log- 只包含错误和严重错误,包含堆栈跟踪,便于快速定位问题- 控制台输出 - 实时显示所有日志,便于开发和调试
使用建议:
- 开发调试:主要查看控制台输出
- 问题排查:查看
error.log快速定位错误 - 流程分析:查看
app.log了解完整执行流程
2025-08-11
WebSocket心跳循环日志增强
问题:WebSocket适配器启动后,_heartbeat_loop 和 _send_loop 的启动日志没有显示,无法确认心跳任务是否正常启动
解决方案:
- 将
_heartbeat_loop和_send_loop的启动日志级别从DEBUG改为INFO - 在
_heartbeat_loop中添加更多调试信息,包括优先级通道的创建和连接状态 - 增强心跳消息发送的日志记录
文件变更:
- 更新
app/core/websocket/adapter.py- 增强心跳循环的日志记录
修改内容:
# 发送循环启动日志级别提升
logger.info(f"发送循环启动: {self.client.name} (out:{self.outbound_channel.name} / in:{self.inbound_channel.name})")
# 心跳循环启动日志级别提升
logger.info(f"心跳循环启动: {self.client.name} (out:{self.outbound_channel.name} / in:{self.inbound_channel.name}) 间隔:{self.heartbeat_interval}秒")
# 添加优先级通道创建和连接日志
logger.info(f"创建优先级通道: {priority_channel_name}")
logger.info(f"优先级通道连接成功: {priority_channel_name}")
# 添加心跳消息发送日志
logger.debug(f"心跳消息已发送: {self.client.name} -> {priority_channel_name}")
优化效果:
- ✅ 可以清楚看到心跳循环和发送循环的启动状态
- ✅ 能够跟踪优先级通道的创建和连接过程
- ✅ 便于调试心跳消息的发送情况
- ✅ 提供更详细的错误信息,包含客户端名称
验证方法:
- 创建WebSocket客户端后,应该能看到以下日志:
- "发送循环启动: test_12 (out:default / in:default)"
- "心跳循环启动: test_12 (out:default / in:default) 间隔:120秒"
- "优先级通道连接成功: default_priority"
日志系统循环导入问题修复
问题:app/utils/log.py 文件存在循环导入问题,导致应用无法启动
- 错误信息:
ImportError: cannot import name 'get_enhanced_logger' from partially initialized module 'app.utils.log' - 根本原因:
log.py导入config.settings,而config.cors又导入log.py,形成循环导入
解决方案:
- 移除
log.py中对app.core.config.settings的导入 - 使用默认配置值替代动态配置
- 保持向后兼容性
文件变更:
- 更新
app/utils/log.py- 修复循环导入问题
修改内容:
# 移除有问题的导入
# from app.core.config.settings import config
# 使用默认值替代动态配置
log_level = level or "INFO" # 默认使用INFO级别
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
修复效果:
- ✅ 解决了循环导入问题
- ✅ 应用可以正常启动
- ✅ 传统日志系统正常工作
- ✅ 结构化日志系统正常工作
- ✅ 保持向后兼容性
测试验证:
- 传统日志测试:
python -c "from app.utils.log import get_logger; logger = get_logger('test'); logger.info('测试')" - 应用启动测试:
python -c "from app.core.app.factory import create_app; print('应用创建成功')" - 结构化日志测试:通过
test_log_write.py验证
影响范围:
- 修复了所有依赖
app.utils.log的模块 - 解决了应用启动失败问题
- 确保日志系统在跨平台环境下正常工作
WebSocket适配器方法跟踪日志
问题:需要为 _heartbeat_loop 和 _send_loop 方法添加入口跟踪日志,便于调试和监控
解决方案:在方法入口处添加简洁的debug级别日志
文件变更:
- 更新
app/core/websocket/adapter.py- 为关键方法添加入口跟踪日志
修改内容:
async def _send_loop(self):
"""发送循环:优先处理优先级Channel,其次处理普通出站Channel"""
logger.debug(f"发送循环启动: {self.client.name} (out:{self.outbound_channel.name} / in:{self.inbound_channel.name})")
try:
# ... 现有代码 ...
async def _heartbeat_loop(self):
"""心跳循环:以优先级消息写入,并由发送循环优先处理"""
logger.debug(f"心跳循环启动: {self.client.name} (out:{self.outbound_channel.name} / in:{self.inbound_channel.name}) 间隔:{self.heartbeat_interval}秒")
try:
# ... 现有代码 ...
优化效果:
- ✅ 便于跟踪WebSocket适配器的生命周期
- ✅ 提供关键方法的执行状态信息
- ✅ 日志简洁,不会产生过多输出
- ✅ 包含客户端名称和通道信息,便于定位问题
应用启动优化 - 移除不必要的WebSocket Channel初始化
应用启动优化 - 移除不必要的WebSocket Channel初始化
问题:startup_event 中初始化默认WebSocket Channels是不必要的,因为:
- Channels是在需要时才创建的(按需创建)
- 真正的客户端连接是在API调用
create_and_connect_client时才进行的 - 启动时创建空的Channels没有实际意义
解决方案:移除 startup_event 中的WebSocket Channel初始化代码
文件变更:
- 更新
app/core/app/factory.py- 移除启动时的WebSocket Channel初始化
修改内容:
# 移除的代码:
# 初始化默认WebSocket Channels
from app.schemas.websocket import WebSocketConfig
from app.core.websocket.manager import websocket_manager
cfg = WebSocketConfig()
for ch in cfg.default_channels:
channel = await websocket_manager.create_channel(ch, cfg.max_channel_size)
await channel.connect()
logger.info("WebSocket默认Channels初始化成功")
优化效果:
- ✅ 减少不必要的启动时间
- ✅ 避免创建无用的空Channels
- ✅ 保持按需创建的设计原则
- ✅ 真正的Channel创建和连接在API调用时进行
设计原则:
- Channels按需创建,避免预创建空Channels
- 客户端连接时自动确保所需Channels存在
- 启动时只初始化必要的服务(如ADB设备监控)
WebSocket连接超时问题修复
问题:WebSocket客户端连接时出现"timed out during opening handshake"错误
ERROR - WebSocket客户端 test_1 连接失败: timed out during opening handshake
根本原因:
- WebSocket连接没有设置超时时间,导致长时间等待
- 心跳间隔配置不一致,代码中默认30秒,但日志显示2分钟
- 缺少专门的超时异常处理
解决方案:
- 增加连接超时配置和异常处理
- 统一心跳间隔配置为120秒(2分钟)
- 添加连接超时测试脚本
文件变更:
- WebSocket客户端超时处理 (
app/core/websocket/client.py):
# 添加连接超时配置
from app.core.config.settings import config
connection_timeout = config.websocket.connection_timeout
# 使用websockets.connect的超时参数
self._websocket = await asyncio.wait_for(
websockets.connect(
self.url,
ssl=ssl_context,
ping_interval=None, # 禁用自动ping,由适配器管理心跳
ping_timeout=None, # 禁用自动ping超时
close_timeout=10 # 关闭超时
),
timeout=connection_timeout
)
# 添加超时异常处理
except asyncio.TimeoutError:
self._state = WebSocketClientState.ERROR
logger.error(f"WebSocket客户端 {self.name} 连接超时: {connection_timeout}秒")
return False
- 配置统一:
app/core/config/settings.py: 心跳间隔改为120秒,连接超时改为60秒app/schemas/websocket.py: 默认心跳间隔改为120秒app/core/websocket/adapter.py: 默认心跳间隔改为120秒app/core/websocket/manager.py: 默认心跳间隔改为120秒
- 测试脚本 (
test_websocket_connection.py):
- 创建WebSocket连接测试脚本
- 测试多种WebSocket服务器(ws://, wss://, 本地)
- 验证连接、状态检查、断开功能
配置优化:
# WebSocket配置
connection_timeout: int = 60 # 连接超时60秒
heartbeat_interval: int = 120 # 心跳间隔2分钟
功能特性:
- ✅ 连接超时自动处理,避免长时间等待
- ✅ 统一的心跳间隔配置(2分钟)
- ✅ 更好的错误日志记录
- ✅ 连接状态实时监控
- ✅ 自动资源清理
使用建议:
- 开发环境:使用较短的超时时间(30-60秒)
- 生产环境:根据网络情况调整超时时间
- 心跳间隔:根据服务器要求调整(通常30秒-5分钟)
测试验证:
# 运行WebSocket连接测试
python test_websocket_connection.py
# 启动本地WebSocket服务器进行测试
python test_websocket_server.py
改进内容:
- URL验证增强:添加URL格式验证,确保以ws://或wss://开头
- 错误处理改进:区分不同类型的错误(超时、URL格式错误、连接失败)
- 日志增强:添加更详细的连接日志,包括URL和超时时间
- 测试工具:
- 更新测试脚本使用更可靠的WebSocket服务器
- 添加API端点测试
- 创建本地WebSocket服务器用于测试
WebSocket SSL证书验证冲突修复
WebSocket SSL证书验证冲突修复
问题:WebSocket客户端连接时出现SSL配置冲突错误
Cannot set verify_mode to CERT_NONE when check_hostname is enabled.
根本原因:在SSL上下文中,当设置verify_mode = ssl.CERT_NONE时,如果check_hostname仍然为True,就会出现配置冲突。
解决方案:修复SSL配置逻辑,确保当不验证证书时同时禁用主机名检查
文件变更:
- 更新
app/core/websocket/client.py- 修复SSL配置冲突
修改内容:
# 根据配置决定是否验证证书和主机名
# 先设置check_hostname,再设置verify_mode
if not config.websocket.ssl_verify_hostname:
ssl_context.check_hostname = False
if not config.websocket.ssl_verify_certificate:
ssl_context.verify_mode = ssl.CERT_NONE
修复逻辑:
- 先设置
check_hostname,再设置verify_mode,避免配置冲突 - 当
ssl_verify_hostname = False时,设置check_hostname = False - 当
ssl_verify_certificate = False时,设置verify_mode = ssl.CERT_NONE - 确保SSL配置的正确顺序和一致性
配置建议:
# 开发环境(跳过所有SSL验证)
WEBSOCKET_SSL_VERIFY_CERTIFICATE=false
WEBSOCKET_SSL_VERIFY_HOSTNAME=false
# 生产环境(启用SSL验证)
WEBSOCKET_SSL_VERIFY_CERTIFICATE=true
WEBSOCKET_SSL_VERIFY_HOSTNAME=true
注意事项:
- 修复了SSL配置冲突问题
- 保持了配置的灵活性
- 开发环境默认跳过SSL验证
- 生产环境建议启用SSL验证
WebSocket心跳间隔参数化
需求:通过API接口create_and_connect_client传递心跳间隔参数,使心跳间隔可配置
解决方案:在请求模型中添加心跳间隔参数,并在适配器中支持自定义心跳间隔
文件变更:
- 更新
app/schemas/websocket.py- 在CreateWebSocketClientRequest中添加heartbeat_interval字段 - 更新
app/core/websocket/adapter.py- 修改WebSocketAdapter支持心跳间隔参数 - 更新
app/core/websocket/manager.py- 修改create_adapter方法传递心跳间隔参数 - 更新
app/api/v1/endpoints/websocket.py- 修改API接口传递心跳间隔参数
修改内容:
- 请求模型更新 (
app/schemas/websocket.py):
class CreateWebSocketClientRequest(BaseModel):
name: str = Field(..., min_length=1, max_length=50, description="客户端名称")
url: str = Field(..., description="WebSocket服务器URL")
heartbeat_interval: int = Field(default=30, ge=5, le=300, description="心跳间隔(秒)")
- 适配器更新 (
app/core/websocket/adapter.py):
def __init__(self, client: WebSocketClient, outbound_channel: WebSocketChannel,
inbound_channel: Optional[WebSocketChannel] = None, heartbeat_interval: int = 30):
# ... 其他参数
self.heartbeat_interval = heartbeat_interval
# ... 日志记录心跳间隔
async def _heartbeat_loop(self):
# ... 心跳逻辑
await asyncio.sleep(self.heartbeat_interval) # 使用配置的心跳间隔
- 管理器更新 (
app/core/websocket/manager.py):
async def create_adapter(self, client_name: str, channel_name: str, heartbeat_interval: int = 30):
# ... 创建适配器时传递心跳间隔参数
adapter = WebSocketAdapter(client, channel, channel, heartbeat_interval)
- API接口更新 (
app/api/v1/endpoints/websocket.py):
async def create_and_connect_client(request: CreateWebSocketClientRequest):
# ... 创建适配器时传递心跳间隔
await websocket_manager.create_adapter(request.name, ch, request.heartbeat_interval)
# ... 响应中包含心跳间隔信息
data={"name": request.name, "url": request.url, "status": "connected", "heartbeat_interval": request.heartbeat_interval}
功能特性:
- 支持通过API接口自定义心跳间隔(5-300秒范围)
- 默认心跳间隔为30秒
- 心跳间隔参数会记录在日志中
- API响应中包含配置的心跳间隔信息
使用示例:
# 创建客户端并设置心跳间隔为60秒
curl -X POST "http://localhost:8000/api/v1/websocket/clients" \
-H "Content-Type: application/json" \
-d '{
"name": "my_client",
"url": "wss://example.com/ws",
"heartbeat_interval": 60
}'
注意事项:
- 心跳间隔必须在5-300秒范围内
- 如果不指定心跳间隔,默认使用30秒
- 心跳间隔会影响网络流量和连接稳定性
- 建议根据网络环境和服务器要求调整心跳间隔
WebSocket SSL证书验证问题修复
WebSocket SSL证书验证问题修复
问题:WebSocket客户端连接时出现SSL证书验证失败错误
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:997)
解决方案:在WebSocket客户端连接时跳过SSL证书验证(仅用于开发测试)
文件变更:
- 更新
app/core/websocket/client.py- 在connect方法中添加SSL证书验证跳过逻辑
修改内容:
# 建立WebSocket连接
# 根据配置决定是否跳过SSL证书验证
ssl_context = None
if self.url.startswith('wss://'):
from app.core.config.settings import config
import ssl
ssl_context = ssl.create_default_context()
# 根据配置决定是否验证证书和主机名
if not config.websocket.ssl_verify_certificate:
ssl_context.verify_mode = ssl.CERT_NONE
if not config.websocket.ssl_verify_hostname:
ssl_context.check_hostname = False
self._websocket = await websockets.connect(self.url, ssl=ssl_context)
配置化设计:
- 在
app/core/config/settings.py中添加WebSocketConfig类 - 支持通过环境变量或配置文件控制SSL验证行为
- 默认开发环境跳过证书验证,生产环境可通过配置启用
环境变量配置:
# 开发环境(跳过SSL验证)
WEBSOCKET_SSL_VERIFY_CERTIFICATE=false
WEBSOCKET_SSL_VERIFY_HOSTNAME=false
# 生产环境(启用SSL验证)
WEBSOCKET_SSL_VERIFY_CERTIFICATE=true
WEBSOCKET_SSL_VERIFY_HOSTNAME=true
注意事项:
- 开发环境默认跳过SSL证书验证
- 生产环境建议启用SSL证书验证
- 可通过环境变量灵活配置
相关修改:
- 启用
requirements.txt中的websockets==12.0依赖 - 添加
WebSocketConfig配置类到app/core/config/settings.py - 创建
test_websocket_ssl.py测试脚本验证修复效果
测试验证: 运行测试脚本验证修复效果:
python test_websocket_ssl.py
2025-08-07
日志系统简化优化
问题:用户要求简化日志系统,不要创建太多日志文件,只分异常和非异常两种
解决方案:重构日志系统,只创建两个日志文件:
logs/app.log- 正常日志(DEBUG、INFO、WARNING级别)logs/error.log- 异常日志(ERROR、CRITICAL级别)
文件变更:
- 更新
app/utils/structured_log.py- 重构_setup_handlers()方法
优化内容:
# 根据日志级别选择文件
# ERROR和CRITICAL级别写入异常日志文件
# 其他级别写入正常日志文件
error_handler = logging.FileHandler("logs/error.log", encoding='utf-8')
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(StructuredFormatter(include_stack_trace=True))
normal_handler = logging.FileHandler("logs/app.log", encoding='utf-8')
normal_handler.setLevel(logging.DEBUG)
normal_handler.setFormatter(StructuredFormatter(include_stack_trace=False))
优势:
- ✅ 简化日志文件管理,只有两个文件
- ✅ 异常日志包含完整堆栈跟踪
- ✅ 正常日志不包含堆栈跟踪,减少文件大小
- ✅ 按日志级别自动分流
- ✅ 保持控制台输出功能
WebSocket API优化
WebSocket API优化
问题:用户指出"创建Channel请求 不是请求的时候创建",需要优化WebSocket API架构
解决方案:重新设计WebSocket API,使用预创建Channel模式,并简化为只包含获取、连接、停止功能
文件变更:
- 创建
app/schemas/websocket.py- WebSocket相关的Pydantic模型 - 创建
app/utils/api_decorators.py- API错误处理装饰器 - 更新
app/services/websocket_service.py- 支持预创建Channel和初始化配置 - 更新
app/api/v1/endpoints/websocket.py- 使用Pydantic模型,简化为获取、连接、停止功能 - 更新
app/core/app/factory.py- 应用启动时初始化WebSocket服务
架构优化:
1. 预创建Channel模式
- 应用启动时自动创建默认Channel(default, system, events)
- 移除动态创建Channel的API
- 支持配置化的Channel管理
2. 简化的API设计
根据用户要求"websocket.py 应只需要获取 跟 连接 跟 停止",简化为三个核心功能:
获取功能:
GET /websocket/clients- 获取所有客户端GET /websocket/channels- 获取所有ChannelGET /websocket/stats- 获取统计信息
连接功能:
POST /websocket/clients- 创建客户端POST /websocket/clients/{name}/connect- 连接客户端POST /websocket/subscribe- 订阅ChannelPOST /websocket/message/send- 发送消息POST /websocket/message/broadcast- 广播消息
停止功能:
POST /websocket/clients/{name}/disconnect- 断开客户端DELETE /websocket/clients/{name}- 移除客户端POST /websocket/unsubscribe- 取消订阅
3. Pydantic模型验证
- 使用Pydantic进行请求参数验证
- 统一的响应格式
- 类型安全的API接口
4. 错误处理优化
- 统一的错误处理装饰器
- 标准化的错误响应格式
- 更好的错误日志记录
移除的API:
POST /websocket/channels- 动态创建ChannelDELETE /websocket/channels/{name}- 移除ChannelGET /websocket/adapters- 获取适配器POST /websocket/adapters- 创建适配器DELETE /websocket/adapters- 移除适配器POST /websocket/initialize- 初始化服务
配置化设计:
class WebSocketConfig(BaseModel):
default_channels: List[str] = ["default", "system", "events"]
max_channel_size: int = 1000
heartbeat_interval: int = 30
reconnect_attempts: int = 5
reconnect_delay: float = 1.0
优势:
- ✅ 更清晰的API设计,只包含核心功能
- ✅ 预创建Channel,避免运行时创建
- ✅ 统一的参数验证和错误处理
- ✅ 配置化的服务管理
- ✅ 更好的类型安全性
- ✅ 简化的使用流程
WebSocket架构重构
问题:用户反馈"client.py 为啥有 channel 不应该只要读取channel 数据发 接收数据往channel 插入吗",指出WebSocket架构设计不合理。
解决方案:引入适配器模式,重新设计WebSocket架构
- WebSocketClient: 只负责WebSocket连接和数据收发
- WebSocketChannel: 负责数据存储和队列管理
- WebSocketAdapter: 负责连接Client和Channel,实现数据双向流动
文件变更:
- 创建
app/core/websocket/模块 - 创建
app/core/websocket/client.py- WebSocket客户端 - 创建
app/core/websocket/channel.py- 数据通道 - 创建
app/core/websocket/adapter.py- 适配器 - 创建
app/core/websocket/manager.py- 管理器 - 创建
app/services/websocket_service.py- 业务服务 - 创建
app/api/v1/endpoints/websocket.py- API接口
架构优势:
- 遵循单一职责原则
- 清晰的层次结构
- 易于扩展和维护
- 支持数据双向流动
数据流:
WebSocket服务器 ↔ WebSocketClient ↔ WebSocketAdapter ↔ WebSocketChannel
WebSocket适配器空数据处理修复
问题:适配器收到 None 类型的消息数据时产生警告日志
解决方案:
- 在适配器的
_send_loop方法中添加对None值的特殊处理 - 将警告日志改为调试日志,避免产生过多警告
文件变更:
- 更新
app/core/websocket/adapter.py- 修复空数据处理
修改内容:
if msg.data is None:
# 如果data为None,跳过发送
logger.debug(f"适配器跳过空数据消息: {self.outbound_channel.name} -> {msg.type}")
elif isinstance(msg.data, dict):
# 正常处理字典数据
# ...
else:
# 处理其他非字典格式数据
# ...
优化效果:
- ✅ 正确处理空数据消息
- ✅ 避免产生不必要的警告日志
- ✅ 保持代码的健壮性
- ✅ 调试信息更清晰
- ✅ 添加调试日志,便于排查消息接收问题
- ✅ 添加详细的消息内容日志,便于调试心跳消息流转
Channel生命周期管理完善
问题:用户指出"没有遵循单一原则是 channel.py他stop 之后能使用吗 ,二次连接webscoket 还能有吗 生命周期"
解决方案:完善Channel的生命周期管理
- 添加
reconnect()方法 - 重新连接功能 - 添加
reset()方法 - 重置状态功能 - 添加
destroy()方法 - 完全销毁功能 - 添加
connection_count属性 - 连接次数统计 - 完善状态管理和错误处理
文件变更:
- 更新
app/core/websocket/channel.py- 完善生命周期管理
功能验证:
- ✅ 支持连接/断开/重连循环
- ✅ 状态管理正确
- ✅ 资源清理完整
- ✅ 二次连接支持
WebSocket Stop方法分析
问题:检查WebSocket架构中各个组件的stop方法是否停止干净,以及二次连接的支持情况
分析结果:
Stop方法停止干净度:✅ 优秀
- WebSocketClient.disconnect(): 正确取消任务、关闭连接、清理资源
- WebSocketAdapter.stop(): 正确取消发送任务、清理消息处理器
- WebSocketManager.cleanup(): 按正确顺序清理所有资源
二次连接支持:✅ 完整
- WebSocketClient: 支持重复连接,状态检查正确
- WebSocketChannel: 支持重新连接,连接次数统计
- WebSocketAdapter: 可以重新启动,任务重新创建
架构优势:
- 所有组件都正确取消异步任务
- 正确清理资源引用
- 状态管理完整
- 支持完整的连接-断开-重连循环
设备管理API重构
问题:用户要求"注册新设备 更新设备 注册设备 api 不对外开放 因为 这是都是需要自动"
解决方案:移除外部设备管理API,保留内部自动化接口
文件变更:
- 更新
app/api/v1/endpoints/devices.py- 移除以下API:POST /devices/register- 注册新设备PUT /devices/{device_id}/update- 更新设备DELETE /devices/{device_id}/unregister- 注销设备GET /devices/{device_id}/status- 获取设备状态GET /devices/protocol/{protocol_type}- 按协议过滤设备
保留功能:
- 设备操作API(点击、输入、截图等)
- ADB管理功能
- 设备列表查询(只读)
设计原则:
- 设备注册/更新/注销由系统自动管理
- 外部API只提供设备操作功能
- 提高系统安全性和稳定性
设备管理架构重构
问题:用户反馈"不用代理冗余代码,后面难以维护",要求移除冗余API文件
解决方案:完全移除冗余文件,统一到devices.py
文件变更:
- 删除
app/api/v1/endpoints/device_operations.py - 删除
app/api/v1/endpoints/enhanced_adb.py - 删除
app/api/v1/endpoints/unified_devices.py - 更新
app/api/v1/endpoints/devices.py- 整合所有设备管理功能 - 重命名
app/services/unified_device_service.py→app/services/device_service.py - 重命名
app/services/enhanced_adb_service.py→app/services/auto_discovery_adb_service.py
架构优化:
- 单一入口点:
/api/v1/devices - 统一设备管理:支持注册设备和自动发现设备
- 清晰的责任分离:API层、服务层、核心层
设备管理器增强
问题:用户选择"方案B:在 DeviceManager 中添加自动发现设备管理"
解决方案:在DeviceManager中集成自动发现设备管理
文件变更:
- 更新
app/core/device/manager.py- 添加自动发现设备管理 - 更新
app/services/device_service.py- 使用统一设备管理
新增功能:
handle_auto_discovered_device_event()- 处理自动发现设备事件get_auto_discovered_devices()- 获取自动发现设备get_all_devices_unified()- 获取所有设备(统一视图)get_device_source()- 获取设备来源remove_auto_discovered_device()- 移除自动发现设备update_auto_discovered_device_info()- 更新自动发现设备信息cleanup_offline_auto_discovered_devices()- 清理离线设备
架构优势:
- 统一的设备状态管理
- 自动发现和手动注册设备统一处理
- 更好的可维护性
应用启动优化
问题:启动时出现"no running event loop"错误
解决方案:使用FastAPI事件处理器管理异步任务
文件变更:
- 更新
app/core/app/factory.py- 添加启动和关闭事件处理器 - 更新
app/services/auto_discovery_adb_service.py- 移除自动启动监控
优化内容:
- 使用
@app.on_event("startup")启动设备监控 - 使用
@app.on_event("shutdown")停止设备监控 - 避免在构造函数中创建异步任务
路由注册修复
问题:路由注册失败,出现"no running event loop"错误
解决方案:修复异步任务创建时机
文件变更:
- 更新
app/api/v1/endpoints/enhanced_adb.py- 使用懒加载初始化服务 - 更新
app/core/app/router.py- 简化路由注册
修复内容:
- 延迟服务初始化到实际使用时
- 移除构造函数中的异步任务创建
- 使用事件处理器管理生命周期
设备监控增强
问题:需要增强设备监控日志和启动触发
解决方案:增强监控功能和日志记录
文件变更:
- 更新
app/services/auto_discovery_adb_service.py- 增强监控功能
增强内容:
- 添加更详细的日志记录
- 改进设备状态处理
- 增强错误处理和恢复机制
初始问题修复
问题:应用启动失败,路由注册错误
解决方案:修复异步任务和事件循环问题
文件变更:
- 更新多个核心文件以修复启动问题
- 优化异步任务管理
- 改进错误处理机制
WebSocket最小API调整(2025-08-07)
-
将
app/api/v1/endpoints/websocket.py简化为最小集合:- 保留
POST /websocket/clients(创建并连接客户端,合并原创建和连接) - 保留
POST /websocket/clients/{name}/disconnect(断开客户端) - 移除其余获取/订阅/广播/统计等接口,遵循"只需获取、连接、停止"的产品要求精简为仅连接与断开(获取由其他内部接口/日志替代)
- 保留
-
目的:
- 降低对外API面,减少维护成本
- 与"Channel预创建 + 适配器内部管理"策略一致
-
影响:
- 如需查询状态,暂通过内部服务统计或后续单独的只读接口再行补充
-
WebSocket改进:
app/core/websocket/client.py支持"*"通配消息处理器(未匹配到具体type时回退)。app/services/websocket_service.py在创建并连接客户端后,自动为其:- 确保默认Channels存在并连接
- 创建并启动与默认Channels的适配器
- 从而保证"按Channel读写"链路即刻可用(发送走Adapter→Client,接收由Client注册的处理器经Adapter写入Channel)。
去服务层解耦(2025-08-07)
-
目标:消除
websocket_service与核心层的重复职责,API与启动流程直接依赖websocket_manager与WebSocketConfig。 -
变更:
app/api/v1/endpoints/websocket.py直接调用websocket_manager完成创建/连接/断开,并在创建成功后初始化默认Channels与适配器。app/core/app/factory.py启动时直接创建并连接默认Channels,关闭时调用websocket_manager.cleanup()。- 后续可删除
app/services/websocket_service.py文件(当前仍保留便于迁移过渡,无对外API依赖)。
-
WebSocket严格版重构:
- 禁用直发与广播:
websocket_service.send_message/broadcast_message改为拒绝外部调用,仅允许写入 Channel 由 Adapter 转发。 - 私有发送:
WebSocketClient.send_message重命名为_send_message,仅供 Adapter 调用;心跳仍为内部私有发送。 - 出入通道分离:
WebSocketAdapter支持outbound_channel与inbound_channel;默认两者同名同一Channel,后续可按需拆分不同Channel。 - 适配器幂等恢复:
WebSocketManager.create_adapter若存在且任务未运行则自动重启,并确保 Channel 连接。 - 创建客户端后自动为默认 Channels 建立适配器并连接,保证 Channel→Adapter→Client 链路即刻可用。
- 心跳数据格式收敛:按用户指定的 .NET 模型,仅发送
{ "Type": "heartbeat", "Payload": { "Message": "ping" } },不附加任何其他字段;由WebSocketAdapter在优先级Channel写入并由发送循环优先发送。
- 禁用直发与广播:
WebSocket适配器心跳循环触发机制分析
问题: 创建并连接客户端后,adapter.py 中的 _heartbeat_loop 什么时候会触发?
分析结果:
-
触发时机:
_heartbeat_loop在适配器启动时自动触发 -
触发流程:
创建客户端 → 连接客户端 → 创建适配器 → 调用 adapter.start() → 启动 _heartbeat_loop -
具体代码路径:
websocket.py第23行: 创建并连接客户端- 第40-42行: 为每个默认Channel创建适配器
manager.py第133-170行:create_adapter方法- 第160行: 调用
await adapter.start() adapter.py第35-50行:start()方法- 第47行: 启动心跳任务
self._heartbeat_task = asyncio.create_task(self._heartbeat_loop())
-
心跳循环机制:
- 心跳间隔: 由
heartbeat_interval参数控制(默认120秒) - 心跳消息: 发送
{"Message": "ping"}格式的JSON - 优先级处理: 心跳消息通过优先级Channel发送,确保优先处理
- 循环条件: 当客户端连接状态为
self.client.is_connected时持续运行
- 心跳间隔: 由
-
停止条件:
- 客户端断开连接
- 适配器被停止(调用
adapter.stop()) - 任务被取消(
asyncio.CancelledError)
结论: _heartbeat_loop 在适配器创建并启动后立即开始运行,按照指定的心跳间隔定期发送心跳消息,直到客户端断开或适配器停止。
日志系统统一更新
问题: 项目中大量文件仍在使用旧的 get_logger(__name__) 方式,需要统一更新为新的结构化日志系统
解决方案: 将所有核心模块的日志导入方式从 get_logger 更新为 get_structured_logger
更新的文件列表:
核心模块 (app/core/)
app/core/websocket/adapter.py- WebSocket适配器app/core/websocket/manager.py- WebSocket管理器app/core/websocket/client.py- WebSocket客户端app/core/websocket/channel.py- WebSocket通道app/core/device/manager.py- 设备管理器app/core/device/dispatcher.py- 设备分发器
API端点 (app/api/v1/endpoints/)
app/api/v1/endpoints/websocket.py- WebSocket APIapp/api/v1/endpoints/devices.py- 设备APIapp/api/v1/endpoints/at.py- AT命令APIapp/api/v1/endpoints/ssh.py- SSH APIapp/api/v1/endpoints/plnk.py- PLNK API
服务层 (app/services/)
app/services/adb_service.py- ADB服务app/services/device_service.py- 设备服务app/services/at_service.py- AT服务app/services/atx_service.py- ATX服务app/services/plnk_service.py- PLNK服务app/services/ssh_service.py- SSH服务
工具类 (app/utils/)
app/utils/api_decorators.py- API装饰器app/utils/adb_utils.py- ADB工具app/utils/tcp_utils.py- TCP工具app/utils/serial_utils.py- 串口工具
更新内容:
# 旧方式
from app.utils.log import get_logger
logger = get_logger(__name__)
# 新方式
from app.utils.structured_log import get_structured_logger, LogLevel
logger = get_structured_logger(__name__, LogLevel.INFO)
优势:
- ✅ 统一使用结构化日志系统
- ✅ 日志文件简化管理(只有
app.log和error.log两个文件) - ✅ 异常日志包含完整堆栈跟踪
- ✅ 正常日志不包含堆栈跟踪,减少文件大小
- ✅ 按日志级别自动分流
- ✅ 保持控制台输出功能
注意事项:
- 测试文件(test_*.py)暂未更新,因为它们可能不需要结构化日志
- 所有核心业务逻辑模块已更新完成
- 新的日志系统提供更好的日志管理和分析能力
日志文件记录问题解决
问题描述: 在新环境中运行应用程序时,logs目录被删除,虽然日志产生但没有记录到文件中
问题分析:
- logs目录不存在导致FileHandler无法创建日志文件
- 结构化日志系统在
_setup_handlers方法中会检查并创建logs目录 - 但可能存在权限问题或路径问题
解决方案:
- 自动创建logs目录: 结构化日志系统已包含自动创建logs目录的逻辑
- 测试日志系统: 通过命令行测试确认日志系统正常工作
- 验证文件创建: 确认
app.log和error.log文件能够正常创建和写入
测试结果:
# 测试命令
python -c "from app.utils.structured_log import get_structured_logger, LogLevel; logger = get_structured_logger('test', LogLevel.DEBUG); logger.info('测试日志'); logger.error('测试错误日志')"
验证结果:
- ✅ logs目录自动创建成功
- ✅ app.log文件正常写入INFO级别日志
- ✅ error.log文件正常写入ERROR级别日志
- ✅ 应用程序启动时日志正常记录
- ✅ JSON格式结构化日志输出正常
日志文件结构:
logs/
├── app.log # 正常日志(DEBUG、INFO、WARNING级别)
├── error.log # 错误日志(ERROR、CRITICAL级别)
└── *.log # 其他模块特定日志文件
关键代码位置:
app/utils/structured_log.py第108-115行:自动创建logs目录- 第116-135行:设置文件处理器和过滤器
- 第136-142行:添加处理器到logger
结论: 日志系统配置正确,能够自动处理logs目录不存在的情况,日志文件记录功能完全正常。
新环境日志问题排查解决方案
问题描述: 在新环境中运行应用程序时,日志输出到控制台但没有创建日志文件记录
解决方案:
1. 创建诊断工具
- 文件:
debug_logging.py- 全面的日志系统诊断脚本 - 功能:
- 环境信息检查(Python版本、工作目录、权限等)
- 基础日志功能测试
- 结构化日志功能测试
- 文件权限测试
- 详细的错误报告
2. 创建排查指南
- 文件:
新环境日志问题排查指南.md- 完整的问题排查文档 - 内容:
- 快速诊断步骤
- 常见问题及解决方案
- 环境检查清单
- 调试命令集合
- 故障排除流程
3. 常见问题类型及解决方案
问题1: logs目录不存在
- 症状: 控制台输出日志,但logs目录不存在
- 解决: 结构化日志系统会自动创建logs目录
问题2: 权限不足
- 症状: 日志输出到控制台,但无法创建文件
- 解决: 检查目录权限,确保有写入权限
问题3: 磁盘空间不足
- 症状: 日志输出到控制台,但文件创建失败
- 解决: 检查磁盘空间,清理或更改日志目录
问题4: 路径问题
- 症状: 工作目录不正确导致文件创建在错误位置
- 解决: 确保在正确的目录下运行应用程序
问题5: 编码问题
- 症状: 文件创建但内容乱码或无法写入中文
- 解决: 确保使用UTF-8编码
4. 诊断脚本功能
# 环境检查
check_environment() # 检查Python版本、工作目录、权限等
test_basic_logging() # 测试标准logging模块
test_structured_logging() # 测试结构化日志系统
test_file_permissions() # 测试文件写入权限
5. 使用方法
# 运行诊断脚本
python debug_logging.py
# 手动测试日志系统
python -c "from app.utils.structured_log import get_structured_logger, LogLevel; logger = get_structured_logger('test', LogLevel.DEBUG); logger.info('测试日志'); logger.error('测试错误日志')"
6. 验证结果
- ✅ 诊断脚本成功创建并测试通过
- ✅ 当前环境日志系统完全正常
- ✅ 提供了完整的问题排查工具和文档
- ✅ 覆盖了所有常见的新环境问题场景
结论: 通过创建诊断工具和排查指南,可以有效解决新环境中日志文件无法创建的问题,并提供系统性的故障排除方法。
WebSocket架构重构 - 遵循单一职责原则
问题描述: 当前WebSocket架构复杂,不符合单一职责原则,一个客户端创建了过多的Channel
重构目标:
- 遵循单一职责原则
- 一个客户端只需要3个Channel
- 简化架构,提高性能
- 更好的并发控制
重构方案:
1. WebSocketClient重构
单一职责:
- 建立和维护WebSocket连接
- 发送原始数据到WebSocket服务器
- 接收原始数据从WebSocket服务器
- 管理连接状态和重连逻辑
不负责:
- Channel管理
- 心跳管理
- 数据路由
- 业务逻辑处理
主要变更:
- 移除心跳相关代码
- 简化消息处理逻辑
- 专注于连接和数据收发
2. WebSocketChannel重构
单一职责:
- 存储和管理消息队列
- 提供消息的发送和接收接口
- 管理Channel的连接状态
- 支持消息优先级和订阅机制
不负责:
- WebSocket连接管理
- 心跳管理
- 数据路由
- 业务逻辑处理
主要变更:
- 添加优先级队列支持
- 增强统计信息
- 简化生命周期管理
3. WebSocketAdapter重构
单一职责:
- 连接WebSocket客户端和Channel
- 实现数据双向流动
- 处理数据格式转换
- 管理发送和接收任务
不负责:
- WebSocket连接管理
- 心跳管理
- Channel管理
- 业务逻辑处理
主要变更:
- 移除心跳功能
- 简化数据转发逻辑
- 专注于适配器职责
4. WebSocketManager重构
单一职责:
- 管理WebSocket客户端的生命周期
- 管理Channel的创建和销毁
- 管理适配器的创建和销毁
- 提供统一的API接口
架构设计:
- 一个客户端只需要3个Channel:心跳、发送、接收
- 心跳Channel:高优先级,用于心跳消息
- 发送Channel:正常优先级,用于业务数据发送
- 接收Channel:接收所有数据
主要变更:
- 自动创建3个Channel
- 简化API接口
- 统一资源管理
5. 新的数据流架构
WebSocket服务器 ↔ WebSocketClient
├── 心跳Channel (高优先级) -> 心跳消息
├── 发送Channel (正常优先级) -> 业务数据
└── 接收Channel -> 接收所有数据
优势:
- ✅ 一个客户端只需要3个Channel
- ✅ 清晰的职责分离
- ✅ 更好的并发控制
- ✅ 简化的架构设计
- ✅ 减少资源消耗
- ✅ 提高性能
文件变更:
app/core/websocket/client.py- 重构客户端,移除心跳功能app/core/websocket/channel.py- 重构Channel,添加优先级支持app/core/websocket/adapter.py- 重构适配器,移除心跳功能app/core/websocket/manager.py- 重构管理器,支持3个Channel架构
使用方式:
# 创建客户端(自动创建3个Channel)
client = await websocket_manager.create_client("test_12", "ws://localhost:8080")
# 发送心跳消息
await websocket_manager.send_heartbeat("test_12")
# 发送业务数据
await websocket_manager.send_message("test_12", "data", {"key": "value"})
# 获取统计信息
stats = websocket_manager.get_stats()
结论: 重构后的架构更加简洁、高效,符合单一职责原则,能够更好地控制并发发送数据,真正体现出Channel的设计价值。
WebSocket stop/start逻辑检查和修复
问题描述: 检查重构后的WebSocket架构中stop/start逻辑是否有问题
检查结果:
1. 引用关系检查
发现的问题:
- API端点仍在使用旧的方法调用(
create_channel,create_adapter) - client.py中有不必要的全局变量声明
修复方案:
- 更新API端点使用新的架构
- 移除不必要的全局变量声明
2. stop/start逻辑检查
发现的问题:
- 适配器stop时取消注册消息处理器可能影响其他适配器
- 客户端disconnect时没有清理消息处理器
- 管理器cleanup时的清理顺序需要优化
修复方案:
适配器stop方法优化:
async def stop(self):
"""停止适配器"""
try:
# 取消发送任务
if self._send_task:
self._send_task.cancel()
# 注意:不取消注册消息处理器,因为可能有多个适配器使用同一个客户端
# 消息处理器会在客户端断开时统一清理
logger.info(f"WebSocket适配器已停止: {self.client.name}")
except Exception as e:
logger.error(f"WebSocket适配器停止时出错: {e}")
客户端disconnect方法优化:
async def disconnect(self):
"""断开WebSocket连接"""
try:
self._state = WebSocketClientState.DISCONNECTED
# 停止接收任务
if self._receive_task:
self._receive_task.cancel()
# 关闭WebSocket连接
if self._websocket:
await self._websocket.close()
self._websocket = None
# 清理消息处理器
self._message_handlers.clear()
logger.info(f"WebSocket客户端 {self.name} 已断开")
except Exception as e:
logger.error(f"WebSocket客户端 {self.name} 断开连接时出错: {e}")
管理器cleanup方法优化:
async def cleanup(self):
"""清理所有资源"""
try:
# 先停止所有适配器(停止数据转发)
for adapter in self._adapters.values():
await adapter.stop()
# 再断开所有客户端(清理连接和消息处理器)
for client in self._clients.values():
await client.disconnect()
# 最后断开所有Channel(清理队列)
for channel in self._channels.values():
await channel.disconnect()
# 清空所有集合
self._adapters.clear()
self._clients.clear()
self._channels.clear()
logger.info("WebSocket管理器清理完成")
except Exception as e:
logger.error(f"WebSocket管理器清理失败: {e}")
3. API端点更新
更新内容:
- 移除旧的方法调用(
create_channel,create_adapter) - 使用新的架构(自动创建3个Channel)
- 添加新的API端点(发送消息、发送心跳、获取统计信息)
- 增强错误处理和日志记录
新增API端点:
POST /websocket/clients/{name}/send- 发送消息POST /websocket/clients/{name}/heartbeat- 发送心跳GET /websocket/stats- 获取统计信息
4. 测试验证
创建测试文件: test_websocket_stop_start.py
- 测试stop/start逻辑
- 测试cleanup逻辑
- 验证资源清理是否完整
测试内容:
- 客户端创建、连接、断开、重新连接、移除
- 消息发送和心跳发送
- 多客户端cleanup
- 状态统计验证
5. 修复效果
优化结果:
- ✅ 修复了适配器stop时的消息处理器冲突问题
- ✅ 完善了客户端disconnect时的资源清理
- ✅ 优化了管理器cleanup时的清理顺序
- ✅ 更新了API端点使用新的架构
- ✅ 移除了不必要的全局变量声明
- ✅ 增强了错误处理和日志记录
架构优势:
- 清晰的职责分离
- 正确的资源清理顺序
- 避免资源泄漏
- 支持重复连接/断开
- 完整的生命周期管理
结论: 经过检查和修复,WebSocket架构的stop/start逻辑现在更加健壮,能够正确处理资源的创建、使用和清理,支持重复连接和断开操作。
WebSocket心跳功能分析和修复
问题描述: 用户观察到 test_1_heartbeat 没有发送数据,需要分析问题原因
问题分析:
1. 架构分析
从日志可以看出,系统创建了3个适配器:
- test_1:heartbeat - 心跳适配器 (out:test_1_heartbeat / in:test_1_heartbeat)
- test_1:send - 发送适配器 (out:test_1_send / in:test_1_receive)
- test_1:receive - 接收适配器 (out:test_1_receive / in:test_1_receive)
2. 问题根源
心跳Channel没有数据源:
- 心跳适配器从
test_1_heartbeatChannel读取数据 - 但是没有任何地方向
test_1_heartbeatChannel发送心跳数据 - 所以心跳Channel一直是空的,没有数据可以发送
3. 解决方案
添加心跳生成器:
- 在WebSocket管理器中添加心跳任务管理
- 每个客户端创建一个心跳任务
- 定期向心跳Channel发送心跳数据
实现方案:
1. 心跳任务管理
class WebSocketManager:
def __init__(self):
self._heartbeat_tasks: Dict[str, asyncio.Task] = {} # 心跳任务
# ... 其他初始化代码 ...
async def create_client(self, name: str, url: str, heartbeat_interval: int = 120):
# ... 创建客户端和Channel ...
# 启动心跳任务
await self._start_heartbeat_task(name, heartbeat_interval)
async def _start_heartbeat_task(self, client_name: str, heartbeat_interval: int):
"""启动心跳任务"""
heartbeat_task = asyncio.create_task(self._heartbeat_loop(client_name, heartbeat_interval))
self._heartbeat_tasks[client_name] = heartbeat_task
2. 心跳循环实现
async def _heartbeat_loop(self, client_name: str, heartbeat_interval: int):
"""心跳循环"""
heartbeat_channel = self._channels.get(f"{client_name}_heartbeat")
while client_name in self._clients and self._clients[client_name].is_connected:
# 创建心跳消息
heartbeat_data = {"Message": "ping"}
heartbeat_message = ChannelMessage(
type="heartbeat",
data=heartbeat_data,
priority=1 # 高优先级
)
# 发送到心跳Channel
await heartbeat_channel.send_message(heartbeat_message)
# 等待下次心跳
await asyncio.sleep(heartbeat_interval)
3. 心跳任务清理
async def remove_client(self, name: str):
# 停止心跳任务
await self._stop_heartbeat_task(name)
# ... 其他清理代码 ...
async def _stop_heartbeat_task(self, client_name: str):
"""停止心跳任务"""
if client_name in self._heartbeat_tasks:
self._heartbeat_tasks[client_name].cancel()
del self._heartbeat_tasks[client_name]
4. 数据流架构
心跳任务 → 心跳Channel → 心跳适配器 → WebSocket客户端
↓
定期发送心跳数据 → 高优先级队列 → 发送循环 → WebSocket服务器
5. 测试验证
创建测试文件: test_heartbeat_function.py
- 测试正常心跳功能
- 测试未连接状态下的心跳
- 验证心跳数据发送
测试内容:
- 使用较短的心跳间隔(5秒)进行测试
- 监控心跳Channel的状态变化
- 验证心跳数据的发送和接收
6. 修复效果
优化结果:
- ✅ 心跳Channel现在有数据源
- ✅ 心跳任务自动管理
- ✅ 支持可配置的心跳间隔
- ✅ 心跳数据高优先级发送
- ✅ 完整的生命周期管理
架构优势:
- 自动心跳生成
- 优先级管理
- 资源自动清理
- 支持动态配置
- 完整的错误处理
7. 使用方式
# 创建客户端(自动启动心跳任务)
client = await websocket_manager.create_client("test_1", "ws://localhost:8080", heartbeat_interval=30)
# 心跳任务会自动运行,无需手动干预
# 心跳数据会自动发送到WebSocket服务器
结论: 通过添加心跳生成器,解决了心跳Channel没有数据源的问题。现在心跳功能完全自动化,支持可配置的心跳间隔,并且具有完整的生命周期管理。
2025-08-12 - WebSocket心跳任务启动问题修复
问题描述
在create_and_connect_client成功之后,心跳循环_heartbeat_loop没有被正确启动,导致没有心跳消息发送。
问题分析
- 在
create_client方法中,心跳任务启动被放在了最后 - 如果在创建适配器过程中出现异常,整个方法会抛出异常,导致心跳任务启动代码不会执行
- 心跳循环缺少详细的日志记录,难以排查问题
解决方案
-
修改
create_client方法:- 添加异常处理,确保即使其他步骤失败也要尝试启动心跳任务
- 在心跳任务启动失败时记录错误但继续创建客户端
- 添加资源清理逻辑
-
增强
_heartbeat_loop方法:- 添加详细的日志记录,包括开始、成功启动、结束等状态
- 改进心跳Channel不存在时的处理逻辑
- 添加finally块确保日志记录完整
修改文件
app/core/websocket/manager.py:- 修改
create_client方法的异常处理逻辑 - 增强
_heartbeat_loop方法的日志记录和错误处理
- 修改
预期效果
- 心跳任务能够正确启动并记录详细日志
- 即使其他组件创建失败,心跳任务也会尝试启动
- 更容易排查心跳相关的问题
测试建议
- 创建WebSocket客户端并检查日志中是否出现心跳任务启动信息
- 验证心跳消息是否正常发送
- 测试异常情况下的心跳任务启动
补充说明
- 心跳数据格式保持为
{"Message": "ping"},与原有格式一致 - 心跳循环现在会在客户端存在时持续运行,无论连接状态如何
- 这样可以确保心跳机制在客户端创建后立即开始工作
WebSocket Channel启动时机修复
问题:create_client 方法中 _create_client_channels 不应该在创建时就启动Channel,应该在 connect_client 时才启动
解决方案:
- 修改
_create_client_channels方法,移除await channel.connect()调用 - 修改
_create_client_adapters方法,移除await adapter.start()调用 - 在
connect_client方法中添加_start_client_channels调用 - 新增
_start_client_channels方法,负责启动客户端的所有Channel和适配器 - 修复启动顺序:先连接WebSocket客户端,成功后再启动Channel和适配器
文件变更:
- 更新
app/core/websocket/manager.py- 修复Channel启动时机和启动顺序
修改内容:
- create_client方法优化:
async def create_client(self, name: str, url: str, heartbeat_interval: int = 120) -> WebSocketClient:
# ... 创建客户端和Channel ...
# 创建3个Channel(不启动)
await self._create_client_channels(name)
# 保存心跳间隔配置,在连接成功后启动
client.heartbeat_interval = heartbeat_interval
logger.info(f"WebSocket管理器创建客户端: {name} -> {url}")
return client
- connect_client方法增强(修复启动顺序):
async def connect_client(self, name: str) -> bool:
# 先连接WebSocket客户端
success = await client.connect()
if success:
# 连接成功后再启动客户端的所有Channel和适配器
await self._start_client_channels(name)
# 最后启动心跳任务
if hasattr(client, 'heartbeat_interval'):
await self._start_heartbeat_task(name, client.heartbeat_interval)
logger.info(f"WebSocket管理器客户端连接成功: {name}")
else:
logger.error(f"WebSocket客户端连接失败: {name}")
return success
- 新增_start_client_channels方法:
async def _start_client_channels(self, client_name: str):
"""启动客户端的所有Channel和适配器"""
# 启动客户端的所有Channel
client_channels = self.get_client_channels(client_name)
for channel_name, channel in client_channels.items():
await channel.connect()
logger.info(f"WebSocket管理器启动Channel: {channel_name}")
# 启动客户端的所有适配器
client_adapters = self.get_client_adapters(client_name)
for adapter_key, adapter in client_adapters.items():
await adapter.start()
logger.info(f"WebSocket管理器启动适配器: {adapter_key}")
优化效果:
- ✅ Channel和适配器在连接时才启动,避免资源浪费
- ✅ 创建客户端时只创建对象,不启动服务
- ✅ 正确的启动顺序:先连接WebSocket,再启动Channel和适配器
- ✅ 连接成功后才启动相关服务,避免发送失败
- ✅ 更好的资源管理和错误处理
- ✅ 清晰的职责分离:创建 vs 启动
设计原则:
- 创建时只创建对象,不启动服务
- 连接成功后才启动所有相关服务
- 按需启动,避免资源浪费
- 保持清晰的职责分离
- 正确的启动顺序:WebSocket连接 → Channel启动 → 适配器启动 → 心跳任务启动
WebSocket数据序列化修复
问题:适配器发送消息时出现 "data is a dict-like object" 错误,因为直接传递字典对象给WebSocket客户端
解决方案:
- 在适配器的
_send_loop方法中添加数据序列化逻辑 - 将字典数据序列化为JSON字符串再发送
- 确保所有数据类型都能正确序列化
文件变更:
- 更新
app/core/websocket/adapter.py- 修复数据序列化问题
修改内容:
# 从Channel接收消息
msg = await self.outbound_channel.receive_message(timeout=0.5)
if msg:
# 将消息数据序列化为JSON字符串
import json
if isinstance(msg.data, dict):
payload = json.dumps(msg.data)
# 发送数据到WebSocket
success = await self.client.send_raw(payload)
if success:
logger.debug(f"适配器发送消息成功: {self.outbound_channel.name} -> {msg.type}")
else:
logger.warning(f"适配器发送消息失败: {self.outbound_channel.name} -> {msg.type}")
else:
# 如果不是字典格式,记录日志但不发送
logger.warning(f"适配器跳过非字典格式消息: {self.outbound_channel.name} -> {msg.type}, 数据类型: {type(msg.data)}")
优化效果:
- ✅ 修复了字典数据序列化问题
- ✅ 心跳消息
{"Message": "ping"}能正确发送 - ✅ 只发送字典格式的数据,确保JSON字符串格式
- ✅ 非字典格式数据会被跳过,只记录日志
- ✅ 适配器发送消息不再失败
- ✅ 心跳功能正常工作
数据流:
心跳任务 → 心跳Channel → 适配器 → JSON序列化 → WebSocket客户端 → WebSocket服务器
WebSocket心跳消息格式修复
问题:心跳消息格式不符合.NET模型要求,需要包含 Type 和 Payload 字段
解决方案:
- 修改心跳消息格式,符合.NET模型规范
- 使用标准的消息结构:
{"Type": "heartbeat", "Payload": {"Message": "ping"}} - 消除冗余:移除不必要的中间变量
- 智能类型处理:ChannelMessage.type在发送时自动添加到data中
文件变更:
- 更新
app/core/websocket/manager.py- 修复心跳消息格式并消除冗余 - 更新
app/core/websocket/adapter.py- 在发送时自动添加Type字段
修改内容:
# 创建心跳消息
heartbeat_message = ChannelMessage(
type="heartbeat", # 这个type会在发送时添加到data中
data={
"Payload": {
"Message": "ping"
}
},
priority=1 # 高优先级,确保优先处理
)
# 适配器发送时会自动添加Type字段
send_data = {
"Type": msg.type,
**msg.data
}
优化效果:
- ✅ 心跳消息格式符合.NET模型规范
- ✅ 包含标准的
Type和Payload字段 - ✅ 消息结构清晰,便于服务器解析
- ✅ 保持与现有架构的兼容性
- ✅ 优先级机制确保心跳消息优先处理
- ✅ 代码结构清晰,导入优化
- ✅ 消除冗余,代码更简洁
- ✅ 智能类型处理,避免字段重复
消息格式:
{
"Type": "heartbeat",
"Payload": {
"Message": "ping"
}
}