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.

72 KiB

修改记录

2025-08-13

WebSocket发送控制器Channel引用问题修复

问题描述: 从日志中观察到 send_controller 出现"Channel不存在,等待重试"的警告:

{"timestamp": "2025-08-13T15:47:19.273901", "level": "WARNING", "message": "Channel不存在,等待重试: test_1", "logger_name": "app.core.websocket.send_controller"}

问题分析:

  1. 根本原因send_controller 在启动时创建了一个新的 WebSocketChannelManager() 实例,而不是使用已经存在的实例
  2. 时序问题:新创建的channel管理器实例中没有之前创建的channel,导致找不到channel
  3. 重试机制send_controller 有重试机制,会等待1秒后重试,这就是为什么看到"等待重试"的警告

解决方案:

  1. 修改 WebSocketSendController 构造函数:接收channel对象而不是channel名称
  2. 修改 WebSocketManager 创建逻辑:传入已存在的channel对象
  3. 移除channel管理器重新创建:直接使用传入的channel对象

文件变更:

  • 更新 app/core/websocket/send_controller.py - 修改构造函数和发送循环逻辑
  • 更新 app/core/websocket/manager.py - 修改send_controller创建逻辑

修改内容:

  1. send_controller.py 构造函数修改
# 修改前
def __init__(self, client: WebSocketClient, heartbeat_channel_name: str, send_channel_name: str):
    self.heartbeat_channel_name = heartbeat_channel_name
    self.send_channel_name = send_channel_name

# 修改后
def __init__(self, client: WebSocketClient, heartbeat_channel: WebSocketChannel, send_channel: WebSocketChannel):
    self.heartbeat_channel = heartbeat_channel
    self.send_channel = send_channel
  1. 发送循环逻辑修改
# 修改前:重新创建channel管理器
from app.core.websocket.channel_manager import WebSocketChannelManager
channel_manager = WebSocketChannelManager()
heartbeat_channel = channel_manager.get_channel(self.heartbeat_channel_name)
send_channel = channel_manager.get_channel(self.send_channel_name)

# 修改后:直接使用传入的channel对象
if not self.heartbeat_channel or not self.send_channel:
    logger.warning(f"Channel不存在,等待重试: {self.client_name}")
    await asyncio.sleep(1)
    continue

if not self.heartbeat_channel.is_connected or not self.send_channel.is_connected:
    logger.warning(f"Channel未连接,等待重试: {self.client_name}")
    await asyncio.sleep(1)
    continue
  1. manager.py 创建逻辑修改
# 修改前
send_controller = WebSocketSendController(
    client=client,
    heartbeat_channel_name=f"{client_name}_heartbeat",
    send_channel_name=f"{client_name}_send"
)

# 修改后
# 获取Channel
heartbeat_channel = self._channel_manager.get_channel(f"{client_name}_heartbeat")
send_channel = self._channel_manager.get_channel(f"{client_name}_send")

if not heartbeat_channel or not send_channel:
    logger.error(f"Channel不存在,无法创建发送控制器: {client_name}")
    return

send_controller = WebSocketSendController(
    client=client,
    heartbeat_channel=heartbeat_channel,
    send_channel=send_channel
)

修复效果:

  • 解决了"Channel不存在"的警告问题
  • 修复了channel引用错误
  • 避免了重复创建channel管理器实例
  • 提高了系统性能和稳定性
  • 保持了重试机制的正确性

架构优势:

  • 正确的依赖注入:通过构造函数传入channel对象
  • 避免重复创建:不再创建新的channel管理器实例
  • 更好的错误处理:在创建send_controller前检查channel是否存在
  • 清晰的职责分离:send_controller专注于发送逻辑,不负责channel管理

验证方法:

  • 创建WebSocket客户端后,不再出现"Channel不存在,等待重试"的警告
  • send_controller能够正常发送消息到WebSocket
  • 心跳和业务数据都能正常发送

结论: 通过修改send_controller的构造函数和manager的创建逻辑,成功解决了channel引用问题。现在send_controller直接使用传入的channel对象,避免了重复创建channel管理器实例的问题,系统运行更加稳定和高效。

WebSocket连接顺序问题修复

问题描述:endpoints.websocket.pycreate_and_connect_client 方法中,存在潜在的时序问题:

  1. create_client 创建客户端和Channel,但不启动Channel
  2. connect_client 启动Channel,然后立即创建发送控制器
  3. 发送控制器可能在Channel完全启动之前就开始运行,导致"Channel不存在"的警告

问题分析:connect_client 方法中,执行顺序是:

  1. 连接客户端
  2. 启动Channel(异步操作)
  3. 立即创建并启动发送控制器
  4. 启动心跳任务
  5. 启动接收消息处理器

问题根源: Channel的启动是异步的,发送控制器可能在Channel完全启动之前就被创建和启动,导致发送控制器无法找到已连接的Channel。

解决方案:

  1. 添加Channel就绪等待机制:在启动Channel后,等待所有Channel完全启动
  2. 修改连接流程:确保Channel完全就绪后再创建发送控制器
  3. 增加超时保护:避免无限等待

文件变更:

  • 更新 app/core/websocket/manager.py - 添加Channel就绪等待机制

修改内容:

  1. 添加 _wait_for_channels_ready 方法
