在 AI 编程时代,有一种代码特别容易疯狂复制:
logger.info("开始处理请求")
if not token:
raise UnauthorizedError()
start = time.time()
result = await service.do_something()
logger.info(f"执行耗时: {time.time() - start}")
return result
一开始只有一个接口,看起来没什么问题。
但当项目发展到几十个甚至几百个接口以后,情况很快变成:
用户接口
├─ 鉴权
├─ 日志
├─ 参数检查
├─ 业务逻辑
└─ 埋点
智能体接口
├─ 鉴权
├─ 日志
├─ 参数检查
├─ 业务逻辑
└─ 埋点
知识库接口
├─ 鉴权
├─ 日志
├─ 参数检查
├─ 业务逻辑
└─ 埋点
...
真正不同的可能只有中间那几行业务代码,其余代码,AI 每写一个接口,就顺手再复制一遍。
这正是 AOP(Aspect-Oriented Programming,面向切面编程) 要解决的问题。
📌 技术名片
AOP / Aspect-Oriented Programming / 面向切面编程
是一种把多个业务模块都会用到的公共逻辑,从具体业务代码中抽离出来,统一处理的软件设计思想。
这些公共逻辑通常被称为:
Cross-Cutting Concerns(横切关注点)
典型包括:日志、身份认证、权限校验、审计、性能统计、链路追踪、缓存、事务、埋点、异常处理。
AOP 的核心并不是某个框架或者某种特殊语法,而是一条非常简单的架构原则:
业务代码负责业务,公共规则负责公共规则。
💡 通俗理解:AOP 就像机场安检
假设机场里有很多登机口。
北京航班一个登机口:
安检
↓
查证件
↓
登记信息
↓
登机
上海航班也是:
安检
↓
查证件
↓
登记信息
↓
登机
…
如果按照很多 AI 写代码的思路,大概会变成:
每个登机口自己配一套安检设备。
显然不合理。现实世界里的做法是:
所有旅客先经过统一安检,然后再进入自己的登机口。
软件也是一样。
没有横切治理时:
Router A
├─ 日志
├─ Token 校验
├─ 权限校验
├─ 业务代码
└─ 性能统计
Router B
├─ 日志
├─ Token 校验
├─ 权限校验
├─ 业务代码
└─ 性能统计
经过抽离之后:
日志 / Request ID
性能 / 审计"] B --> C["Authentication
认证 / 权限"] C --> D[Router] D --> E[Service] E --> F[Repository]
于是 Router 可以重新专注于一件事情:
处理业务。
💡 不要把 AOP 理解成“必须使用 AOP 框架”
很多开发者第一次看到 AOP,会想到 Java 的 Spring AOP。
例如:
@Before
@After
@Around
但这只是 AOP 的一种实现方式。AOP 真正重要的是:
识别横切关注点,并把它们从业务代码中抽离。
在 Python + FastAPI 项目中,完全可以使用:
Middleware
依赖注入
Decorator
ContextVar
统一异常处理
实现相同的架构目标。例如 miniagent 当前就采用了这种方式。
仓库中的核心目录已经明确把通用能力放到了 app/core,其中包含:
core/
├── audit_context.py
├── deps.py
├── logger_config.py
├── security/
│ ├── auth_permission.py
│ ├── jwt_auth.py
│ └── ...
└── service_container.py
而不是把认证、日志、审计逻辑散落到每一个业务 Router 中。
这实际上就是一种非常符合 Python/FastAPI 风格的 AOP 思想落地。
一、架构规范:横切逻辑应该放在哪里?
对于一个 FastAPI 项目,可以给 AI 建立一个非常简单的判断标准。
全局请求级逻辑 → Middleware
Middleware(中间件) 适合处理所有 HTTP 请求都可能涉及的问题。
例如:
请求日志
Request ID
接口耗时
全局审计
链路追踪
统一 Header
基本结构:
@app.middleware("http")
async def middleware(request, call_next):
# 请求之前
...
response = await call_next(request)
# 请求之后
...
return response
它的思想就是:
Before
↓
Request → Middleware → Router
↑
After
也就是 AOP 中非常典型的:
在业务逻辑执行前后统一插入公共行为。
二、miniagent 的真实实现
下图比较完整的展示了 miniagent 的 AOP 实现思路:

