You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

67 KiB

修改记录

2025-08-12

WebSocket心跳任务启动时机修复

问题:心跳任务应该在客户端连接成功后才启动,而不是在创建客户端时就启动

解决方案

  1. 修改create_client方法,移除心跳任务启动逻辑
  2. connect_client方法中添加心跳任务启动逻辑
  3. 为WebSocketClient类添加heartbeat_interval属性

文件变更

  • 更新 app/core/websocket/manager.py - 修改心跳任务启动时机
  • 更新 app/core/websocket/client.py - 添加heartbeat_interval属性

修改内容

  1. 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
  1. 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
  1. WebSocketClient类增强
def __init__(self, url: str, name: str = "default"):
    # ... 其他属性 ...
    self.heartbeat_interval: Optional[int] = None  # 心跳间隔配置

优化效果

  • 心跳任务在连接成功后启动,避免无效的心跳
  • 心跳间隔配置保存在客户端对象中
  • 连接失败时不会启动心跳任务
  • 更好的资源管理和错误处理

设计原则

  • 心跳任务只在连接成功时启动
  • 心跳间隔配置与客户端绑定
  • 连接失败时不会产生无效的心跳任务
  • 保持清晰的职责分离

日志系统完全统一 - 移除冗余文件

问题:项目前期不需要向后兼容,log.py 文件造成冗余和混乱

  • get_enhanced_logger() 只是简单包装了 get_structured_logger()
  • 两个文件功能重复,增加维护成本
  • 导入混乱,不利于统一管理

解决方案

  1. 删除 app/utils/log.py 文件
  2. 将所有使用 get_enhanced_logger() 的文件改为直接使用 get_structured_logger()
  3. 统一日志使用方式

文件变更

  • 删除 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.pylog.py 之间存在功能冗余,增加了维护成本

  • get_enhanced_logger() 只是简单包装了 get_structured_logger()
  • 两个文件都创建了默认日志记录器
  • 功能分散,不利于统一管理

解决方案

  1. 简化 log.py 为纯向后兼容包装器
  2. 移除 structured_log.py 中的默认日志记录器创建
  3. 明确标记各功能的用途和状态

文件变更

  • 更新 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() 方法

解决方案

  1. 统一所有业务代码和核心代码使用 get_structured_logger()
  2. 移除对 get_enhanced_logger() 的依赖
  3. 更新所有测试文件使用新的日志方式
  4. 保持 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.py
  • app/core/middleware/request.py
  • app/core/handlers/exception_handlers.py
  • app/core/config/cors.py
  • app/core/app/router.py
  • app/core/app/factory.py
  • 所有测试文件

优化效果

  • 统一日志使用方式,便于维护
  • 减少依赖层级,提高性能
  • 保持向后兼容性
  • 便于统一配置和管理

日志系统TRACE级别问题修复

问题structured_log.py 中定义了 LogLevel.TRACE,但Python的 logging 模块没有 TRACE 级别,导致 AttributeError: module 'logging' has no attribute 'TRACE'

解决方案

  1. structured_log.py 中添加自定义 TRACE 级别到 logging 模块
  2. 修复 _log 方法中的级别映射,添加默认值处理
  3. 修复 StructuredLogger 初始化中的级别设置问题
  4. 修复 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 的启动日志没有显示,无法确认心跳任务是否正常启动

解决方案

  1. _heartbeat_loop_send_loop 的启动日志级别从 DEBUG 改为 INFO
  2. _heartbeat_loop 中添加更多调试信息,包括优先级通道的创建和连接状态
  3. 增强心跳消息发送的日志记录

文件变更

  • 更新 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,形成循环导入

解决方案

  1. 移除 log.py 中对 app.core.config.settings 的导入
  2. 使用默认配置值替代动态配置
  3. 保持向后兼容性

文件变更

  • 更新 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是不必要的,因为:

  1. Channels是在需要时才创建的(按需创建)
  2. 真正的客户端连接是在API调用 create_and_connect_client 时才进行的
  3. 启动时创建空的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

根本原因

  1. WebSocket连接没有设置超时时间,导致长时间等待
  2. 心跳间隔配置不一致,代码中默认30秒,但日志显示2分钟
  3. 缺少专门的超时异常处理