async def _wait_for_channels_ready(self, client_name: str, timeout: float = 5.0):
    """等待客户端的所有Channel完全启动"""
    import asyncio
    start_time = asyncio.get_event_loop().time()
    
    while asyncio.get_event_loop().time() - start_time < timeout:
        # 检查所有Channel是否都已连接
        client_channels = self._channel_manager.get_client_channels(client_name)
        all_connected = True
        
        for channel_name, channel in client_channels.items():
            if not channel.is_connected:
                all_connected = False
                logger.debug(f"等待Channel连接: {channel_name}")
                break
        
        if all_connected:
            logger.info(f"所有Channel已准备就绪: {client_name}")
            return True
        
        # 等待100ms后再次检查
        await asyncio.sleep(0.1)
    
    logger.warning(f"等待Channel就绪超时: {client_name} (超时时间: {timeout}秒)")
    return False
  1. 修改 connect_client 方法
async def connect_client(self, name: str) -> bool:
    """连接指定客户端"""
    try:
        # 1. 连接客户端
        success = await self._client_manager.connect_client(name)
        if not success:
            logger.error(f"WebSocket客户端连接失败: {name}")
            return False
        
        # 2. 启动Channel
        await self._channel_manager.start_client_channels(name)
        
        # 3. 等待Channel完全启动(确保Channel状态为connected)
        await self._wait_for_channels_ready(name)
        
        # 4. 创建并启动发送控制器
        await self._create_and_start_send_controller(name)
        
        # 5. 启动心跳任务
        client = self._client_manager.get_client(name)
        if client and hasattr(client, 'heartbeat_interval'):
            await self._heartbeat_manager.start_heartbeat_task(name, client, client.heartbeat_interval)
        
        # 6. 启动接收消息处理器
        await self._create_and_start_receive_processor(name)
        
        logger.info(f"WebSocket管理器客户端连接成功: {name}")
        return True
        
    except Exception as e:
        logger.error(f"WebSocket管理器连接客户端失败: {name} - {e}")
        return False

修复效果:

  • 解决了Channel启动时序问题
  • 确保发送控制器在Channel完全就绪后才启动
  • 避免了"Channel不存在"的警告
  • 增加了超时保护,避免无限等待
  • 提供了详细的调试日志,便于问题排查

架构优势:

  • 正确的依赖顺序:客户端 → Channel → 发送控制器 → 心跳任务 → 接收处理器
  • 可靠的启动机制:确保每个组件都在依赖组件就绪后才启动
  • 完善的错误处理:超时保护和详细的日志记录
  • 更好的稳定性:避免因时序问题导致的组件启动失败

验证方法:

  • 创建WebSocket客户端后,不再出现"Channel不存在,等待重试"的警告
  • 所有Channel都能正确启动并连接
  • 发送控制器能够正常发送消息
  • 心跳和业务数据都能正常发送

结论: 通过添加Channel就绪等待机制,成功解决了WebSocket连接过程中的时序问题。现在系统能够确保所有组件按照正确的顺序启动,避免了因时序问题导致的组件启动失败,提高了系统的稳定性和可靠性。

WebSocket断开连接顺序问题修复

问题描述:disconnect_client 方法中,存在潜在的时序问题:

  1. 发送控制器在第2步被停止,但可能仍在访问Channel
  2. Channel在第4步才被停止,这可能导致发送控制器在停止过程中出现"Channel不存在"的警告
  3. 需要确保发送控制器完全停止后再停止Channel

问题分析:disconnect_client 方法中,执行顺序是:

  1. 停止心跳任务
  2. 停止发送控制器
  3. 停止接收消息处理器
  4. 停止Channel
  5. 断开客户端

问题根源: 发送控制器在停止过程中,如果Channel还没有被停止,它可能会继续尝试访问Channel。虽然发送控制器的 _unified_send_loop 会检查 _running 标志并退出,但在停止过程中仍可能出现时序问题。

解决方案: 在停止发送控制器和停止Channel之间添加一个小的延迟,确保发送控制器完全停止后再停止Channel。

文件变更:

  • 更新 app/core/websocket/manager.py - 优化断开连接顺序

修改内容:

修改 disconnect_client 方法

async def disconnect_client(self, name: str) -> bool:
    """断开指定客户端"""
    try:
        # 1. 停止心跳任务
        await self._heartbeat_manager.stop_heartbeat_task(name)
        
        # 2. 停止发送控制器
        await self._stop_send_controller(name)
        
        # 3. 停止接收消息处理器
        await self._stop_receive_processor(name)
        
        # 4. 等待发送控制器完全停止(确保不再访问Channel)
        await asyncio.sleep(0.1)
        
        # 5. 停止Channel
        await self._channel_manager.stop_client_channels(name)
        
        # 6. 断开客户端
        success = await self._client_manager.disconnect_client(name)
        
        logger.info(f"WebSocket管理器客户端断开成功: {name}")
        return success
        
    except Exception as e:
        logger.error(f"WebSocket管理器断开客户端失败: {name} - {e}")
        return False

修复效果:

  • 解决了断开连接过程中的时序问题
  • 确保发送控制器完全停止后再停止Channel
  • 避免了"Channel不存在"的警告
  • 提供了更可靠的资源清理顺序
  • 保持了断开连接过程的稳定性

