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) <noreply@anthropic.com>
This commit is contained in:
2026-03-06 00:57:35 +08:00
parent 5b01caf8e3
commit 889f4f15d5
6 changed files with 37 additions and 30 deletions

8
.gitignore vendored
View File

@@ -1,6 +1,8 @@
config.txt
outlook_manager.db
.codebuddy
_pychache_
__pycache__
*.pyc
config.txt
.env
# WAL临时文件不需要跟踪
data/*.db-wal
data/*.db-shm

11
auth.py
View File

@@ -4,7 +4,7 @@ OAuth2认证模块
处理Microsoft OAuth2令牌获取和刷新
"""
import requests
import httpx
import logging
from typing import Optional
from fastapi import HTTPException
@@ -36,8 +36,8 @@ async def get_access_token(refresh_token: str, check_only: bool = False, client_
}
try:
# 使用requests而不是httpx因为exp.py验证有效
response = requests.post(TOKEN_URL, data=data)
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()
@@ -56,16 +56,15 @@ async def get_access_token(refresh_token: str, check_only: bool = False, client_
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}")
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

BIN
data/outlook_manager.db Normal file

Binary file not shown.

View File

@@ -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)')
@@ -310,8 +315,8 @@ class DatabaseManager:
cursor = conn.cursor()
cursor.execute('''
DELETE FROM email_cache
WHERE created_at < datetime('now', '-{} days')
'''.format(days))
WHERE created_at < datetime('now', ? || ' days')
''', (f'-{days}',))
deleted_count = cursor.rowcount
conn.commit()
return deleted_count

View File

@@ -9,6 +9,8 @@ services:
volumes:
# 挂载配置文件,便于修改邮箱配置
- ./config.txt:/app/config.txt
# 持久化SQLite数据库防止容器重建丢失数据
- ./data:/app/data
# 可选:挂载日志目录
- ./logs:/app/logs
environment:

View File

@@ -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