system.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """系统管理接口(契约1.23),对齐 Java 版 SystemController。
  2. 资源监控(实时/趋势/进程)、存储占用、转换器状态、一键体检、
  3. 系统信息、运行参数、运行日志、维护操作(预览缓存清理/强制下线)。
  4. 查询类只读;维护操作记审计。全部要求系统管理权限(manage_system),
  5. 运行日志并入日志审计权限(manage_audit_log,单开窗口查看原始日志)。
  6. """
  7. from __future__ import annotations
  8. from flask import request, send_file
  9. from dms.api.v1.blueprint import api_v1
  10. from dms.common.enums import AuditAction, AuditTarget
  11. from dms.common.errors import (
  12. InvalidArgumentError,
  13. ResourceNotFoundError,
  14. UserSelfProtectionError,
  15. )
  16. from dms.common.response import serialize_id, success_response
  17. from dms.extensions import db
  18. from dms.models import User
  19. from dms.security.auth_context import get_auth_context
  20. from dms.security.decorators import (
  21. audit_log_required,
  22. bearer_auth_required,
  23. system_management_required,
  24. )
  25. from dms.services.audit_service import business_audit
  26. from dms.services.system_management_service import (
  27. clear_preview_cache,
  28. converter_status,
  29. current_metrics,
  30. health_check,
  31. log_file_path,
  32. metric_history,
  33. process_metrics,
  34. read_log_tail,
  35. runtime_config,
  36. storage_usage,
  37. system_info,
  38. )
  39. def _positive_int(value: str | None, default: int, maximum: int) -> int:
  40. if value is None or not value.strip():
  41. return default
  42. try:
  43. number = int(value.strip())
  44. except ValueError as exc:
  45. raise InvalidArgumentError("参数必须是整数") from exc
  46. if number < 1 or number > maximum:
  47. raise InvalidArgumentError(f"参数取值范围为 1~{maximum}")
  48. return number
  49. @api_v1.get("/system/metrics/current")
  50. @bearer_auth_required
  51. @system_management_required
  52. def system_metrics_current():
  53. return success_response(current_metrics())
  54. @api_v1.get("/system/metrics/history")
  55. @bearer_auth_required
  56. @system_management_required
  57. def system_metrics_history():
  58. hours = _positive_int(request.args.get("hours"), default=24, maximum=720)
  59. return success_response(metric_history(hours))
  60. @api_v1.get("/system/process")
  61. @bearer_auth_required
  62. @system_management_required
  63. def system_process():
  64. return success_response(process_metrics())
  65. @api_v1.get("/system/storage-usage")
  66. @bearer_auth_required
  67. @system_management_required
  68. def system_storage_usage():
  69. return success_response({"entries": storage_usage()})
  70. @api_v1.get("/system/converter")
  71. @bearer_auth_required
  72. @system_management_required
  73. def system_converter():
  74. return success_response(converter_status())
  75. @api_v1.get("/system/health")
  76. @bearer_auth_required
  77. @system_management_required
  78. def system_health():
  79. return success_response({"items": health_check()})
  80. @api_v1.get("/system/info")
  81. @bearer_auth_required
  82. @system_management_required
  83. def system_information():
  84. return success_response(system_info())
  85. @api_v1.get("/system/runtime-config")
  86. @bearer_auth_required
  87. @system_management_required
  88. def system_runtime_config():
  89. return success_response({"entries": runtime_config()})
  90. @api_v1.get("/system/logs")
  91. @bearer_auth_required
  92. @system_management_required
  93. def system_logs():
  94. lines = _positive_int(request.args.get("lines"), default=500, maximum=5000)
  95. level = (request.args.get("level") or "").strip() or None
  96. return success_response(read_log_tail(lines=lines, level=level))
  97. @api_v1.get("/system/logs/download")
  98. @bearer_auth_required
  99. @audit_log_required
  100. def system_logs_download():
  101. """原始运行日志文件下载(日志审计页签单开窗口查看)。"""
  102. path = log_file_path()
  103. if path is None or not path.is_file():
  104. raise ResourceNotFoundError("运行日志文件未启用或不存在")
  105. return send_file(
  106. path,
  107. mimetype="text/plain",
  108. as_attachment=True,
  109. download_name=path.name,
  110. )
  111. @api_v1.post("/system/maintenance/preview-cache")
  112. @bearer_auth_required
  113. @system_management_required
  114. def system_clear_preview_cache():
  115. """清空预览缓存(可重建),记 PREVIEW_CACHE_CLEAR 审计。"""
  116. result = clear_preview_cache()
  117. db.session.add(
  118. business_audit(
  119. action=AuditAction.PREVIEW_CACHE_CLEAR,
  120. target=AuditTarget.SYSTEM,
  121. target_id=0,
  122. target_name="preview",
  123. detail=result,
  124. )
  125. )
  126. db.session.commit()
  127. return success_response(result)
  128. @api_v1.post("/system/users/<user_id>/force-offline")
  129. @bearer_auth_required
  130. @system_management_required
  131. def system_force_offline(user_id: str):
  132. """强制下线:auth_version + 1 使目标用户全部访问令牌立即失效。"""
  133. from dms.services.user_service import parse_string_id
  134. numeric_id = parse_string_id(user_id, name="userId")
  135. if numeric_id is None:
  136. raise InvalidArgumentError("userId不能为空")
  137. if get_auth_context().user_id == numeric_id:
  138. raise UserSelfProtectionError("不能强制自己下线")
  139. user = db.session.get(User, numeric_id)
  140. if user is None or user.is_deleted:
  141. raise ResourceNotFoundError("用户不存在")
  142. user.auth_version += 1
  143. db.session.add(
  144. business_audit(
  145. action=AuditAction.FORCE_OFFLINE,
  146. target=AuditTarget.USER,
  147. target_id=user.id,
  148. target_name=user.username,
  149. detail={"authVersion": user.auth_version},
  150. )
  151. )
  152. db.session.commit()
  153. return success_response(
  154. {
  155. "userId": serialize_id(user.id),
  156. "username": user.username,
  157. "authVersion": user.auth_version,
  158. }
  159. )