架构优势:

  • 正确的断开顺序:心跳任务 → 发送控制器 → 接收处理器 → 等待 → Channel → 客户端
  • 可靠的资源清理:确保每个组件都在依赖组件停止后才停止
  • 完善的错误处理:避免因时序问题导致的资源泄漏
  • 更好的稳定性:避免因时序问题导致的断开失败

验证方法:

  • 断开WebSocket客户端后,不再出现"Channel不存在"的警告
  • 所有组件都能正确停止和清理
  • 资源清理完整,没有泄漏
  • 支持重复连接和断开操作

结论: 通过优化断开连接顺序,成功解决了WebSocket断开过程中的时序问题。现在系统能够确保所有组件按照正确的顺序停止,避免了因时序问题导致的资源清理失败,提高了系统的稳定性和可靠性。

WebSocket重复连接问题修复

问题描述: create_and_connect_client 在第一次释放后,第二次重新连接时可能出现以下问题:

  1. 客户端状态不一致:客户端可能处于断开状态,但 create_client 会直接返回它
  2. Channel状态不一致:Channel可能处于断开状态,但 create_client_channels 会直接返回它们
  3. 发送控制器重复创建:可能创建多个发送控制器实例
  4. 心跳任务重复启动:可能启动多个心跳任务

问题分析: 在第二次连接时,各个管理器会直接返回已存在的资源,但不会检查资源的状态,导致:

  • 已断开的客户端被直接使用
  • 已断开的Channel被直接使用
  • 发送控制器和心跳任务可能重复创建

解决方案:

  1. 客户端状态检查:在创建客户端前检查是否已存在,如果已连接则先断开
  2. 发送控制器重复检查:在创建发送控制器前检查是否已存在,如果存在则先停止
  3. 接收处理器重复检查:在创建接收处理器前检查是否已存在,如果存在则先停止
  4. 心跳任务重复检查:在启动心跳任务前先停止已存在的任务

文件变更:

  • 更新 app/core/websocket/manager.py - 添加重复连接检查和处理

修改内容:

  1. 修改 create_client 方法
async def create_client(self, name: str, url: str, heartbeat_interval: int = 120) -> WebSocketClient:
    """创建WebSocket客户端并自动创建3个Channel"""
    try:
        # 检查是否已存在客户端
        existing_client = self._client_manager.get_client(name)
        if existing_client:
            logger.info(f"WebSocket客户端 {name} 已存在,检查状态")
            
            # 如果客户端已连接,先断开
            if existing_client.is_connected:
                logger.info(f"WebSocket客户端 {name} 已连接,先断开")
                await self._client_manager.disconnect_client(name)
            
            # 更新心跳间隔配置
            existing_client.heartbeat_interval = heartbeat_interval
            client = existing_client
        else:
            # 1. 创建客户端
            client = await self._client_manager.create_client(name, url, heartbeat_interval)
        
        # 2. 创建Channel(如果已存在会返回已存在的Channel)
        await self._channel_manager.create_client_channels(name)
        
        # 3. 注册消息处理器
        await self._register_client_handlers(name)
        
        logger.info(f"WebSocket管理器创建客户端成功: {name} -> {url}")
        return client
        
    except Exception as e:
        logger.error(f"WebSocket管理器创建客户端失败: {name} - {e}")
        raise
  1. 修改 _create_and_start_send_controller 方法
async def _create_and_start_send_controller(self, client_name: str):
    """创建并启动发送控制器"""
    try:
        client = self._client_manager.get_client(client_name)
        if not client:
            logger.error(f"WebSocket客户端 {client_name} 不存在")
            return
        
        # 检查是否已存在发送控制器
        if client_name in self._send_controllers:
            existing_controller = self._send_controllers[client_name]
            logger.info(f"WebSocket发送控制器 {client_name} 已存在,先停止")
            await existing_controller.stop()
            del self._send_controllers[client_name]
        
        # 获取Channel
        heartbeat_channel = self._channel_manager.get_channel(f"{client_name}_heartbeat")
        send_channel = self._channel_manager.get_channel(f"{client_name}_send")
        
        if not heartbeat_channel or not send_channel:
            logger.error(f"Channel不存在,无法创建发送控制器: {client_name}")
            return
        
        # 创建发送控制器
        send_controller = WebSocketSendController(
            client=client,
            heartbeat_channel=heartbeat_channel,
            send_channel=send_channel
        )
        
        # 启动发送控制器
        success = await send_controller.start()
        if success:
            self._send_controllers[client_name] = send_controller
            logger.info(f"WebSocket管理器启动发送控制器成功: {client_name}")
        else:
            logger.error(f"WebSocket管理器启动发送控制器失败: {client_name}")
        
    except Exception as e:
        logger.error(f"WebSocket管理器创建发送控制器失败: {client_name} - {e}")
  1. 修改 _create_and_start_receive_processor 方法
