""" 数据库清理服务 - 清理过期的验证码和会话 """ from __future__ import annotations from datetime import UTC, datetime, timedelta from app.database.email_verification import EmailVerification, LoginSession from app.log import logger from sqlmodel import col, select from sqlmodel.ext.asyncio.session import AsyncSession class DatabaseCleanupService: """数据库清理服务""" @staticmethod async def cleanup_expired_verification_codes(db: AsyncSession) -> int: """ 清理过期的邮件验证码 Args: db: 数据库会话 Returns: int: 清理的记录数 """ try: # 查找过期的验证码记录 current_time = datetime.now(UTC) stmt = select(EmailVerification).where(EmailVerification.expires_at < current_time) result = await db.exec(stmt) expired_codes = result.all() # 删除过期的记录 deleted_count = 0 for code in expired_codes: await db.delete(code) deleted_count += 1 await db.commit() if deleted_count > 0: logger.debug(f"[Cleanup Service] Cleaned up {deleted_count} expired email verification codes") return deleted_count except Exception as e: await db.rollback() logger.error(f"[Cleanup Service] Error cleaning expired verification codes: {e!s}") return 0 @staticmethod async def cleanup_expired_login_sessions(db: AsyncSession) -> int: """ 清理过期的登录会话 Args: db: 数据库会话 Returns: int: 清理的记录数 """ try: # 查找过期的登录会话记录 current_time = datetime.now(UTC) stmt = select(LoginSession).where(LoginSession.expires_at < current_time) result = await db.exec(stmt) expired_sessions = result.all() # 删除过期的记录 deleted_count = 0 for session in expired_sessions: await db.delete(session) deleted_count += 1 await db.commit() if deleted_count > 0: logger.debug(f"[Cleanup Service] Cleaned up {deleted_count} expired login sessions") return deleted_count except Exception as e: await db.rollback() logger.error(f"[Cleanup Service] Error cleaning expired login sessions: {e!s}") return 0 @staticmethod async def cleanup_old_used_verification_codes(db: AsyncSession, days_old: int = 7) -> int: """ 清理旧的已使用验证码记录 Args: db: 数据库会话 days_old: 清理多少天前的已使用记录,默认7天 Returns: int: 清理的记录数 """ try: # 查找指定天数前的已使用验证码记录 cutoff_time = datetime.now(UTC) - timedelta(days=days_old) stmt = select(EmailVerification).where(col(EmailVerification.is_used).is_(True)) result = await db.exec(stmt) all_used_codes = result.all() # 筛选出过期的记录 old_used_codes = [code for code in all_used_codes if code.used_at and code.used_at < cutoff_time] # 删除旧的已使用记录 deleted_count = 0 for code in old_used_codes: await db.delete(code) deleted_count += 1 await db.commit() if deleted_count > 0: logger.debug( f"[Cleanup Service] Cleaned up {deleted_count} used verification codes older than {days_old} days" ) return deleted_count except Exception as e: await db.rollback() logger.error(f"[Cleanup Service] Error cleaning old used verification codes: {e!s}") return 0 @staticmethod async def cleanup_old_verified_sessions(db: AsyncSession, days_old: int = 30) -> int: """ 清理旧的已验证会话记录 Args: db: 数据库会话 days_old: 清理多少天前的已验证记录,默认30天 Returns: int: 清理的记录数 """ try: # 查找指定天数前的已验证会话记录 cutoff_time = datetime.now(UTC) - timedelta(days=days_old) stmt = select(LoginSession).where(col(LoginSession.is_verified).is_(True)) result = await db.exec(stmt) all_verified_sessions = result.all() # 筛选出过期的记录 old_verified_sessions = [ session for session in all_verified_sessions if session.verified_at and session.verified_at < cutoff_time ] # 删除旧的已验证记录 deleted_count = 0 for session in old_verified_sessions: await db.delete(session) deleted_count += 1 await db.commit() if deleted_count > 0: logger.debug( f"[Cleanup Service] Cleaned up {deleted_count} verified sessions older than {days_old} days" ) return deleted_count except Exception as e: await db.rollback() logger.error(f"[Cleanup Service] Error cleaning old verified sessions: {e!s}") return 0 @staticmethod async def run_full_cleanup(db: AsyncSession) -> dict[str, int]: """ 运行完整的清理流程 Args: db: 数据库会话 Returns: dict: 各项清理的结果统计 """ results = {} # 清理过期的验证码 results["expired_verification_codes"] = await DatabaseCleanupService.cleanup_expired_verification_codes(db) # 清理过期的登录会话 results["expired_login_sessions"] = await DatabaseCleanupService.cleanup_expired_login_sessions(db) # 清理7天前的已使用验证码 results["old_used_verification_codes"] = await DatabaseCleanupService.cleanup_old_used_verification_codes(db, 7) # 清理30天前的已验证会话 results["old_verified_sessions"] = await DatabaseCleanupService.cleanup_old_verified_sessions(db, 30) total_cleaned = sum(results.values()) if total_cleaned > 0: logger.debug( f"[Cleanup Service] Full cleanup completed, total cleaned: {total_cleaned} records - {results}" ) return results @staticmethod async def get_cleanup_statistics(db: AsyncSession) -> dict[str, int]: """ 获取清理统计信息 Args: db: 数据库会话 Returns: dict: 统计信息 """ try: current_time = datetime.now(UTC) cutoff_7_days = current_time - timedelta(days=7) cutoff_30_days = current_time - timedelta(days=30) # 统计过期的验证码数量 expired_codes_stmt = select(EmailVerification).where(EmailVerification.expires_at < current_time) expired_codes_result = await db.exec(expired_codes_stmt) expired_codes_count = len(expired_codes_result.all()) # 统计过期的登录会话数量 expired_sessions_stmt = select(LoginSession).where(LoginSession.expires_at < current_time) expired_sessions_result = await db.exec(expired_sessions_stmt) expired_sessions_count = len(expired_sessions_result.all()) # 统计7天前的已使用验证码数量 old_used_codes_stmt = select(EmailVerification).where(col(EmailVerification.is_used).is_(True)) old_used_codes_result = await db.exec(old_used_codes_stmt) all_used_codes = old_used_codes_result.all() old_used_codes_count = len( [code for code in all_used_codes if code.used_at and code.used_at < cutoff_7_days] ) # 统计30天前的已验证会话数量 old_verified_sessions_stmt = select(LoginSession).where(col(LoginSession.is_verified).is_(True)) old_verified_sessions_result = await db.exec(old_verified_sessions_stmt) all_verified_sessions = old_verified_sessions_result.all() old_verified_sessions_count = len( [ session for session in all_verified_sessions if session.verified_at and session.verified_at < cutoff_30_days ] ) return { "expired_verification_codes": expired_codes_count, "expired_login_sessions": expired_sessions_count, "old_used_verification_codes": old_used_codes_count, "old_verified_sessions": old_verified_sessions_count, "total_cleanable": expired_codes_count + expired_sessions_count + old_used_codes_count + old_verified_sessions_count, } except Exception as e: logger.error(f"[Cleanup Service] Error getting cleanup statistics: {e!s}") return { "expired_verification_codes": 0, "expired_login_sessions": 0, "old_used_verification_codes": 0, "old_verified_sessions": 0, "total_cleanable": 0, }