异常与统一封装架构:规范统一返回值、全局异常与参数校验

AI 很擅长快速写接口,比如你告诉它:

"增加一个创建用户接口。"

它很可能几分钟就能给出一段能运行的代码,但如果项目里没有统一规范,不同接口很快就会变成这样:

json 复制代码
{
  "success": true
}

另一个接口返回:

json 复制代码
{
  "code": 0,
  "msg": "ok",
  "result": {}
}

再一个接口出错时直接返回:

json 复制代码
{
  "error": "user not found"
}

甚至有些地方直接把数据库异常抛给前端。

单个接口都"能跑",整个系统却越来越难维护。

所以,AI 编程里有一类非常重要、却经常被忽略的基础架构:

统一返回值 + 全局异常处理 + 统一参数校验。

它们的作用,就是提前规定:

成功怎么返回,失败怎么返回,非法输入在哪里拦截。


📌 技术名片

统一异常与接口封装架构

指通过统一响应模型、统一异常体系和统一入参校验机制,让整个系统的接口具有一致的输入、输出和错误处理方式。

这里常见三个核心概念:

  • API Response Envelope:接口响应封装,即规定所有接口统一返回什么结构;
  • Global Exception Handling:全局异常处理,即异常集中处理,而不是每个接口各写一遍;
  • Validator:校验器,用于在数据进入业务逻辑之前检查格式、范围和合法性。

💡 一句话理解

可以把 API 想象成机场安检。

旅客进入机场以后:

text 复制代码
检查证件
 ↓
检查行李
 ↓
符合规则
 ↓
进入候机区

如果有问题:

text 复制代码
证件错误
行李违规
身份异常

不会让每一个登机口自己决定:

"这个人该怎么办?"

而是由统一的安检规则处理。

软件系统也一样:

text 复制代码
用户请求
 ↓
参数校验
 ↓
业务处理
 ↓
统一返回

发生异常时:

text 复制代码
业务异常
系统异常
 ↓
统一异常处理
 ↓
标准错误响应

这对 AI 尤其重要。因为如果没有规则,AI 很容易:

每写一个接口,就重新发明一次返回格式和错误处理方式。


一、反面教材:AI 的"自由发挥"

假设让 AI 实现一个创建用户接口:

用户名至少 3 个字符,密码必须符合安全规则。如果用户名已经存在,要给出错误提示。

没有架构约束时,AI 很可能写成:

python 复制代码
@router.post("/users")
async def create_user(data: dict):
    if len(data["username"]) < 3:
        return {
            "success": False,
            "message": "username too short"
        }

    if len(data["password"]) < 8:
        return {
            "code": 400,
            "error": "invalid password"
        }

    user = await db.get_user(data["username"])

    if user:
        raise HTTPException(
            status_code=400,
            detail="user already exists"
        )

    try:
        new_user = await db.create_user(data)

        return {
            "result": new_user,
            "status": "ok"
        }

    except Exception as e:
        return {
            "error": str(e)
        }

功能似乎完整。

但里面已经出现了很多问题。


问题 1:返回格式不统一

同一个接口里甚至出现了:

json 复制代码
{
  "success": false
}

和:

json 复制代码
{
  "code": 400
}

以及:

json 复制代码
{
  "status": "ok"
}

前端每调用一个接口,都要重新猜:

这次到底应该判断 successcode 还是 status


问题 2:参数校验散落在业务代码里

python 复制代码
if len(data["username"]) < 3:
python 复制代码
if len(data["password"]) < 8:

这些本质上属于:

输入是否合法?

却混进了业务逻辑。

下一个 AI 再写一个"修改用户"接口,很可能又复制一遍。


问题 3:异常处理方式不一致

用户名存在:

python 复制代码
raise HTTPException(...)

密码错误:

python 复制代码
return {...}

数据库异常:

python 复制代码
except Exception as e:
    return {"error": str(e)}

三种错误,三套处理方式。


问题 4:系统内部错误直接暴露给用户

python 复制代码
str(e)

可能把:

text 复制代码
数据库表名
SQL 语句
服务器路径
内部配置

直接返回前端。

这不仅难看,还可能带来安全风险。


问题 5:每个接口都在重复造轮子

当系统有 100 个接口以后,就可能出现:

text 复制代码
100 套参数判断
100 套 try / except
20 种返回结构
10 种错误格式

