forked from erp-dev/erp
223 lines
7.6 KiB
Python
223 lines
7.6 KiB
Python
import queue
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 存储所有活动的 SSE 连接队列(按商户ID组织)
|
||
_connections = {} # {merchant_id: set([conn_queue1, conn_queue2, ...])}
|
||
|
||
|
||
def get_all_connections_count():
|
||
"""获取所有商户的连接总数"""
|
||
total = 0
|
||
for merchant_id, connections in _connections.items():
|
||
total += len(connections)
|
||
return total
|
||
|
||
|
||
def get_merchant_connections(merchant_id):
|
||
"""获取指定商户的所有连接队列"""
|
||
return _connections.get(merchant_id, set())
|
||
|
||
|
||
def get_all_merchant_ids():
|
||
"""获取所有有活跃连接的商户ID列表"""
|
||
return list(_connections.keys())
|
||
|
||
|
||
def push_connection(merchant_id, conn_queue):
|
||
"""
|
||
将一个新的连接队列添加到指定商户的连接集合中
|
||
|
||
参数:
|
||
- merchant_id: 商户ID
|
||
- conn_queue: queue.Queue 实例
|
||
"""
|
||
if merchant_id not in _connections:
|
||
_connections[merchant_id] = set()
|
||
|
||
_connections[merchant_id].add(conn_queue)
|
||
total_connections = sum(len(conns) for conns in _connections.values())
|
||
logger.info(f"商户 {merchant_id} 的新 SSE 连接建立,该商户连接数: {len(_connections[merchant_id])},总连接数: {total_connections}")
|
||
|
||
|
||
def remove_connection(merchant_id, conn_queue):
|
||
"""
|
||
从指定商户的连接集合中移除一个连接队列
|
||
|
||
参数:
|
||
- merchant_id: 商户ID
|
||
- conn_queue: queue.Queue 实例
|
||
"""
|
||
if merchant_id in _connections:
|
||
_connections[merchant_id].discard(conn_queue)
|
||
|
||
# 如果该商户没有其他连接,则移除整个商户记录
|
||
if not _connections[merchant_id]:
|
||
del _connections[merchant_id]
|
||
|
||
total_connections = sum(len(conns) for conns in _connections.values())
|
||
logger.info(f"商户 {merchant_id} 的 SSE 连接断开,该商户连接数: {len(_connections.get(merchant_id, set()))},总连接数: {total_connections}")
|
||
else:
|
||
logger.warning(f"尝试从未注册的商户 {merchant_id} 移除连接")
|
||
|
||
|
||
def cleanup_all_connections():
|
||
"""
|
||
清理所有连接(用于服务器关闭时)
|
||
"""
|
||
all_connections = {}
|
||
for merchant_id, connections in list(_connections.items()):
|
||
all_connections[merchant_id] = list(connections)
|
||
for conn_queue in connections:
|
||
try:
|
||
# 发送关闭信号到队列
|
||
if hasattr(conn_queue, 'put_nowait'):
|
||
conn_queue.put_nowait({'type': 'server_shutdown', 'message': 'Server shutting down'})
|
||
logger.debug(f'shutdown connection {conn_queue} for merchant {merchant_id}')
|
||
except Exception:
|
||
pass # 忽略错误,因为连接可能已经断开
|
||
|
||
_connections.clear()
|
||
logger.info("所有SSE连接已清理")
|
||
|
||
|
||
def shutdown_merchant_connections(merchant_id):
|
||
"""
|
||
关闭指定商户的所有连接
|
||
|
||
参数:
|
||
- merchant_id: 商户ID
|
||
|
||
返回:
|
||
- int: 关闭的连接数
|
||
"""
|
||
if merchant_id not in _connections:
|
||
return 0
|
||
|
||
connections = _connections[merchant_id]
|
||
count = len(connections)
|
||
|
||
# 向每个连接发送关闭信号
|
||
for conn_queue in list(connections):
|
||
try:
|
||
conn_queue.put_nowait({
|
||
'type': 'server_shutdown',
|
||
'message': 'Server shutting down your connections, please reconnect later'
|
||
})
|
||
except queue.Full:
|
||
# 队列满了,跳过
|
||
pass
|
||
except Exception:
|
||
# 其他异常,也跳过
|
||
pass
|
||
|
||
# 删除所有连接
|
||
del _connections[merchant_id]
|
||
|
||
logger.info(f"关闭商户 {merchant_id} 的 {count} 个 SSE 连接")
|
||
return count
|
||
|
||
|
||
def push_sse_event_to_all(event_data: dict):
|
||
"""
|
||
向所有连接的客户端广播一个 SSE 事件(同步队列版本)
|
||
|
||
参数:
|
||
- event_data: 要发送的事件数据(字典)
|
||
"""
|
||
disconnected = {}
|
||
|
||
for merchant_id, connections in list(_connections.items()):
|
||
disconnected[merchant_id] = []
|
||
|
||
for conn_queue in connections:
|
||
try:
|
||
# 使用put_nowait非阻塞发送消息
|
||
conn_queue.put_nowait(event_data)
|
||
except queue.Full:
|
||
# 队列满了,说明客户端处理消息太慢,跳过这条消息
|
||
logger.warning(f"商户 {merchant_id} 队列已满,跳过消息: {event_data.get('type', 'unknown')}")
|
||
except Exception as e:
|
||
# 其他异常可能表示连接已断开
|
||
logger.error(f"向商户 {merchant_id} 的队列发送消息失败: {e}")
|
||
disconnected[merchant_id].append(conn_queue)
|
||
|
||
# 清理断开的连接
|
||
for merchant_id, conn_queues in disconnected.items():
|
||
for conn_queue in conn_queues:
|
||
remove_connection(merchant_id, conn_queue)
|
||
|
||
|
||
def push_sse_event_to_merchant(merchant_id, event_data: dict):
|
||
"""
|
||
向指定商户的所有连接客户端广播一个 SSE 事件(同步队列版本)
|
||
|
||
参数:
|
||
- merchant_id: 目标商户ID
|
||
- event_data: 要发送的事件数据(字典)
|
||
"""
|
||
if merchant_id not in _connections:
|
||
logger.info(f"商户 {merchant_id} 没有活跃连接,跳过消息发送")
|
||
return
|
||
|
||
disconnected = []
|
||
|
||
for conn_queue in list(_connections[merchant_id]):
|
||
try:
|
||
# 使用put_nowait非阻塞发送消息
|
||
conn_queue.put_nowait(event_data)
|
||
except queue.Full:
|
||
# 队列满了,说明客户端处理消息太慢,跳过这条消息
|
||
logger.warning(f"商户 {merchant_id} 队列已满,跳过消息: {event_data.get('type', 'unknown')}")
|
||
except Exception as e:
|
||
# 其他异常可能表示连接已断开
|
||
logger.error(f"向商户 {merchant_id} 的队列发送消息失败: {e}")
|
||
disconnected.append(conn_queue)
|
||
|
||
# 清理断开的连接
|
||
for conn_queue in disconnected:
|
||
remove_connection(merchant_id, conn_queue)
|
||
|
||
logger.info(f"向商户 {merchant_id} 广播 SSE 事件: type={event_data.get('type', 'unknown')}, 接收客户端数={len(_connections.get(merchant_id, set()))}")
|
||
|
||
|
||
def push_simple_message_with_object_id(event_type: str, message: str, object_id):
|
||
"""
|
||
向所有连接的客户端广播一个简单消息事件,包含关联对象ID(WSGI同步版本)
|
||
|
||
参数:
|
||
- event_type: 事件类型字符串
|
||
- message: 消息内容字符串
|
||
- object_id: 关联对象的ID(整数或字符串)
|
||
"""
|
||
event_data = {
|
||
'mode': 'simple_message',
|
||
'type': event_type,
|
||
'message': message,
|
||
'object_id': object_id,
|
||
}
|
||
push_sse_event_to_all(event_data)
|
||
total_connections = sum(len(conns) for conns in _connections.values())
|
||
logger.info(f"广播 SSE 事件: type={event_type}, object_id={object_id}, 接收客户端数={total_connections}")
|
||
|
||
|
||
def push_simple_message_with_object_id_to_merchant(merchant_id, event_type: str, message: str, object_id):
|
||
"""
|
||
向指定商户的所有连接客户端广播一个简单消息事件,包含关联对象ID
|
||
|
||
参数:
|
||
- merchant_id: 目标商户ID
|
||
- event_type: 事件类型字符串
|
||
- message: 消息内容字符串
|
||
- object_id: 关联对象的ID(整数或字符串)
|
||
"""
|
||
event_data = {
|
||
'mode': 'simple_message',
|
||
'type': event_type,
|
||
'message': message,
|
||
'object_id': object_id,
|
||
'merchant_id': merchant_id, # 添加商户ID,便于客户端验证
|
||
}
|
||
push_sse_event_to_merchant(merchant_id, event_data)
|