""" 操作日志管理服务模块 ============================================================= 该模块实现了操作日志管理相关的所有业务逻辑。 包括操作日志的分页查询、详情查询、添加、删除、清空、统计等功能。 命名规范: - 函数参数使用驼峰命名(如 logId, pageNo) - 表单字段使用下划线命名(如 title, log_type)- 与数据库字段保持一致 - 返回数据使用驼峰命名(如 createUser, updateTime)- 与前端规范保持一致 - 私有函数以下划线开头(如 _apply_filters, _build_page_data) 功能模块: 1. 分页查询:支持按标题模糊搜索、操作类型筛选、来源筛选、状态筛选、时间范围筛选 2. 详情查询:根据ID获取单个操作日志的详细信息 3. 日志添加:记录用户操作日志 4. 日志删除:删除指定日志(支持批量) 5. 日志清空:清空所有操作日志 6. 统计查询:获取操作日志统计信息 7. 类型查询:获取操作日志类型列表 """ import logging from datetime import datetime from flask import request from sqlalchemy import and_ from application.constants.common import PAGE_SIZE from application.constants.dict import OPERATION_LOG_SOURCE_LIST, OPERATION_LOG_TYPE_LIST from application.models.operation_log import OperationLog from extensions import db from utils import R from utils.model_utils import models_to_list, model_to_dict from utils.request import parse_id_list, like_contains # 获取日志记录器 logger = logging.getLogger(__name__) # ============================================================ # 操作日志分页查询 # ============================================================ def get_operation_log_page(): """ 查询操作日志分页数据 Args: request: Flask request对象,包含查询参数 - pageNo: 当前页码(可选,默认为1) - pageSize: 每页记录数(可选,默认为PAGE_SIZE) - title: 请求标题(可选,模糊查询) - type: 请求类型(可选,精确查询) - source: 请求来源(可选,0-后台,1-前台) - status: 请求状态(可选,0-正常,1-异常) - startTime: 开始时间(可选) - endTime: 结束时间(可选) Returns: R: 包含分页数据的JSON响应,格式为: { "records": 日志列表, "total": 总记录数, "size": 每页大小, "current": 当前页码, "pages": 总页数 } """ try: # 获取分页参数 page_no = int(request.args.get('pageNo', 1)) page_size = int(request.args.get('pageSize', PAGE_SIZE)) # 应用筛选条件 query = _apply_filters() # 记录总数 total = query.count() # 计算总页数 pages = (total + page_size - 1) // page_size if total > 0 else 0 # 分页查询(按创建时间倒序) log_list = query.order_by(OperationLog.create_time.desc()) \ .limit(page_size) \ .offset((page_no - 1) * page_size) \ .all() # 构建返回数据 page_data = _build_page_data(log_list, total, page_size, page_no, pages) return R.ok(data=page_data) except Exception as e: logger.error(f"查询操作日志分页数据异常: {str(e)}") return R.failed(msg="查询失败,请稍后重试") def _apply_filters(): """ 应用查询筛选条件 根据请求参数动态构建查询条件: - 自动过滤已删除记录(is_delete == 0) - 支持请求标题模糊查询 - 支持操作类型精确查询 - 支持请求来源精确查询 - 支持请求状态精确查询 - 支持时间范围查询 Returns: Query: 应用筛选后的SQLAlchemy查询对象 """ # 基础查询:过滤已删除记录 query = OperationLog.query.filter(OperationLog.is_delete == 0) # 请求标题模糊筛选 title = request.args.get('title') if title: query = query.filter(OperationLog.title.like(like_contains(title), escape='\\')) # 请求类型精确筛选 log_type = request.args.get('type') if log_type: query = query.filter(OperationLog.type == log_type) # 请求来源精确筛选 source = request.args.get('source') if source: query = query.filter(OperationLog.source == source) # 请求状态精确筛选 status = request.args.get('status') if status: query = query.filter(OperationLog.status == status) # 时间范围筛选 start_time = request.args.get('startTime') end_time = request.args.get('endTime') if start_time: try: start_date = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') query = query.filter(OperationLog.create_time >= start_date) except ValueError: pass if end_time: try: end_date = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S') query = query.filter(OperationLog.create_time <= end_date) except ValueError: pass return query def _build_page_data(log_list, total, page_size, page_no, pages): """ 构建分页返回数据 Args: log_list: 操作日志对象列表 total: 总记录数 page_size: 每页大小 page_no: 当前页码 pages: 总页数 Returns: dict: 分页数据,包含 records、total、size、current、pages """ # 将模型对象列表转换为字典列表 records = models_to_list(log_list) # 遍历数据源 for record in records: # 日志来源 record['sourceText'] = OPERATION_LOG_SOURCE_LIST.get(record.get('source'), '') # 日志类型 record['typeText'] = OPERATION_LOG_TYPE_LIST.get(record.get('type'), '') return { 'records': records, 'total': total, 'size': page_size, 'current': page_no, 'pages': pages } # ============================================================ # 操作日志详情查询 # ============================================================ def get_operation_log_detail(log_id): """ 根据ID查询操作日志详情(返回字典格式) Args: log_id: 操作日志ID Returns: dict/None: 操作日志详情字典,不存在时返回 None """ try: log = get_info(log_id) if not log: return None data = model_to_dict(log) # 日志来源 data['sourceText'] = OPERATION_LOG_SOURCE_LIST.get(data.get('source'), '') return data except Exception as e: logger.error(f"查询操作日志详情异常,ID: {log_id}, 错误: {str(e)}") return None def get_info(log_id: int): """ 根据ID查询操作日志信息(返回模型对象) Args: log_id: 操作日志ID Returns: OperationLog/None: 操作日志模型对象,不存在或已删除时返回 None """ if not log_id or log_id <= 0: return None try: log = OperationLog.query.filter( and_(OperationLog.id == log_id, OperationLog.is_delete == 0) ).first() return log except Exception as e: logger.error(f"查询操作日志信息异常,ID: {log_id}, 错误: {str(e)}") return None def record_operation_log(title, log_type, source, status, **kwargs): """ 记录操作日志(便捷方法) 供业务接口直接调用,无需通过HTTP请求。 使用独立session写入,不受请求事务commit/rollback影响。 Args: title: 请求标题 log_type: 请求类型 source: 请求来源(0-后台,1-前台) status: 操作状态(0-正常,1-异常) **kwargs: 其他可选参数 - method: 请求方法 - request_method: 请求方式 - url: 请求URL - param: 请求参数 - result: 返回结果 - ip: IP地址 - location: IP归属地 - os: 操作系统 - browser: 浏览器 - consume_time: 请求耗时 - error: 错误信息 - username: 操作用户 Returns: OperationLog/None: 创建的日志对象,失败返回None """ try: log_data = { 'title': title, 'type': log_type, 'source': source, 'method': kwargs.get('method', ''), 'request_method': kwargs.get('request_method', ''), 'url': kwargs.get('url', ''), 'param': kwargs.get('param', ''), 'result': kwargs.get('result', ''), 'ip': kwargs.get('ip', ''), 'location': kwargs.get('location', ''), 'os': kwargs.get('os', ''), 'browser': kwargs.get('browser', ''), 'consume_time': kwargs.get('consume_time', 0), 'status': status, 'error': kwargs.get('error', ''), } log = OperationLog(**log_data) log.create_user = kwargs.get('username', 'system') log.update_user = kwargs.get('username', 'system') # 使用独立session写入,不受请求事务影响 # 日志是审计数据,无论业务成功或失败都应持久化 from extensions.sqlalchemy import commit_independently commit_independently(log) return log except Exception as e: logger.error(f"记录操作日志异常: {str(e)}") return None # ============================================================ # 操作日志删除 # ============================================================ def delete_operation_logs(log_ids): """ 删除操作日志(批量删除) 删除 Args: log_ids: 日志ID字符串,多个ID用逗号分隔,如 "1,2,3" Returns: R: 操作结果 """ return OperationLog.batch_delete_r(log_ids)