Skip to content

Commit fe2dd6b

Browse files
committed
feat: SQLite数据库路径集中化与服务稳定性增强
2 parents 0914eb7 + a8a46b2 commit fe2dd6b

File tree

8 files changed

+88
-24
lines changed

8 files changed

+88
-24
lines changed

src/backEnd/app.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@ async def lifespan(app: FastAPI):
3030
"""FastAPI 生命周期管理"""
3131
# 启动时,从配置加载刷新间隔
3232
initial_interval = get_refresh_interval()
33-
print(f"[WebSocket] 启动 WebSocket 管理器,刷新间隔: {initial_interval} 分钟")
33+
logger.info(f"启动 WebSocket 管理器,刷新间隔: {initial_interval} 分钟")
3434
ws_manager.start(initial_interval)
3535
logger.info(f"WebSocket 管理器已启动,刷新间隔: {initial_interval} 分钟")
3636
yield
3737
# 关闭时
3838
await ws_manager.stop()
39-
print("[WebSocket] WebSocket 管理器已停止")
4039
logger.info("WebSocket 管理器已停止")
4140

4241
app = FastAPI(lifespan=lifespan)

src/backEnd/main.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,35 @@
33
import tempfile
44
import os
55

6+
7+
def disable_windows_quick_edit():
8+
"""
9+
禁用 Windows 控制台的 Quick Edit Mode。
10+
Quick Edit Mode 会导致点击控制台窗口时进程输出被暂停,
11+
必须按 Enter 才能恢复,这会导致后端服务卡住。
12+
"""
13+
if sys.platform != "win32":
14+
return
15+
try:
16+
import ctypes
17+
kernel32 = ctypes.windll.kernel32
18+
# STD_INPUT_HANDLE = -10
19+
handle = kernel32.GetStdHandle(-10)
20+
mode = ctypes.c_ulong()
21+
if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
22+
# ENABLE_QUICK_EDIT_MODE = 0x0040
23+
# ENABLE_EXTENDED_FLAGS = 0x0080 (必须设置才能修改 Quick Edit)
24+
mode.value &= ~0x0040 # 禁用 Quick Edit
25+
mode.value |= 0x0080 # 启用 Extended Flags
26+
kernel32.SetConsoleMode(handle, mode)
27+
print("[INFO] Windows Quick Edit Mode 已禁用,控制台点击不会再暂停进程")
28+
except Exception as e:
29+
print(f"[WARNING] 无法禁用 Quick Edit Mode: {e}")
30+
31+
32+
# 在服务启动前禁用 Windows Quick Edit Mode
33+
disable_windows_quick_edit()
34+
635
# 配置 Python 模块导入路径 - 必须在所有项目模块导入之前完成
736
current_dir = os.path.dirname(os.path.abspath(__file__))
837
sqlmap_path = os.path.join(current_dir, "third_lib", "sqlmap")
@@ -164,7 +193,9 @@ def main(username, password):
164193

165194
uvicorn.run(app=app, host="127.0.0.1", port=8775, reload=False, log_config='./uvicorn_config.json')
166195
except Exception as e:
167-
print(e)
196+
import traceback
197+
print(f"\n[ERROR] 服务启动失败: {e}")
198+
traceback.print_exc()
168199

169200
if __name__ == "__main__":
170201
username = "admin"

src/backEnd/model/Database.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import logging
12
import sqlite3
23
import threading
34
import time
45

5-
from third_lib.sqlmap.lib.core.data import logger
6+
from third_lib.sqlmap.lib.core.data import logger as sqlmap_logger
7+
8+
logger = logging.getLogger(__name__)
69
from third_lib.sqlmap.lib.core.common import getSafeExString
710

811

@@ -33,6 +36,8 @@ def commit(self):
3336
self.connection.commit()
3437

3538
def execute(self, statement, arguments=None):
39+
max_retries = 5
40+
retry_count = 0
3641
with self.lock:
3742
while True:
3843
try:
@@ -44,14 +49,22 @@ def execute(self, statement, arguments=None):
4449
if "locked" not in getSafeExString(ex):
4550
raise
4651
else:
47-
time.sleep(1)
52+
retry_count += 1
53+
if retry_count > max_retries:
54+
logger.error(f"Database locked after {max_retries} retries, giving up")
55+
raise sqlite3.OperationalError(f"Database locked after {max_retries} retries")
56+
wait_time = min(0.05 * (2 ** retry_count), 1.0)
57+
logger.warning(f"Database locked, retry {retry_count}/{max_retries} after {wait_time:.2f}s")
58+
time.sleep(wait_time)
4859
else:
4960
break
5061

5162
if statement.lstrip().upper().startswith("SELECT"):
5263
return self.cursor.fetchall()
5364

5465
def only_execute(self, statement, arguments=None):
66+
max_retries = 5
67+
retry_count = 0
5568
with self.lock:
5669
while True:
5770
try:
@@ -63,19 +76,30 @@ def only_execute(self, statement, arguments=None):
6376
if "locked" not in getSafeExString(ex):
6477
raise
6578
else:
66-
time.sleep(1)
79+
retry_count += 1
80+
if retry_count > max_retries:
81+
logger.error(f"Database locked after {max_retries} retries, giving up")
82+
raise sqlite3.OperationalError(f"Database locked after {max_retries} retries")
83+
wait_time = min(0.05 * (2 ** retry_count), 1.0)
84+
logger.warning(f"Database locked, retry {retry_count}/{max_retries} after {wait_time:.2f}s")
85+
time.sleep(wait_time)
6786
else:
6887
break
6988