当前 main.py 中,请求进入统一日志中间件后,会创建 request_id、记录开始时间、建立审计上下文,然后再调用真正的 Router;请求结束后统一记录状态码、耗时、审计结果,并把 X-Request-ID 返回客户端。
与此同时,AuthPermission 集中处理 JWT 验证、用户解析、用户状态检查、权限缓存和权限判断,并把身份信息写入审计上下文。
而 audit_context.py 通过 ContextVar 保存当前请求的:
request_id
method
path
ip_address
user_id
username
change_count
使这些信息可以沿异步调用链传播,而不用层层作为函数参数传递。
这三个组件实际上形成了:
Middleware
+
Dependency / Permission
+
ContextVar
三层横切体系。
这并不是传统意义上依靠复杂代理机制实现的“重型 AOP”。
反而更加符合 FastAPI 项目的特点:
用框架原生能力实现 AOP 思想。
1. 日志不是每个接口自己写
miniagent 的 main.py 中就存在一个统一的 HTTP 请求日志中间件:
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Record all HTTP requests and inject request_id into every log line."""
request_id = str(uuid4())
request.state.request_id = request_id
start_time = time.time()
with logger.contextualize(request_id=request_id):
logger.info(
f"📥 {request.method} {request.url.path}"
)
response = await call_next(request)
process_time = time.time() - start_time
logger.info(
f"📤 {request.method} {request.url.path} "
f"- {response.status_code} ({process_time:.3f}s)"
)
response.headers["X-Process-Time"] = str(process_time)
response.headers["X-Request-ID"] = request_id
return response
这是根据当前仓库代码做的节选,实际实现还同时处理异常、审计和登录日志。
它带来的变化非常重要。以前可能是:
@router.get("/users")
async def list_users():
logger.info("GET /users start")
start = time.time()
result = await user_service.list_users()
logger.info(
f"GET /users finished: {time.time() - start}"
)
return result
现在 Router 可以直接写:
@router.get("/users")
async def list_users():
return await user_service.list_users()
因为:
谁访问了?
访问什么接口?
什么时候开始?
什么时候结束?
状态码多少?
耗时多少?
Request ID 是什么?
这些事情已经不属于 Router,Middleware 会统一完成。
这就是:
把横切逻辑从业务代码中切出去。
2. 身份和权限 → Dependency
并不是所有横切逻辑都适合 Middleware。
例如:
/user/profile
只需要登录。
而:
/admin/users
可能需要 system:user:list 权限。
又比如:
/admin/users/{id}
删除用户时可能要求 system:user:delete 权限。
显然不能简单地让一个全局 Middleware 判断所有业务权限。
这时更适合使用 FastAPI 的:
Dependency Injection(依赖注入)
也就是:
Depends(...)
3. 鉴权:不是每个 Router 都重新解析 JWT
miniagent 中实现了统一的 AuthPermission ,它负责:
JWT 验证
↓
取得用户
↓
检查用户状态
↓
取得权限集合
↓
权限校验
JWT(JSON Web Token,JSON Web 令牌) 是一种常见的身份认证令牌格式。
而不是让每个接口自己写:
token = request.headers.get("Authorization")
username = verify_token(token)
user = await find_user(username)
permissions = await load_permissions(user.id)
if permission not in permissions:
raise HTTPException(...)
[miniagent[(https://github.com/liupras/miniagent) 把这一套流程封装进了 AuthPermission。
例如真正的权限检查核心逻辑非常清晰:
async def check(
self,
user_id: int,
required: str,
) -> None:
perms = await self.get_permissions(user_id)
if SUPER_PERMISSION in perms or required in perms:
return
raise_forbidden(
"auth.permission_denied",
required=required,
)
而权限数据本身还会使用缓存,避免每次请求都重新读取数据库。
这样业务层只需要表达:
这个接口需要什么权限。
而不是重新实现:
权限系统到底怎么工作。
4. 声明规则,而不是重复实现规则
这是 AOP 思想非常重要的一点。
好的业务代码应该尽量接近:
@router.delete("/users/{user_id}")
async def delete_user(...):
return await user_service.delete(user_id)
同时通过 Dependency、Decorator 或其他统一机制声明:
需要登录
需要 system:user:delete 权限
而不是:
@router.delete("/users/{user_id}")
async def delete_user(...):
# 解析 Authorization
...
# 校验 JWT
...
# 查询用户
...
# 查询权限
...
# 判断 super admin
...
# 写访问日志
...
# 记录开始时间
...
# ===== 终于开始真正业务 =====
await user_service.delete(user_id)
# ===== 业务结束 =====
# 写审计日志
...
# 写耗时日志
...
return ...
后者最大的危险,并不是代码长。
而是:
AI 特别擅长复制这种代码。
然后复制几十次。
5. 把权限包装成可调用依赖
AuthPermission 中还有一个很值得 AI 项目借鉴的设计:
class Permission:
def __init__(
self,
permission_code: str,
) -> None:
self._code = permission_code
async def __call__(
self,
request: Request,
credentials = Depends(_bearer_scheme),
) -> int:
auth = request.app.state.container.auth
user_id = await auth.resolve_user_id(
credentials.credentials
)
await auth.check(
user_id,
self._code
)
return user_id
这样权限组件本身就变成了一个 FastAPI 可识别的依赖对象。
整个关系可以理解为:
Router
│
│ 声明权限
↓
Permission("system:user:delete")
│
↓
AuthPermission
│
├── Verify JWT
├── Resolve User
├── Load Permission
└── Check Permission
Router 不需要理解:
JWT 怎么解析?
用户怎么查?
权限缓存在哪里?
超级管理员怎么判断?
它只负责表达:
我要 system:user:delete
这就是一种非常重要的架构思想:
业务层声明意图,基础设施层负责实现机制。
6. ContextVar 解决跨层上下文传递
日志和审计还有一个很麻烦的问题。
假设调用链是:
HTTP Request
↓
Router
↓
Service
↓
Repository
↓
Database
如果每一层都需要:
request_id
user_id
username
最直接的写法可能是:
service.run(
request_id=request_id,
user_id=user_id,
...
)
然后:
repository.save(
request_id=request_id,
user_id=user_id,
...
)
这很快又会污染所有函数参数。
miniagent 为审计上下文使用了:
ContextVar(Context Variable,上下文变量)
代码中定义了:
_audit_context: ContextVar[
Optional[AuditRequestContext]
] = ContextVar(
"audit_request_context",
default=None
)
请求进入时建立上下文:
begin_audit_context(...)
请求结束时:
reset_audit_context(...)
而身份认证成功后,则调用:
set_audit_user(
user.id,
user.username
)
把用户身份挂到当前请求对应的审计上下文中。
于是整个流程变成:
HTTP Request
↓
Middleware
│
├── request_id
├── method
├── path
└── ip
↓
Audit Context
↓
AuthPermission
│
├── user_id
└── username
↓
Audit Context
↓
Service / Repository
这就是另一种横切能力:
请求上下文沿着整个异步调用链传播,但不污染业务函数参数。
三、AOP 到底有什么好处?
1. 减少重复代码
最直接的变化是:
100 个接口
不再意味着:
100 份日志代码
100 份鉴权代码
100 份耗时代码
100 份审计代码
公共规则只实现一次。
2. 修改规则只改一处
假设以后日志需要增加:
request_id
client_ip
user_id
latency
如果日志散落在 200 个接口中:
修改成本 ≈ 200 次
如果使用 Middleware:
修改成本 ≈ 1 次
这才是架构真正解决的问题。
3. 业务代码更容易读
好的 Router 应该让人一眼看到:
接收什么请求
调用什么 Service
返回什么结果
而不是先阅读几十行:
token
logger
permission
cache
metrics
audit
才能找到真正业务。
4. 公共规则更加一致
如果让 AI 自己写 50 次鉴权,它可能得到 50 个微妙不同的版本。
例如:
if permission not in permissions:
另一个:
if required_permission not in permissions:
另一个忘记超级管理员:
if required not in permissions:
另一个忘记判断用户是否禁用。
AOP 的价值之一就是:
让公共规则只有一个权威实现。
四、给 AI 建立明确的 AOP 架构规则
Project Rules(项目规则)里可以直接写:
## 横切关注点
日志、认证、鉴权、审计、指标统计、链路追踪和异常处理属于横切关注点。
禁止在 Router 或业务 Service 中重复实现这些逻辑。
统一规则:
- 全局 HTTP 请求逻辑使用 Middleware;
- 身份认证和权限校验优先使用 FastAPI Depends;
- 只有依赖注入不合适时才使用 Decorator;
- 请求级上下文使用 ContextVar;
- 异常和响应转换使用全局异常处理;
Router 只负责请求编排;
Service 只负责业务逻辑;
Repository 只负责数据访问。
这几行规则,对 AI 的约束力往往比一句:
请写高质量代码
强得多。
不要告诉 AI“写个鉴权”
如果直接说:
给这个接口增加权限控制。
AI 很可能现场创造一套权限逻辑。更好的提示词是:
请为该接口增加权限控制。
要求:
1. 不允许在 Router 中解析 JWT;
2. 不允许重新实现权限判断;
3. 使用项目已有的 AuthPermission;
4. 权限检查通过 FastAPI Depends 或现有 Permission 机制完成;
5. Router 只声明 required permission;
6. 复用 ServiceContainer 中现有 auth 单例;
7. 不改变现有统一异常体系。
这实际上是在告诉 AI:
不要发明机制,只使用项目已经存在的机制。
日志类需求也应该这样告诉 AI
错误提示词:
给每个接口增加访问日志。
AI 很可能直接修改几十个 Router:
logger.info(...)
更好的提示词:
需要增加 HTTP 请求访问日志。
请先判断该功能是否属于横切关注点。
要求:
1. 不允许逐个修改 Router;
2. 使用 FastAPI Middleware 统一实现;
3. 每个请求生成 request_id;
4. 记录 method、path、status_code 和 latency;
5. response 返回 X-Request-ID;
6. 日志上下文应自动传播到当前请求中的其他模块;
7. 不污染 Service 和 Repository 的业务参数。
这句话:
请先判断是否属于横切关注点
非常值得加入 AI 项目规则。
埋点也不要让业务代码变成“圣诞树”
很多系统后期都会加入:
用户点击
Agent 调用次数
LLM Token 消耗
接口耗时
知识库命中率
工具成功率
如果全部写进业务:
metrics.inc("agent_call")
logger.info(...)
tracer.start(...)
audit.record(...)
result = await agent.run()
metrics.observe(...)
logger.info(...)
tracer.end(...)
最后业务逻辑反而只剩:
result = await agent.run()
这种代码虽然能跑,但已经说明横切逻辑侵入业务太深。
更健康的方向应该是:
Metrics
Logging
Tracing
Audit
│
│ 横向切入
↓
────────────────────
Router → Service
────────────────────
而不是:
Router
↓
Metrics
↓
Logging
↓
Tracing
↓
Auth
↓
Audit
↓
Business
总结
AOP 并不神秘。它解决的本质问题就是:
把那些“到处都需要,但又不真正属于任何一个业务”的代码,从业务中抽离出来。
日志、鉴权、审计、埋点、性能统计、链路追踪,都是典型的横切关注点。
对于 FastAPI 项目,没有必要为了追求“AOP”这个名字引入复杂框架。
一个非常实用的组合已经足够:
Middleware
+
Dependency Injection
+
Decorator
+
ContextVar
+
Global Exception Handler
[miniagent[(https://github.com/liupras/miniagent) 当前采用的也正是这个方向。
而在 AI 编程中,我们还应该进一步把它写成明确的项目规则:
发现重复的横切逻辑时,不要继续复制。先停下来判断,它是否应该被抽到 Middleware、Dependency、Decorator 或统一基础设施中。
最终,我们真正希望 AI 写出来的不是:
每个文件单独看都挺漂亮。
而是:
整个项目放在一起,仍然像一个人设计出来的。
这才是架构在 AI 编程时代真正的价值。
开源代码
🪐祝您好运🪐