解决方案

  1. 增加连接超时配置和异常处理
  2. 统一心跳间隔配置为120秒(2分钟)
  3. 添加连接超时测试脚本

文件变更

  1. 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
  1. 配置统一
  • app/core/config/settings.py: 心跳间隔改为120秒,连接超时改为60秒
  • app/schemas/websocket.py: 默认心跳间隔改为120秒
  • app/core/websocket/adapter.py: 默认心跳间隔改为120秒
  • app/core/websocket/manager.py: 默认心跳间隔改为120秒
  1. 测试脚本 (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

改进内容

  1. URL验证增强:添加URL格式验证,确保以ws://或wss://开头
  2. 错误处理改进:区分不同类型的错误(超时、URL格式错误、连接失败)
  3. 日志增强:添加更详细的连接日志,包括URL和超时时间
  4. 测试工具
    • 更新测试脚本使用更可靠的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接口传递心跳间隔参数

修改内容

  1. 请求模型更新 (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="心跳间隔(秒)")
  1. 适配器更新 (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)  # 使用配置的心跳间隔
  1. 管理器更新 (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)
  1. 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 - 获取所有Channel
  • GET /websocket/stats - 获取统计信息

连接功能

  • POST /websocket/clients - 创建客户端
  • POST /websocket/clients/{name}/connect - 连接客户端
  • POST /websocket/subscribe - 订阅Channel
  • POST /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 - 动态创建Channel
  • DELETE /websocket/channels/{name} - 移除Channel
  • GET /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 类型的消息数据时产生警告日志

解决方案

  1. 在适配器的 _send_loop 方法中添加对 None 值的特殊处理
  2. 将警告日志改为调试日志,避免产生过多警告

文件变更

  • 更新 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.pyapp/services/device_service.py
  • 重命名 app/services/enhanced_adb_service.pyapp/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_managerWebSocketConfig

  • 变更:

    • 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_channelinbound_channel;默认两者同名同一Channel,后续可按需拆分不同Channel。
    • 适配器幂等恢复:WebSocketManager.create_adapter 若存在且任务未运行则自动重启,并确保 Channel 连接。
    • 创建客户端后自动为默认 Channels 建立适配器并连接,保证 Channel→Adapter→Client 链路即刻可用。
    • 心跳数据格式收敛:按用户指定的 .NET 模型,仅发送 { "Type": "heartbeat", "Payload": { "Message": "ping" } },不附加任何其他字段;由 WebSocketAdapter 在优先级Channel写入并由发送循环优先发送。

WebSocket适配器心跳循环触发机制分析

问题: 创建并连接客户端后,adapter.py 中的 _heartbeat_loop 什么时候会触发?

分析结果:

  1. 触发时机: _heartbeat_loop 在适配器启动时自动触发

  2. 触发流程:

    创建客户端 → 连接客户端 → 创建适配器 → 调用 adapter.start() → 启动 _heartbeat_loop
    
  3. 具体代码路径:

    • 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())
  4. 心跳循环机制:

    • 心跳间隔: 由 heartbeat_interval 参数控制(默认120秒)
    • 心跳消息: 发送 {"Message": "ping"} 格式的JSON
    • 优先级处理: 心跳消息通过优先级Channel发送,确保优先处理
    • 循环条件: 当客户端连接状态为 self.client.is_connected 时持续运行
  5. 停止条件:

    • 客户端断开连接
    • 适配器被停止(调用 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 API
  • app/api/v1/endpoints/devices.py - 设备API
  • app/api/v1/endpoints/at.py - AT命令API
  • app/api/v1/endpoints/ssh.py - SSH API
  • app/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.logerror.log 两个文件)
  • 异常日志包含完整堆栈跟踪
  • 正常日志不包含堆栈跟踪,减少文件大小
  • 按日志级别自动分流
  • 保持控制台输出功能

注意事项:

  • 测试文件(test_*.py)暂未更新,因为它们可能不需要结构化日志
  • 所有核心业务逻辑模块已更新完成
  • 新的日志系统提供更好的日志管理和分析能力

日志文件记录问题解决

