Files
g0v0-server/app/service/database_cleanup_service.py
MingxuanGame d23f32f08d refactor(log): refactor the whole project
format: {time:YYYY-MM-DD HH:mm:ss} [{level}] | {name} | {message}
{name} is:
- Uvicorn: log from uvicorn server (#228B22)
- Service: log from class of `app.service` (blue)
- Fetcher: log from fetchers (magenta)
- Task: log from `app.tasks` (#FFD700)
- System: log from `system_logger` (red)
- Normal: log from `log(name)` (#FFC1C1)
- Default: the module name of caller

if you are writing services or tasks, you can just call `logger.`, we will pack it with name `Service` or `Task`
if you want to print fetcher logs, system-related logs, or normal logs, use `logger = (fetcher_logger / system_logger / log)(name)`
2025-10-03 11:53:05 +00:00

428 lines
14 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.
"""
数据库清理服务 - 清理过期的验证码和会话
"""
from __future__ import annotations
from datetime import timedelta
from app.database.auth import OAuthToken
from app.database.verification import EmailVerification, LoginSession, TrustedDevice
from app.log import logger
from app.utils import utcnow
from sqlmodel import col, func, 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 = utcnow()
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"Cleaned up {deleted_count} expired email verification codes")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"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 = utcnow()
stmt = select(LoginSession).where(
LoginSession.expires_at < current_time, col(LoginSession.is_verified).is_(False)
)
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"Cleaned up {deleted_count} expired login sessions")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"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 = utcnow() - 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"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"Error cleaning old used verification codes: {e!s}")
return 0
@staticmethod
async def cleanup_unverified_login_sessions(db: AsyncSession, hours_old: int = 1) -> int:
"""
清理指定小时前创建但仍未验证的登录会话
Args:
db: 数据库会话
hours_old: 清理多少小时前创建但仍未验证的会话默认1小时
Returns:
int: 清理的记录数
"""
try:
# 计算截止时间
cutoff_time = utcnow() - timedelta(hours=hours_old)
# 查找指定时间前创建且仍未验证的会话记录
stmt = select(LoginSession).where(
col(LoginSession.is_verified).is_(False), LoginSession.created_at < cutoff_time
)
result = await db.exec(stmt)
unverified_sessions = result.all()
# 删除未验证的会话记录
deleted_count = 0
for session in unverified_sessions:
await db.delete(session)
deleted_count += 1
await db.commit()
if deleted_count > 0:
logger.debug(f"Cleaned up {deleted_count} unverified login sessions older than {hours_old} hour(s)")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"Error cleaning unverified login sessions: {e!s}")
return 0
@staticmethod
async def cleanup_outdated_verified_sessions(db: AsyncSession) -> int:
"""
清理过期会话记录
Args:
db: 数据库会话
Returns:
int: 清理的记录数
"""
try:
stmt = select(LoginSession).where(
col(LoginSession.is_verified).is_(True), col(LoginSession.token_id).is_(None)
)
result = await db.exec(stmt)
# 删除旧的已验证记录
deleted_count = 0
for session in result.all():
await db.delete(session)
deleted_count += 1
await db.commit()
if deleted_count > 0:
logger.debug(f"Cleaned up {deleted_count} outdated verified sessions")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"Error cleaning outdated verified sessions: {e!s}")
return 0
@staticmethod
async def cleanup_outdated_trusted_devices(db: AsyncSession) -> int:
"""
清理过期的受信任设备记录
Args:
db: 数据库会话
Returns:
int: 清理的记录数
"""
try:
# 查找过期的受信任设备记录
current_time = utcnow()
stmt = select(TrustedDevice).where(TrustedDevice.expires_at < current_time)
result = await db.exec(stmt)
expired_devices = result.all()
# 删除过期的记录
deleted_count = 0
for device in expired_devices:
await db.delete(device)
deleted_count += 1
await db.commit()
if deleted_count > 0:
logger.debug(f"Cleaned up {deleted_count} expired trusted devices")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"Error cleaning expired trusted devices: {e!s}")
return 0
@staticmethod
async def cleanup_outdated_tokens(db: AsyncSession) -> int:
"""
清理过期的 OAuth 令牌
Args:
db: 数据库会话
Returns:
int: 清理的记录数
"""
try:
current_time = utcnow()
stmt = select(OAuthToken).where(OAuthToken.refresh_token_expires_at < current_time)
result = await db.exec(stmt)
expired_tokens = result.all()
deleted_count = 0
for token in expired_tokens:
await db.delete(token)
deleted_count += 1
await db.commit()
if deleted_count > 0:
logger.debug(f"Cleaned up {deleted_count} expired OAuth tokens")
return deleted_count
except Exception as e:
await db.rollback()
logger.error(f"Error cleaning expired OAuth tokens: {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)
# 清理1小时前未验证的登录会话
results["unverified_login_sessions"] = await DatabaseCleanupService.cleanup_unverified_login_sessions(db, 1)
# 清理7天前的已使用验证码
results["old_used_verification_codes"] = await DatabaseCleanupService.cleanup_old_used_verification_codes(db, 7)
# 清理过期的受信任设备
results["outdated_trusted_devices"] = await DatabaseCleanupService.cleanup_outdated_trusted_devices(db)
# 清理过期的 OAuth 令牌
results["outdated_oauth_tokens"] = await DatabaseCleanupService.cleanup_outdated_tokens(db)
# 清理过期token 过期)的已验证会话
results["outdated_verified_sessions"] = await DatabaseCleanupService.cleanup_outdated_verified_sessions(db)
total_cleaned = sum(results.values())
if total_cleaned > 0:
logger.debug(f"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 = utcnow()
cutoff_1_hour = current_time - timedelta(hours=1)
cutoff_7_days = current_time - timedelta(days=7)
cutoff_30_days = current_time - timedelta(days=30)
# 统计过期的验证码数量
expired_codes_stmt = (
select(func.count()).select_from(EmailVerification).where(EmailVerification.expires_at < current_time)
)
expired_codes_result = await db.exec(expired_codes_stmt)
expired_codes_count = expired_codes_result.one()
# 统计过期的登录会话数量
expired_sessions_stmt = (
select(func.count()).select_from(LoginSession).where(LoginSession.expires_at < current_time)
)
expired_sessions_result = await db.exec(expired_sessions_stmt)
expired_sessions_count = expired_sessions_result.one()
# 统计1小时前未验证的登录会话数量
unverified_sessions_stmt = (
select(func.count())
.select_from(LoginSession)
.where(col(LoginSession.is_verified).is_(False), LoginSession.created_at < cutoff_1_hour)
)
unverified_sessions_result = await db.exec(unverified_sessions_stmt)
unverified_sessions_count = unverified_sessions_result.one()
# 统计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天前的已验证会话数量
outdated_verified_sessions_stmt = select(LoginSession).where(col(LoginSession.is_verified).is_(True))
outdated_verified_sessions_result = await db.exec(outdated_verified_sessions_stmt)
all_verified_sessions = outdated_verified_sessions_result.all()
outdated_verified_sessions_count = len(
[
session
for session in all_verified_sessions
if session.verified_at and session.verified_at < cutoff_30_days
]
)
# 统计过期的 OAuth 令牌数量
outdated_tokens_stmt = (
select(func.count()).select_from(OAuthToken).where(OAuthToken.refresh_token_expires_at < current_time)
)
outdated_tokens_result = await db.exec(outdated_tokens_stmt)
outdated_tokens_count = outdated_tokens_result.one()
# 统计过期的受信任设备数量
outdated_devices_stmt = (
select(func.count()).select_from(TrustedDevice).where(TrustedDevice.expires_at < current_time)
)
outdated_devices_result = await db.exec(outdated_devices_stmt)
outdated_devices_count = outdated_devices_result.one()
return {
"expired_verification_codes": expired_codes_count,
"expired_login_sessions": expired_sessions_count,
"unverified_login_sessions": unverified_sessions_count,
"old_used_verification_codes": old_used_codes_count,
"outdated_verified_sessions": outdated_verified_sessions_count,
"outdated_oauth_tokens": outdated_tokens_count,
"outdated_trusted_devices": outdated_devices_count,
"total_cleanable": expired_codes_count
+ expired_sessions_count
+ unverified_sessions_count
+ old_used_codes_count
+ outdated_verified_sessions_count
+ outdated_tokens_count
+ outdated_devices_count,
}
except Exception as e:
logger.error(f"Error getting cleanup statistics: {e!s}")
return {
"expired_verification_codes": 0,
"expired_login_sessions": 0,
"unverified_login_sessions": 0,
"old_used_verification_codes": 0,
"outdated_verified_sessions": 0,
"outdated_oauth_tokens": 0,
"outdated_trusted_devices": 0,
"total_cleanable": 0,
}