Skip to content

Commit c3584c4

Browse files
committed
feat: 实现WebSocket定时刷新与任务创建通知机制
- 新增 WebSocket 连接管理器 (websocket_manager.py) - 后端定时广播刷新通知给前端 - 新任务创建时自动触发前端刷新 - 刷新间隔配置存储在后端 (system_config.json) - 新增配置 API (/api/config/refresh-interval) - 前端从后端加载和保存刷新间隔配置 - 刷新间隔范围调整为 1-60 分钟
1 parent 9065637 commit c3584c4

File tree

11 files changed

+1006
-108
lines changed

11 files changed

+1006
-108
lines changed

src/backEnd/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,4 @@ gunicorn.pid
225225
# 会话文件
226226
sessions/
227227
*.session
228+
data/

src/backEnd/api/commonApi/configController.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# 系统配置管理控制器
22
import os
3+
import json
34
import logging
45
from typing import Optional
6+
from pathlib import Path
57

68
from fastapi import APIRouter, Depends, status
79
from pydantic import BaseModel
@@ -13,11 +15,68 @@
1315
get_default_http_request_temp_dir
1416
)
1517
from utils.auth import get_current_user
18+
from utils.websocket_manager import ws_manager
1619

1720
logger = logging.getLogger(__name__)
1821

1922
router = APIRouter(prefix="/config")
2023

24+
# 配置文件路径
25+
CONFIG_FILE = Path(__file__).parent.parent.parent / "data" / "system_config.json"
26+
27+
# 默认配置
28+
DEFAULT_CONFIG = {
29+
"refreshInterval": 5 # 默认5分钟
30+
}
31+
32+
33+
def _ensure_config_dir():
34+
"""确保配置目录存在"""
35+
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
36+
37+
38+
def _load_system_config() -> dict:
39+
"""加载系统配置"""
40+
try:
41+
if CONFIG_FILE.exists():
42+
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
43+
config = json.load(f)
44+
# 合并默认配置
45+
return {**DEFAULT_CONFIG, **config}
46+
except Exception as e:
47+
logger.error(f"加载系统配置失败: {e}")
48+
return DEFAULT_CONFIG.copy()
49+
50+
51+
def _save_system_config(config: dict):
52+
"""保存系统配置"""
53+
try:
54+
_ensure_config_dir()
55+
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
56+
json.dump(config, f, ensure_ascii=False, indent=2)
57+
logger.info(f"系统配置已保存: {config}")
58+
except Exception as e:
59+
logger.error(f"保存系统配置失败: {e}")
60+
raise
61+
62+
63+
def get_refresh_interval() -> int:
64+
"""获取刷新间隔(分钟)"""
65+
config = _load_system_config()
66+
return config.get("refreshInterval", DEFAULT_CONFIG["refreshInterval"])
67+
68+
69+
def set_refresh_interval(interval: int):
70+
"""设置刷新间隔(分钟)"""
71+
# 限制范围 1-60 分钟
72+
interval = max(1, min(60, interval))
73+
config = _load_system_config()
74+
config["refreshInterval"] = interval
75+
_save_system_config(config)
76+
# 更新 WebSocket 管理器的刷新间隔
77+
ws_manager.update_refresh_interval(interval)
78+
return interval
79+
2180

2281
class TempDirConfigRequest(BaseModel):
2382
"""临时文件目录配置请求"""
@@ -170,3 +229,70 @@ async def reset_temp_dir_config(current_user: dict = Depends(get_current_user)):
170229
success=False,
171230
code=status.HTTP_500_INTERNAL_SERVER_ERROR
172231
)
232+
233+
234+
class RefreshIntervalRequest(BaseModel):
235+
"""刷新间隔配置请求"""
236+
interval: int # 刷新间隔(分钟)
237+
238+
239+
@router.get('/refresh-interval')
240+
async def get_refresh_interval_config(current_user: dict = Depends(get_current_user)):
241+
"""
242+
获取数据刷新间隔配置
243+
"""
244+
try:
245+
interval = get_refresh_interval()
246+
247+
return BaseResponseMsg(
248+
data={
249+
"refreshInterval": interval,
250+
"minInterval": 1,
251+
"maxInterval": 60
252+
},
253+
msg="success",
254+
success=True,
255+
code=status.HTTP_200_OK
256+
)
257+
except Exception as e:
258+
logger.error(f"获取刷新间隔配置失败: {e}")
259+
return BaseResponseMsg(
260+
data=None,
261+
msg=f"Failed to get refresh interval: {str(e)}",
262+
success=False,
263+
code=status.HTTP_500_INTERNAL_SERVER_ERROR
264+
)
265+
266+
267+
@router.post('/refresh-interval')
268+
async def set_refresh_interval_config(
269+
request: RefreshIntervalRequest,
270+
current_user: dict = Depends(get_current_user)
271+
):
272+
"""
273+
设置数据刷新间隔
274+
- interval: 刷新间隔(分钟),范围 1-60
275+
"""
276+
try:
277+
new_interval = set_refresh_interval(request.interval)
278+
279+
logger.info(f"刷新间隔已设置为 {new_interval} 分钟")
280+
281+
return BaseResponseMsg(
282+
data={
283+
"refreshInterval": new_interval,
284+
"minInterval": 1,
285+
"maxInterval": 60
286+
},
287+
msg=f"刷新间隔已设置为 {new_interval} 分钟",
288+
success=True,
289+
code=status.HTTP_200_OK
290+
)
291+
except Exception as e:
292+
logger.error(f"设置刷新间隔失败: {e}")
293+
return BaseResponseMsg(
294+
data=None,
295+
msg=f"Failed to set refresh interval: {str(e)}",
296+
success=False,
297+
code=status.HTTP_500_INTERNAL_SERVER_ERROR
298+
)

