Skip to content

Web层与请求处理 ​

Web层 ​

概述 ​

后端 Web 层位于 backend/app/framework/web/,是 FastAPI 应用「接收请求 → 处理 → 返回响应」一整套横切能力的封装所在。它屏蔽了响应格式、异常、分页、限流、链路追踪等重复样板代码,让业务模块(四大子系统)只聚焦于自己的领域逻辑。

Web 层覆盖的能力:

  • 统一响应(response.py):所有接口返回一致结构,并自动完成小驼峰转换与日期格式化。
  • 异常处理(exception.py):自定义业务异常与全局异常处理器,统一错误输出。
  • 中间件(middleware.py):链路追踪、GZip 压缩、请求日志、CORS、IP 黑名单与演示模式拦截。
  • 请求封装(request.py / database/params.py):客户端 IP 解析、分页与查询参数的自动小驼峰映射。
  • 分页(page.py):分页结果模型与内存分页工具。
  • 操作日志(log_route.py):基于 OperationLogRoute 自动采集请求/响应并脱敏落库。
  • 限流(security/ratelimit.py):基于 Redis 的全局请求频率限制。

应用启动时会按 main.py → create_app() 装配这些能力:register_exceptions 注册异常处理器、register_middlewares 注册中间件、include_router 挂载业务路由、reset_api_docs 暴露接口文档。

统一响应 ​

response.py 定义了统一的响应模型与响应类,保证前后端契约稳定。

ResponseSchema 是所有响应的统一结构:

python
class ResponseSchema(BaseModel, Generic[T]):
    code: int       # 业务状态码,成功为 0
    msg: str        # 提示消息
    data: T | None  # 业务数据
    status_code: int  # HTTP 状态码
    success: bool   # 是否成功

业务代码中不应直接返回 dict,而应使用以下响应类(均继承自 Starlette 的 JSONResponse):

  • SuccessResponse:成功响应,支持 data / msg / code / status_code 等参数。
  • ErrorResponse:错误响应,用于手动返回业务错误。
  • StreamResponse:流式响应(如 AI 流式输出、大文件下载)。
  • UploadFileResponse:文件下载响应,自动以附件形式返回。

Controller 的典型写法:

python
@UserRouter.get("/page", response_model=ResponseSchema[PageResultSchema[UserOutSchema]])
async def get_user_list_controller(
    page: Annotated[PaginationQueryParam, Depends()],
    search: Annotated[UserQueryParam, Depends()],
    auth: Annotated[AuthSchema, Depends(AuthPermission(["system:user:query"]))],
) -> JSONResponse:
    result = await UserService(auth).page(page_no=page.page_no, page_size=page.page_size, search=search)
    return SuccessResponse(data=result, msg="查询用户成功")

返回 None 数据时(如删除接口)可省略 data 参数:SuccessResponse(msg="删除用户成功")。

两个关键约定:

  • 小驼峰转换:响应体在序列化前会递归把 snake_case 的 key 转换成 camelCase(如 user_name → userName),与前端字段命名保持一致。
  • 日期格式化:datetime / date / time 统一格式化为 YYYY-MM-DD HH:mm:ss / YYYY-MM-DD / HH:mm:ss,避免前端再做时区与格式处理。

业务状态码 ​

status_code.py 中的 RET 枚举集中管理全部业务状态码,便于前后端对齐错误语义。约定如下:

  • 0~200:成功状态码(如 OK=0、SUCCESS=200)。
  • 400~600:HTTP 标准错误语义(UNAUTHORIZED=401、FORBIDDEN=403 等)。
  • 4000+:自定义业务错误码(如 DATAEXIST=4003、PARAMERR=4103、RATE_LIMIT_EXCEEDED=4503、各类 Token/权限错误码)。

CustomException 允许携带自定义 code 与 msg,二者会原样进入响应体,前端据此做差异化提示。

异常处理 ​

exception.py 通过 register_exception_handlers(app) 注册全局异常处理器,所有未捕获异常都会被收敛为统一的 ErrorResponse,不会出现裸的 Stack Trace 暴露给前端。