async def _create_and_start_receive_processor(self, client_name: str):
    """创建并启动接收消息处理器"""
    try:
        # 检查是否已存在接收处理器
        if client_name in self._receive_tasks:
            existing_task = self._receive_tasks[client_name]
            logger.info(f"WebSocket接收消息处理器 {client_name} 已存在,先停止")
            if not existing_task.done():
                existing_task.cancel()
                try:
                    await existing_task
                except asyncio.CancelledError:
                    pass
            del self._receive_tasks[client_name]
        
        # 获取接收Channel
        receive_channel = self._channel_manager.get_channel(f"{client_name}_receive")
        if not receive_channel:
            logger.error(f"接收Channel不存在: {client_name}_receive")
            return
        
        # 创建接收消息处理任务
        receive_task = asyncio.create_task(self._receive_message_loop(client_name, receive_channel))
        self._receive_tasks[client_name] = receive_task
        
        logger.info(f"WebSocket管理器启动接收消息处理器成功: {client_name}")
        
    except Exception as e:
        logger.error(f"WebSocket管理器创建接收消息处理器失败: {client_name} - {e}")
  1. 修改心跳任务启动逻辑
# 5. 启动心跳任务
client = self._client_manager.get_client(name)
if client and hasattr(client, 'heartbeat_interval'):
    # 检查是否已存在心跳任务
    await self._heartbeat_manager.stop_heartbeat_task(name)  # 先停止已存在的任务
    await self._heartbeat_manager.start_heartbeat_task(name, client, client.heartbeat_interval)

修复效果:

  • 解决了重复连接时的状态不一致问题
  • 确保客户端在重新连接前处于正确的状态
  • 避免了发送控制器的重复创建
  • 避免了接收处理器的重复创建
  • 避免了心跳任务的重复启动
  • 提供了完整的资源清理和重新创建机制

架构优势:

  • 正确的重复连接处理:检查现有资源状态,确保清理后重新创建
  • 可靠的资源管理:避免资源泄漏和重复创建
  • 完善的错误处理:确保每个步骤都有适当的错误处理
  • 更好的稳定性:支持完整的连接-断开-重连循环

验证方法:

  • 创建WebSocket客户端后断开
  • 再次调用 create_and_connect_client 重新连接
  • 验证所有组件都能正确重新创建和启动
  • 验证没有资源泄漏或重复创建

结论: 通过添加重复连接检查和处理机制,成功解决了WebSocket重复连接时的问题。现在系统能够正确处理已存在资源的清理和重新创建,确保每次连接都是干净和稳定的状态。

2025-08-12

修复WebSocket发送控制器消息格式

问题_send_message 方法违背了原先的消息格式要求,应该按照原先的方式:

  1. 在发送时添加 Type 字段(大写T)
  2. 使用 websocket_serializer.serialize(send_data) 序列化
  3. 使用 self.client.send_raw(payload) 发送

解决方案: 修改 app/core/websocket/send_controller.py 中的 _send_message 方法,恢复正确的消息格式和发送流程。

文件变更

  • 更新 app/core/websocket/send_controller.py - 修复消息发送格式

修改内容

  1. 修复消息数据构建
# 在发送时添加Type字段
send_data = {
    "Type": message.type,
    **message.data
}
  1. 使用序列化器序列化
# 使用序列化器序列化消息
from app.core.websocket.serializer import websocket_serializer
payload = websocket_serializer.serialize(send_data)
  1. 使用send_raw发送
# 发送数据到WebSocket
success = await self.client.send_raw(payload)

修改前后对比

  • 修改前:构建复杂的数据结构,使用 send_message 方法
  • 修改后:按照原先格式构建数据,使用序列化器序列化,使用 send_raw 发送

修复WebSocket心跳管理器消息格式

问题_send_heartbeat_message 方法违背了原先的消息格式要求,应该按照与 send_controller.py_send_message 方法相同的方式实现。

解决方案: 修改 app/core/websocket/heartbeat_manager.py 中的 _send_heartbeat_message 方法,使用正确的消息格式和发送流程。

文件变更

  • 更新 app/core/websocket/heartbeat_manager.py - 修复心跳消息发送格式
  • 添加 ChannelMessage 导入

修改内容

  1. 创建ChannelMessage对象
# 创建心跳消息
heartbeat_message = ChannelMessage(
    type="heartbeat",  # 这个type会在发送时添加到data中
    data={
        "Payload": {
            "Message": "ping"
        }
    },
    priority=1  # 高优先级,确保优先处理
)
  1. 使用序列化器序列化
# 在发送时添加Type字段
send_data = {
    "Type": heartbeat_message.type,
    **heartbeat_message.data
}

# 使用序列化器序列化消息
from app.core.websocket.serializer import websocket_serializer
payload = websocket_serializer.serialize(send_data)
  1. 使用send_raw发送
# 发送数据到WebSocket
success = await client.send_raw(payload)

修改前后对比

  • 修改前:构建简单的心跳数据,使用 send_message 方法
  • 修改后:创建 ChannelMessage 对象,使用序列化器序列化,使用 send_raw 发送

删除冗余的心跳发送方法

问题send_heartbeat 方法是冗余的,用不上,应该删除以简化代码。

解决方案: 删除 app/core/websocket/heartbeat_manager.py 中的 send_heartbeat 方法。

文件变更

  • 更新 app/core/websocket/heartbeat_manager.py - 删除冗余的 send_heartbeat 方法

修改内容: 删除了以下冗余方法:

async def send_heartbeat(self, client_name: str) -> bool:
    """手动发送心跳消息"""
    # ... 整个方法被删除

原因

  • 心跳管理器已经有自动的心跳循环机制
  • 手动发送心跳的方法在实际使用中不会被调用
  • 删除冗余代码可以提高代码的简洁性和可维护性

