from datetime import datetime, timedelta from typing import Any from sqlalchemy import delete from sqlalchemy.ext.asyncio import AsyncSession from app.config.setting import settings from app.core.base_schema import AuthSchema, PageResultSchema from app.core.database import async_db_session from app.core.exceptions import CustomException from app.core.logger import logger from app.utils.common_util import search_to_dict from app.utils.excel_util import ExcelUtil from .crud import LoginLogCRUD, OperationLogCRUD from .model import OperationLogModel from .schema import ( LoginLogDetailOutSchema, LoginLogOutSchema, LoginLogQueryParam, OperationLogDetailOutSchema, OperationLogOutSchema, OperationLogQueryParam, ) class LoginLogService: """登录日志管理服务""" def __init__(self, auth: AuthSchema, db: AsyncSession) -> None: self.auth = auth self.db = db async def detail(self, id: int) -> LoginLogDetailOutSchema: obj = await LoginLogCRUD(self.auth, self.db).get_or_404(id=id) return LoginLogDetailOutSchema.model_validate(obj) async def page( self, page_no: int, page_size: int, search: LoginLogQueryParam | None = None, order_by: list[dict[str, str]] | None = None, ) -> PageResultSchema[LoginLogOutSchema]: return await LoginLogCRUD(self.auth, self.db).page( offset=(page_no - 1) * page_size, limit=page_size, order_by=order_by or [{"updated_time": "desc"}], search=search_to_dict(search), out_schema=LoginLogOutSchema, ) async def delete(self, ids: list[int]) -> None: if not ids: raise CustomException(msg="删除失败,删除对象不能为空") existing = await LoginLogCRUD(self.auth, self.db).get_list(search={"id": ("in", ids)}) existing_map = {obj.id for obj in existing} for nid in ids: if nid not in existing_map: raise CustomException(msg=f"删除失败,ID为{nid}的数据不存在") await LoginLogCRUD(self.auth, self.db).delete(ids=ids) class OperationLogService: """操作日志管理服务""" def __init__(self, auth: AuthSchema, db: AsyncSession) -> None: self.auth = auth self.db = db @staticmethod async def cleanup_operation_log() -> bool: from .model import LoginLogModel retention_days = settings.OPERATION_LOG_RETENTION_DAYS cutoff = datetime.now() - timedelta(days=retention_days) async with async_db_session() as session: op_stmt = delete(OperationLogModel).where(OperationLogModel.created_time < cutoff) op_result: Any = await session.execute(op_stmt) login_stmt = delete(LoginLogModel).where(LoginLogModel.created_time < cutoff) login_result: Any = await session.execute(login_stmt) await session.commit() logger.info(f"操作日志清理完成: 操作日志 {op_result.rowcount} 条, 登录日志 {login_result.rowcount} 条") return True async def page( self, page_no: int, page_size: int, search: OperationLogQueryParam | None = None, order_by: list[dict[str, str]] | None = None, ) -> PageResultSchema[OperationLogOutSchema]: crud = OperationLogCRUD(self.auth, self.db) return await crud.page( offset=(page_no - 1) * page_size, limit=page_size, order_by=order_by or [{"id": "desc"}], search=search_to_dict(search), out_schema=OperationLogOutSchema, ) async def detail(self, id: int) -> OperationLogDetailOutSchema: crud = OperationLogCRUD(self.auth, self.db) obj = await crud.get_or_404(id=id) return OperationLogDetailOutSchema.model_validate(obj) async def delete(self, ids: list[int]) -> None: if not ids: raise CustomException(msg="删除失败,删除对象不能为空") existing = await OperationLogCRUD(self.auth, self.db).get_list(search={"id": ("in", ids)}) existing_map = {obj.id for obj in existing} for nid in ids: if nid not in existing_map: raise CustomException(msg="删除失败,该数据不存在") crud = OperationLogCRUD(self.auth, self.db) await crud.delete(ids=ids) async def get_list( self, search: OperationLogQueryParam | None = None, order_by: list[dict[str, str]] | None = None, ) -> list[OperationLogOutSchema]: crud = OperationLogCRUD(self.auth, self.db) obj_list = await crud.get_list( search=search_to_dict(search), order_by=order_by or [{"id": "desc"}], ) return [OperationLogOutSchema.model_validate(obj) for obj in obj_list] @staticmethod def export_list(operation_log_list: list[dict[str, Any]]) -> bytes: """导出操作日志列表""" mapping_dict = { "id": "日志编号", "request_path": "请求路径", "request_method": "请求方法", "request_ip": "请求IP", "request_payload": "请求参数", "response_code": "响应状态码", "process_time": "耗时(ms)", "created_time": "操作时间", "created_id": "操作用户ID", } return ExcelUtil.export_list2excel(list_data=operation_log_list, mapping_dict=mapping_dict)