异常类型与处理策略:

  • CustomException:业务异常,按自身的 code / msg / status_code 返回。
  • HTTPException:FastAPI 原生 HTTP 异常,detail 作为 msg。
  • RequestValidationError:参数校验失败(422),常见错误已做中文友好映射(如「缺少必填项」「类型错误」)。
  • ResponseValidationError:响应结构校验失败(500),提示「服务器响应格式错误」。
  • SQLAlchemyError:数据库异常,对唯一冲突、外键约束、必填缺失等给出明确提示;连接类错误返回 503。
  • ValueError:值异常,直接以消息返回 400。
  • 兜底 Exception:未捕获异常统一返回「服务器内部错误」(500)。

抛业务异常的推荐方式:

python
from app.framework.web.exception import CustomException

if not user:
    raise CustomException(msg="用户不存在", code=RET.DATAERR.code)

前端对接:所有异常都会落到 @vben/request 的 errorMessageResponseInterceptor,统一 ElMessage.error 提示;401 等特殊码会触发登录过期/令牌刷新流程(详见前端 api/request.ts)。

中间件 ​

中间件在 register_middlewares 中按 settings.MIDDLEWARE_LIST 的顺序注册,顺序直接影响行为,当前默认顺序:

  • CorrelationIdMiddleware:读取请求头 X-Correlation-ID,缺失则生成 UUID;响应头回写该值,并在请求生命周期内通过 ContextVar 暴露(get_correlation_id()),用于操作日志与跨服务链路追踪。
  • CustomGZipMiddleware:对超过 GZIP_MIN_SIZE 的响应做 GZip 压缩,GZIP_ENABLE 关闭时不注册。
  • CustomCORSMiddleware:基于 ALLOW_ORIGINS / ALLOW_METHODS / ALLOW_HEADERS 等配置放行跨域,CORS_ORIGIN_ENABLE 关闭时不注册。
  • RequestLogMiddleware:负责请求/响应基础日志,并承担两项安全拦截:
    • IP 黑名单:命中 ip_black_list 的请求直接返回「IP 已被黑名单」。
    • 演示模式:开启 demo_enable 后,非 GET 请求且不在白名单内的,返回「演示环境,禁止操作」。
    • 同时给响应追加 X-Process-Time 头,记录处理耗时。

中间件的安全配置(IP 白/黑名单、演示开关、白名单接口)来自系统参数缓存,通过调整「系统管理 - 参数配置」即可动态生效。

请求处理 ​

一次请求从进入到返回,依次经过以下环节:

客户端 IP 解析 ​

request.py 的 get_client_ip(request) 优先读取 X-Forwarded-For 的第一个地址(适配反向代理场景),回退到 request.client.host。中间件与鉴权逻辑均复用此函数,保证获取的客户端 IP 一致。

查询参数与分页参数 ​

请求入参通过 database/params.py 的 QueryParam 体系声明,核心好处是自动小驼峰别名:

  • PaginationQueryParam 提供 pageNo / pageSize / orderBy 三个分页参数(默认第 1 页、每页 10 条、最多 100 条)。
  • orderBy 支持 JSON 字符串(如 [{"id":"desc"}]),自动解析为排序字典;非法值回退为按 id desc。
  • 子类字段若未显式声明别名,会在实例化时自动补上小驼峰别名(create_time → createTime),与前端传参对齐。
  • QueryOperator 枚举统一描述字段的 SQL 过滤方式(eq / like / in / between / gt / ge 等),配合 BaseQueryParam 提供时间范围等通用查询能力。

Controller 中通过 Depends() 注入即可,无需手动解析 query string:

python
async def get_user_list_controller(
    page: Annotated[PaginationQueryParam, Depends()],
    search: Annotated[UserQueryParam, Depends()],
) -> JSONResponse:
    ...

请求上下文 ​

middleware.py 中的 RequestContext 数据类承载当前请求的身份与来源信息(user_id、user_type、session_id、jwt_user_info、login_location 等),由鉴权中间件/依赖写入 request.state.ctx,供操作日志、权限等模块在请求生命周期内读取。