src/backEnd/api/commonApi/webTaskController.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from model.requestModel.TaskRequest import TaskAddRequest
1111
from service.taskService import taskService
1212
from utils.auth import get_current_user
13+
from utils.websocket_manager import ws_manager
1314

1415
logger = logging.getLogger(__name__)
1516

@@ -73,6 +74,9 @@ async def add_task_from_web(
7374

7475
if res.success:
7576
logger.info(f"[Web] Task created successfully: {res.data}")
77+
# 通知前端刷新数据
78+
task_id = res.data.get('taskid') if isinstance(res.data, dict) else None
79+
await ws_manager.notify_task_created(task_id)
7680
else:
7781
logger.warning(f"[Web] Task creation failed: {res.msg}")
7882

src/backEnd/app.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import logging
22
import time
33
import os
4+
import asyncio
45
from typing import Union
5-
from fastapi import FastAPI
6+
from contextlib import asynccontextmanager
7+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
68
from fastapi.responses import RedirectResponse
79
from fastapi.staticfiles import StaticFiles
810
from fastapi.responses import FileResponse
@@ -16,9 +18,27 @@
1618
from api.commonApi.scanPreset import router as scan_preset_router
1719
from api.commonApi.webTaskController import router as web_task_router
1820
from config import VERSION
21+
from utils.websocket_manager import ws_manager
22+
from api.commonApi.configController import get_refresh_interval
1923

2024
logger = logging.getLogger(__name__)
21-
app = FastAPI()
25+
26+
# 生命周期管理
27+
@asynccontextmanager
28+
async def lifespan(app: FastAPI):
29+
"""FastAPI 生命周期管理"""
30+
# 启动时,从配置加载刷新间隔
31+
initial_interval = get_refresh_interval()
32+
print(f"[WebSocket] 启动 WebSocket 管理器,刷新间隔: {initial_interval} 分钟")
33+
ws_manager.start(initial_interval)
34+
logger.info(f"WebSocket 管理器已启动,刷新间隔: {initial_interval} 分钟")
35+
yield
36+
# 关闭时
37+
await ws_manager.stop()
38+
print("[WebSocket] WebSocket 管理器已停止")
39+
logger.info("WebSocket 管理器已停止")
40+
41+
app = FastAPI(lifespan=lifespan)
2242

2343
# 记录服务启动时间
2444
START_TIME = time.time()
@@ -81,6 +101,52 @@ def health_check():
81101
}
82102
}
83103

104+
# WebSocket 端点
105+
@app.websocket("/ws")
106+
async def websocket_endpoint(websocket: WebSocket):
107+
"""
108+
WebSocket 连接端点
109+
110+
用于实时通信,后端定时推送刷新通知给前端
111+
"""
112+
await ws_manager.connect(websocket)
113+
try:
114+
while True:
115+
# 接收客户端消息
116+
data = await websocket.receive_json()
117+
await ws_manager.handle_message(websocket, data)
118+
except WebSocketDisconnect:
119+
await ws_manager.disconnect(websocket)
120+
except Exception as e:
121+
logger.error(f"WebSocket 错误: {e}")
122+
await ws_manager.disconnect(websocket)
123+
124+
@app.get("/api/ws/status")
125+
def get_ws_status():
126+
"""获取 WebSocket 状态"""
127+
return {
128+
"code": 200,
129+
"success": True,
130+
"message": "success",
131+
"data": {
132+
"connectionCount": ws_manager.connection_count,
133+
"refreshInterval": ws_manager.refresh_interval
134+
}
135+
}
136+
137+
@app.post("/api/ws/refresh-interval")
138+
def set_refresh_interval(interval: int):
139+
"""设置刷新间隔"""
140+
ws_manager.update_refresh_interval(interval)
141+
return {
142+
"code": 200,
143+
"success": True,
144+
"message": "success",
145+
"data": {
146+
"refreshInterval": ws_manager.refresh_interval
147+
}
148+
}
149+
84150
# 返回 index.html 文件
85151
@app.get("/")
86152
async def read_root():

0 commit comments

Comments
 (0)