添加WebSocket接收消息处理逻辑

问题_handle_message 方法将消息放入了接收Channel,但是缺少处理逻辑来读取和处理这些消息,接收Channel就像一个"黑洞"。

解决方案: 在 WebSocketManager 中添加完整的接收消息处理逻辑,包括接收消息循环和业务处理。

文件变更

  • 更新 app/core/websocket/manager.py - 添加接收消息处理逻辑

修改内容

  1. 添加接收任务管理
# 接收消息处理器任务
self._receive_tasks: Dict[str, asyncio.Task] = {}
  1. 创建接收消息处理器
async def _create_and_start_receive_processor(self, client_name: str):
    """创建并启动接收消息处理器"""
    # 获取接收Channel
    receive_channel = self._channel_manager.get_channel(f"{client_name}_receive")
    # 创建接收消息处理任务
    receive_task = asyncio.create_task(self._receive_message_loop(client_name, receive_channel))
    self._receive_tasks[client_name] = receive_task
  1. 接收消息处理循环
async def _receive_message_loop(self, client_name: str, receive_channel):
    """接收消息处理循环"""
    while receive_channel.is_connected:
        # 从接收Channel获取消息
        message = await receive_channel.get_message()
        if message:
            # 处理接收到的消息
            await self._process_received_message(client_name, message)
  1. 消息业务处理
async def _process_received_message(self, client_name: str, message: ChannelMessage):
    """处理接收到的消息"""
    # 根据消息类型进行不同的业务处理
    if message.type == "heartbeat":
        await self._handle_heartbeat_response(client_name, message)
    elif message.type == "data":
        await self._handle_data_message(client_name, message)
    elif message.type == "command":
        await self._handle_command_message(client_name, message)
  1. 完善生命周期管理
  • 在连接客户端时启动接收处理器
  • 在断开连接时停止接收处理器
  • 在清理时停止所有接收任务
  • 在统计信息中包含接收处理器状态

架构完整性: 现在WebSocket架构具有完整的双向通信能力:

  • 发送功能:由WebSocketSendController统一管理
  • 接收功能:由WebSocketManager的接收消息处理器管理
  • 心跳功能:由WebSocketHeartbeatManager管理
  • Channel管理:由WebSocketChannelManager管理

数据流

接收:WebSocket → WebSocketClient → WebSocketManager → ReceiveChannel → 接收处理器 → 业务处理
发送:业务逻辑 → SendChannel → 发送控制器 → WebSocketClient → WebSocket
心跳:心跳管理器 → HeartbeatChannel → 发送控制器 → WebSocketClient → WebSocket

修复WebSocket架构中的遗漏代码

问题:在代码审查中发现了一些遗漏和错误,需要修复以确保架构的完整性和正确性。

解决方案: 修复WebSocket架构中的各种遗漏和错误。

文件变更

  • 更新 app/core/websocket/send_controller.py - 修复方法名错误
  • 更新 app/core/websocket/manager.py - 修复方法名错误和心跳发送方法

修改内容

  1. 修复方法名错误
# 修改前:错误的方法名
message = await send_channel.get_message()
message = await heartbeat_channel.get_message()
message = await receive_channel.get_message()

# 修改后:正确的方法名
message = await send_channel.receive_message()
message = await heartbeat_channel.receive_message()
message = await receive_channel.receive_message()
  1. 修复心跳发送方法
# 修改前:调用已删除的方法
return await self._heartbeat_manager.send_heartbeat(client_name)

# 修改后:直接调用内部方法
success = await self._heartbeat_manager._send_heartbeat_message(client_name, client)
  1. 语法检查通过
  • 所有WebSocket模块的语法检查都通过
  • 没有语法错误或未定义的变量
  • 异常处理完整

修复的问题

  • 修复了 get_message() 方法名错误,改为正确的 receive_message()
  • 修复了已删除的 send_heartbeat() 方法调用
  • 确保所有异常处理完整
  • 验证了所有模块的语法正确性

架构完整性确认

  • 发送功能:WebSocketSendController 正常工作
  • 接收功能:WebSocketManager 接收处理器正常工作
  • 心跳功能:WebSocketHeartbeatManager 正常工作
  • Channel管理:WebSocketChannelManager 正常工作
  • 客户端管理:WebSocketClientManager 正常工作
  • 序列化:WebSocketMessageSerializer 正常工作

WebSocket架构重构 - 统一发送控制器

WebSocket架构重构 - 统一发送控制器

问题:用户指出现在的架构有问题,每个 channel 都有一个适配器,这样还是会有多个发送循环在并发发送数据,无法真正实现优先级控制。建议所有 channel 共用一个控制器,这样可以真正实现统一的优先级管理。

解决方案

  1. 重构WebSocket架构,引入统一的发送控制器
  2. 移除多个适配器,改为一个发送控制器管理所有channel
  3. 实现真正的优先级控制,避免并发发送竞争
  4. 简化架构,提高性能和可维护性

文件变更

  • 更新 app/core/websocket/manager.py - 重构为统一发送控制器架构

修改内容

  1. 新增WebSocketSendController类
