| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202 |
- """操作日志:基于 sys_api_request_log 适配 RuoYi OperLog 视图"""
- from __future__ import annotations
- from datetime import datetime
- from typing import Any, Dict, List, Optional, Tuple
- from sqlalchemy import desc, func, or_
- from sqlalchemy.orm import Session
- from app.core.oper_log_infer import business_type_from_path, title_from_path
- from app.models.models import SysApiRequestLog, SysUser
- _FMT = "%Y-%m-%d %H:%M:%S"
- def _oper_status(status_code: int) -> int:
- return 0 if 200 <= int(status_code or 0) < 400 else 1
- def _row_to_oper_log(row: Any) -> Dict[str, Any]:
- path = getattr(row, "path", "") or ""
- http_method = getattr(row, "http_method", "") or ""
- status_code = int(getattr(row, "status_code", 0) or 0)
- create_at = getattr(row, "create_at", None)
- oper_time = create_at.strftime(_FMT) if isinstance(create_at, datetime) else ""
- oper_name = getattr(row, "create_name", None) or getattr(row, "username", None) or ""
- request_body = getattr(row, "request_body", None) or ""
- response_body = getattr(row, "response_body", None) or ""
- stored_title = getattr(row, "business_title", None)
- stored_type = getattr(row, "business_type", None)
- title = stored_title or title_from_path(path)
- business_type = stored_type if stored_type is not None else business_type_from_path(path, http_method)
- oper_summary = getattr(row, "oper_summary", None) or ""
- return {
- "id": getattr(row, "id", ""),
- "title": title,
- "businessType": business_type,
- "businessId": getattr(row, "business_id", None) or "",
- "operSummary": oper_summary,
- "method": http_method,
- "operName": oper_name,
- "operUrl": path,
- "operIp": getattr(row, "ip_address", "") or "",
- "operLocation": "",
- "operParam": request_body,
- "jsonResult": response_body,
- "status": _oper_status(status_code),
- "costTime": int(getattr(row, "duration_ms", 0) or 0),
- "operTime": oper_time,
- "errorMsg": "" if _oper_status(status_code) == 0 else f"HTTP {status_code}",
- }
- def _apply_oper_log_scope(q, db: Session, user_info: dict, tenant_no: str):
- """操作日志按 oper_user_id(user_id/create_by)应用数据权限。"""
- from app.core.config import settings
- from app.core.data_scope import _collect_scope_ids, _resolve_user_role_scopes
- if not user_info or user_info.get("role") == "super_admin":
- return q
- user_id = user_info.get("user_id")
- if not user_id:
- return q.filter(False)
- effective_tenant = tenant_no or user_info.get("tenant_no") or settings.DEFAULT_TENANT_NO
- role_scopes = _resolve_user_role_scopes(db, user_id, effective_tenant)
- if not role_scopes:
- return q.filter(False)
- if any(rs["data_scope"] == "1" for rs in role_scopes):
- return q
- dept_ids, user_ids = _collect_scope_ids(
- db, user_info, user_id, effective_tenant, role_scopes
- )
- conditions = []
- if user_ids:
- conditions.append(
- or_(
- SysApiRequestLog.user_id.in_(user_ids),
- SysApiRequestLog.create_by.in_(user_ids),
- )
- )
- if dept_ids:
- dept_user_ids = (
- db.query(SysUser.id)
- .filter(
- SysUser.dept_id.in_(dept_ids),
- SysUser.del_flag == 0,
- SysUser.tenant_no == effective_tenant,
- )
- )
- conditions.append(SysApiRequestLog.user_id.in_(dept_user_ids))
- if not conditions:
- return q.filter(False)
- return q.filter(or_(*conditions))
- def _oper_log_row_in_scope(db: Session, row, user_info: dict, tenant_no: str) -> bool:
- from app.core.data_scope import row_in_data_scope
- class _OperLogScopeRow:
- create_by = getattr(row, "user_id", None) or getattr(row, "create_by", None)
- create_dept_id = None
- return row_in_data_scope(db, _OperLogScopeRow(), user_info, tenant_no)
- def list_oper_logs(
- db: Session,
- *,
- page: int = 1,
- page_size: int = 10,
- title: Optional[str] = None,
- business_type: Optional[int] = None,
- oper_name: Optional[str] = None,
- status: Optional[int] = None,
- business_id: Optional[str] = None,
- business_title: Optional[str] = None,
- ids: Optional[List[str]] = None,
- max_page_size: Optional[int] = None,
- user_info: Optional[dict] = None,
- tenant_no: Optional[str] = None,
- ) -> Tuple[List[Dict[str, Any]], int]:
- q = db.query(SysApiRequestLog).filter(SysApiRequestLog.is_del == 0)
- effective_tenant = tenant_no or (user_info or {}).get("tenant_no")
- if effective_tenant:
- q = q.filter(SysApiRequestLog.tenant_no == effective_tenant)
- if user_info:
- q = _apply_oper_log_scope(q, db, user_info, effective_tenant or "")
- if business_id:
- q = q.filter(SysApiRequestLog.business_id == business_id.strip())
- if business_title:
- kw = f"%{business_title.strip()}%"
- q = q.filter(SysApiRequestLog.business_title.like(kw))
- if title:
- title_kw = f"%{title.strip()}%"
- q = q.filter(SysApiRequestLog.business_title.like(title_kw))
- if business_type is not None:
- q = q.filter(SysApiRequestLog.business_type == int(business_type))
- if oper_name:
- kw = f"%{oper_name.strip()}%"
- q = q.filter(
- or_(
- SysApiRequestLog.username.like(kw),
- SysApiRequestLog.create_name.like(kw),
- )
- )
- if status is not None:
- if int(status) == 0:
- q = q.filter(SysApiRequestLog.status_code >= 200, SysApiRequestLog.status_code < 400)
- else:
- q = q.filter(
- or_(
- SysApiRequestLog.status_code < 200,
- SysApiRequestLog.status_code >= 400,
- )
- )
- if ids:
- q = q.filter(SysApiRequestLog.id.in_(ids))
- total = int(q.with_entities(func.count(SysApiRequestLog.id)).order_by(None).scalar() or 0)
- page = max(1, page)
- cap = int(max_page_size) if max_page_size else 500
- page_size = min(max(1, page_size), cap)
- start = (page - 1) * page_size
- rows = (
- q.order_by(desc(SysApiRequestLog.create_at))
- .offset(start)
- .limit(page_size)
- .all()
- )
- return [_row_to_oper_log(r) for r in rows], total
- def get_oper_log(
- db: Session, log_id: str, *, user_info: Optional[dict] = None, tenant_no: Optional[str] = None
- ) -> Optional[Dict[str, Any]]:
- row = (
- db.query(SysApiRequestLog)
- .filter(SysApiRequestLog.id == log_id, SysApiRequestLog.is_del == 0)
- .first()
- )
- if not row:
- return None
- if user_info and not _oper_log_row_in_scope(db, row, user_info, tenant_no):
- return None
- if tenant_no and row.tenant_no and row.tenant_no != tenant_no:
- return None
- return _row_to_oper_log(row)
|