1
0
forked from erp-dev/erp
Files
erpnew/sse/services.py

223 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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):
"""
向所有连接的客户端广播一个简单消息事件包含关联对象IDWSGI同步版本
参数:
- 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)