签入版本
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
|
||||
"""通用中间件包:CORS、请求限流、数据库会话、操作日志、请求 hook。"""
|
||||
|
||||
from middleware.cors import register_cors
|
||||
from middleware.rate_limit import rate_limit_middleware
|
||||
from middleware.db_session import db_session_middleware
|
||||
from middleware.operation_log import operation_log_middleware
|
||||
from middleware.logger import register_middleware
|
||||
from middleware.upload_size_limit import upload_size_limit_middleware
|
||||
|
||||
__all__ = [
|
||||
'register_cors',
|
||||
'rate_limit_middleware',
|
||||
'db_session_middleware',
|
||||
'operation_log_middleware',
|
||||
'register_middleware',
|
||||
'upload_size_limit_middleware',
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 跨域中间件
|
||||
# | 说明: CORS 与反向代理头中间件注册
|
||||
# +======================================================================
|
||||
|
||||
"""跨域(CORS)与反向代理头中间件注册。"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
||||
|
||||
from core.config.app import CORS_ALLOWED_ORIGINS
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 注册 CORS 中间件
|
||||
# ============================================================
|
||||
def register_cors(app: FastAPI):
|
||||
"""注册 CORS 与 ProxyHeaders 中间件"""
|
||||
origins_str = CORS_ALLOWED_ORIGINS.strip()
|
||||
if origins_str:
|
||||
origins = [o.strip() for o in origins_str.split(',') if o.strip()]
|
||||
allow_credentials = True
|
||||
allow_headers = [
|
||||
"Authorization", "Content-Type", "Accept",
|
||||
"X-Requested-With", "X-Process-Time",
|
||||
]
|
||||
else:
|
||||
origins = ["*"]
|
||||
allow_credentials = False
|
||||
allow_headers = ["*"]
|
||||
app.add_middleware(ProxyHeadersMiddleware)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=allow_credentials,
|
||||
allow_methods=['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
||||
allow_headers=allow_headers,
|
||||
max_age=3600
|
||||
)
|
||||
@@ -0,0 +1,101 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 数据库会话中间件
|
||||
# | 说明: 请求级 DB 会话管理,写操作按业务成功与否 commit/rollback
|
||||
# +======================================================================
|
||||
|
||||
"""数据库会话中间件:请求级 DB 会话管理(原 core/middleware.py)。"""
|
||||
|
||||
import json
|
||||
|
||||
from starlette.requests import Request
|
||||
|
||||
from core.database import SessionLocal, _db_ctx
|
||||
from utils.response_body import read_response_body, rebuild_response
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 辅助函数
|
||||
# ============================================================
|
||||
def _is_business_success(body: bytes) -> bool:
|
||||
"""仅当响应体为 JSON 且业务码 code == 0 视为成功
|
||||
|
||||
本应用所有业务响应均为 R.ok()/R.failed() JSON(HTTP 200 承载 code)。
|
||||
非 JSON / 空体 2xx 无法从响应体确认业务结果,写操作按失败处理(回滚),
|
||||
避免把无法确认成功的事务误提交。
|
||||
"""
|
||||
if not body:
|
||||
return False
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except Exception:
|
||||
return False
|
||||
if not isinstance(data, dict):
|
||||
return False
|
||||
return data.get('code') == 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 数据库会话中间件
|
||||
# ============================================================
|
||||
async def db_session_middleware(request: Request, call_next):
|
||||
"""请求级 DB 会话:写操作按业务成功与否 commit/rollback,finally 关闭"""
|
||||
db = SessionLocal()
|
||||
_db_ctx.set(db)
|
||||
try:
|
||||
response = await call_next(request)
|
||||
if request.method in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
||||
# BaseHTTPMiddleware 下响应为流式,需缓冲后读取业务码,再重建响应返回
|
||||
body, consumed = await read_response_body(response)
|
||||
if consumed:
|
||||
response = rebuild_response(response, body)
|
||||
if _is_business_success(body):
|
||||
db.commit()
|
||||
else:
|
||||
db.rollback()
|
||||
return response
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,64 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 请求日志中间件
|
||||
# | 说明: 请求响应拦截 hook,可自定义业务逻辑
|
||||
# +======================================================================
|
||||
|
||||
"""请求响应拦截 hook 注册(当前为空实现,可自定义业务逻辑)。"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
from starlette.requests import Request
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 注册日志中间件
|
||||
# ============================================================
|
||||
def register_middleware(app: FastAPI):
|
||||
"""
|
||||
请求响应拦截 hook(当前为空实现,可自定义业务逻辑)
|
||||
"""
|
||||
|
||||
@app.middleware("http")
|
||||
async def logger_request(request: Request, call_next):
|
||||
response = await call_next(request)
|
||||
return response
|
||||
@@ -0,0 +1,242 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 操作日志中间件
|
||||
# | 说明: 自动拦截写请求并记录操作日志
|
||||
# +======================================================================
|
||||
|
||||
"""操作日志中间件:自动拦截写请求并记录操作日志(原 core/middleware.py)。"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.requests import Request
|
||||
|
||||
from core.security import get_user_id
|
||||
from utils.response_body import read_response_body, rebuild_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 不记录操作日志的路径前缀(统一 /api/v1 前缀后同步)
|
||||
_EXCLUDE_PATHS = (
|
||||
'/api/v1/login',
|
||||
'/api/v1/logout',
|
||||
'/api/v1/captcha',
|
||||
'/api/v1/upload',
|
||||
'/api/v1/loginlog',
|
||||
'/api/v1/operationlog',
|
||||
'/api/v1/index/menu',
|
||||
'/api/v1/index/user',
|
||||
)
|
||||
|
||||
# 操作日志 result 字段最大长度(防止超长响应体撑爆日志表)
|
||||
_RESULT_MAX_LEN = 2000
|
||||
# 响应体解析/采样前缀上限:code 位于 R 响应 JSON 最前,失败响应(data:null)体积小,
|
||||
# 只解析/解码前缀即可判定业务状态并采样 result,避免大响应体全量 json.loads/解码
|
||||
_READ_LIMIT = 8192
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 辅助函数
|
||||
# ============================================================
|
||||
def _should_log(request: Request) -> bool:
|
||||
"""判断当前请求是否需要记录操作日志"""
|
||||
if request.method not in ('POST', 'PUT', 'DELETE', 'PATCH'):
|
||||
return False
|
||||
path = request.url.path
|
||||
# 按路径段边界匹配排除前缀(path == prefix 或 prefix + '/'),
|
||||
# 避免子串匹配误伤其他模块路由(如 '/index/user' 子串会命中 '/index/userInfo')。
|
||||
return not any(
|
||||
path == prefix or path.startswith(prefix + '/')
|
||||
for prefix in _EXCLUDE_PATHS
|
||||
)
|
||||
|
||||
|
||||
def _get_route_summary(request: Request) -> str:
|
||||
"""从路由获取操作标题(summary)"""
|
||||
try:
|
||||
route = request.scope.get('route')
|
||||
if route and hasattr(route, 'summary') and route.summary:
|
||||
return route.summary
|
||||
except Exception:
|
||||
pass
|
||||
return request.method + ' ' + request.url.path
|
||||
|
||||
|
||||
# URL 路径末段 → 操作类型(与数据字典 operation_type 对应)
|
||||
_TYPE_MAP = {
|
||||
'add': 1, # 新增
|
||||
'update': 2, # 修改
|
||||
'delete': 3, # 删除
|
||||
'batchdelete': 4, # 批量删除
|
||||
'status': 5, # 状态设置
|
||||
'resetpwd': 6, # 重置密码
|
||||
'updatepassword': 7, # 修改密码
|
||||
'import': 8, # 导入
|
||||
'export': 9, # 导出
|
||||
'kickout': 10, # 强退
|
||||
'generate': 11, # 生成代码
|
||||
'clear': 12, # 清空数据
|
||||
'auth': 13, # 授权
|
||||
'refresh': 14, # 缓存
|
||||
'openaccount': 15, # 开账号
|
||||
}
|
||||
|
||||
|
||||
def _infer_type(request: Request) -> int:
|
||||
"""从 URL 路径末段推断操作类型,匹配不到返回 0(其它)"""
|
||||
path = request.url.path.rstrip('/')
|
||||
last_segment = path.split('/')[-1].lower()
|
||||
# 跳过纯数字路径参数(如 /delete/123 → 取 delete)
|
||||
if last_segment.isdigit():
|
||||
parts = path.split('/')
|
||||
last_segment = parts[-2].lower() if len(parts) >= 2 else ''
|
||||
return _TYPE_MAP.get(last_segment, 0)
|
||||
|
||||
|
||||
def _get_method_name(request: Request) -> str:
|
||||
"""从请求路径提取路由方法名(如 /api/v1/user/update → v1.user.update)"""
|
||||
path = request.url.path
|
||||
# 移除 /api/ 前缀
|
||||
if path.startswith('/api/'):
|
||||
path = path[5:]
|
||||
# 移除路径参数(如 /delete/123 → /delete)
|
||||
parts = [p for p in path.split('/') if p and not p.isdigit()]
|
||||
return '.'.join(parts)
|
||||
|
||||
|
||||
def _attach_background(response, task: BackgroundTask) -> None:
|
||||
"""把日志落库挂到响应后台任务;若响应已有后台任务则链式追加,避免覆盖"""
|
||||
prev = getattr(response, 'background', None)
|
||||
if prev is None:
|
||||
response.background = task
|
||||
return
|
||||
|
||||
async def _combined():
|
||||
await prev()
|
||||
await task()
|
||||
|
||||
response.background = BackgroundTask(_combined)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 操作日志中间件
|
||||
# ============================================================
|
||||
async def operation_log_middleware(request: Request, call_next):
|
||||
"""操作日志中间件:自动拦截写请求并记录操作日志
|
||||
|
||||
日志落库通过 response.background 延迟到响应返回客户端后执行(Starlette 在发送
|
||||
响应体后自动运行后台任务),不再在响应返回前阻塞等待 ip2region 读盘 + 独立
|
||||
会话写入,消除每次写操作的延迟与连接占用。仅解析/采样响应体前缀,避免大响应
|
||||
体全量 json.loads / 解码。
|
||||
"""
|
||||
if not _should_log(request):
|
||||
return await call_next(request)
|
||||
|
||||
start_time = time.time()
|
||||
status = 0
|
||||
error_msg = ''
|
||||
result_msg = ''
|
||||
param_msg = ''
|
||||
response = None
|
||||
|
||||
# 读取请求体作为参数记录(读取后需重建,否则后续处理拿不到body)
|
||||
try:
|
||||
body_bytes = await request.body()
|
||||
if body_bytes:
|
||||
param_msg = body_bytes.decode('utf-8', errors='replace')[:_RESULT_MAX_LEN]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
response = await call_next(request)
|
||||
# 缓冲响应体读取业务状态;因消费了流式体需重建响应,避免客户端收到空响应
|
||||
body, consumed = await read_response_body(response)
|
||||
if consumed:
|
||||
response = rebuild_response(response, body)
|
||||
# 只解析响应体前缀:code 位于 R 响应 JSON 最前;大响应体截断导致解析失败时
|
||||
# 按成功(status=0)处理,与业务实际一致(失败响应 data:null 体积小可完整解析)
|
||||
sample = body[:_READ_LIMIT] if body else b''
|
||||
if sample:
|
||||
try:
|
||||
data = json.loads(sample)
|
||||
if data.get('code', 0) != 0:
|
||||
status = 1
|
||||
error_msg = data.get('msg', '')
|
||||
except Exception:
|
||||
pass
|
||||
result_msg = body[:_READ_LIMIT].decode('utf-8', errors='replace')[:_RESULT_MAX_LEN]
|
||||
return response
|
||||
except Exception as e:
|
||||
status = 1
|
||||
error_msg = str(e)
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
consume_time = int((time.time() - start_time) * 1000)
|
||||
user_id = get_user_id(request)
|
||||
if user_id and user_id > 0:
|
||||
# 懒加载业务 service,避免 core → 业务模块顶层依赖
|
||||
from modules.operation_log import service as operation_log
|
||||
kwargs = dict(
|
||||
title=_get_route_summary(request),
|
||||
type=_infer_type(request),
|
||||
method=_get_method_name(request),
|
||||
url=request.url.path,
|
||||
param=param_msg,
|
||||
result=result_msg,
|
||||
consume_time=consume_time,
|
||||
status=status,
|
||||
error=error_msg,
|
||||
)
|
||||
if response is not None:
|
||||
# 响应正常生成:挂后台任务,响应返回客户端后异步落库,不阻塞响应
|
||||
_attach_background(
|
||||
response, BackgroundTask(operation_log.record_operation_log, request, **kwargs))
|
||||
else:
|
||||
# 请求异常无响应可挂(低频):线程池同步写入,避免阻塞事件循环
|
||||
await asyncio.to_thread(operation_log.record_operation_log, request, **kwargs)
|
||||
except Exception as e:
|
||||
logger.error(f"操作日志中间件异常: {str(e)}")
|
||||
@@ -0,0 +1,115 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 请求限流中间件
|
||||
# | 说明: 基于 Redis 的请求级 IP 限流,采用滑动窗口计数器
|
||||
# +======================================================================
|
||||
|
||||
"""基于 Redis 的请求级 IP 限流中间件(默认关闭,opt-in)。
|
||||
|
||||
采用滑动窗口计数器:按当前窗口与上一窗口的计数时间加权,消除固定窗口边界处的
|
||||
2× 突发(固定窗口在窗口边界整体重置,边界两侧可连续打满配额)。
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
from fastapi import Request
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from core.config.rate_limit import (
|
||||
RATE_LIMIT_ENABLED,
|
||||
RATE_LIMIT_LIMIT,
|
||||
RATE_LIMIT_WINDOW_SECONDS,
|
||||
RATE_LIMIT_KEY_PREFIX,
|
||||
)
|
||||
from core.redis_client import sliding_window_incr
|
||||
from utils.ip import get_client_ip
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 限流中间件
|
||||
# ============================================================
|
||||
async def rate_limit_middleware(request: Request, call_next):
|
||||
"""按客户端 IP 做滑动窗口限流,超限直接返回统一 200 + code 响应"""
|
||||
if not RATE_LIMIT_ENABLED:
|
||||
return await call_next(request)
|
||||
|
||||
# CORS 预检请求不计数,避免浏览器预检占用限流额度
|
||||
if request.method == 'OPTIONS':
|
||||
return await call_next(request)
|
||||
|
||||
# 直接读 app.state(lifespan 注入),不依赖 contextvar 注入顺序;Redis 未就绪时降级放行
|
||||
redis = getattr(request.app.state, 'redis', None)
|
||||
if redis is None:
|
||||
return await call_next(request)
|
||||
|
||||
client_ip = get_client_ip(request)
|
||||
now = time.time()
|
||||
window_index = int(now // RATE_LIMIT_WINDOW_SECONDS)
|
||||
current_key = f"{RATE_LIMIT_KEY_PREFIX}{client_ip}:{window_index}"
|
||||
prev_key = f"{RATE_LIMIT_KEY_PREFIX}{client_ip}:{window_index - 1}"
|
||||
|
||||
# 原子滑动窗口计数(Lua 内完成 incr/expire/get/加权),消除窗口边界 2× 突发。
|
||||
# Redis 运行期故障(连接重置/超时/连接池耗尽,此时 redis 对象仍非 None)时降级放行
|
||||
# (fail-open),避免 Redis 一挂全站请求失败——仅判 redis is None 覆盖不到运行期异常
|
||||
try:
|
||||
count = await sliding_window_incr(
|
||||
redis, current_key, prev_key, RATE_LIMIT_WINDOW_SECONDS, window_index, now)
|
||||
except Exception as e:
|
||||
logger.warning(f"限流计数异常,降级放行: {e}")
|
||||
return await call_next(request)
|
||||
|
||||
# 直接返回而非抛异常:BaseHTTPMiddleware 内抛异常会绕过全局异常处理器
|
||||
if count > RATE_LIMIT_LIMIT:
|
||||
try:
|
||||
ttl = await redis.ttl(current_key)
|
||||
except Exception:
|
||||
ttl = RATE_LIMIT_WINDOW_SECONDS
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"code": 1, "data": None, "msg": f"请求过于频繁,请 {ttl} 秒后再试", "ok": False},
|
||||
)
|
||||
|
||||
return await call_next(request)
|
||||
@@ -0,0 +1,91 @@
|
||||
# +======================================================================
|
||||
# | DjangoAdmin 企业数字化开发平台 [ 赋能开发者,助力企业发展 ]
|
||||
# +----------------------------------------------------------------------
|
||||
# | Copyright (c) 2021-2026 小蚂蚁云团队 版权所有
|
||||
# +----------------------------------------------------------------------
|
||||
# | 开源协议: Apache-2.0
|
||||
# | 重要声明: 未经书面许可,不得移除或修改本框架相关版权标识。
|
||||
# +----------------------------------------------------------------------
|
||||
# | 官方网站: https://www.djangoadmin.cn
|
||||
# +----------------------------------------------------------------------
|
||||
# | 研发团队: 小蚂蚁云团队 荣誉出品
|
||||
# +======================================================================
|
||||
# | 版权和免责声明:
|
||||
# | 一、知识产权声明
|
||||
# | 本团队对该软件框架产品(以下简称“本产品”)拥有完整知识产权,包括但不限于商标权、专利权、
|
||||
# | 著作权、商业秘密等。前述权利均受中华人民共和国及相关国际法律法规保护。未经本团队书面授权,
|
||||
# | 任何个人或单位不得基于本产品本身申请任何形式的知识产权。
|
||||
# | 二、使用限制与禁止条款
|
||||
# | 您理解并同意,禁止将本产品用于以下任何行为:
|
||||
# | 1. 任何违反中华人民共和国法律法规的用途;
|
||||
# | 2. 任何侵害他人合法权益(包括但不限于人身权、财产权、知识产权等)的行为;
|
||||
# | 3. 任何危害国家安全、公共利益及社会道德风尚的行为。
|
||||
# | 三、责任豁免
|
||||
# | 因使用本产品而产生的任何损失或法律纠纷(包括但不限于直接损失、间接损失、附带损失、衍生损失
|
||||
# | 以及因违约、侵权等引发的责任),本团队均不承担任何法律责任。若因违规使用本产品导致任何第三
|
||||
# | 方提起索赔或行政机关进行调查,您应独立承担全部责任。
|
||||
# | 四、违约与法律责任
|
||||
# | 如发现任何违反本声明的行为,本团队有权:
|
||||
# | 1. 无条件配合公安机关及其他有权机关的调查取证工作;
|
||||
# | 2. 依法追究相关方的民事责任,并要求赔偿全部损失;
|
||||
# | 3. 保留进一步采取刑事控告等法律措施的权利。
|
||||
# | 五、许可范围说明
|
||||
# | 1、被授权方应视本产品源代码为核心机密信息,负有最高标准的保密与保管义务。禁止以任何形式(包
|
||||
# | 括但不限于直接或间接的分享、复制、传播、分发、转售、出租、开源、反向工程、反编译、反汇编或通
|
||||
# | 过网络服务提供给任何第三方)泄露、披露或处置源代码的全部或部分内容。任何未经授权的复制、使用、
|
||||
# | 传播行为均构成对本团队知识产权的严重侵权,本团队有权依法追究其全部法律责任,并要求赔偿因此造
|
||||
# | 成的一切经济损失及商誉损害。
|
||||
# | 2、本产品仅限于公司或个人在法律允许范围内,用于内部且合规的软件产品研发。具体权利义务以《软件
|
||||
# | 许可协议》附件为准。
|
||||
# +======================================================================
|
||||
|
||||
# +======================================================================
|
||||
# | 模块: 上传大小限制中间件
|
||||
# | 说明: HTTP 层上传体积预拦截,按 Content-Length 提前拒绝超大请求体
|
||||
# +======================================================================
|
||||
|
||||
"""HTTP 层上传体积预拦截中间件:按 Content-Length 提前拒绝超大请求体(DoS 纵深防御)。
|
||||
|
||||
背景:FastAPI/Starlette 解析 multipart 时会把整个请求体 spool 到临时盘/内存,
|
||||
业务层(_read_upload_limited)在 handler 内才做大小校验,超大请求体仍会先耗尽
|
||||
磁盘/带宽。本中间件在读取请求体之前按 Content-Length 直接拦截,把 DoS 面关在
|
||||
业务处理之外。
|
||||
"""
|
||||
|
||||
from fastapi import Request
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from core.config.app import UPLOAD_HTTP_MAX_SIZE_MB, UPLOAD_MAX_SIZE_MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 上传大小限制中间件
|
||||
# ============================================================
|
||||
async def upload_size_limit_middleware(request: Request, call_next):
|
||||
"""按 Content-Length 提前拦截超大请求体
|
||||
|
||||
仅拦截声明了 Content-Length 的请求;分块传输(无 Content-Length)无法从头部
|
||||
判定,交由业务层流式读取(_read_upload_limited)兜底,读取超限即中断。
|
||||
"""
|
||||
if request.method not in ('POST', 'PUT', 'PATCH'):
|
||||
return await call_next(request)
|
||||
|
||||
content_length = request.headers.get('content-length')
|
||||
if content_length is None:
|
||||
return await call_next(request)
|
||||
|
||||
try:
|
||||
length = int(content_length)
|
||||
except (TypeError, ValueError):
|
||||
# 非法 Content-Length:无法判定大小,交由业务层兜底
|
||||
return await call_next(request)
|
||||
|
||||
# 阈值比业务上限放宽(容纳 multipart 边界等开销),精确大小仍由业务层校验
|
||||
if length > UPLOAD_HTTP_MAX_SIZE_MB * 1024 * 1024:
|
||||
# 直接返回而非抛异常:BaseHTTPMiddleware 内抛异常会绕过全局异常处理器
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"code": 1, "data": None, "msg": f"文件大小超过限制,最大允许 {UPLOAD_MAX_SIZE_MB}MB", "ok": False},
|
||||
)
|
||||
|
||||
return await call_next(request)
|
||||
Reference in New Issue
Block a user