diff --git a/app/services/api_executor.py b/app/services/api_executor.py index 9de43a5..2eba13f 100644 --- a/app/services/api_executor.py +++ b/app/services/api_executor.py @@ -1,4 +1,4 @@ -import json +import json import logging import time from datetime import datetime @@ -6,10 +6,9 @@ from typing import Any, Dict, Optional import requests from requests import Response - -from app.extensions import db from flask import current_app +from app.extensions import db from app.models import ApiCallLog, ApiConfig logger = logging.getLogger(__name__) @@ -27,6 +26,7 @@ def _parse_json_field(raw: Optional[str]) -> Optional[Dict[str, Any]]: def execute_api(api_config: ApiConfig) -> None: """ 根据配置调用 API,带重试,并将每次尝试都记录为日志(保留历史)。 + 支持流式响应:边接收边写入 response_body,便于前端实时查看。 """ request_time = datetime.utcnow() headers = _parse_json_field(api_config.headers) or {} @@ -46,9 +46,11 @@ def execute_api(api_config: ApiConfig) -> None: while attempt < total_attempts: last_response: Optional[Response] = None + log_entry: Optional[ApiCallLog] = None start_ts = time.time() attempt += 1 try: + # stream=True 便于流式响应实时写日志;非流式也兼容 last_response = requests.request( method=api_config.http_method, url=api_config.url, @@ -57,29 +59,74 @@ def execute_api(api_config: ApiConfig) -> None: data=None if json_body is not None else data, json=json_body, timeout=timeout, + stream=True, ) + + # 创建进行中的日志,前端可以立即看到“执行中” + log_entry = ApiCallLog( + api_id=api_config.id, + request_time=request_time, + success=None, # 进行中 + http_status_code=last_response.status_code, + response_body="", + ) + db.session.add(log_entry) + db.session.commit() + + max_body_length = 20000 # 避免日志过大 + last_flush = time.time() + encoding = last_response.encoding or "utf-8" + for chunk in last_response.iter_content(chunk_size=1024): + if not chunk: + continue + try: + text_part = chunk.decode(encoding, errors="ignore") + except Exception: + text_part = "" + if not text_part: + continue + + current_body = log_entry.response_body or "" + if len(current_body) < max_body_length: + log_entry.response_body = (current_body + text_part)[:max_body_length] + + now = time.time() + # 每秒刷新一次数据库,兼顾实时性与写入开销 + if now - last_flush >= 1: + db.session.commit() + last_flush = now + + # 流结束后,根据状态码判定是否成功 if 200 <= last_response.status_code < 300: last_error = None else: - body_snippet = (last_response.text or "")[:200] + body_snippet = (log_entry.response_body or "")[:200] last_error = f"Non-2xx status: {last_response.status_code}, body: {body_snippet}" except requests.RequestException as exc: last_error = str(exc) + except Exception as exc: + last_error = str(exc) - duration_ms = int((time.time() - start_ts) * 1000) + end_time = datetime.utcnow() + duration_ms = int((end_time - request_time).total_seconds() * 1000) success = last_error is None - log_entry = ApiCallLog( - api_id=api_config.id, - request_time=request_time, - response_time=datetime.utcnow(), - success=success, - http_status_code=last_response.status_code if last_response else None, - error_message=(f"[第{attempt}次尝试/{total_attempts}] {last_error}" if last_error else None), - response_body=(last_response.text[:2000] if last_response and last_response.text else None), - duration_ms=duration_ms, - ) - db.session.add(log_entry) + # 更新日志最终状态;如果之前没有创建(例如请求异常前失败),则创建一条 + if log_entry is None: + log_entry = ApiCallLog( + api_id=api_config.id, + request_time=request_time, + ) + db.session.add(log_entry) + + log_entry.response_time = end_time + log_entry.duration_ms = duration_ms + log_entry.success = success + if last_response: + log_entry.http_status_code = last_response.status_code + if last_error: + log_entry.error_message = f"[第{attempt}次尝试/{total_attempts}] {last_error}" + try: db.session.commit() except Exception: diff --git a/app/templates/logs/detail.html b/app/templates/logs/detail.html index 900002c..71bbb45 100644 --- a/app/templates/logs/detail.html +++ b/app/templates/logs/detail.html @@ -1,19 +1,73 @@ -{% extends "base.html" %} +{% extends "base.html" %} {% block title %}日志详情{% endblock %} {% block content %}
请求时间: {{ log.request_time|to_cst }}
-响应时间: {{ log.response_time|to_cst }}
-是否成功: {{ log.success }}
-HTTP 状态码: {{ log.http_status_code or '-' }}
-耗时 (ms): {{ log.duration_ms or '-' }}
-错误信息: {{ log.error_message or '-' }}
+请求时间: {{ log.request_time|to_cst }}
+响应时间: {{ log.response_time|to_cst or '-' }}
+状态: + {% if log.success is none %} + 进行中 + {% elif log.success %} + 成功 + {% else %} + 失败 + {% endif %} +
+HTTP 状态码: {{ log.http_status_code if log.http_status_code is not none else '-' }}
+耗时 (ms): {{ log.duration_ms if log.duration_ms is not none else '-' }}
+错误信息: {{ log.error_message or '-' }}
响应内容:
-{{ log.response_body or '-' }}
+ {{ log.response_body or '-' }}