class WebSocketSendController:
    """WebSocket发送控制器 - 统一管理所有Channel的数据发送
    
    单一职责:
    - 统一管理客户端的所有Channel数据发送
    - 实现优先级控制:send_channel优先,heartbeat_channel其次
    - 避免多个适配器并发发送导致的竞争问题
    """
    
    def __init__(self, client: WebSocketClient, channels: Dict[str, WebSocketChannel]):
        self.client = client
        self.channels = channels
        self._send_task: Optional[asyncio.Task] = None
        
        # 获取相关channel
        self.send_channel = channels.get(f"{client.name}_send")
        self.heartbeat_channel = channels.get(f"{client.name}_heartbeat")
        self.receive_channel = channels.get(f"{client.name}_receive")
    
    async def _unified_send_loop(self):
        """统一的发送循环:实现真正的优先级控制
        
        优先级机制:
        1. 优先发送 send_channel 的数据
        2. 只有在 send_channel 没有数据时才发送 heartbeat_channel 的数据
        3. 避免多个适配器并发发送
        """
        while self.client.is_connected:
            try:
                msg = None
                
                # 优先级1:优先处理 send_channel
                if self.send_channel and self.send_channel.is_connected:
                    msg = await self.send_channel.receive_message(timeout=0.1)
                    if msg:
                        logger.debug(f"发送控制器从 send_channel 接收消息: {msg.type}")
                
                # 优先级2:如果 send_channel 没有数据,处理 heartbeat_channel
                if not msg and self.heartbeat_channel and self.heartbeat_channel.is_connected:
                    # 再次确认 send_channel 确实没有数据
                    if not self.send_channel or not self.send_channel.is_connected or self.send_channel.queue_size() == 0:
                        msg = await self.heartbeat_channel.receive_message(timeout=0.1)
                        if msg:
                            logger.debug(f"发送控制器从 heartbeat_channel 接收消息: {msg.type}")
                    else:
                        # send_channel 有数据,跳过心跳处理
                        await asyncio.sleep(0.05)
                        continue
                
                # 处理消息发送
                if msg:
                    await self._send_message(msg)
                else:
                    # 没有消息时短暂等待
                    await asyncio.sleep(0.05)
                    
            except asyncio.TimeoutError:
                continue
            except Exception as e:
                logger.error(f"统一发送循环异常: {e}")
                await asyncio.sleep(0.1)
  1. 重构WebSocketManager
class WebSocketManager:
    def __init__(self):
        self._clients: Dict[str, WebSocketClient] = {}
        self._channels: Dict[str, WebSocketChannel] = {}
        self._send_controllers: Dict[str, WebSocketSendController] = {}  # 发送控制器
        self._heartbeat_tasks: Dict[str, asyncio.Task] = {}  # 心跳任务
    
    async def _create_send_controller(self, client_name: str):
        """为客户端创建发送控制器"""
        client = self._clients[client_name]
        
        # 获取客户端的所有Channel
        client_channels = {}
        for channel_name, channel in self._channels.items():
            if channel_name.startswith(f"{client_name}_"):
                client_channels[channel_name] = channel
        
        # 创建发送控制器
        controller = WebSocketSendController(client, client_channels)
        self._send_controllers[client_name] = controller
    
    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()
        
        # 启动发送控制器
        if client_name in self._send_controllers:
            await self._send_controllers[client_name].start()

架构优势

  • 真正的优先级控制:只有一个发送循环,完全避免并发竞争
  • 简化架构:移除多个适配器,改为一个发送控制器
  • 统一管理:所有channel的数据发送由同一个控制器管理
  • 性能提升:减少异步任务数量,降低系统开销
  • 易于维护:架构更清晰,职责更明确
  • 智能心跳:在心跳循环中实现智能心跳机制

数据流优化

业务数据 → send_channel → 发送控制器 → WebSocket
    ↓
心跳数据 → heartbeat_channel → 发送控制器 → WebSocket
    ↓
接收数据 → WebSocket → receive_channel

设计原则

  • 一个客户端一个发送控制器:统一管理所有数据发送
  • 真正的优先级控制:避免多个发送循环并发竞争
  • 简化架构:移除不必要的适配器层
  • 保持智能心跳:在心跳循环中实现智能检查机制

WebSocket架构进一步整合 - 移除发送控制器

问题:用户询问 WebSocketSendController 和 WebSocketManager 的关系,以及是否还能进一步串联优化

分析

  1. WebSocketSendControllerWebSocketManager 是分离的,需要传递channels
  2. WebSocketSendController 只是简单的转发,功能相对单一
  3. 可以进一步整合,让 WebSocketManager 直接管理发送逻辑

解决方案

  1. WebSocketSendController 的功能完全整合到 WebSocketManager
  2. 移除独立的发送控制器类
  3. WebSocketManager 中直接管理发送任务
  4. 简化架构,减少组件间的依赖关系

文件变更

  • 更新 app/core/websocket/manager.py - 整合发送控制器功能

修改内容

  1. 移除WebSocketSendController类
# 完全移除独立的WebSocketSendController类
# 将其功能整合到WebSocketManager中
  1. 整合发送功能到WebSocketManager
