Files
certificate-system/backend/app/services/pdf_cleanup.py

189 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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