From 642cf76b61f4ec8491a030d04a3bb64d50bfc352 Mon Sep 17 00:00:00 2001 From: Jeason <1710884619@qq.com> Date: Wed, 18 Mar 2026 09:33:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E6=89=B9=E5=88=A0=E9=99=A4=EF=BC=8C?= =?UTF-8?q?=E6=AF=8F=E6=89=B9=201000=20=E6=9D=A1=EF=BC=8C=E5=88=A0?= =?UTF-8?q?=E5=AE=8C=E4=B8=80=E6=89=B9=20commit=20=E4=B8=80=E6=AC=A1?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E4=BC=9A=E9=94=81=E8=A1=A8=20=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BF=9D=E7=95=99=2030=20=E5=A4=A9=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=20SIGNIN?= =?UTF-8?q?=5FLOG=5FRETAIN=5FDAYS=20=E8=B0=83=E6=95=B4=20=E6=AF=8F?= =?UTF-8?q?=E5=A4=A9=E5=87=8C=E6=99=A8=203=20=E7=82=B9=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=EF=BC=8C=E9=94=99=E5=BC=80=E7=AD=BE=E5=88=B0=E9=AB=98=E5=B3=B0?= =?UTF-8?q?=20=E6=9C=89=E5=AE=8C=E6=95=B4=E6=97=A5=E5=BF=97=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=88=A0=E4=BA=86=E5=A4=9A=E5=B0=91=E6=9D=A1=E3=80=81?= =?UTF-8?q?=E6=88=AA=E6=AD=A2=E6=97=A5=E6=9C=9F=E6=98=AF=E4=BB=80=E4=B9=88?= =?UTF-8?q?=20=E7=94=A8=E7=8B=AC=E7=AB=8B=20engine=20=E9=81=BF=E5=85=8D?= =?UTF-8?q?=E4=B9=8B=E5=89=8D=E7=9A=84=20event=20loop=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/task_scheduler/app/celery_app.py | 5 ++ .../task_scheduler/app/tasks/signin_tasks.py | 56 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/backend/task_scheduler/app/celery_app.py b/backend/task_scheduler/app/celery_app.py index d0e8971..47f0cab 100644 --- a/backend/task_scheduler/app/celery_app.py +++ b/backend/task_scheduler/app/celery_app.py @@ -13,6 +13,7 @@ import sys import logging from celery import Celery +from celery.schedules import crontab # 确保 shared 模块可导入 sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../..")) @@ -42,6 +43,10 @@ celery_app.conf.update( "task": "task_scheduler.app.tasks.signin_tasks.check_and_run_due_tasks", "schedule": 60.0, # 每分钟检查一次(轻量查询,只在到期前 5 分钟才真正提交) }, + "cleanup-old-logs": { + "task": "task_scheduler.app.tasks.signin_tasks.cleanup_old_signin_logs", + "schedule": crontab(hour=3, minute=0), # 每天凌晨 3 点 + }, }, ) diff --git a/backend/task_scheduler/app/tasks/signin_tasks.py b/backend/task_scheduler/app/tasks/signin_tasks.py index 32de6bf..4a4618d 100644 --- a/backend/task_scheduler/app/tasks/signin_tasks.py +++ b/backend/task_scheduler/app/tasks/signin_tasks.py @@ -396,3 +396,59 @@ async def _do_single_signin(cookies: Dict[str, str], topic: dict) -> dict: return {"status": "failed", "message": f"code={code}, msg={msg}"} except Exception as e: return {"status": "failed", "message": str(e)} + + +# =============== 日志清理 =============== + +# 保留天数,可通过环境变量覆盖 +SIGNIN_LOG_RETAIN_DAYS = int(os.getenv("SIGNIN_LOG_RETAIN_DAYS", "30")) +# 每批删除条数,避免长时间锁表 +CLEANUP_BATCH_SIZE = 1000 + + +@celery_app.task +def cleanup_old_signin_logs(): + """ + 清理超过 N 天的签到日志。 + 分批删除,每批最多 CLEANUP_BATCH_SIZE 条,避免锁表。 + """ + logger.info(f"🧹 开始清理 {SIGNIN_LOG_RETAIN_DAYS} 天前的签到日志...") + return _run_async(_async_cleanup()) + + +async def _async_cleanup(): + from sqlalchemy import delete, func + + cutoff = datetime.now() - timedelta(days=SIGNIN_LOG_RETAIN_DAYS) + total_deleted = 0 + + SessionFactory, eng = _make_session() + try: + async with SessionFactory() as session: + while True: + # 分批删除:先查出一批 id,再按 id 删除 + stmt = ( + select(SigninLog.id) + .where(SigninLog.signed_at < cutoff) + .limit(CLEANUP_BATCH_SIZE) + ) + result = await session.execute(stmt) + ids = [row[0] for row in result.all()] + + if not ids: + break + + del_stmt = delete(SigninLog).where(SigninLog.id.in_(ids)) + await session.execute(del_stmt) + await session.commit() + total_deleted += len(ids) + logger.info(f"🧹 已删除 {total_deleted} 条...") + + # 如果本批不满,说明已经删完 + if len(ids) < CLEANUP_BATCH_SIZE: + break + finally: + await eng.dispose() + + logger.info(f"✅ 日志清理完成,共删除 {total_deleted} 条(截止 {cutoff.strftime('%Y-%m-%d')})") + return {"deleted": total_deleted, "cutoff": cutoff.isoformat()}