class WebSocketManager:
    def __init__(self):
        self._clients: Dict[str, WebSocketClient] = {}
        self._channels: Dict[str, WebSocketChannel] = {}
        self._send_tasks: Dict[str, asyncio.Task] = {}  # 发送任务
        self._heartbeat_tasks: Dict[str, asyncio.Task] = {}  # 心跳任务
    
    async def _start_send_task(self, client_name: str):
        """启动发送任务"""
        # 停止已存在的发送任务
        if client_name in self._send_tasks:
            self._send_tasks[client_name].cancel()
        
        # 创建新的发送任务
        send_task = asyncio.create_task(self._unified_send_loop(client_name))
        self._send_tasks[client_name] = send_task
    
    async def _unified_send_loop(self, client_name: str):
        """统一的发送循环:实现真正的优先级控制"""
        client = self._clients.get(client_name)
        send_channel = self._channels.get(f"{client_name}_send")
        heartbeat_channel = self._channels.get(f"{client_name}_heartbeat")
        
        while client.is_connected:
            try:
                msg = None
                
                # 优先级1:优先处理 send_channel
                if send_channel and send_channel.is_connected:
                    msg = await send_channel.receive_message(timeout=0.1)
                
                # 优先级2:如果 send_channel 没有数据,处理 heartbeat_channel
                if not msg and heartbeat_channel and heartbeat_channel.is_connected:
                    if send_channel.queue_size() == 0:
                        msg = await heartbeat_channel.receive_message(timeout=0.1)
                
                # 处理消息发送
                if msg:
                    await self._send_message(client_name, msg)
                else:
                    await asyncio.sleep(0.05)
                    
            except asyncio.TimeoutError:
                continue
            except Exception as e:
                logger.error(f"统一发送循环异常: {e}")
                await asyncio.sleep(0.1)
    
    async def _send_message(self, client_name: str, msg: ChannelMessage):
        """发送消息到WebSocket"""
        # 序列化并发送消息
        send_data = {"Type": msg.type, **msg.data}
        payload = websocket_serializer.serialize(send_data)
        client = self._clients.get(client_name)
        if client:
            success = await client.send_raw(payload)
  1. 简化客户端创建流程
async def create_client(self, name: str, url: str, heartbeat_interval: int = 120):
    # 创建客户端
    client = WebSocketClient(url, name)
    self._clients[name] = client
    
    # 创建3个Channel
    await self._create_client_channels(name)
    
    # 注册消息处理器
    await self._register_client_handlers(name)
    
    # 保存心跳间隔配置
    client.heartbeat_interval = heartbeat_interval

架构优势

  • 完全整合:WebSocketManager直接管理所有功能
  • 简化关系:移除组件间的复杂依赖关系
  • 统一管理:所有功能集中在一个类中
  • 减少代码:移除独立的发送控制器类
  • 更易维护:架构更简洁,职责更清晰

新的关系图

WebSocketManager (统一管理器)
├── WebSocketClient (1个)
├── WebSocketChannel (3个: heartbeat, send, receive)
├── SendTask (1个) - 直接管理发送逻辑
└── HeartbeatTask (1个) - 管理心跳逻辑

设计原则

  • 统一管理:WebSocketManager直接管理所有功能
  • 简化架构:移除不必要的中间层
  • 直接串联:Client ↔ Manager ↔ Channel 直接关联
  • 职责清晰:每个组件都有明确的职责

WebSocket代码冗余清理

问题:重构后发现代码中存在冗余,需要清理

解决方案

  1. 移除不再使用的WebSocketAdapter导入
  2. 删除整个adapter.py文件
  3. 添加缺失的接收数据处理功能
  4. 清理测试文件中的冗余导入

文件变更

  • 删除 app/core/websocket/adapter.py - 完全移除适配器文件
  • 更新 app/core/websocket/manager.py - 移除冗余导入,添加接收数据处理
  • 更新 test_input_output_logging.py - 移除冗余导入

修改内容

  1. 移除冗余导入
# 移除不再使用的导入
# from app.core.websocket.adapter import WebSocketAdapter
  1. 添加接收数据处理
async def _create_send_controller(self, client_name: str):
    # ... 创建发送控制器 ...
    
    # 注册接收消息处理器,处理从WebSocket接收到的数据
    receive_channel = self._channels.get(f"{client_name}_receive")
    if receive_channel:
        # 创建异步消息处理器
        async def message_handler(msg):
            await self._handle_received_message(client_name, msg)
        client.register_message_handler("*", message_handler)
        logger.info(f"WebSocket管理器注册接收消息处理器: {client_name}")

async def _handle_received_message(self, client_name: str, message: Dict[str, Any]):
    """处理从WebSocket接收到的消息,插入到接收Channel"""
    try:
        receive_channel = self._channels.get(f"{client_name}_receive")
        if not receive_channel:
            logger.warning(f"接收Channel不存在: {client_name}_receive")
            return
        
        # 创建Channel消息
        channel_message = ChannelMessage(
            type=message.get("type", "data"),
            data=message.get("data"),
            priority=message.get("priority", 0)
        )
        
        # 插入到接收Channel
        success = await receive_channel.send_message(channel_message)
        
        if success:
            logger.debug(f"管理器接收消息成功: {message.get('type')} -> {receive_channel.name}")
        else:
            logger.warning(f"管理器接收消息失败: {message.get('type')} -> {receive_channel.name}")
            
    except Exception as e:
        logger.error(f"管理器处理WebSocket消息异常: {e}")