真正的问题不是 AI 不会写代码。

而是:

没有统一规范时,AI 会非常高效地制造不一致。


二、架构规则:人类先把"设计图纸"画好

解决这个问题,可以把整个请求过程标准化:

text 复制代码
客户端请求
    ↓
Schema / Validator
入参校验
    ↓
API
    ↓
Service
    ↓
业务异常
    ↓
Global Exception Handler
全局异常处理
    ↓
ApiResponse
统一返回

然后给 AI 明确几条规则。


规则 1:所有普通 API 使用统一返回结构

例如统一规定:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {}
}

三个字段各司其职:

text 复制代码
code
业务 / 状态代码

message
给人看的提示信息

data
真正返回的数据

成功时:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 12,
    "username": "tom"
  }
}

失败时:

json 复制代码
{
  "code": 404,
  "message": "User '12' not found"
}

前端不用再猜。


规则 2:API 不要到处自己拼 JSON

不要:

python 复制代码
return {
    "success": True,
    "result": data
}

也不要:

python 复制代码
return {
    "status": "ok",
    "payload": data
}

统一使用:

python 复制代码
return ApiResponse(data=data)

这样返回格式只有一份定义。


规则 3:输入格式由 Schema 和 Validator 负责

例如:

python 复制代码
class UserCreate(BaseModel):
    username: str = Field(
        ...,
        min_length=3,
        max_length=100
    )

这里的 Field 可以理解为:

字段规则。

它直接规定:

text 复制代码
username
最少 3 个字符
最多 100 个字符

AI 不需要在每一个 API 里重新写:

python 复制代码
if len(username) < 3:

规则 4:复杂字段使用 Validator

有些校验不是简单长度就能完成。

例如密码可能要求:

text 复制代码
至少多少位
包含大小写字母
包含数字
包含特殊字符

这时候就适合使用:

Validator(校验器)

把密码规则集中在一个地方。

以后:

text 复制代码
创建用户
重置密码
修改密码

全部复用。


规则 5:业务错误使用统一异常类型

例如:

text 复制代码
用户不存在
Agent 不存在
知识库不存在

它们都属于:

Not Found(资源不存在)

可以统一使用:

python 复制代码
NotFoundError

而:

text 复制代码
用户名已存在
Agent 名称重复

可以统一归入:

python 复制代码
AlreadyExistsError

这样 Service 只需要表达:

"发生了什么业务错误。"

不用关心 HTTP 最终应该返回 404、409 还是其他状态码。


规则 6:异常集中到全局处理

业务代码可以:

python 复制代码
raise NotFoundError(...)

全局异常处理器负责:

text 复制代码
NotFoundError
      ↓
HTTP 404
      ↓
ApiResponse

而不是每一个接口:

python 复制代码
try:
    ...
except:
    ...

重复几十遍。


三、这样做有什么好处?

对于传统开发,这叫工程规范。对于 AI 编程,它还有更直接的价值。


1. AI 不再随意创造返回结构

项目里只有:

python 复制代码
ApiResponse

这一套规则。

AI 看到现有代码以后,更容易继续写:

python 复制代码
return ApiResponse(data=result)

2. 前端调用变得非常简单

前端可以统一认为:

text 复制代码
code
message
data

永远存在。

于是统一 HTTP Client 就可以统一处理:

text 复制代码
成功
错误
Token 失效
提示信息

不用每个页面单独适配。


3. 校验规则不会散落

例如密码规则如果修改:

最低 8 位改成最低 12 位。

如果规则集中在 Validator,只改一个地方即可。

如果散落在:

text 复制代码
注册
创建用户
修改密码
重置密码
管理员后台

五个接口中,就很容易漏改。


4. Service 更干净

业务代码不需要反复:

python 复制代码
try:
    ...
except HTTPException:
    ...

它只处理业务:

text 复制代码
用户不存在
 ↓
抛 NotFoundError

用户已存在
 ↓
抛 AlreadyExistsError

错误怎样转换成 HTTP,由外层统一完成。


5. AI 更容易理解"什么错误属于哪里"

可以建立非常清楚的边界:

text 复制代码
输入格式错误
 → Validator

业务规则错误
 → Domain Error

系统未知错误
 → Global Exception Handler

响应格式
 → ApiResponse

这相当于把错误处理也进行了分层。


四、AI 最容易出现的"失控现场"

