外观
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 白/黑名单、演示开关、白名单接口)来自系统参数缓存,通过调整「系统管理 - 参数配置」即可动态生效。
请求处理
一次请求从进入到返回,依次经过以下环节:
客户端 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加密、响应体自动解密,对业务代码透明。