oper_log.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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 apply_oper_log_filters(
  90. q,
  91. db: Session,
  92. *,
  93. title: Optional[str] = None,
  94. business_type: Optional[int] = None,
  95. oper_name: Optional[str] = None,
  96. title_in: Optional[str] = None,
  97. oper_name_in: Optional[str] = None,
  98. status: Optional[int] = None,
  99. business_id: Optional[str] = None,
  100. business_title: Optional[str] = None,
  101. ids: Optional[List[str]] = None,
  102. user_info: Optional[dict] = None,
  103. tenant_no: Optional[str] = None,
  104. ):
  105. """列表与列头聚合共用查询条件,包含租户及操作人数据权限。"""
  106. q = q.filter(SysApiRequestLog.is_del == 0)
  107. effective_tenant = tenant_no or (user_info or {}).get("tenant_no")
  108. if effective_tenant:
  109. q = q.filter(SysApiRequestLog.tenant_no == effective_tenant)
  110. if user_info:
  111. q = _apply_oper_log_scope(q, db, user_info, effective_tenant or "")
  112. if business_id:
  113. q = q.filter(SysApiRequestLog.business_id == business_id.strip())
  114. if business_title:
  115. kw = f"%{business_title.strip()}%"
  116. q = q.filter(SysApiRequestLog.business_title.like(kw))
  117. if title:
  118. title_kw = f"%{title.strip()}%"
  119. q = q.filter(SysApiRequestLog.business_title.like(title_kw))
  120. title_in_values = [s.strip() for s in str(title_in or "").split(",") if s.strip()]
  121. if title_in_values:
  122. q = q.filter(SysApiRequestLog.business_title.in_(title_in_values))
  123. if business_type is not None:
  124. q = q.filter(SysApiRequestLog.business_type == int(business_type))
  125. if oper_name:
  126. kw = f"%{oper_name.strip()}%"
  127. q = q.filter(
  128. or_(
  129. SysApiRequestLog.username.like(kw),
  130. SysApiRequestLog.create_name.like(kw),
  131. )
  132. )
  133. oper_name_in_values = [s.strip() for s in str(oper_name_in or "").split(",") if s.strip()]
  134. if oper_name_in_values:
  135. q = q.filter(SysApiRequestLog.create_name.in_(oper_name_in_values))
  136. if status is not None:
  137. if int(status) == 0:
  138. q = q.filter(SysApiRequestLog.status_code >= 200, SysApiRequestLog.status_code < 400)
  139. else:
  140. q = q.filter(
  141. or_(
  142. SysApiRequestLog.status_code < 200,
  143. SysApiRequestLog.status_code >= 400,
  144. )
  145. )
  146. if ids:
  147. q = q.filter(SysApiRequestLog.id.in_(ids))
  148. return q
  149. def list_oper_logs(
  150. db: Session,
  151. *,
  152. page: int = 1,
  153. page_size: int = 10,
  154. title: Optional[str] = None,
  155. business_type: Optional[int] = None,
  156. oper_name: Optional[str] = None,
  157. title_in: Optional[str] = None,
  158. oper_name_in: Optional[str] = None,
  159. status: Optional[int] = None,
  160. business_id: Optional[str] = None,
  161. business_title: Optional[str] = None,
  162. ids: Optional[List[str]] = None,
  163. max_page_size: Optional[int] = None,
  164. user_info: Optional[dict] = None,
  165. tenant_no: Optional[str] = None,
  166. ) -> Tuple[List[Dict[str, Any]], int]:
  167. q = apply_oper_log_filters(
  168. db.query(SysApiRequestLog),
  169. db,
  170. title=title,
  171. business_type=business_type,
  172. oper_name=oper_name,
  173. title_in=title_in,
  174. oper_name_in=oper_name_in,
  175. status=status,
  176. business_id=business_id,
  177. business_title=business_title,
  178. ids=ids,
  179. user_info=user_info,
  180. tenant_no=tenant_no,
  181. )
  182. total = int(q.with_entities(func.count(SysApiRequestLog.id)).order_by(None).scalar() or 0)
  183. page = max(1, page)
  184. cap = int(max_page_size) if max_page_size else 500
  185. page_size = min(max(1, page_size), cap)
  186. start = (page - 1) * page_size
  187. rows = (
  188. q.order_by(desc(SysApiRequestLog.create_at))
  189. .offset(start)
  190. .limit(page_size)
  191. .all()
  192. )
  193. return [_row_to_oper_log(r) for r in rows], total
  194. def get_oper_log(
  195. db: Session, log_id: str, *, user_info: Optional[dict] = None, tenant_no: Optional[str] = None
  196. ) -> Optional[Dict[str, Any]]:
  197. row = (
  198. db.query(SysApiRequestLog)
  199. .filter(SysApiRequestLog.id == log_id, SysApiRequestLog.is_del == 0)
  200. .first()
  201. )
  202. if not row:
  203. return None
  204. if user_info and not _oper_log_row_in_scope(db, row, user_info, tenant_no):
  205. return None
  206. if tenant_no and row.tenant_no and row.tenant_no != tenant_no:
  207. return None
  208. return _row_to_oper_log(row)