7089
return self.cursor
7190

7291
def init(self):
7392
self.execute(
74-
"CREATE TABLE logs(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, datetime TEXT, level TEXT, message TEXT)")
93+
"CREATE TABLE IF NOT EXISTS logs(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, datetime TEXT, level TEXT, message TEXT)")
7594
self.execute(
76-
"CREATE TABLE data(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, status INTEGER, content_type INTEGER, value TEXT)")
95+
"CREATE TABLE IF NOT EXISTS data(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, status INTEGER, content_type INTEGER, value TEXT)")
7796
self.execute(
78-
"CREATE TABLE errors(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, error TEXT)")
97+
"CREATE TABLE IF NOT EXISTS errors(id INTEGER PRIMARY KEY AUTOINCREMENT, taskid INTEGER, error TEXT)")
98+
99+
# IPC 表是进程间临时通信数据,每次启动时清空旧数据,避免无限增长
100+
self.execute("DELETE FROM logs")
101+
self.execute("DELETE FROM data")
102+
self.execute("DELETE FROM errors")
79103

80104
# 创建持久化请求头规则表
81105
self.execute("""

src/backEnd/pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ name = "sqlmapwebui"
33
version = "0.1.0"
44
description = "Add your description here"
55
requires-python = ">=3.10"
6-
dependencies = []
6+
dependencies = [
7+
"psutil",
8+
]
79

810
[project.optional-dependencies]
911
thirdparty = [

src/backEnd/service/taskService.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import pdb
21
import os
32
import sys
43
import asyncio

src/backEnd/utils/task_monitor.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@
33
from model.TaskStatus import TaskStatus
44
from third_lib.sqlmap.lib.core.data import logger
55

6-
import psutil
7-
86
from model.DataStore import DataStore
97

8+
# psutil 导入容错处理
9+
try:
10+
import psutil
11+
# 模块级别预热:psutil.cpu_percent 首次调用 interval=0 会返回 0.0(无意义)
12+
# 需要先执行一次 interval=1 的调用来初始化内部状态
13+
psutil.cpu_percent(interval=1)
14+
_has_psutil = True
15+
except ImportError:
16+
psutil = None
17+
_has_psutil = False
18+
1019

1120
def get_max_tasks_count():
1221
"""
@@ -17,8 +26,11 @@ def get_max_tasks_count():
1726
# 获取逻辑核心数
1827
logical_cores = os.cpu_count() or 1
1928

20-
# 获取当前 CPU 平均占用率(过去 1 秒的平均值)
21-
cpu_usage = psutil.cpu_percent(interval=1)
29+
# 获取当前 CPU 平均占用率(非阻塞,立即返回)
30+
if _has_psutil:
31+
cpu_usage = psutil.cpu_percent(interval=0)
32+
else:
33+
cpu_usage = 50 # psutil 不可用时,默认假设 50% CPU 使用率
2234

2335
# 根据 CPU 使用率动态调整最大任务数
2436
# 如果 CPU 使用率较高,则减少最大任务数

src/backEnd/utils/websocket_manager.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,30 +155,24 @@ def _restart_refresh_task(self) -> None:
155155

156156
async def _refresh_loop(self) -> None:
157157
"""定时刷新循环"""
158-
print(f"[WebSocket] 定时刷新循环已启动,间隔: {self._refresh_interval} 分钟")
159158
logger.info(f"定时刷新循环已启动,间隔: {self._refresh_interval} 分钟")
160159
while self._is_running:
161160
try:
162161
# 等待指定的刷新间隔(转换为秒)
163162
wait_seconds = self._refresh_interval * 60
164-
print(f"[WebSocket] 下一次刷新将在 {self._refresh_interval} 分钟后 ({wait_seconds}秒)")
165-
logger.info(f"下一次刷新将在 {self._refresh_interval} 分钟后 ({wait_seconds}秒)")
163+
logger.debug(f"下一次刷新将在 {self._refresh_interval} 分钟后 ({wait_seconds}秒)")
166164
await asyncio.sleep(wait_seconds)
167165

168166
# 只有有连接时才广播
169167
if self._active_connections:
170-
print(f"[WebSocket] 定时刷新触发,准备广播到 {self.connection_count} 个连接")
171168
logger.info(f"定时刷新触发,准备广播到 {self.connection_count} 个连接")
172169
await self.broadcast_refresh()
173170
else:
174-
print("[WebSocket] 定时刷新触发,但没有活跃连接,跳过广播")
175171
logger.info("定时刷新触发,但没有活跃连接,跳过广播")
176172
except asyncio.CancelledError:
177-
print("[WebSocket] 定时刷新任务被取消")
178173
logger.info("定时刷新任务被取消")
179174
break
180175
except Exception as e:
181-
print(f"[WebSocket] 定时刷新循环出错: {e}")
182176
logger.error(f"定时刷新循环出错: {e}")
183177
await asyncio.sleep(5) # 出错后短暂等待再重试
184178

@@ -193,7 +187,6 @@ def start(self, initial_interval: int = None) -> None:
193187
self._refresh_interval = max(1, min(60, initial_interval))
194188
self._is_running = True
195189
self._refresh_task = asyncio.create_task(self._refresh_loop())
196-
print(f"[WebSocket] WebSocket 管理器已启动,刷新间隔: {self._refresh_interval}分钟")
197190
logger.info(f"WebSocket 管理器已启动,刷新间隔: {self._refresh_interval}分钟")
198191

199192
async def stop(self) -> None:

src/backEnd/uv.lock

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)