diff --git a/README.md b/README.md index 5fe82d9..eeb9074 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ CapacityReport 用于每周导入 Excel/CSV 数据,按 `Configure.json` 与 `R - 后端:FastAPI + Uvicorn + Supervisor - 前端:Vue 3 + TypeScript + Vite + Naive UI -- 数据库:MySQL 8.0+,通过 PyMySQL + SQLAlchemy 访问 +- 数据库:MySQL 8.0+,通过 PyMySQL 访问 - 数据处理:Pandas + OpenPyXL - 部署:Docker + Docker Compose diff --git a/app/api/routers/database.py b/app/api/routers/database.py index 01add7d..2cde9d5 100644 --- a/app/api/routers/database.py +++ b/app/api/routers/database.py @@ -56,11 +56,8 @@ def _dataframe_from_table(db: DatabaseManager, table_name: str) -> pd.DataFrame: @router.post("/api/database/test") async def test_database(): db = DatabaseManager(state.config) - try: - success, message = db.test_connection() - return {"success": success, "message": message} - finally: - db.dispose() + success, message = db.test_connection() + return {"success": success, "message": message} @router.get("/api/database/info") @@ -70,8 +67,6 @@ async def get_database_info(): return {"success": True, **db.get_server_info()} except Exception as exc: return {"success": False, "error": str(exc)} - finally: - db.dispose() @router.get("/api/database/tables") @@ -82,8 +77,6 @@ async def get_tables(): return {"tables": db.get_tables()} except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/info") @@ -93,8 +86,6 @@ async def get_table_info(table_name: str = Body(..., embed=True)): return db.get_table_info(table_name) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/data") @@ -110,8 +101,6 @@ async def query_table_data( return db.query_table(table_name, page, page_size, order_by=order_by, order_dir=order_dir) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/query") @@ -135,8 +124,6 @@ async def query_table_with_filter( ) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/truncate") @@ -147,8 +134,6 @@ async def truncate_table(table_name: str = Body(..., embed=True)): return {"success": True, "message": f"表 {table_name} 已清空"} except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/drop") @@ -159,8 +144,6 @@ async def drop_table(table_name: str = Body(..., embed=True)): return {"success": True, "message": f"表 {table_name} 已删除"} except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/table/drop-all") @@ -176,8 +159,6 @@ async def drop_all_tables(): } except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/database/execute") @@ -192,8 +173,6 @@ async def execute_sql(sql: str = Body(..., embed=True)): raise except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() @router.post("/api/download") @@ -226,8 +205,6 @@ async def download_table( raise except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) from exc - finally: - db.dispose() timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename_prefix = requested_tables[0] if len(requested_tables) == 1 else "tables" diff --git a/app/api/routers/health.py b/app/api/routers/health.py index 813062a..55383fd 100644 --- a/app/api/routers/health.py +++ b/app/api/routers/health.py @@ -19,15 +19,12 @@ async def health_check(): try: db_manager = DatabaseManager(state.config) - try: - server_info = db_manager.get_server_info() - checks["database"] = { - "status": "ok", - "version": server_info.get("version", "unknown"), - "load_data_infile": server_info.get("load_data_infile", False), - } - finally: - db_manager.dispose() + server_info = db_manager.get_server_info() + checks["database"] = { + "status": "ok", + "version": server_info.get("version", "unknown"), + "load_data_infile": server_info.get("load_data_infile", False), + } except Exception as exc: checks["database"] = {"status": "error", "message": str(exc)} @@ -39,4 +36,3 @@ async def health_check(): "uptime_pid": os.getpid(), "checks": checks, } - diff --git a/app/auth.py b/app/auth.py index 482425a..c2f8686 100644 --- a/app/auth.py +++ b/app/auth.py @@ -4,7 +4,6 @@ import hashlib import hmac import json import time -from pathlib import Path from typing import Optional from app.config import BASE_DIR @@ -81,4 +80,3 @@ def _sign(header: str, payload: str) -> str: hashlib.sha256, ).digest() return base64.urlsafe_b64encode(raw).decode().rstrip("=") - diff --git a/app/database.py b/app/database.py index 0073712..a647196 100644 --- a/app/database.py +++ b/app/database.py @@ -2,14 +2,9 @@ 数据库连接与操作模块 - 性能优化版 """ import pymysql -from urllib.parse import quote from contextlib import contextmanager from typing import Any, Dict, List, Optional, Tuple -import sqlalchemy -from sqlalchemy import create_engine -from sqlalchemy.pool import QueuePool - from app.config import AppConfig @@ -18,33 +13,6 @@ class DatabaseManager: def __init__(self, config: AppConfig): self.config = config - self._engine: Optional[sqlalchemy.Engine] = None - - @property - def engine(self) -> sqlalchemy.Engine: - """获取 SQLAlchemy 引擎(带连接池)- 优化配置""" - if self._engine is None: - mysql = self.config.mysql - self._engine = create_engine( - f'mysql+pymysql://' - f'{quote(mysql.user)}:' - f'{quote(mysql.passwd)}@' - f'{quote(mysql.host)}:' - f'{mysql.port}/' - f'{quote(mysql.dbname)}?charset=utf8mb4', - poolclass=QueuePool, - pool_size=10, # 增大连接池 - max_overflow=20, # 增大溢出连接 - pool_pre_ping=True, - pool_recycle=3600, - echo=False, - # 性能优化参数 - connect_args={ - 'local_infile': True, # 允许 LOAD DATA LOCAL - 'autocommit': False, - } - ) - return self._engine @contextmanager def get_connection(self): @@ -230,15 +198,6 @@ class DatabaseManager: "total_pages": (total + page_size - 1) // page_size } - def delete_rows(self, table_name: str, condition: str, params: List[Any]) -> int: - """删除符合条件的行""" - with self.get_connection() as conn: - with conn.cursor() as cursor: - sql = f"DELETE FROM `{table_name}` WHERE {condition}" - cursor.execute(sql, params) - conn.commit() - return cursor.rowcount - def truncate_table(self, table_name: str) -> bool: """清空表""" with self.get_connection() as conn: @@ -272,7 +231,7 @@ class DatabaseManager: try: cursor.execute(f"DROP TABLE IF EXISTS `{table}`") dropped_tables.append(table) - except Exception as e: + except Exception: # 记录错误但继续删除其他表 pass @@ -434,9 +393,3 @@ class DatabaseManager: with conn.cursor() as cursor: cursor.execute(sql) conn.commit() - - def dispose(self): - """释放连接池""" - if self._engine: - self._engine.dispose() - self._engine = None diff --git a/app/history.py b/app/history.py index 69799df..16d0c34 100644 --- a/app/history.py +++ b/app/history.py @@ -49,7 +49,7 @@ class HistoryManager: """加载历史记录""" try: return json.loads(HISTORY_FILE.read_text(encoding='utf-8')) - except: + except (OSError, json.JSONDecodeError): return [] def _save(self, records: List[Dict[str, Any]]): diff --git a/app/processor.py b/app/processor.py index 12a479c..fb2acc8 100644 --- a/app/processor.py +++ b/app/processor.py @@ -220,8 +220,6 @@ class DataProcessor: finally: # 清理临时目录 self._cleanup_temp_dir() - # 释放数据库连接 - self.db.dispose() return self.results diff --git a/build/build.py b/build/build.py index 44746f6..3803504 100644 --- a/build/build.py +++ b/build/build.py @@ -8,10 +8,12 @@ CapacityReport 统一构建脚本 import os import sys import json +import platform import shutil import subprocess import re import tarfile +import traceback from pathlib import Path # ============================================================================ @@ -37,9 +39,7 @@ MYSQL_PORT = 3306 # 应用端口映射 APP_PORT_HOST = 19081 -APP_PORT_CONTAINER = 9081 MYSQL_PORT_HOST = 13306 -MYSQL_PORT_CONTAINER = 3306 # ============================================================================ # 脚本目录配置 @@ -51,14 +51,12 @@ DIST_DIR = PROJECT_ROOT / "dist" BUILD_DIR = PROJECT_ROOT / "build" TEMP_BUILD_DIR = BUILD_DIR / "temp" -# 颜色输出(Windows 支持) -import platform if platform.system() == 'Windows': try: import ctypes kernel32 = ctypes.windll.kernel32 kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) - except: + except OSError: pass class Colors: @@ -95,7 +93,7 @@ def run_cmd(cmd, check=True, capture_output=False): text=True ) return result - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: if check: print_error(f"命令执行失败: {' '.join(cmd)}") sys.exit(1) @@ -198,7 +196,7 @@ def image_exists(image_name): check=True ) return image_name in result.stdout - except: + except (subprocess.CalledProcessError, FileNotFoundError): return False def format_size(size_bytes): @@ -668,14 +666,10 @@ def build_full_package(): sys.exit(1) # 检查 Docker Compose - docker_compose_cmd = None try: subprocess.run(["docker", "compose", "version"], capture_output=True, check=True) - docker_compose_cmd = "docker compose" - except: - if check_command("docker-compose"): - docker_compose_cmd = "docker-compose" - else: + except (subprocess.CalledProcessError, FileNotFoundError): + if not check_command("docker-compose"): print_error("未检测到 Docker Compose") sys.exit(1) @@ -689,14 +683,14 @@ def build_full_package(): if not image_exists(python_image): print_info(f"Python 镜像不存在,正在拉取: {python_image}") run_cmd(["docker", "pull", python_image]) - print_info(f"Python 镜像拉取完成") + print_info("Python 镜像拉取完成") else: print_info(f"Python 镜像已存在: {python_image}") if not image_exists(mysql_image): print_info(f"MySQL 镜像不存在,正在拉取: {mysql_image}") run_cmd(["docker", "pull", mysql_image]) - print_info(f"MySQL 镜像拉取完成") + print_info("MySQL 镜像拉取完成") else: print_info(f"MySQL 镜像已存在: {mysql_image}") @@ -728,7 +722,7 @@ def build_full_package(): if dockerignore_build.read_text() == dockerignore_root.read_text(): dockerignore_root.unlink() print_info("已清理临时 .dockerignore") - except: + except OSError: pass # 标记 MySQL 镜像(提取版本号) @@ -844,7 +838,7 @@ def build_update_package(): if not image_exists(python_image): print_info(f"Python 镜像不存在,正在拉取: {python_image}") run_cmd(["docker", "pull", python_image]) - print_info(f"Python 镜像拉取完成") + print_info("Python 镜像拉取完成") else: print_info(f"Python 镜像已存在: {python_image}") @@ -876,7 +870,7 @@ def build_update_package(): if dockerignore_build.read_text() == dockerignore_root.read_text(): dockerignore_root.unlink() print_info("已清理临时 .dockerignore") - except: + except OSError: pass # 导出镜像 @@ -992,6 +986,5 @@ if __name__ == "__main__": sys.exit(1) except Exception as e: print_error(f"构建失败: {e}") - import traceback traceback.print_exc() sys.exit(1) diff --git a/docs/project_context.md b/docs/project_context.md index 178cec6..3e6dad8 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,5 +1,12 @@ # 项目上下文记录 +## 2026-05-19:清理后端冗余代码和未用依赖 + +- `app/database.py` 移除未使用的 SQLAlchemy 连接池、`engine` 属性、`dispose()` 空释放路径和未引用的 `delete_rows()`;数据库访问统一保留现有 PyMySQL 上下文连接。 +- `app/api/routers/database.py`、`app/api/routers/health.py` 和 `app/processor.py` 同步去除无效 `dispose()` 调用,避免保留没有实际资源释放意义的样板代码。 +- `requirements.txt`、`run.bat` 和 `README.md` 移除 SQLAlchemy 依赖和说明;`build/build.py` 清理无用端口常量、内联导入和宽泛异常捕获。 +- 已清理本地 `.ruff_cache/` 与重复的 `ReportScript.sql.bak`;已执行 `.venv\Scripts\python.exe -m compileall app build`、`uvx ruff check .`、`uvx vulture app build --min-confidence 80` 和 `npm run build`,均通过。 + ## 2026-05-19:移除旧版 HTML 前端和双端口托管 - 删除 `frontend_old/` 旧版 HTML/CSS/JS 前端及其本地 Monaco 资源,项目只保留 Vue 3 新前端。 @@ -324,7 +331,7 @@ ### 依赖与清理 -- Python 依赖保留当前代码实际使用项:`fastapi`、`uvicorn[standard]`、`python-multipart`、`pymysql`、`sqlalchemy`、`cryptography`、`pandas`、`openpyxl`、`chardet`、`supervisor`。 +- Python 依赖保留当前代码实际使用项:`fastapi`、`uvicorn[standard]`、`python-multipart`、`pymysql`、`cryptography`、`pandas`、`openpyxl`、`chardet`、`supervisor`。 - 已移除未使用的 `sqlparse`、`aiofiles`、`python-dateutil`。 - 旧静态目录、根目录打包产物、日志和 Python 编译缓存属于可清理产物,不应提交。 diff --git a/requirements.txt b/requirements.txt index 7c8deda..2946c94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ python-multipart # Database pymysql -sqlalchemy cryptography # Data processing diff --git a/run.bat b/run.bat index 12139b0..1e7f5c3 100644 --- a/run.bat +++ b/run.bat @@ -16,7 +16,7 @@ if not exist "%PYTHON_EXE%" ( exit /b 1 ) -"%PYTHON_EXE%" -c "import fastapi, uvicorn, pandas, openpyxl, pymysql, sqlalchemy, chardet" >nul 2>&1 +"%PYTHON_EXE%" -c "import fastapi, uvicorn, pandas, openpyxl, pymysql, chardet" >nul 2>&1 if errorlevel 1 ( echo [INFO] Installing or repairing Python dependencies... uv pip install -r requirements.txt