189 lines
5.7 KiB
Python
189 lines
5.7 KiB
Python
import logging
|
||
import subprocess
|
||
from datetime import datetime, timedelta
|
||
from pathlib import Path
|
||
|
||
from sqlalchemy import select, update
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.core.config import settings
|
||
from app.core.paths import data_path
|
||
from app.db.session import get_db
|
||
from app.models import Certificate
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def cleanup_expired_pdfs() -> dict:
|
||
"""
|
||
清理超过指定天数未访问的PDF文件
|
||
|
||
Returns:
|
||
dict: 清理结果统计
|
||
"""
|
||
logger.info("[PDF清理] 开始清理过期PDF文件...")
|
||
|
||
# 使用配置中的清理天数
|
||
cleanup_days = settings.pdf_cleanup_days
|
||
|
||
# 计算截止时间
|
||
cutoff_time = datetime.utcnow() - timedelta(days=cleanup_days)
|
||
|
||
# 统计结果
|
||
result = {
|
||
"total_checked": 0,
|
||
"cleaned_files": 0,
|
||
"cleaned_records": 0,
|
||
"cleanup_days": cleanup_days,
|
||
"errors": [],
|
||
}
|
||
|
||
try:
|
||
# 获取数据库会话
|
||
db = next(get_db())
|
||
|
||
# 查询需要清理的证书
|
||
# 条件:PDF已生成,且最后访问时间早于截止时间,或从未访问过
|
||
stmt = select(Certificate).where(
|
||
Certificate.pdf_status == "generated",
|
||
Certificate.pdf_file_path.isnot(None),
|
||
(
|
||
(Certificate.pdf_last_accessed_at < cutoff_time) |
|
||
(Certificate.pdf_last_accessed_at.is_(None))
|
||
)
|
||
)
|
||
|
||
certificates = db.execute(stmt).scalars().all()
|
||
result["total_checked"] = len(certificates)
|
||
|
||
for cert in certificates:
|
||
try:
|
||
# 删除PDF文件
|
||
if cert.pdf_file_path:
|
||
pdf_path = Path(cert.pdf_file_path)
|
||
if pdf_path.exists():
|
||
subprocess.run(["rm", "-f", str(pdf_path)], check=False)
|
||
result["cleaned_files"] += 1
|
||
logger.info(f"[PDF清理] 已删除文件: {cert.pdf_file_path}")
|
||
|
||
# 更新数据库记录
|
||
cert.pdf_status = "cleaned"
|
||
cert.pdf_file_path = None
|
||
cert.pdf_last_accessed_at = None
|
||
result["cleaned_records"] += 1
|
||
|
||
except Exception as e:
|
||
error_msg = f"清理证书 {cert.certificate_no} 失败: {str(e)}"
|
||
result["errors"].append(error_msg)
|
||
logger.error(f"[PDF清理] {error_msg}")
|
||
|
||
# 提交数据库更改
|
||
db.commit()
|
||
|
||
logger.info(f"[PDF清理] 清理完成: 检查 {result['total_checked']} 个, "
|
||
f"删除 {result['cleaned_files']} 个文件, "
|
||
f"更新 {result['cleaned_records']} 条记录")
|
||
|
||
except Exception as e:
|
||
logger.error(f"[PDF清理] 清理过程发生错误: {str(e)}")
|
||
result["errors"].append(f"清理过程发生错误: {str(e)}")
|
||
|
||
return result
|
||
|
||
|
||
def get_pdf_cleanup_stats() -> dict:
|
||
"""
|
||
获取PDF清理统计信息
|
||
|
||
Returns:
|
||
dict: 统计信息
|
||
"""
|
||
try:
|
||
db = next(get_db())
|
||
|
||
# 使用配置中的清理天数
|
||
cleanup_days = settings.pdf_cleanup_days
|
||
|
||
# 统计总数
|
||
total_stmt = select(Certificate).where(Certificate.pdf_status == "generated")
|
||
total_count = len(db.execute(total_stmt).scalars().all())
|
||
|
||
# 统计需要清理的
|
||
cutoff_time = datetime.utcnow() - timedelta(days=cleanup_days)
|
||
cleanup_stmt = select(Certificate).where(
|
||
Certificate.pdf_status == "generated",
|
||
Certificate.pdf_file_path.isnot(None),
|
||
(
|
||
(Certificate.pdf_last_accessed_at < cutoff_time) |
|
||
(Certificate.pdf_last_accessed_at.is_(None))
|
||
)
|
||
)
|
||
cleanup_count = len(db.execute(cleanup_stmt).scalars().all())
|
||
|
||
# 统计PDF文件大小
|
||
total_size = 0
|
||
pdf_cache_dir = data_path("pdf-cache")
|
||
if pdf_cache_dir.exists():
|
||
for pdf_file in pdf_cache_dir.rglob("*.pdf"):
|
||
total_size += pdf_file.stat().st_size
|
||
|
||
return {
|
||
"total_pdfs": total_count,
|
||
"need_cleanup": cleanup_count,
|
||
"total_size_mb": round(total_size / (1024 * 1024), 2),
|
||
"cleanup_days": cleanup_days,
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"[PDF统计] 获取统计信息失败: {str(e)}")
|
||
return {
|
||
"total_pdfs": 0,
|
||
"need_cleanup": 0,
|
||
"total_size_mb": 0,
|
||
"cleanup_days": settings.pdf_cleanup_days,
|
||
"error": str(e),
|
||
}
|
||
|
||
|
||
def manual_cleanup_certificate(certificate_no: str) -> bool:
|
||
"""
|
||
手动清理指定证书的PDF文件
|
||
|
||
Args:
|
||
certificate_no: 证书编号
|
||
|
||
Returns:
|
||
bool: 是否成功清理
|
||
"""
|
||
try:
|
||
db = next(get_db())
|
||
|
||
# 查询证书
|
||
stmt = select(Certificate).where(Certificate.certificate_no == certificate_no)
|
||
cert = db.execute(stmt).scalar_one_or_none()
|
||
|
||
if not cert:
|
||
logger.warning(f"[手动清理] 证书不存在: {certificate_no}")
|
||
return False
|
||
|
||
# 删除PDF文件
|
||
if cert.pdf_file_path:
|
||
pdf_path = Path(cert.pdf_file_path)
|
||
if pdf_path.exists():
|
||
subprocess.run(["rm", "-f", str(pdf_path)], check=False)
|
||
logger.info(f"[手动清理] 已删除文件: {cert.pdf_file_path}")
|
||
|
||
# 更新数据库记录
|
||
cert.pdf_status = "cleaned"
|
||
cert.pdf_file_path = None
|
||
cert.pdf_last_accessed_at = None
|
||
|
||
db.commit()
|
||
|
||
logger.info(f"[手动清理] 清理完成: {certificate_no}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"[手动清理] 清理失败: {certificate_no}, 错误: {str(e)}")
|
||
return False
|