在 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 写代码的思路,大概会变成:

每个登机口自己配一套安检设备。

显然不合理。现实世界里的做法是:

flowchart TB A[旅客请求] --> B[统一入口] subgraph CROSS["统一安检(横切逻辑)"] B --> C[身份检查] C --> D[安全检查] D --> E[信息登记] end E --> F[登机区] F --> G[北京航班] F --> H[上海航班] F --> I[广州航班]

所有旅客先经过统一安检,然后再进入自己的登机口。

软件也是一样。

没有横切治理时:

Router A
 ├─ 日志
 ├─ Token 校验
 ├─ 权限校验
 ├─ 业务代码
 └─ 性能统计

Router B
 ├─ 日志
 ├─ Token 校验
 ├─ 权限校验
 ├─ 业务代码
 └─ 性能统计

经过抽离之后:

flowchart TB A[Request] --> B["Middleware
日志 / 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 实现思路:

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. 日志不是每个接口自己写

miniagentmain.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 编程时代真正的价值。


开源代码


🪐祝您好运🪐