| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- """``/api/v1`` Blueprint及其局部HTTP基础设施。"""
- from __future__ import annotations
- import logging
- from flask import Blueprint, Flask, request
- from werkzeug.exceptions import RequestEntityTooLarge
- from dms.common.errors import DmsError
- from dms.common.request_context import REQUEST_ID_HEADER, get_request_id, initialize_request_id
- from dms.common.response import error_response
- logger = logging.getLogger(__name__)
- api_v1 = Blueprint("dms_api_v1", __name__, url_prefix="/api/v1")
- EXPOSED_RESPONSE_HEADERS = ("Content-Disposition", "X-Request-Id")
- @api_v1.before_request
- def _before_dms_request() -> None:
- initialize_request_id()
- @api_v1.after_request
- def _after_dms_request(response):
- response.headers[REQUEST_ID_HEADER] = get_request_id()
- return response
- @api_v1.errorhandler(DmsError)
- def _handle_dms_error(error: DmsError):
- return error_response(
- error.code,
- error.message,
- details=error.details,
- status=error.status_code,
- )
- @api_v1.errorhandler(RequestEntityTooLarge)
- def _handle_request_too_large(_error: RequestEntityTooLarge):
- return error_response(
- "PAYLOAD_TOO_LARGE",
- "请求内容过大",
- details=None,
- status=413,
- )
- @api_v1.errorhandler(Exception)
- def _handle_unexpected_error(error: Exception):
- logger.exception("未处理的DMS接口异常", exc_info=error)
- return error_response(
- "INTERNAL_ERROR",
- "服务器内部错误",
- details=None,
- status=500,
- )
- def register_dms_routing_error_handlers(app: Flask) -> None:
- """让未匹配到Blueprint的 ``/api/v1`` 404也使用DMS错误结构。
- 其他路径直接返回Werkzeug原始异常,保持既有 ``/api/*`` 行为。
- """
- @app.errorhandler(404)
- def _handle_routing_not_found(error):
- if request.path == "/api/v1" or request.path.startswith("/api/v1/"):
- try:
- initialize_request_id()
- except DmsError as request_id_error:
- return _handle_dms_error(request_id_error)
- return error_response(
- "RESOURCE_NOT_FOUND",
- "请求的资源不存在",
- details=None,
- status=404,
- )
- return error
- def _merge_exposed_headers(existing: str | None) -> str:
- """大小写无关地合并浏览器可读响应头,并保持已有声明。"""
- values: list[str] = []
- seen: set[str] = set()
- for value in (existing or "").split(","):
- normalized = value.strip()
- key = normalized.lower()
- if normalized and key not in seen:
- values.append(normalized)
- seen.add(key)
- for value in EXPOSED_RESPONSE_HEADERS:
- key = value.lower()
- if key not in seen:
- values.append(value)
- seen.add(key)
- return ", ".join(values)
- def register_dms_response_headers(app: Flask) -> None:
- """仅为DMS路径补充跨域可读响应头,覆盖路由级错误。"""
- @app.after_request
- def _expose_dms_response_headers(response):
- if request.path == "/api/v1" or request.path.startswith("/api/v1/"):
- response.headers["Access-Control-Expose-Headers"] = (
- _merge_exposed_headers(
- response.headers.get("Access-Control-Expose-Headers")
- )
- )
- return response
- from dms.api.v1 import health # noqa: E402,F401
- from dms.api.v1 import ( # noqa: E402,F401
- audit,
- auth,
- categories,
- documents,
- organizations,
- recycle_bin,
- ui_dictionaries,
- users,
- )
|