一个非常典型的失控过程是:

第一次 AI 写:

python 复制代码
return {"success": True}

第二次看到另一个项目习惯,于是写:

python 复制代码
return {
    "code": 0,
    "message": "ok"
}

第三次又直接:

python 复制代码
raise HTTPException(...)

第四次为了"保险":

python 复制代码
try:
    ...
except Exception:
    ...

最后系统里可能出现:

text 复制代码
API A → HTTPException
API B → 自定义 JSON
API C → ApiResponse
API D → try / except
API E → 返回 None

而 AI 有一个非常现实的特点:

它经常会模仿项目里已经存在的代码。

项目里如果同时存在五种写法,后续 AI 就很难知道:

哪一种才是真正的标准?

所以统一异常架构还有一个非常重要的价值:

给 AI 提供大量一致的正确范例。


五、提示词落地:把规则教给 AI

仅仅告诉 AI:

"注意异常处理。"

几乎没有实际作用。

更有效的是写成具体工程规则。

例如可以加入项目级规则:

text 复制代码
## API 响应和验证规则

所有标准的 JSON API 必须使用项目统一的

ApiResponse 响应信封。

标准响应字段:
- code
- message
- data

规则:
- 除非现有协议明确要求,否则请勿创建其他响应结构,例如:success/result/payload/status。
- API 路由应返回 ApiResponse,而不是手动构建响应字典。
- 请求负载必须使用现有的 Pydantic schema。
- 简单的输入约束(例如 length、range 和 required 字段)应在 Pydantic 字段定义中声明。
- 可重用或复杂的验证规则应使用项目现有的验证器。
- 请勿在 API 路由中重复验证逻辑。
- 业务错误必须使用项目的域异常类型,例如:NotFoundError 和 AlreadyExistsError。
- 请勿在服务中将业务错误转换为 HTTPException。
- 不要为每个 API 路由添加重复的 try/except 代码块。
- 让全局异常处理机制将已知的异常转换为标准化的 API 响应。
- 意外的内部异常不得在生产环境中暴露敏感的实现细节。

以后让 AI 开发接口时,可以进一步提示:

text 复制代码
实现"创建用户"接口。

请严格遵守项目现有的统一接口规范:

1. 请求参数使用已有 Pydantic Schema;
2. 字段长度、范围等规则放在 Schema / Validator;
3. API 返回统一使用 ApiResponse;
4. 用户已存在等业务错误使用现有 Domain Error;
5. 不要在 Router 中重复编写 try/except;
6. 不要自行创建新的错误返回格式;
7. 优先复用项目已有 Validator 和异常类型。

实现前先检查:
app/schemas/common.py
相关业务 Schema
现有 Service
全局异常处理代码。

这时候 AI 不再是在"自由设计一个接口",而是在:

现有异常与响应协议下增加一个接口。


六、正面产出:miniagent 是怎么做的?

miniagent 已经把:

text 复制代码
统一响应
业务异常
全局异常处理
Pydantic 参数校验

几个部分组合起来了,参见下图:

flowchart TD EX[&#34;Python Exception<br/>Python 异常基类&#34;] BASE[&#34;BaseDomainError<br/>业务异常基类&#34;] NOTFOUND[&#34;NotFoundError<br/>资源不存在&#34;] EXISTS[&#34;AlreadyExistsError<br/>资源已存在&#34;] EMPTY[&#34;EmptyDataError<br/>数据为空&#34;] BAD[&#34;BadRequestError<br/>错误请求&#34;] READONLY[&#34;ReadOnlyError<br/>资源只读&#34;] INVALID[&#34;InvalidValueError<br/>值不合法&#34;] HANDLER[&#34;Global Exception Handler<br/>全局异常处理&#34;] RESPONSE[&#34;ApiResponse<br/>统一返回格式<br/>code · message · data&#34;] EX --> BASE BASE --> NOTFOUND BASE --> EXISTS BASE --> EMPTY BASE --> BAD BASE --> READONLY BASE --> INVALID NOTFOUND --> HANDLER EXISTS --> HANDLER EMPTY --> HANDLER BAD --> HANDLER READONLY --> HANDLER INVALID --> HANDLER HANDLER --> RESPONSE

1. 统一返回值:ApiResponse

miniagentbackend/app/schemas/common.py 定义了统一顶层响应模型:

python 复制代码
class ApiResponse(BaseModel, Generic[T]):
    """
    Generic top-level API response envelope.
    """

    code: int = Field(
        200,
        description="Business status code, 200 = success"
    )

    message: str = Field(
        "success",
        description="Human-readable status message"
    )

    data: Optional[T] = Field(
        None,
        description="Response payload"
    )

也就是说,普通接口统一围绕:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {}
}

