From 889f4f15d58d097a3f607798c66a59b4994c8347 Mon Sep 17 00:00:00 2001 From: huangzhenpc Date: Fri, 6 Mar 2026 00:57:35 +0800 Subject: [PATCH] Optimize database and deployment: fix SQL injection, async auth, persist data - Move SQLite DB to data/ directory and track in git for portability - Fix SQL injection in cleanup_old_emails (use parameterized query) - Replace sync requests with async httpx in auth.py - Enable WAL mode and foreign keys for SQLite - Add UNIQUE constraint and foreign key to account_tags table - Remove redundant indexes on primary key columns - Mount data/ volume in docker-compose for persistence - Remove unused requests dependency Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 8 +++++--- auth.py | 33 ++++++++++++++++----------------- data/outlook_manager.db | Bin 0 -> 73728 bytes database.py | 23 ++++++++++++++--------- docker-compose.yml | 2 ++ requirements.txt | 1 - 6 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 data/outlook_manager.db diff --git a/.gitignore b/.gitignore index 4bf1290..0854a6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ config.txt -outlook_manager.db .codebuddy -_pychache_ +__pycache__ *.pyc -config.txt \ No newline at end of file +.env +# WAL临时文件不需要跟踪 +data/*.db-wal +data/*.db-shm \ No newline at end of file diff --git a/auth.py b/auth.py index 57bd084..7f1dad8 100644 --- a/auth.py +++ b/auth.py @@ -4,7 +4,7 @@ OAuth2认证模块 处理Microsoft OAuth2令牌获取和刷新 """ -import requests +import httpx import logging from typing import Optional from fastapi import HTTPException @@ -34,43 +34,42 @@ async def get_access_token(refresh_token: str, check_only: bool = False, client_ 'refresh_token': refresh_token, 'scope': 'https://outlook.office.com/IMAP.AccessAsUser.All offline_access' } - + try: - # 使用requests而不是httpx,因为exp.py验证有效 - response = requests.post(TOKEN_URL, data=data) - response.raise_for_status() - + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post(TOKEN_URL, data=data) + response.raise_for_status() + token_data = response.json() access_token = token_data.get('access_token') - + if not access_token: error_msg = f"获取 access_token 失败: {token_data.get('error_description', '响应中未找到 access_token')}" logger.error(error_msg) if check_only: return None raise HTTPException(status_code=401, detail=error_msg) - + new_refresh_token = token_data.get('refresh_token') if new_refresh_token and new_refresh_token != refresh_token: logger.debug("提示: refresh_token 已被服务器更新") - + return access_token - - except requests.exceptions.HTTPError as http_err: + + except httpx.HTTPStatusError as http_err: logger.error(f"请求 access_token 时发生HTTP错误: {http_err}") - if http_err.response is not None: - logger.error(f"服务器响应: {http_err.response.status_code} - {http_err.response.text}") - + logger.error(f"服务器响应: {http_err.response.status_code} - {http_err.response.text}") + if check_only: return None raise HTTPException(status_code=401, detail="Refresh token已过期或无效,需要重新获取授权") - - except requests.exceptions.RequestException as e: + + except httpx.RequestError as e: logger.error(f"请求 access_token 时发生网络错误: {e}") if check_only: return None raise HTTPException(status_code=500, detail="Token acquisition failed") - + except Exception as e: logger.error(f"解析 access_token 响应时出错: {e}") if check_only: diff --git a/data/outlook_manager.db b/data/outlook_manager.db new file mode 100644 index 0000000000000000000000000000000000000000..15e2957979eec46b0f0c7e1cdd1b4e1bcfdf4399 GIT binary patch literal 73728 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 sqlite3.Connection: @@ -28,6 +30,9 @@ class DatabaseManager: if not hasattr(self._local, 'connection'): self._local.connection = sqlite3.connect(self.db_path) self._local.connection.row_factory = sqlite3.Row + # 启用WAL模式,提升并发读性能 + self._local.connection.execute('PRAGMA journal_mode=WAL') + self._local.connection.execute('PRAGMA foreign_keys=ON') return self._local.connection def init_database(self): @@ -51,10 +56,11 @@ class DatabaseManager: cursor.execute(''' CREATE TABLE IF NOT EXISTS account_tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, - email TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, tags TEXT NOT NULL, -- JSON格式存储标签数组 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (email) REFERENCES accounts(email) ON DELETE CASCADE ) ''') @@ -114,9 +120,8 @@ class DatabaseManager: cursor.execute(f"ALTER TABLE claude_payment_status ADD COLUMN {col} TEXT {default}") logger.info(f"已为 claude_payment_status 添加 {col} 列") - # 创建索引 - cursor.execute('CREATE INDEX IF NOT EXISTS idx_accounts_email ON accounts(email)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_account_tags_email ON account_tags(email)') + # 创建索引(accounts.email 是 PRIMARY KEY,无需额外索引) + # account_tags.email 已有 UNIQUE 约束,无需额外索引 cursor.execute('CREATE INDEX IF NOT EXISTS idx_email_cache_email ON email_cache(email)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_email_cache_message_id ON email_cache(message_id)') @@ -309,9 +314,9 @@ class DatabaseManager: conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' - DELETE FROM email_cache - WHERE created_at < datetime('now', '-{} days') - '''.format(days)) + DELETE FROM email_cache + WHERE created_at < datetime('now', ? || ' days') + ''', (f'-{days}',)) deleted_count = cursor.rowcount conn.commit() return deleted_count diff --git a/docker-compose.yml b/docker-compose.yml index 0fd28e4..73264f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: volumes: # 挂载配置文件,便于修改邮箱配置 - ./config.txt:/app/config.txt + # 持久化SQLite数据库,防止容器重建丢失数据 + - ./data:/app/data # 可选:挂载日志目录 - ./logs:/app/logs environment: diff --git a/requirements.txt b/requirements.txt index 80398d7..8ad8727 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ fastapi==0.104.1 uvicorn[standard]==0.24.0 pydantic==2.5.0 httpx==0.25.2 -requests==2.31.0 python-multipart==0.0.6 jinja2==3.1.2 aiofiles==23.2.1 \ No newline at end of file