oper_log.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """操作日志:基于 sys_api_request_log 适配 RuoYi OperLog 视图"""
  2. from __future__ import annotations
  3. from datetime import datetime
  4. from typing import Any, Dict, List, Optional, Tuple
  5. from sqlalchemy import desc, func, or_
  6. from sqlalchemy.orm import Session
  7. from app.core.oper_log_infer import business_type_from_path, title_from_path
  8. from app.models.models import SysApiRequestLog, SysUser
  9. _FMT = "%Y-%m-%d %H:%M:%S"
  10. def _oper_status(status_code: int) -> int:
  11. return 0 if 200 <= int(status_code or 0) < 400 else 1
  12. def _row_to_oper_log(row: Any) -> Dict[str, Any]:
  13. path = getattr(row, "path", "") or ""
  14. http_method = getattr(row, "http_method", "") or ""
  15. status_code = int(getattr(row, "status_code", 0) or 0)
  16. create_at = getattr(row, "create_at", None)
  17. oper_time = create_at.strftime(_FMT) if isinstance(create_at, datetime) else ""
  18. oper_name = getattr(row, "create_name", None) or getattr(row, "username", None) or ""
  19. request_body = getattr(row, "request_body", None) or ""
  20. response_body = getattr(row, "response_body", None) or ""
  21. stored_title = getattr(row, "business_title", None)
  22. stored_type = getattr(row, "business_type", None)
  23. title = stored_title or title_from_path(path)
  24. business_type = stored_type if stored_type is not None else business_type_from_path(path, http_method)
  25. oper_summary = getattr(row, "oper_summary", None) or ""
  26. return {
  27. "id": getattr(row, "id", ""),
  28. "title": title,
  29. "businessType": business_type,
  30. "businessId": getattr(row, "business_id", None) or "",
  31. "operSummary": oper_summary,
  32. "method": http_method,
  33. "operName": oper_name,
  34. "operUrl": path,
  35. "operIp": getattr(row, "ip_address", "") or "",
  36. "operLocation": "",
  37. "operParam": request_body,
  38. "jsonResult": response_body,
  39. "status": _oper_status(status_code),
  40. "costTime": int(getattr(row, "duration_ms", 0) or 0),
  41. "operTime": oper_time,
  42. "errorMsg": "" if _oper_status(status_code) == 0 else f"HTTP {status_code}",
  43. }
  44. def _apply_oper_log_scope(q, db: Session, user_info: dict, tenant_no: str):
  45. """操作日志按 oper_user_id(user_id/create_by)应用数据权限。"""
  46. from app.core.config import settings
  47. from app.core.data_scope import _collect_scope_ids, _resolve_user_role_scopes
  48. if not user_info or user_info.get("role") == "super_admin":
  49. return q
  50. user_id = user_info.get("user_id")
  51. if not user_id:
  52. return q.filter(False)
  53. effective_tenant = tenant_no or user_info.get("tenant_no") or settings.DEFAULT_TENANT_NO
  54. role_scopes = _resolve_user_role_scopes(db, user_id, effective_tenant)
  55. if not role_scopes:
  56. return q.filter(False)
  57. if any(rs["data_scope"] == "1" for rs in role_scopes):
  58. return q
  59. dept_ids, user_ids = _collect_scope_ids(
  60. db, user_info, user_id, effective_tenant, role_scopes
  61. )
  62. conditions = []
  63. if user_ids:
  64. conditions.append(
  65. or_(
  66. SysApiRequestLog.user_id.in_(user_ids),
  67. SysApiRequestLog.create_by.in_(user_ids),
  68. )
  69. )
  70. if dept_ids:
  71. dept_user_ids = (
  72. db.query(SysUser.id)
  73. .filter(
  74. SysUser.dept_id.in_(dept_ids),
  75. SysUser.del_flag == 0,
  76. SysUser.tenant_no == effective_tenant,
  77. )
  78. )
  79. conditions.append(SysApiRequestLog.user_id.in_(dept_user_ids))
  80. if not conditions:
  81. return q.filter(False)
  82. return q.filter(or_(*conditions))
  83. def _oper_log_row_in_scope(db: Session, row, user_info: dict, tenant_no: str) -> bool:
  84. from app.core.data_scope import row_in_data_scope
  85. class _OperLogScopeRow:
  86. create_by = getattr(row, "user_id", None) or getattr(row, "create_by", None)
  87. create_dept_id = None
  88. return row_in_data_scope(db, _OperLogScopeRow(), user_info, tenant_no)
  89. def list_oper_logs(
  90. db: Session,
  91. *,
  92. page: int = 1,
  93. page_size: int = 10,
  94. title: Optional[str] = None,
  95. business_type: Optional[int] = None,
  96. oper_name: Optional[str] = None,
  97. status: Optional[int] = None,
  98. business_id: Optional[str] = None,
  99. business_title: Optional[str] = None,
  100. ids: Optional[List[str]] = None,
  101. max_page_size: Optional[int] = None,
  102. user_info: Optional[dict] = None,
  103. tenant_no: Optional[str] = None,
  104. ) -> Tuple[List[Dict[str, Any]], int]:
  105. q = db.query(SysApiRequestLog).filter(SysApiRequestLog.is_del == 0)
  106. effective_tenant = tenant_no or (user_info or {}).get("tenant_no")
  107. if effective_tenant:
  108. q = q.filter(SysApiRequestLog.tenant_no == effective_tenant)
  109. if user_info:
  110. q = _apply_oper_log_scope(q, db, user_info, effective_tenant or "")
  111. if business_id:
  112. q = q.filter(SysApiRequestLog.business_id == business_id.strip())
  113. if business_title:
  114. kw = f"%{business_title.strip()}%"
  115. q = q.filter(SysApiRequestLog.business_title.like(kw))
  116. if title:
  117. title_kw = f"%{title.strip()}%"
  118. q = q.filter(SysApiRequestLog.business_title.like(title_kw))
  119. if business_type is not None:
  120. q = q.filter(SysApiRequestLog.business_type == int(business_type))
  121. if oper_name:
  122. kw = f"%{oper_name.strip()}%"
  123. q = q.filter(
  124. or_(
  125. SysApiRequestLog.username.like(kw),
  126. SysApiRequestLog.create_name.like(kw),
  127. )
  128. )
  129. if status is not None:
  130. if int(status) == 0:
  131. q = q.filter(SysApiRequestLog.status_code >= 200, SysApiRequestLog.status_code < 400)
  132. else:
  133. q = q.filter(
  134. or_(
  135. SysApiRequestLog.status_code < 200,
  136. SysApiRequestLog.status_code >= 400,
  137. )
  138. )
  139. if ids:
  140. q = q.filter(SysApiRequestLog.id.in_(ids))
  141. total = int(q.with_entities(func.count(SysApiRequestLog.id)).order_by(None).scalar() or 0)
  142. page = max(1, page)
  143. cap = int(max_page_size) if max_page_size else 500
  144. page_size = min(max(1, page_size), cap)
  145. start = (page - 1) * page_size
  146. rows = (
  147. q.order_by(desc(SysApiRequestLog.create_at))
  148. .offset(start)
  149. .limit(page_size)
  150. .all()
  151. )
  152. return [_row_to_oper_log(r) for r in rows], total
  153. def get_oper_log(
  154. db: Session, log_id: str, *, user_info: Optional[dict] = None, tenant_no: Optional[str] = None
  155. ) -> Optional[Dict[str, Any]]:
  156. row = (
  157. db.query(SysApiRequestLog)
  158. .filter(SysApiRequestLog.id == log_id, SysApiRequestLog.is_del == 0)
  159. .first()
  160. )
  161. if not row:
  162. return None
  163. if user_info and not _oper_log_row_in_scope(db, row, user_info, tenant_no):
  164. return None
  165. if tenant_no and row.tenant_no and row.tenant_no != tenant_no:
  166. return None
  167. return _row_to_oper_log(row)