展开。

这里还使用了:

python 复制代码
Generic[T]

Generic 的意思是泛型

可以简单理解成:

data 可以装不同类型的数据,但外面的 code / message / data 外壳保持不变。

例如:

text 复制代码
ApiResponse[UserOut]
ApiResponse[AgentOut]
ApiResponse[PageResult]

内部数据不同,外层协议统一。


2. 分页结果也统一封装

同一个文件里还定义了:

python 复制代码
class PageResult(BaseModel, Generic[T]):
    total: int
    page: int
    page_size: int
    data: List[T]

因此分页接口不需要今天返回:

text 复制代码
rows
count
current

明天又变成:

text 复制代码
items
total
pageNum

而是统一:

json 复制代码
{
  "total": 100,
  "page": 1,
  "page_size": 20,
  "data": []
}

这对 AI 特别重要,因为 AI 新增分页接口时,可以直接复用现有结构。


3. 业务异常也有统一基类

miniagent 定义:

python 复制代码
class BaseDomainError(Exception):
    """
    Business Logic Exception Base Class
    """

即:

领域业务异常基类。

然后继续派生:

python 复制代码
class NotFoundError(BaseDomainError):
    ...

class AlreadyExistsError(BaseDomainError):
    ...

class EmptyDataError(BaseDomainError):
    ...

class ReadOnlyError(BaseDomainError):
    ...

class InvalidValueError(BaseDomainError):
    ...

这样业务代码遇到错误时,可以不需要重新创造:

text 复制代码
UserNotExistException
MissingAgentException
KBNotFoundException
NoDocumentException

各种完全不同的错误体系,而是尽量归入现有语义。


4. 错误信息还统一接入 I18n

这里的 I18n 是:

Internationalization,国际化。

BaseDomainError 可以通过:

python 复制代码
def to_detail(self) -> str:
    return _translate(...)

把异常转成对应语言的提示信息。

这意味着:

text 复制代码
业务异常
 ↓
统一错误类型
 ↓
统一国际化文案

而不是每个 AI 新写一个接口就硬编码:

python 复制代码
"User not found"

七、全局异常:异常只需要"抛",不用到处"接"

miniagent 在应用入口中提供了:

python 复制代码
def handle_exception(exc: Exception) -> JSONResponse:

统一处理不同类型异常,例如:

text 复制代码
Service
 ↓
NotFoundError
 ↓
Global Exception Handler
 ↓
HTTP 404
 ↓
ApiResponse

对于:

python 复制代码
AlreadyExistsError

则统一转成:

text 复制代码
HTTP 409 Conflict

其中 Conflict 的意思是:

资源状态冲突。

例如创建一个已经存在的用户名,就很适合这种语义。


未知异常也统一兜底

如果不是已知业务异常:

python 复制代码
error_data = {
    "error":
        str(exc)
        if settings.debug
        else t("common.error_500")
}

这里体现了一个非常重要的生产环境原则:

开发环境:

text 复制代码
可以看到详细错误
方便调试

生产环境:

text 复制代码
隐藏内部实现
返回统一错误信息

避免直接把内部异常暴露给普通用户。


八、Validator:错误输入尽量不要进入业务层

再看 miniagent 的用户 Schema。

它没有在创建用户的 API 里面写:

python 复制代码
if len(username) < 3:

而是直接定义:

python 复制代码
class UserCreate(BaseModel):

    username: str = Field(
        ...,
        min_length=3,
        max_length=100
    )

    nickname: Optional[str] = Field(
        None,
        max_length=100
    )

    avatar: Optional[str] = Field(
        None,
        max_length=500
    )

这意味着:

用户名长度不合法时,请求还没真正进入核心业务逻辑,就已经被数据模型拦下。


密码校验进一步复用 Validator

miniagent 当前定义:

python 复制代码
PasswordValue = Annotated[
    str,
    Field(max_length=128),
    AfterValidator(validate_password)
]

这里出现两个术语。

Annotated

Annotated 可以理解成:

