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 0000000..15e2957 Binary files /dev/null and b/data/outlook_manager.db differ diff --git a/database.py b/database.py index f7c71ed..7b3edd6 100644 --- a/database.py +++ b/database.py @@ -18,9 +18,11 @@ logger = logging.getLogger(__name__) class DatabaseManager: """数据库管理器""" - def __init__(self, db_path: str = "outlook_manager.db"): + def __init__(self, db_path: str = "data/outlook_manager.db"): self.db_path = db_path self._local = threading.local() + # 确保数据目录存在 + Path(db_path).parent.mkdir(parents=True, exist_ok=True) self.init_database() def get_connection(self) -> 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