Files
Aislo/common_util/common_util_temp_cleanup.py
eomsangdonandClaude Opus 5 4066504ba9 feat(B01): 시스템 로그에 대상·접속 정보 기록 + 1년 보관 정리 (2026-09-06 사용자 확정)
- 기록을 `common_util_audit.record_audit()` 한 곳으로 모음 — 여섯 자리에 흩어져 있던
  raw INSERT 제거
- 대상 식별자 칸 신설(`017_audit_log_detail.sql`, 적용 완료) — 기존 `resource_id` 는 INT 라
  프로젝트 UUID 를 못 담아 늘 NULL 이었음. 프로젝트 생성·수정·삭제·회사 생성이 대상을 남김
- 접속 주소·브라우저 기록 — 라우터가 요청을 넘겨 주고, 프록시 뒤에서는 X-Forwarded-For 우선
- 보관 기간 `AUDIT_LOG_RETENTION_DAYS` 기본 365일, 임시 보관함 정리 루프에 얹어 함께 정리
- 화면: 시스템 로그 표에 대상·접속 주소 열 추가(대상은 UUID 앞 8자만)

자체검증 — 프로젝트 생성·하드삭제를 실화면에서 돌려 두 줄 모두
`프로젝트 e1040640… · 127.0.0.1 · 2026-09-06 09:54/09:55` 로 남는 것 확인.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 18:57:22 +09:00

88 lines
3.3 KiB
Python

"""임시 보관함 만료 정리.
라이다 원본은 수십 GB라 방치하면 디스크를 금방 채운다. 파일이 다 올라온 시각 기준
`TEMP_UPLOAD_RETENTION_DAYS`가 지난 묶음을 주기적으로 지운다(2026-08-08 사용자 지시).
보관 기간·주기는 `config/config_system.py`에서 조정한다.
"""
import asyncio
import logging
import os
import shutil
from B03_FileInput.B03_FileInput_Repository_Temp import (
delete_temp_batch,
list_expired_temp_batches,
)
from common_util.common_util_audit import purge_expired_audit_logs
from common_util.common_util_storage import resolve_temp_batch_path, temp_upload_root
from config.config_db import get_db_pool
from config.config_system import (
TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS,
TEMP_UPLOAD_RETENTION_DAYS,
)
logger = logging.getLogger(__name__)
async def cleanup_expired_temp_uploads() -> int:
"""만료된 보관함 묶음을 폴더째 지우고 DB 행도 제거한다. 지운 개수를 돌려준다."""
pool = get_db_pool()
removed = 0
try:
async with pool.acquire() as connection:
expired = await list_expired_temp_batches(connection)
for batch in expired:
batch_id = str(batch["id"])
user_id = int(batch["user_id"])
try:
batch_root = resolve_temp_batch_path(user_id, batch_id, create=False)
shutil.rmtree(batch_root, ignore_errors=True)
except ValueError:
logger.warning("보관함 경로 해석 실패(행만 삭제): batch_id=%s", batch_id)
await delete_temp_batch(connection, batch_id=batch_id, user_id=user_id)
removed += 1
logger.info(
"임시 보관함 만료 삭제: batch_id=%s name=%s expires_at=%s",
batch_id,
batch.get("name"),
batch.get("expires_at"),
)
if removed:
await connection.commit()
_remove_empty_user_dirs()
except Exception:
logger.exception("임시 보관함 만료 정리 실패")
return removed
def _remove_empty_user_dirs() -> None:
"""묶음이 모두 사라진 사용자 폴더는 함께 정리한다."""
root = temp_upload_root()
if not os.path.isdir(root):
return
for entry in os.listdir(root):
path = os.path.join(root, entry)
if os.path.isdir(path) and not os.listdir(path):
try:
os.rmdir(path)
except OSError:
pass
async def cleanup_expired_temp_uploads_loop() -> None:
"""서버 시작 직후 한 번, 이후 설정된 주기마다 만료분을 정리한다."""
interval_seconds = max(1, TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS) * 3600
logger.info(
"임시 보관함 정리 루프 시작: 보관 %d일, 주기 %d시간",
TEMP_UPLOAD_RETENTION_DAYS,
TEMP_UPLOAD_CLEANUP_INTERVAL_HOURS,
)
while True:
removed = await cleanup_expired_temp_uploads()
if removed:
logger.info("임시 보관함 정리 완료: %d건 삭제", removed)
# 시스템 로그 보관 기간 정리도 같은 주기에 얹는다 — 루프를 따로 두지 않는다.
await purge_expired_audit_logs()
await asyncio.sleep(interval_seconds)