1. 为什么接口设计和异常处理值得单独拎出来做一个小项目
很多开发者写后端接口,习惯是先把业务逻辑跑通,接口能返回数据就算完事。至于参数校验、错误码设计、异常兜底,往往是等线上出了问题才回头补。我自己早期做项目也是这个路子,结果就是前端联调时反复来问“这个情况返回什么”,测试提的 bug 里有一半是边界场景没处理,运维半夜告警一看是某个空指针把整个请求打挂了。
接口设计和异常处理这两件事,单独看都不算“难”,但它们是最容易被忽略、又最影响项目质量的部分。一个设计得好的接口,前端拿到文档就能直接写代码,不需要反复确认;一套清晰的异常处理机制,能让线上问题在日志里一眼定位,而不是靠猜。这个实战项目的核心思路,就是让 AI 来帮你把这两块补齐——不是让 AI 替你写业务逻辑,而是让它帮你把那些“你知道该做但总是懒得做”的细节一次性补全。
具体来说,这个项目适合几类人:一是刚入行不久、还没形成接口设计规范的开发者;二是接手了老项目、接口风格混乱想统一的人;三是想用 AI 提效但不知道怎么下手的同学。整个实战不需要复杂的框架,一个普通的 Web 项目加上一个能对话的 AI 工具就够了。我会把完整的思路、提示词设计、落地步骤和踩过的坑都讲清楚,你照着做就能复现。
2. 接口设计的核心要素拆解与 AI 介入点
2.1 一个合格接口到底要包含哪些信息
在让 AI 帮忙之前,得先明确“合格”的标准。我总结下来,一个接口至少要说清楚这几件事:请求方法、路径、请求参数(含类型和是否必填)、响应结构、成功和失败的判定方式、以及各种边界情况的返回。很多人写接口文档只写“成功返回什么”,失败情况一笔带过,这就是后面扯皮的根源。
举个具体的例子。假设有个“查询用户订单列表”的接口,光写GET /orders?userId=1是不够的。你得说明:userId 是必填还是可选?如果传了不存在的 userId 返回什么?分页参数默认值是多少?订单为空时返回空数组还是 null?这些细节不写清楚,前端就得靠猜,测试就得靠试。
AI 在这个环节的价值,是它能根据你给的业务描述,快速生成一份覆盖全面的接口定义草案。你不需要从零开始想,只需要在它给的草案上做增删改。这比对着空白文档发呆效率高得多。
2.2 让 AI 生成接口草案的提示词怎么写
提示词的质量直接决定输出质量。我试过很多种写法,最后总结出一个比较稳的模板,核心是给足上下文、明确输出格式、指定边界要求。
你是一名资深后端工程师。请为以下业务场景设计 RESTful 接口: 业务描述:用户可以通过手机号查询自己的订单列表,支持按时间范围筛选和分页。 要求: 1. 列出所有相关接口(查询列表、查询详情等) 2. 每个接口说明:请求方法、路径、请求参数(名称/类型/是否必填/说明)、响应字段 3. 明确列出所有可能的失败情况,并给出对应的 HTTP 状态码和业务错误码 4. 分页参数的默认值和最大值要写清楚 5. 用表格形式输出这个提示词的关键点在于:指定了角色(资深后端)、给了具体业务、明确了输出格式(表格)、强制要求覆盖失败情况。实测下来,这样生成的草案基本能覆盖 80% 的场景,剩下的 20% 是业务特有的逻辑,需要你自己补。
2.3 接口路径和命名的取舍逻辑
AI 生成的路径有时候会偏“教科书”,比如/api/v1/user/order/list这种层层嵌套的写法。实际项目里要不要加版本号、要不要加/api前缀,取决于你的部署方式。如果是前后端分离且网关统一处理前缀,那接口本身就不用再写/api。
命名上我倾向于用复数名词表示资源集合,比如/orders而不是/order,用 HTTP 方法表达动作而不是把动词塞进路径。GET /orders是查列表,GET /orders/{id}是查详情,POST /orders是创建。这套约定 AI 是懂的,但你得在提示词里明确要求它遵守,否则它可能给你生成/getOrderList这种风格。
提示:如果你的项目已经有既定的接口风格,一定要在提示词里把现有风格贴给 AI 看,让它照着来。否则生成的东西和现有代码风格打架,改起来比自己写还累。
3. 异常处理体系的分层设计与错误码规划
3.1 异常处理为什么要分层
异常处理最容易犯的错,是把所有异常都塞在一个 try-catch 里,然后统一返回“系统错误”。这样做的后果是,前端分不清是参数错了还是服务器挂了,用户看到的是“操作失败请重试”,而你在日志里也找不到具体原因。
合理的做法是分层。我一般分成三层:参数校验层、业务逻辑层、系统兜底层。参数校验层负责拦截格式错误、必填缺失这类问题,返回 400 类错误;业务逻辑层处理业务规则不满足的情况,比如“余额不足”“订单已取消”,返回对应的业务错误码;系统兜底层捕获所有未预期的异常,记录详细日志,对外返回统一的 500 错误,但不暴露内部细节。
这个分层结构可以让 AI 帮你生成对应的异常类和处理框架。你只需要告诉它你的技术栈(比如 Spring Boot、Express、FastAPI),它就能给出对应的代码骨架。
3.2 错误码怎么设计才不乱
错误码设计有个常见的坑:要么全用 HTTP 状态码,要么自己造一套数字码但毫无规律。我的经验是两者结合——HTTP 状态码表达“请求层面”的结果,业务错误码表达“业务层面”的具体原因。
业务错误码我习惯用分段的方式,比如:
| 错误码段 | 含义 | 示例 |
|---|---|---|
| 10000-19999 | 通用错误 | 10001 参数缺失 |
| 20000-29999 | 用户相关 | 20001 用户不存在 |
| 30000-39999 | 订单相关 | 30001 订单不存在 |
| 40000-49999 | 支付相关 | 40001 余额不足 |
这样分段的好处是,看到错误码就能大致定位问题模块。AI 生成错误码时,你可以把这个分段规则告诉它,让它按规则分配,而不是随机给数字。
3.3 用 AI 批量生成错误码和异常类
有了分段规则,就可以让 AI 批量生成了。提示词可以这样写:
基于以下错误码分段规则,为订单模块生成完整的错误码枚举和对应的异常类: 分段规则: - 30000-30999:订单查询相关 - 31000-31999:订单创建相关 - 32000-32999:订单取消相关 技术栈:Java + Spring Boot 要求: 1. 每个错误码包含 code 和 message 2. 生成对应的 BusinessException 异常类 3. 给出一个全局异常处理器示例AI 生成后,你要做的是检查错误码有没有重复、message 是否清晰、异常类是否符合你项目的包结构。这一步不能省,AI 偶尔会给出重复的 code 或者语义模糊的 message。
4. 完整实操流程:从零到可运行的接口与异常框架
4.1 环境准备与项目初始化
这个实战我用一个最小的 Web 项目来演示,技术栈选 Python + FastAPI,原因是它自带请求校验和异常处理机制,能直观看到效果。你用 Java、Node 或者 Go 都行,思路是一样的。
先建项目、装依赖:
mkdir api-demo && cd api-demo python -m venv venv source venv/bin/activate pip install fastapi uvicorn pydantic项目结构我习惯这样组织:
api-demo/ ├── main.py ├── models/ │ └── order.py ├── schemas/ │ └── order.py ├── services/ │ └── order_service.py ├── exceptions/ │ ├── business.py │ └── handlers.py └── errors/ └── codes.py这个结构把数据模型、请求响应结构、业务逻辑、异常定义、错误码分开,后面维护起来清晰。
4.2 用 AI 生成接口定义并落地
把前面 2.2 节的提示词跑一遍,拿到接口草案后,我把它转成 FastAPI 的代码。先定义请求和响应结构:
from pydantic import BaseModel, Field from typing import Optional, List from datetime import datetime class OrderQueryParams(BaseModel): user_id: int = Field(..., gt=0, description="用户ID,必填") start_time: Optional[datetime] = Field(None, description="开始时间") end_time: Optional[datetime] = Field(None, description="结束时间") page: int = Field(1, ge=1, description="页码,默认1") page_size: int = Field(20, ge=1, le=100, description="每页数量,默认20,最大100") class OrderItem(BaseModel): order_id: str amount: float status: str created_at: datetime class OrderListResponse(BaseModel): total: int page: int page_size: int items: List[OrderItem]这里有几个细节值得说。page_size我设了最大值 100,这是防止有人传个 100000 把数据库拖垮。user_id加了gt=0校验,负数直接拦掉。这些约束在提示词里如果没写,AI 可能不会主动加,所以我在提示词里专门强调了“分页参数的默认值和最大值要写清楚”。
4.3 异常处理框架的代码实现
先定义错误码:
class ErrorCode: PARAM_MISSING = (10001, "参数缺失") PARAM_INVALID = (10002, "参数格式错误") USER_NOT_FOUND = (20001, "用户不存在") ORDER_NOT_FOUND = (30001, "订单不存在") ORDER_STATUS_INVALID = (30002, "订单状态不允许此操作") SYSTEM_ERROR = (50000, "系统内部错误")再定义业务异常:
class BusinessException(Exception): def __init__(self, error_code: tuple, detail: str = None): self.code = error_code[0] self.message = error_code[1] self.detail = detail super().__init__(self.message)然后是全局异常处理器:
from fastapi import Request from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError @app.exception_handler(BusinessException) async def business_exception_handler(request: Request, exc: BusinessException): return JSONResponse( status_code=200, content={ "code": exc.code, "message": exc.message, "detail": exc.detail } ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=400, content={ "code": ErrorCode.PARAM_INVALID[0], "message": ErrorCode.PARAM_INVALID[1], "detail": str(exc.errors()) } ) @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.exception("未预期异常") return JSONResponse( status_code=500, content={ "code": ErrorCode.SYSTEM_ERROR[0], "message": ErrorCode.SYSTEM_ERROR[1] } )这里有个设计决策需要解释:业务异常我返回的 HTTP 状态码是 200,而不是 400 或 500。原因是业务错误(比如订单不存在)在 HTTP 语义上请求是成功的,只是业务结果不满足。前端拿到 200 后看code字段判断具体结果。这个做法在业界有争议,有的团队坚持用 HTTP 状态码表达一切。我的建议是团队内部统一就行,关键是别混着来——一会儿用状态码一会儿用业务码,前端会疯。
4.4 业务逻辑中如何抛出和处理异常
在 service 层,业务规则不满足时直接抛 BusinessException:
def get_order_detail(order_id: str, user_id: int): order = db.query_order(order_id) if not order: raise BusinessException(ErrorCode.ORDER_NOT_FOUND, f"订单 {order_id} 不存在") if order.user_id != user_id: raise BusinessException(ErrorCode.ORDER_NOT_FOUND, "无权访问该订单") return order注意这里“无权访问”我也返回了 ORDER_NOT_FOUND,而不是单独搞一个“无权限”错误码。这是安全考虑——如果返回“无权限”,攻击者就能通过错误码判断哪些订单 ID 是存在的。这种细节 AI 一般不会主动想到,需要你在提示词里点明“注意不要通过错误信息泄露资源是否存在”。
5. 常见问题排查与避坑经验实录
5.1 AI 生成内容不准确怎么办
最常见的问题是 AI 生成的错误码重复,或者 message 语义模糊。我的处理方式是分两步:先让它生成,然后单独发一轮对话让它自查——“请检查以上错误码是否有重复,message 是否清晰无歧义,列出所有问题”。这一轮自查能揪出大部分低级错误。
另一个问题是 AI 会“过度设计”,比如给你生成一大堆你用不上的错误码。这时候要果断删,错误码不是越多越好,只保留实际会触发的场景。我见过一个项目定义了 200 多个错误码,结果实际用到的不到 30 个,剩下的全是维护负担。
5.2 异常处理里最容易踩的坑
第一个坑是吞异常。except Exception: pass这种写法是灾难,出了问题什么都查不到。至少要记录日志,而且日志里要带上请求上下文(比如 trace_id、用户 ID、请求参数)。
第二个坑是异常信息泄露。直接把数据库报错信息返回给前端,可能暴露表结构甚至 SQL 语句。全局异常处理器里对外返回的 message 必须是预设的通用文案,详细信息只写日志。
第三个坑是异常层级混乱。自定义异常继承关系没理清,导致 catch 的时候抓不到。建议所有业务异常继承同一个基类,全局处理器只抓这个基类。
5.3 接口联调阶段的典型问题速查
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 前端收到 422 | 请求体格式不符合 schema | 检查字段类型和必填项 |
| 业务错误码返回但 HTTP 是 200 | 业务异常处理器生效 | 确认前端是否按 code 判断 |
| 500 错误无详细信息 | 全局兜底捕获 | 查服务端日志找堆栈 |
| 分页参数传大值导致超时 | 缺少最大值限制 | 在 schema 里加 le 约束 |
| 时间范围筛选无效 | 时区或格式问题 | 统一用 ISO 8601 格式 |
5.4 让 AI 帮你做接口自测用例
接口和异常框架写完后,可以让 AI 生成测试用例。提示词:
为以下接口生成测试用例,覆盖正常场景和所有异常场景: [贴上接口定义] 要求: 1. 每个用例说明:输入、预期 HTTP 状态码、预期业务错误码 2. 覆盖边界值:分页最大页、时间范围跨年、userId 为 0 等 3. 用表格输出生成的用例可以直接转成 pytest 或 JUnit 测试代码。我实测下来,AI 生成的边界用例比我自己想的还全,尤其是那些“传空字符串”“传超大数字”之类的场景,人容易漏。
6. 把这套方法沉淀成团队规范
一个人用这套方法提效是一回事,让整个团队都用起来是另一回事。我的做法是把提示词模板、错误码分段规则、异常处理骨架整理成一个内部文档,新项目直接复制。AI 生成的接口草案必须经过 review 才能进代码库,review 的重点是错误码有没有重复、边界场景有没有覆盖、异常信息有没有泄露风险。
另外,接口文档和代码要同步维护。我见过太多项目文档和实际返回对不上,前端按文档写结果跑不通。用 AI 生成文档的好处是,你可以把代码贴给它,让它反向生成文档,这样至少能保证文档和代码是一致的。每次接口变更后跑一遍,比人工维护靠谱。
这套流程跑顺之后,一个新接口从设计到可联调,时间能压缩一半以上,而且质量比自己闷头写更稳定。关键是把 AI 当成一个不知疲倦的初级工程师,它负责出草案和查漏,你负责把关和决策。这个分工用好了,接口设计和异常处理这两块最烦人的活,就不再是负担了。