"""请求日志管理 API(admin):/admin/api/request-log - GET /admin/api/request-log 列表(摘要,含筛选) - GET /admin/api/request-log/{id} 详情(含完整 body/headers) - DELETE /admin/api/request-log/{id} 删除单条 - DELETE /admin/api/request-log 清空全部 - GET /admin/api/request-log/stats 统计 - GET /admin/api/request-log/settings 读取容量配置 - PUT /admin/api/request-log/settings 设置容量配置 - GET /admin/api/request-log/devices 设备列表(聚合统计) - GET /admin/api/request-log/devices/{device_id} 某设备最近请求(简化视图) """ from __future__ import annotations from typing import Optional from fastapi import APIRouter, Body, Depends, Query from ..errors import HttpError from ..services import request_log_store from ._common import ok_with_cors from .admin import _require_admin router = APIRouter(prefix="/admin/api/request-log", tags=["request-log"]) @router.get("") async def list_records( _admin: str = Depends(_require_admin), limit: int = Query(default=50, ge=1, le=500), offset: int = Query(default=0, ge=0), path: Optional[str] = Query(default=None), method: Optional[str] = Query(default=None), status: Optional[str] = Query(default=None), access_key: Optional[str] = Query(default=None), ok: Optional[int] = Query(default=None, ge=0, le=1), hours: Optional[int] = Query(default=None, ge=1, le=720), keyword: Optional[str] = Query(default=None), category: Optional[str] = Query(default="business"), device_id: Optional[str] = Query(default=None), ): result = request_log_store.list_records( limit=limit, offset=offset, path=path, method=method, status=status, access_key=access_key, ok=bool(ok) if ok is not None else None, hours=hours, keyword=keyword, category=category, device_id=device_id, ) return ok_with_cors(result) @router.get("/stats") async def stats( _admin: str = Depends(_require_admin), hours: int = Query(default=24, ge=1, le=720), category: Optional[str] = Query(default="business"), ): return ok_with_cors(request_log_store.stats(hours=hours, category=category)) @router.get("/settings") async def get_settings(_admin: str = Depends(_require_admin)): return ok_with_cors({"limit": request_log_store.get_limit()}) @router.put("/settings") async def put_settings( body: dict = Body(default={}), _admin: str = Depends(_require_admin), ): limit = body.get("limit") if limit is None: raise HttpError("limit is required", status=400, code="bad_request") new_limit = request_log_store.set_limit(int(limit)) return ok_with_cors({"limit": new_limit}) @router.get("/devices") async def list_devices( _admin: str = Depends(_require_admin), hours: Optional[int] = Query(default=None, ge=1, le=720), limit: int = Query(default=100, ge=1, le=500), offset: int = Query(default=0, ge=0), keyword: Optional[str] = Query(default=None), ): """按 device_id 聚合的设备列表(含统计与最近活跃信息)。""" return ok_with_cors( request_log_store.list_devices( hours=hours, limit=limit, offset=offset, keyword=keyword, ) ) @router.get("/devices/{device_id}") async def device_records( device_id: str, _admin: str = Depends(_require_admin), limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), hours: Optional[int] = Query(default=None, ge=1, le=720), keyword: Optional[str] = Query(default=None), scope: Optional[str] = Query(default=None), ): """某设备的最近请求记录(简化视图:含从请求体提取的文本摘要)。 keyword: 关键词,在 request_body / response_body 上 LIKE 搜索。 scope: 'req' 仅请求、'resp' 仅响应、'both' 或空 两者都搜。 """ return ok_with_cors( request_log_store.list_device_records( device_id=device_id, limit=limit, offset=offset, hours=hours, keyword=keyword, scope=scope, ) ) @router.delete("/devices/{device_id}") async def clear_device_records( device_id: str, _admin: str = Depends(_require_admin), ): """清空某设备的全部请求记录。""" count = request_log_store.clear_device_records(device_id) return ok_with_cors({"cleared": count, "device_id": device_id}) @router.get("/{rid}") async def get_record( rid: int, _admin: str = Depends(_require_admin), ): rec = request_log_store.get_record(rid) if not rec: raise HttpError(f"request log not found: {rid}", status=404, code="not_found") return ok_with_cors({"record": rec}) @router.delete("/{rid}") async def delete_record( rid: int, _admin: str = Depends(_require_admin), ): ok = request_log_store.delete_record(rid) if not ok: raise HttpError(f"request log not found: {rid}", status=404, code="not_found") return ok_with_cors({"deleted": True, "id": rid}) @router.delete("") async def clear_all(_admin: str = Depends(_require_admin)): count = request_log_store.clear_all() return ok_with_cors({"cleared": count})