分页 ​

分页结果模型 PageResultSchema[T] 描述标准分页返回:

python
class PageResultSchema(BaseModel, Generic[T]):
    page_no: int | None    # 当前页码
    page_size: int | None  # 每页数量
    total: int             # 总记录数
    has_next: bool | None  # 是否有下一页
    items: list[T]         # 当前页数据

Controller 通过 response_model=ResponseSchema[PageResultSchema[UserOutSchema]] 声明分页返回类型,Service/DAO 层使用 CRUDBase.page(数据库侧 OFFSET/LIMIT)完成高效分页;PageUtil.paginate 仅用于无法走 SQL 分页的数据源(如 Redis 列表、本地文件列表、ORM 反射出的全量表名)。

前端对接:分页接口返回的 data 即 PageResultSchema,items 为列表、total 用于表格分页器,字段已转为小驼峰(pageNo / pageSize / hasNext)。

操作日志 ​

log_route.py 的 OperationLogRoute 是一个自定义的 APIRoute,业务路由通过 APIRouter(route_class=OperationLogRoute, ...) 启用。它在路由处理完成后自动采集信息,并以 BackgroundTask 异步写入操作日志表,不阻塞主响应。

采集与处理要点:

  • 记录范围由 OPERATION_LOG_RECORD 与 OPERATION_RECORD_METHOD 控制(默认对 POST/PUT/PATCH/DELETE 等写操作记录)。
  • 自动从路径参数、查询参数、请求体、表单、响应体中提取业务编号(biz_id)与业务对象名称,拼接出操作动作(如「修改部门【研发部】」)。
  • 敏感字段脱敏:OPERATION_LOG_SENSITIVE_FIELDS 配置的字段(password、token、captcha 等)命中后替换为 ***,同时兼容小驼峰与蛇形两种命名。
  • 文件字段只记录文件名与类型,避免 UploadFile 无法序列化。
  • 链路追踪号优先取请求上下文中的 trace_id,缺失时回退到 X-Correlation-ID。

限流 ​

security/ratelimit.py 基于 Redis 与 fastapi-limiter / pyrate-limiter 实现全局请求限流,在应用启动时(app_init.py 的 lifespan)构造并挂载为全局路由依赖:

python
global_rate_limiter = await build_global_rate_limiter(
    redis=app.state.redis, prefix=settings.REQUEST_LIMITER_REDIS_PREFIX
)
app.router.dependencies.append(Depends(global_rate_limiter))
  • 默认策略:10 秒内最多 200 次请求(可通过 Rate 调整)。
  • 超出阈值时触发 http_limit_callback,抛出 CustomException(业务码 10429,HTTP 429,提示「请求过于频繁,请 N 秒后再试」),响应 data 携带 Retry-After。
  • WebSocket 场景使用 ws_limit_callback,超限时直接关闭连接(code 1008)。
  • 限流状态存储在 Redis(前缀 REQUEST_LIMITER_REDIS_PREFIX),无需全局初始化/关闭,依赖自身管理连接。

该限流为全局兜底,若某些接口需要差异化阈值,可在接口上单独叠加 RateLimiter 依赖。

前端对接 ​

前端 apps/web-ele/src/api/request.ts 已与后端 Web 层约定对齐:

  • 统一响应拦截:codeField: 'code'、dataField: 'data'、successCode: 0,即业务成功以 code === 0 判定,并自动抽取 data 返回给调用方。
  • 错误处理:errorMessageResponseInterceptor 统一弹错,code === 401 触发登录过期或令牌刷新(doRefreshToken → doReAuthenticate)。
  • 字段命名:后端已做小驼峰转换,前端无需再转;分页直接用 data.items / data.total。
  • 链路追踪:请求失败时可在响应头查看 X-Correlation-ID,配合后端 X-Process-Time 与操作日志定位问题。
  • 加密(可选):开启 API 加密时,请求体经 apiEncrypt 加密、响应体自动解密,对业务代码透明。