1
0
forked from erp-dev/erp

feat: sse && multi_merchant completed

This commit is contained in:
2025-11-11 10:56:20 +08:00
parent b2078dfa46
commit 2aafb93aad
43 changed files with 2445 additions and 244 deletions

154
sse/simple_sse.py Normal file
View File

@@ -0,0 +1,154 @@
"""
简化的SSE实现专门解决uvicorn无法退出的问题
"""
import json
import time
import threading
from collections import defaultdict
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
# 全局状态
_clients = {} # 存储客户端连接的状态
_client_counter = 0
_shutdown_flag = threading.Event()
_lock = threading.Lock()
def get_next_client_id():
"""获取下一个客户端ID"""
global _client_counter
with _lock:
_client_counter += 1
return _client_counter
def add_client(client_id):
"""添加客户端"""
with _lock:
_clients[client_id] = {
'active': True,
'messages': [],
'last_heartbeat': time.time()
}
def remove_client(client_id):
"""移除客户端"""
with _lock:
_clients.pop(client_id, None)
def broadcast_message(message):
"""广播消息给所有客户端"""
with _lock:
for client_id, client_data in _clients.items():
if client_data['active']:
client_data['messages'].append(message)
def get_client_messages(client_id):
"""获取客户端的消息"""
with _lock:
if client_id in _clients:
messages = _clients[client_id]['messages'][:]
_clients[client_id]['messages'].clear()
_clients[client_id]['last_heartbeat'] = time.time()
return messages
return []
def cleanup_clients():
"""清理所有客户端"""
global _shutdown_flag
_shutdown_flag.set()
with _lock:
for client_data in _clients.values():
client_data['active'] = False
_clients.clear()
@csrf_exempt
def simple_sse_view(request):
"""简化的SSE视图"""
if request.method == 'OPTIONS':
response = HttpResponse()
origin = request.META.get('HTTP_ORIGIN')
if origin:
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Methods'] = 'GET, OPTIONS'
response['Access-Control-Allow-Headers'] = 'authorization, content-type, cache-control, accept'
response['Access-Control-Allow-Credentials'] = 'true'
response['Access-Control-Max-Age'] = '86400'
return response
def event_generator():
client_id = get_next_client_id()
add_client(client_id)
try:
# 发送连接成功消息
yield f"data: {json.dumps({'type': 'connected', 'client_id': client_id})}\n\n"
# 主循环 - 使用更短的检查间隔
iterations = 0
while not _shutdown_flag.is_set():
# 每5次迭代检查一次消息约0.5秒)
if iterations % 5 == 0:
messages = get_client_messages(client_id)
for message in messages:
yield f"data: {json.dumps(message)}\n\n"
# 每50次迭代发送心跳约5秒
if iterations % 50 == 0:
yield f": heartbeat {time.time()}\n\n"
# 短暂睡眠让出CPU并允许快速响应关闭信号
time.sleep(0.1)
iterations += 1
# 如果关闭标志被设置,退出循环
if _shutdown_flag.is_set():
break
# 发送关闭消息
yield f"data: {json.dumps({'type': 'shutdown', 'message': 'Server shutting down'})}\n\n"
except Exception as e:
print(f"SSE error for client {client_id}: {e}")
finally:
remove_client(client_id)
print(f"Client {client_id} disconnected")
response = HttpResponse(event_generator(), content_type='text/event-stream')
response['Cache-Control'] = 'no-cache'
response['X-Accel-Buffering'] = 'no'
# CORS headers
origin = request.META.get('HTTP_ORIGIN')
if origin:
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Credentials'] = 'true'
return response
def broadcast_test_message():
"""广播测试消息"""
message = {
'type': 'test',
'message': 'Test message',
'timestamp': time.time()
}
broadcast_message(message)
return len(_clients)
def get_status():
"""获取状态"""
with _lock:
return {
'clients': len(_clients),
'shutdown': _shutdown_flag.is_set()
}