feat: T-02 审计中间件——http_access 全量访问审计(auth 注入 request.state, 排障路径跳过, 写库失败降级) + 独立 request_id(X-Request-Id 白名单透传贯通, B7 复审 P3-4) + 422/404/405/500 统一错误体(500 由 trace 中间件兜底生成并回写 trace 头, B7 复审 P2-2) + input_guard_log 双写(deps 401/403 留痕, platform 网关跳过 ENUM 口径, 挂账⑧收口) + repo.insert_input_guard_log + tests/test_audit_middleware 12 例, 240 绿
This commit is contained in:
+30
-7
@@ -1,24 +1,35 @@
|
||||
"""统一 API 响应外壳(B7 · 挂账④)。
|
||||
"""统一 API 响应外壳(B7 · 挂账④;T-02 扩展)。
|
||||
|
||||
成功响应:业务字段平铺不变(B5 评审 P2-2 口径「路由返回体不变」),trace_id
|
||||
经 X-Trace-Id 响应头贯通(main 中间件);错误响应统一 JWT 手册 §10 结构
|
||||
{error_code, message, trace_id, request_id}(request_id 沿用 trace_id,
|
||||
独立请求级标识尚未引入)。main 与测试 app 共用 register_error_handlers。
|
||||
经 X-Trace-Id 响应头贯通;错误响应统一 JWT 手册 §10 结构
|
||||
{error_code, message, trace_id, request_id}——request_id 为独立请求级标识
|
||||
(T-02,B7 复审 P3-4 收口),trace_id/request_id 分别对齐响应头。
|
||||
|
||||
T-02 补齐(B7 复审 P2-2):422 请求校验失败、404/405 路由方法错误改用统一
|
||||
错误体(原 FastAPI detail 结构);未捕获异常的 500 由 main.trace 中间件
|
||||
兜底(异常穿透 exception handler,直接在中间件层生成错误体并回写 trace 头)。
|
||||
main 与测试 app 共用 register_error_handlers。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
from app.utils.exceptions import ApiError, PermissionDenied
|
||||
from app.utils.trace import current_trace, new_trace
|
||||
from app.utils.trace import current_request_id, current_trace, new_trace, new_request_id
|
||||
|
||||
# 路由级 HTTP 状态 → 错误码(手册 §10 之外的平台通用码)
|
||||
_HTTP_ERROR_CODES = {404: "NOT_FOUND", 405: "METHOD_NOT_ALLOWED"}
|
||||
|
||||
|
||||
def error_body(error_code: str, message: str) -> dict[str, str]:
|
||||
"""手册 §10 错误体(trace 缺失时兜底生成,保证响应可归因)。"""
|
||||
"""手册 §10 错误体(trace/request 缺失时兜底生成,保证响应可归因)。"""
|
||||
tid = current_trace() or new_trace()
|
||||
return {"error_code": error_code, "message": message, "trace_id": tid, "request_id": tid}
|
||||
rid = current_request_id() or new_request_id()
|
||||
return {"error_code": error_code, "message": message, "trace_id": tid, "request_id": rid}
|
||||
|
||||
|
||||
def register_error_handlers(app: FastAPI) -> None:
|
||||
@@ -31,3 +42,15 @@ def register_error_handlers(app: FastAPI) -> None:
|
||||
@app.exception_handler(PermissionDenied)
|
||||
async def _permission_denied_handler(request: Request, exc: PermissionDenied) -> JSONResponse:
|
||||
return JSONResponse(status_code=403, content=error_body(exc.code, str(exc)))
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def _http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
|
||||
code = _HTTP_ERROR_CODES.get(exc.status_code, "HTTP_ERROR")
|
||||
return JSONResponse(status_code=exc.status_code, content=error_body(code, str(exc.detail)))
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def _validation_error_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content=error_body("REQUEST_VALIDATION_FAILED", "request validation failed"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user