给一个数据类型附加额外规则。

这里基础类型仍然是:

python 复制代码
str

但同时增加:

text 复制代码
最大长度 128
+
密码 Validator

AfterValidator

AfterValidator 可以理解成:

基础类型校验完成以后,再执行一个自定义校验函数。

这里调用的是:

python 复制代码
validate_password

于是创建用户:

python 复制代码
class UserCreate(BaseModel):
    password: PasswordValue

重置密码:

python 复制代码
class UserPasswordReset(BaseModel):
    password: PasswordValue

都复用同一套密码规则。

这就是典型的:

一次定义,到处复用。


九、最终形成了一条非常清晰的请求链

miniagent 这些真实实现组合起来,可以得到:

于是不同职责变得非常清楚:

text 复制代码
Schema / Validator
负责"输入是否合法"

Service
负责"业务能不能做"

Domain Error
负责"业务出了什么问题"

Global Exception Handler
负责"错误怎样转换成 HTTP 响应"

ApiResponse
负责"最后返回长什么样"

这就是统一异常架构真正的价值。


十、架构赋能后的 AI 会怎么写?

假设现在再告诉 AI:

"增加一个修改用户名功能。"

没有架构时,AI 可能从头设计:

text 复制代码
参数判断
返回 JSON
try / except
错误信息

有了 miniagent 这样的规则以后,它应该优先思考:

text 复制代码
1. UserUpdate 是否已有 username 字段?
       ↓
2. Field 是否已经规定长度?
       ↓
3. UserService 中增加业务操作
       ↓
4. 重名时抛 AlreadyExistsError
       ↓
5. Router 调用 Service
       ↓
6. 返回 ApiResponse

而不是重新创造一套规则。

这就是所谓:

架构赋能后的 AI 输出。

不是 AI 突然"更聪明"了,而是:

我们把它可以自由决定的事情减少了。


结语

统一异常与接口封装,看起来只是几个不起眼的基础类:

text 复制代码
ApiResponse
PageResult
BaseDomainError
Validator

但对于 AI 编程来说,它们实际上建立了一套非常重要的"交通规则"。

它告诉 AI:

text 复制代码
输入怎么检查
成功怎么返回
业务错误怎么表达
系统错误在哪里处理
前端最终看到什么

如果没有这些规则,AI 会在每一个接口里自由发挥;而 AI 越能写代码,这种"不一致"产生得越快。

所以:

好的异常架构,不是为了让代码多几个基类,而是为了让整个系统只有一套错误语言和接口语言。

在 AI 编程时代,更应该把这套规范提前固化进 项目规则 / Project Rules、系统提示词 / System Prompt 或项目上下文中。

最终形成:

text 复制代码
输入不合法
 → Validator 拦截

业务不允许
 → Domain Error 表达

系统发生异常
 → Global Handler 兜底

无论成功失败
 → 统一协议返回

这样 AI 每新增一个接口,实际上都在复用同一套工程规则。

不要让 AI 每写一个接口,就重新发明一次"什么叫成功,什么叫失败"。

先统一规则,再让 AI 写业务;这才是统一异常与封装架构在 AI 编程中的真正价值。

开源代码


🪐祝您好运🪐

相关推荐
AI枫林晚1 小时前
DeepSeek Harness 功能与特性解析:一个“一切皆插件“的 Agent 运行时
架构
晴天161 小时前
DeepSeek Harness 插件开发:从零到跑起来-Day18
ai·架构·deepseek
宋哥转AI2 小时前
深入理解 AI Agent · MEMORY #01:从认知科学到记忆分层,建立完整的记忆认知框架
人工智能·agent·ai编程
卡卡罗特AI2 小时前
AI总乱改代码?一个规则文件帮你搞定!99%的人都没设置!附万能模板!
openai·ai编程
晚安code2 小时前
Function Calling 会被 MCP 取代吗?理清两者关系与使用细节
ai编程
晚安code2 小时前
MCP Server 开发入门:手把手写一个能跑的 Server,三种协议怎么选
python·ai编程
代码方舟3 小时前
零信任架构实战:基于天远人企关联构建自动化企业尽职调查网关
java·人工智能·架构·自动化
Shadow(⊙o⊙)3 小时前
Linux网络部分——TCP服务端、客户端架构分析多进程、多线程、线程池
c++·tcp/ip·架构