优化效果

  • 移除了所有冗余代码和文件
  • 添加了完整的接收数据处理功能
  • 保持了架构的完整性和一致性
  • 代码更加简洁和高效
  • 没有功能缺失,所有原有功能都得到保留

架构完整性

  • 发送功能:由WebSocketSendController统一管理
  • 接收功能:由WebSocketManager的消息处理器管理
  • 心跳功能:由心跳任务管理
  • Channel管理:由WebSocketManager统一管理

WebSocket智能心跳机制优化

问题:用户要求 heartbeat_channel 和 send_channel 保持分离,但在心跳任务向 heartbeat_channel 插入数据之前,先检查 send_channel 是否有数据。如果 send_channel 有数据,就不向 heartbeat_channel 插入心跳数据;如果 send_channel 没有数据,才向 heartbeat_channel 插入心跳数据。

解决方案

  1. 在心跳循环中实现智能心跳机制
  2. 在向 heartbeat_channel 插入心跳数据之前,先检查 send_channel 队列状态
  3. 简化 _send_loop 方法,移除复杂的优先级机制
  4. 保持架构简洁,职责清晰

文件变更

  • 更新 app/core/websocket/manager.py - 实现智能心跳机制
  • 更新 app/core/websocket/adapter.py - 简化发送循环

修改内容

  1. 智能心跳机制实现
async def _heartbeat_loop(self, client_name: str, heartbeat_interval: int):
    """心跳循环
    
    智能心跳机制:
    - 在向 heartbeat_channel 插入心跳数据之前,先检查 send_channel 是否有数据
    - 如果 send_channel 有数据,跳过心跳发送,避免占用带宽
    - 如果 send_channel 没有数据,才发送心跳数据
    """
    
    heartbeat_channel = self._channels.get(f"{client_name}_heartbeat")
    send_channel = self._channels.get(f"{client_name}_send")
    
    while client_name in self._clients:
        try:
            client = self._clients[client_name]
            
            # 智能心跳机制:检查 send_channel 是否有数据
            should_send_heartbeat = True
            if send_channel and send_channel.is_connected:
                # 检查 send_channel 队列是否有数据
                if send_channel.queue_size() > 0:
                    should_send_heartbeat = False
                    logger.debug(f"跳过心跳发送: {client_name} send_channel 有数据 (队列大小: {send_channel.queue_size()})")
                else:
                    logger.debug(f"准备发送心跳: {client_name} send_channel 无数据")
            else:
                logger.debug(f"准备发送心跳: {client_name} send_channel 不可用")
            
            # 只有在需要时才发送心跳
            if should_send_heartbeat:
                # 创建心跳消息并发送到心跳Channel
                heartbeat_message = ChannelMessage(
                    type="heartbeat",
                    data={"Payload": {"Message": "ping"}},
                    priority=1
                )
                success = await heartbeat_channel.send_message(heartbeat_message)
                if success:
                    logger.info(f"心跳消息已发送到Channel: {client_name}_heartbeat")
                else:
                    logger.warning(f"心跳消息发送失败: {client_name}_heartbeat")
            else:
                logger.debug(f"心跳发送已跳过: {client_name} (send_channel 有业务数据)")
            
            # 等待下次心跳
            await asyncio.sleep(heartbeat_interval)
            
        except asyncio.CancelledError:
            logger.info(f"心跳任务被取消: {client_name}")
            break
        except Exception as e:
            logger.error(f"心跳循环异常: {client_name} - {e}")
            await asyncio.sleep(5)
  1. 简化发送循环
async def _send_loop(self):
    """发送循环:从Channel读取数据发送到WebSocket
    
    简化设计:
    - 每个适配器只处理自己的 outbound_channel
    - 心跳机制在心跳循环中处理,避免在发送循环中做优先级判断
    - 保持架构简洁,职责清晰
    """
    
    while self.client.is_connected and self.outbound_channel.is_connected:
        try:
            # 从Channel接收消息
            msg = await self.outbound_channel.receive_message(timeout=0.5)
            if msg:
                # 处理消息发送逻辑...
                logger.info(f"适配器收到消息: {self.outbound_channel.name} -> type: {msg.type}")
            else:
                # 没有消息时短暂等待
                await asyncio.sleep(0.05)
                
        except asyncio.TimeoutError:
            continue
        except Exception as e:
            logger.error(f"适配器发送循环异常: {e}")
            await asyncio.sleep(0.1)

优化效果

  • 实现了智能心跳机制,避免心跳数据占用带宽
  • heartbeat_channel 和 send_channel 保持分离
  • 在心跳插入前检查 send_channel 状态
  • 简化了发送循环,移除了复杂的优先级机制
  • 保持了架构的清晰和简洁
  • 每个适配器只处理自己的 channel,职责明确
  • 添加了详细的调试日志,便于跟踪心跳发送状态

数据流优化

业务数据 → send_channel → 发送适配器 → WebSocket
    ↓
心跳检查 → 如果send_channel为空 → heartbeat_channel → 心跳适配器 → WebSocket

设计原则

  • 心跳机制在心跳循环中处理,不在发送循环中做优先级判断
  • 每个适配器只处理自己的 channel,保持职责单一
  • 智能的心跳发送,避免不必要的网络流量
  • 保持架构简洁,易于维护和理解

WebSocket心跳任务启动时机修复

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 测试脚本验证修复效果

测试验证: 运行测试脚本验证修复效果: