Files
shanxia2.0/backend/app/crud/monitor/oper_log.py
T
zlwuandCursor b3b6ec187a feat(system): add Excel export for operlog and dict type lists
Wire ListExportDropdown with filtered/selected backend xlsx export.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 17:28:57 +08:00

203 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""操作日志:基于 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)