问题描述: 在新环境中运行应用程序时,logs目录被删除,虽然日志产生但没有记录到文件中

问题分析:

  1. logs目录不存在导致FileHandler无法创建日志文件
  2. 结构化日志系统在_setup_handlers方法中会检查并创建logs目录
  3. 但可能存在权限问题或路径问题

解决方案:

  1. 自动创建logs目录: 结构化日志系统已包含自动创建logs目录的逻辑
  2. 测试日志系统: 通过命令行测试确认日志系统正常工作
  3. 验证文件创建: 确认app.logerror.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

重构目标:

  1. 遵循单一职责原则
  2. 一个客户端只需要3个Channel
  3. 简化架构,提高性能
  4. 更好的并发控制

重构方案:

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_heartbeat Channel读取数据
  • 但是没有任何地方向 test_1_heartbeat Channel发送心跳数据
  • 所以心跳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没有被正确启动,导致没有心跳消息发送。

问题分析

  1. create_client方法中,心跳任务启动被放在了最后
  2. 如果在创建适配器过程中出现异常,整个方法会抛出异常,导致心跳任务启动代码不会执行
  3. 心跳循环缺少详细的日志记录,难以排查问题

解决方案

  1. 修改create_client方法

    • 添加异常处理,确保即使其他步骤失败也要尝试启动心跳任务
    • 在心跳任务启动失败时记录错误但继续创建客户端
    • 添加资源清理逻辑
  2. 增强_heartbeat_loop方法

    • 添加详细的日志记录,包括开始、成功启动、结束等状态
    • 改进心跳Channel不存在时的处理逻辑
    • 添加finally块确保日志记录完整

修改文件

  • app/core/websocket/manager.py
    • 修改create_client方法的异常处理逻辑
    • 增强_heartbeat_loop方法的日志记录和错误处理

预期效果

  • 心跳任务能够正确启动并记录详细日志
  • 即使其他组件创建失败,心跳任务也会尝试启动
  • 更容易排查心跳相关的问题

测试建议

  1. 创建WebSocket客户端并检查日志中是否出现心跳任务启动信息
  2. 验证心跳消息是否正常发送
  3. 测试异常情况下的心跳任务启动

补充说明

  • 心跳数据格式保持为 {"Message": "ping"},与原有格式一致
  • 心跳循环现在会在客户端存在时持续运行,无论连接状态如何
  • 这样可以确保心跳机制在客户端创建后立即开始工作

WebSocket Channel启动时机修复

问题create_client 方法中 _create_client_channels 不应该在创建时就启动Channel,应该在 connect_client 时才启动

解决方案

  1. 修改 _create_client_channels 方法,移除 await channel.connect() 调用
  2. 修改 _create_client_adapters 方法,移除 await adapter.start() 调用
  3. connect_client 方法中添加 _start_client_channels 调用
  4. 新增 _start_client_channels 方法,负责启动客户端的所有Channel和适配器
  5. 修复启动顺序:先连接WebSocket客户端,成功后再启动Channel和适配器

文件变更

  • 更新 app/core/websocket/manager.py - 修复Channel启动时机和启动顺序

修改内容

  1. 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
  1. 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
  1. 新增_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客户端

解决方案

  1. 在适配器的 _send_loop 方法中添加数据序列化逻辑
  2. 将字典数据序列化为JSON字符串再发送
  3. 确保所有数据类型都能正确序列化

文件变更

  • 更新 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模型要求,需要包含 TypePayload 字段

解决方案

  1. 修改心跳消息格式,符合.NET模型规范
  2. 使用标准的消息结构:{"Type": "heartbeat", "Payload": {"Message": "ping"}}
  3. 消除冗余:移除不必要的中间变量
  4. 智能类型处理: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模型规范
  • 包含标准的 TypePayload 字段
  • 消息结构清晰,便于服务器解析
  • 保持与现有架构的兼容性
  • 优先级机制确保心跳消息优先处理
  • 代码结构清晰,导入优化
  • 消除冗余,代码更简洁
  • 智能类型处理,避免字段重复

消息格式

{
    "Type": "heartbeat",
    "Payload": {
        "Message": "ping"
    }
}