| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- """系统管理接口(契约1.23),对齐 Java 版 SystemController。
- 资源监控(实时/趋势/进程)、存储占用、转换器状态、一键体检、
- 系统信息、运行参数、运行日志、维护操作(预览缓存清理/强制下线)。
- 查询类只读;维护操作记审计。全部要求系统管理权限(manage_system),
- 运行日志并入日志审计权限(manage_audit_log,单开窗口查看原始日志)。
- """
- from __future__ import annotations
- from flask import request, send_file
- from dms.api.v1.blueprint import api_v1
- from dms.common.enums import AuditAction, AuditTarget
- from dms.common.errors import (
- InvalidArgumentError,
- ResourceNotFoundError,
- UserSelfProtectionError,
- )
- from dms.common.response import serialize_id, success_response
- from dms.extensions import db
- from dms.models import User
- from dms.security.auth_context import get_auth_context
- from dms.security.decorators import (
- audit_log_required,
- bearer_auth_required,
- system_management_required,
- )
- from dms.services.audit_service import business_audit
- from dms.services.system_management_service import (
- clear_preview_cache,
- converter_status,
- current_metrics,
- health_check,
- log_file_path,
- metric_history,
- process_metrics,
- read_log_tail,
- runtime_config,
- storage_usage,
- system_info,
- )
- def _positive_int(value: str | None, default: int, maximum: int) -> int:
- if value is None or not value.strip():
- return default
- try:
- number = int(value.strip())
- except ValueError as exc:
- raise InvalidArgumentError("参数必须是整数") from exc
- if number < 1 or number > maximum:
- raise InvalidArgumentError(f"参数取值范围为 1~{maximum}")
- return number
- @api_v1.get("/system/metrics/current")
- @bearer_auth_required
- @system_management_required
- def system_metrics_current():
- return success_response(current_metrics())
- @api_v1.get("/system/metrics/history")
- @bearer_auth_required
- @system_management_required
- def system_metrics_history():
- hours = _positive_int(request.args.get("hours"), default=24, maximum=720)
- return success_response(metric_history(hours))
- @api_v1.get("/system/process")
- @bearer_auth_required
- @system_management_required
- def system_process():
- return success_response(process_metrics())
- @api_v1.get("/system/storage-usage")
- @bearer_auth_required
- @system_management_required
- def system_storage_usage():
- return success_response({"entries": storage_usage()})
- @api_v1.get("/system/converter")
- @bearer_auth_required
- @system_management_required
- def system_converter():
- return success_response(converter_status())
- @api_v1.get("/system/health")
- @bearer_auth_required
- @system_management_required
- def system_health():
- return success_response({"items": health_check()})
- @api_v1.get("/system/info")
- @bearer_auth_required
- @system_management_required
- def system_information():
- return success_response(system_info())
- @api_v1.get("/system/runtime-config")
- @bearer_auth_required
- @system_management_required
- def system_runtime_config():
- return success_response({"entries": runtime_config()})
- @api_v1.get("/system/logs")
- @bearer_auth_required
- @system_management_required
- def system_logs():
- lines = _positive_int(request.args.get("lines"), default=500, maximum=5000)
- level = (request.args.get("level") or "").strip() or None
- return success_response(read_log_tail(lines=lines, level=level))
- @api_v1.get("/system/logs/download")
- @bearer_auth_required
- @audit_log_required
- def system_logs_download():
- """原始运行日志文件下载(日志审计页签单开窗口查看)。"""
- path = log_file_path()
- if path is None or not path.is_file():
- raise ResourceNotFoundError("运行日志文件未启用或不存在")
- return send_file(
- path,
- mimetype="text/plain",
- as_attachment=True,
- download_name=path.name,
- )
- @api_v1.post("/system/maintenance/preview-cache")
- @bearer_auth_required
- @system_management_required
- def system_clear_preview_cache():
- """清空预览缓存(可重建),记 PREVIEW_CACHE_CLEAR 审计。"""
- result = clear_preview_cache()
- db.session.add(
- business_audit(
- action=AuditAction.PREVIEW_CACHE_CLEAR,
- target=AuditTarget.SYSTEM,
- target_id=0,
- target_name="preview",
- detail=result,
- )
- )
- db.session.commit()
- return success_response(result)
- @api_v1.post("/system/users/<user_id>/force-offline")
- @bearer_auth_required
- @system_management_required
- def system_force_offline(user_id: str):
- """强制下线:auth_version + 1 使目标用户全部访问令牌立即失效。"""
- from dms.services.user_service import parse_string_id
- numeric_id = parse_string_id(user_id, name="userId")
- if numeric_id is None:
- raise InvalidArgumentError("userId不能为空")
- if get_auth_context().user_id == numeric_id:
- raise UserSelfProtectionError("不能强制自己下线")
- user = db.session.get(User, numeric_id)
- if user is None or user.is_deleted:
- raise ResourceNotFoundError("用户不存在")
- user.auth_version += 1
- db.session.add(
- business_audit(
- action=AuditAction.FORCE_OFFLINE,
- target=AuditTarget.USER,
- target_id=user.id,
- target_name=user.username,
- detail={"authVersion": user.auth_version},
- )
- )
- db.session.commit()
- return success_response(
- {
- "userId": serialize_id(user.id),
- "username": user.username,
- "authVersion": user.auth_version,
- }
- )
|