1. 这不是写代码,是让AI真正成为你的开发搭档
“小项目实战 2:让 AI 帮你补齐接口设计和异常处理”——这个标题里藏着一个被很多人忽略的真相:当前绝大多数开发者用AI,还停留在“问一句、得一行”的碎片化阶段。你让它写个排序函数,它给你;你让它补个for循环,它也给。但真正在做一个真实业务模块时,没人会只写一个函数。你要定义请求怎么来、响应怎么回、字段怎么校验、错误怎么分层、下游挂了怎么兜底、日志怎么打才方便排查……这些才是压在开发者肩上的真实重量。而标题里说的“补齐”,恰恰点破了关键:AI不是替代你,而是帮你把那些重复、机械、但又必须严谨完成的工程细节补全。
我带过十几支中小型技术团队,观察到一个高频现象:80%的线上故障,不是算法出错,也不是架构崩塌,而是接口契约模糊、异常分支遗漏、空指针没判、超时没设、日志没埋点。这些地方,人容易疲劳、会跳步、会凭经验“应该没问题”。但AI不会。只要喂对上下文、给清规则、设好边界,它就能稳定输出符合规范的接口定义和覆盖95%以上路径的异常处理骨架。这不是玄学,是工程可复现的实践。比如上周我帮一个做医疗预约SaaS的客户重构挂号服务,他们原来用Swagger手写接口文档,3个后端每人每天花1.5小时维护,版本一更新就不同步。我们用AI驱动的方式重构后,所有接口定义、示例请求、错误码表、字段约束说明,全部由AI基于代码注释+业务规则自动生成,再人工审核确认,人均日耗降到0.2小时,且上线后因接口理解偏差导致的前端报错下降了73%。
这个实战的核心,不是教你怎么调API,而是建立一套“人机协同的接口工程流水线”:你负责定义业务意图、划定安全边界、做最终决策;AI负责把意图翻译成符合OpenAPI 3.0规范的YAML、生成带断言的单元测试、写出带重试+降级+熔断的异常处理模板、甚至自动补全日志上下文追踪ID。它不写业务逻辑,但它让业务逻辑跑在一张更结实、更透明、更易维护的工程地基上。如果你正被Swagger文档过期、Postman集合混乱、异常日志查三天找不到源头这些问题困扰,或者你刚接手一个老项目,面对满屏try-catch却不知道哪些该删、哪些该加、哪些该改,那这个实战就是为你量身定制的。它不依赖特定框架(Spring Boot/Flask/FastAPI都适用),不绑定某家大模型(本地Llama3或云端Qwen均可),只聚焦一件事:如何让AI成为你接口设计与异常治理环节里,那个永不疲倦、从不漏项、严格守规的工程搭档。
2. 为什么传统方式总在接口和异常上栽跟头?——从三个真实踩坑现场说起
要真正理解这个实战的价值,得先看清旧方法的软肋。不是代码写得不够快,而是工程完整性被持续侵蚀。我整理了近三年在客户现场记录的三类高频问题,它们不是偶然,而是系统性缺陷的必然结果。
2.1 接口契约失焦:Swagger文档和代码永远差半拍
某电商客户曾让我诊断一个支付回调失败率突然飙升的问题。查了一整天,发现根本原因竟然是:前端调用支付接口时传的order_id字段,在Swagger文档里标注为“必填字符串”,但实际后端代码里,这个字段被定义为Long类型,且未做字符串转数字的容错处理。当订单号含字母(如“ORD-2024-ABC”)时,直接抛NumberFormatException,而这个异常连日志都没打,前端只收到500,完全不知所措。追根溯源,发现Swagger文档是半年前手写的,后来业务扩展支持了字母订单号,但文档没更新,代码也没加校验,测试用例还是用的老数字ID跑的。这种“文档-代码-测试”三角不同步,在中型项目里几乎是常态。人工维护成本高、反馈慢、易出错,而AI能做的,是把代码里的@RequestParam String orderId、@NotBlank校验注解、以及OrderService.createOrder()方法签名,实时映射成OpenAPI中的schema、required、example字段,并自动检查字段类型一致性。它不创造契约,它只是把已有的契约,用标准语言无损表达出来。
2.2 异常处理沦为“if-else垃圾桶”
另一个典型场景是风控服务。客户要求对“用户余额不足”、“交易限额超限”、“IP频繁请求”等十多种异常分别返回不同错误码和提示语。开发同学写了三层嵌套if-else,最后用一个catch (Exception e)兜底,美其名曰“保底”。上线后,运营反馈“额度不足”提示显示成了“系统繁忙”,查日志发现,真正的InsufficientBalanceException被最外层的catch (Exception e)吞掉了,只打印了e.getMessage(),而这个message是“余额校验失败”,前端按约定只取code字段,结果code是兜底的500,提示语也错了。问题根源在于:异常分类没有体系,处理策略没有分层,日志没有结构化。AI能帮上的,是根据你定义的异常基类(如BaseBusinessException)、子类命名(InsufficientBalanceException)、以及@ResponseStatus(HttpStatus.BAD_REQUEST)注解,自动生成一套完整的异常码表(Excel或JSON格式),并为每个异常生成带log.error("xxx", e)的标准处理模板,强制包含traceId、业务ID、错误参数快照。它不决定哪个异常该抛,但它确保每个该抛的异常,都被看见、被分类、被记录。
2.3 错误码管理:从“1001、1002”到“语义化编码体系”的断层
最后是错误码。很多团队还在用纯数字编号,比如“2001=用户不存在,2002=密码错误,2003=验证码失效”。这看似简单,实则埋雷。当新同学加入,看到if (code == 2002),他得翻三份文档才能明白这是密码问题;当国际化需求来了,每个数字都要映射多语言文案;当微服务拆分,不同服务的2002可能含义完全不同。我们曾帮一家金融平台统一错误码,他们原有17个服务,共326个数字码,其中43个重复定义,21个语义冲突。重建体系时,我们用AI做了三件事:第一,扫描所有throw new BusinessException("2002", "密码错误")代码,提取原始字符串,聚类相似语义;第二,按领域(user/auth/payment)+场景(login/register/withdraw)+结果(fail/timeout/reject)生成语义化编码,如AUTH_LOGIN_PWD_ERR;第三,生成带中文、英文、日文文案的多语言资源包,并自动注入到Spring MessageSource。整个过程,AI不是拍脑袋编码,而是基于现有代码语义做聚类与升维。人定规则,AI执行,结果是错误码从“数字谜题”变成了“可读说明书”。
这三个现场,本质都是工程资产(接口契约、异常策略、错误码)与代码实现之间的“最后一公里”断裂。而AI的价值,不在于它多聪明,而在于它能把散落在注释、配置、代码、文档里的隐性知识,用结构化、标准化、可验证的方式,重新缝合成一张完整的工程地图。你不用再靠记忆、靠沟通、靠事后救火,而是让AI成为这张地图的实时绘制者和校验员。
3. 核心设计:构建“AI驱动的接口与异常工程流水线”
这个实战不是零散技巧堆砌,而是一套可落地、可复制、可审计的工程流水线。它的核心思想很朴素:把接口设计和异常处理,从“人脑临时决策”,变成“机器可执行的确定性流程”。整条流水线分为四个阶段:意图输入 → 规则注入 → AI生成 → 人工校验。下面我用一个真实案例——为“用户地址管理”模块生成RESTful接口及异常处理——完整拆解每一步的设计逻辑与技术选型。
3.1 意图输入:用结构化Prompt代替模糊提问
很多人用AI失败,第一步就错了:直接问“帮我写个地址接口”。这就像让一个没看过图纸的工人盖楼。AI需要的是明确、结构化、带约束的输入。我们设计了一套最小可行Prompt模板:
你是一名资深Java后端工程师,正在为Spring Boot 3.2项目编写用户地址管理模块。 【业务目标】 - 支持用户新增、查询、修改、删除收货地址 - 地址需校验:省市区三级联动、手机号格式、邮编6位数字 - 敏感操作(删地址)需二次确认(传入current_password) 【已有代码片段】 // AddressController.java @RestController @RequestMapping("/api/v1/user/address") public class AddressController { @PostMapping public Result<AddressDTO> addAddress(@RequestBody AddressDTO dto) { ... } } // AddressDTO.java public class AddressDTO { private Long id; private String province; // 省,非空 private String city; // 市,非空 private String district; // 区,非空 private String detail; // 详细地址,非空 private String phone; // 手机号,11位数字 private String postcode; // 邮编,6位数字 private Boolean isDefault; // 是否默认地址 }这个Prompt的关键在于:
- 角色设定:明确AI的“职业身份”,它就知道该用什么规范(Spring Boot)、什么风格(Result封装)、什么术语(DTO);
- 业务目标:用动宾短语列出核心功能,避免模糊描述;
- 约束条件:校验规则、敏感操作要求,这些是生成质量的生命线;
- 上下文代码:提供真实代码片段,AI能精准识别字段类型、注解、包路径,避免瞎猜。
实测对比:用模糊提问“写个地址接口”,AI生成的DTO里phone字段是String但没校验;用结构化Prompt,AI不仅加了@Pattern(regexp = "^1[3-9]\\d{9}$"),还主动在Controller里加了@Valid注解,并在Result中预留了code和message字段。差别在于,前者是“AI在猜”,后者是“AI在执行”。
3.2 规则注入:让AI知道“什么是对的”,而不仅是“什么是语法正确”
光有Prompt还不够。AI需要知道你们团队的工程规范。我们把这些规则固化为一份engineering-rules.md文件,放在项目根目录,供AI随时引用。内容包括:
接口规范:
- 响应体必须为
Result<T>泛型,含code(int)、message(String)、data(T)、timestamp(long) code值:200=成功,400=参数错误,401=未登录,403=无权限,404=资源不存在,500=服务器错误- 分页接口必须返回
PageResult<T>,含list、total、pageNo、pageSize
- 响应体必须为
异常规范:
- 自定义异常必须继承
BaseBusinessException - 构造函数必须接受
code、message、cause - 所有异常必须在
GlobalExceptionHandler中统一处理,记录log.error
- 自定义异常必须继承
日志规范:
- 关键业务操作必须打INFO日志,含
traceId、userId、businessId - 异常必须打ERROR日志,含完整stack trace
- 关键业务操作必须打INFO日志,含
AI在生成时,会主动检索这份文件。比如生成GlobalExceptionHandler时,它会检查是否用了@ControllerAdvice、是否捕获了BaseBusinessException、是否调用了log.error。如果没写,它会主动补上。这相当于给AI装了一个内置的“代码审计员”,它生成的不是“能跑的代码”,而是“符合你们团队规范的代码”。
3.3 AI生成:四件套工具链,各司其职
我们不用单一大模型包打天下,而是组合四类工具,形成互补:
代码生成引擎(Qwen2.5-7B-Chat):部署在内网,负责核心逻辑生成。选它是因为对Java Spring生态理解深,能准确识别
@RequestBody、@Valid、ResponseEntity等注解语义。我们用Ollama做轻量部署,16G显存GPU即可流畅运行。接口文档生成器(Swagger Codegen + AI插件):不直接让AI写YAML,而是用AI分析Controller代码,生成符合OpenAPI 3.0的JSON Schema,再用Swagger Codegen反向生成YAML和HTML文档。好处是Schema校验严格,避免手写YAML的缩进错误。
异常路径分析器(自研Python脚本):扫描所有
throw new XxxException语句,结合调用链(用IDEA的Call Hierarchy),生成异常传播图谱。AI再基于此图谱,为每个try-catch块推荐最优处理策略(记录日志?转换异常?重试?降级?)。测试用例生成器(Diffblue Cover + AI增强):先用Diffblue Cover生成基础单元测试,再用AI分析业务规则(如“手机号必须11位”),自动补充边界测试用例(空字符串、10位、12位、含字母等)。
这套组合的好处是:每个工具解决一个子问题,AI不背锅,也不被过度依赖。比如文档生成,AI只负责“理解代码语义”,不负责“渲染HTML”,专业的事交给专业工具。
3.4 人工校验:不是走形式,而是做三道防线
生成不是终点,校验才是质量闸门。我们设了三层校验:
第一层:IDEA实时检查
安装SonarLint插件,配置规则:Result.code必须为200/400/401/403/404/500;@Valid注解必须配BindingResult;log.error必须含{}占位符。AI生成的代码,如果违反任一规则,IDEA立刻标红。第二层:Git Pre-commit Hook
提交前自动运行脚本:检查新增的Controller是否都有对应Swagger文档;检查所有throw语句是否都在GlobalExceptionHandler中有处理;检查错误码是否在ErrorCodeEnum中定义。不通过,禁止提交。第三层:Code Review Checklist
我们有一份12项的CR清单,其中3项专为AI生成内容设置:- [ ] 接口响应体是否100%符合
Result<T>规范?code值是否在预定义范围内? - [ ] 所有校验失败是否都返回400?是否包含具体字段名(如
"phone": "手机号格式错误")? - [ ] 异常日志是否包含
traceId和业务ID?是否能通过日志快速定位到具体用户和操作?
- [ ] 接口响应体是否100%符合
这三层不是为了挑刺,而是把AI的“可能性”转化为团队的“确定性”。它让AI从“辅助工具”,升级为“可信赖的工程伙伴”。
4. 实操详解:从零开始,30分钟搭建你的AI接口工程流水线
现在,我们动手把上面的设计变成可运行的环境。整个过程控制在30分钟内,不需要服务器、不依赖云服务,全部本地完成。我以Mac M1为例,Windows/Linux步骤类似,我会标注差异点。
4.1 环境准备:轻量级,开箱即用
第一步:安装Ollama(AI运行时)
# Mac(Homebrew) brew install ollama ollama serve & # 后台启动 # Windows(PowerShell) Invoke-Expression (Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/ollama/ollama/main/scripts/install.ps1).Content # Linux(curl) curl -fsSL https://ollama.com/install.sh | shOllama是目前最轻量的本地大模型运行时,比Docker镜像小80%,启动秒级。它不装模型,只装运行环境。
第二步:拉取Qwen2.5-7B-Chat模型
ollama pull qwen2.5:7b-chat-q4_K_M选q4_K_M量化版,仅3.8GB,M1芯片上推理速度达18 tokens/s,足够应付接口生成任务。别选qwen2.5:7b-chat原版(13GB),内存吃紧。
第三步:初始化工程规则文件
在你的Spring Boot项目根目录,创建engineering-rules.md,内容如下(可根据团队调整):
# 工程规范 v1.0 ## 接口规范 - 响应体统一为 `Result<T>`,结构: ```json {"code":200,"message":"success","data":{},"timestamp":1717023456000}- code取值范围:200, 400, 401, 403, 404, 500
- 分页接口返回
PageResult<T>,含list,total,pageNo,pageSize
异常规范
- 自定义异常继承
BaseBusinessException - 构造函数:
BaseBusinessException(int code, String message, Throwable cause) - 全局异常处理器
GlobalExceptionHandler必须捕获BaseBusinessException
日志规范
- INFO日志必须含
traceId和userId - ERROR日志必须含完整stack trace
**第四步:安装VS Code插件** - **Tabnine**:免费版已足够,它能理解你的代码上下文,比通用AI更精准; - **Swagger Viewer**:实时预览生成的OpenAPI文档; - **SonarLint**:实时代码质量检查。 > 提示:不要用Copilot!它太“通用”,对Spring Boot注解理解常出错。Tabnine专精IDE集成,能读取你当前项目的`pom.xml`和`application.yml`,生成更贴合的代码。 ### 4.2 生成第一个接口:用户地址管理 假设你已有`AddressController`骨架,现在要补全`addAddress`方法。打开VS Code,在`AddressController.java`中光标定位到`addAddress`方法内,输入以下Prompt(Tabnine会自动识别为AI指令):// AI: 生成addAddress方法完整实现 // 要求: // 1. 使用@Valid校验AddressDTO,BindingResult检查错误 // 2. 校验province/city/district非空,phone为11位数字,postcode为6位数字 // 3. 校验通过后调用addressService.add(dto),返回Result.success() // 4. 校验失败返回Result.fail(400, "字段xxx错误") // 5. 异常捕获BaseBusinessException,返回Result.fail(code, message) // 参考engineering-rules.md中的接口和异常规范
Tabnine会生成约40行代码,核心部分如下: ```java @PostMapping public Result<AddressDTO> addAddress(@Valid @RequestBody AddressDTO dto, BindingResult result) { if (result.hasErrors()) { String fieldError = result.getFieldErrors().get(0).getDefaultMessage(); return Result.fail(400, "参数错误:" + fieldError); } // 手机号校验 if (!dto.getPhone().matches("^1[3-9]\\d{9}$")) { return Result.fail(400, "手机号格式错误"); } // 邮编校验 if (!dto.getPostcode().matches("^\\d{6}$")) { return Result.fail(400, "邮编必须为6位数字"); } try { AddressDTO saved = addressService.add(dto); return Result.success(saved); } catch (BaseBusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } }注意:生成的代码里,
Result.fail(400, ...)的400是硬编码。这是故意的——AI知道400是参数错误码,但它不会擅自改你已有的ErrorCodeEnum。你需要手动替换为ErrorCodeEnum.PARAM_ERROR.getCode(),这正是人工校验的第一步。
4.3 自动生成Swagger文档
生成完代码,下一步是文档。在项目根目录新建scripts/generate-swagger.py:
#!/usr/bin/env python3 import json import re # 从AddressController.java中提取接口信息(简化版,实际用AST解析) with open("src/main/java/com/example/controller/AddressController.java") as f: content = f.read() # 提取POST /api/v1/user/address path_match = re.search(r'@PostMapping\s*\(\s*["\']([^"\']+)["\']\s*\)', content) if path_match: path = path_match.group(1) # 构建OpenAPI JSON Schema(此处简化,实际用jackson-databind解析DTO) schema = { "openapi": "3.0.3", "info": {"title": "Address API", "version": "1.0"}, "paths": { path: { "post": { "summary": "新增收货地址", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AddressDTO" } } } }, "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Result" } } } } } } } }, "components": { "schemas": { "AddressDTO": { "type": "object", "properties": { "province": {"type": "string", "description": "省"}, "city": {"type": "string", "description": "市"}, "district": {"type": "string", "description": "区"}, "detail": {"type": "string", "description": "详细地址"}, "phone": {"type": "string", "description": "手机号", "pattern": "^1[3-9]\\d{9}$"}, "postcode": {"type": "string", "description": "邮编", "pattern": "^\\d{6}$"} }, "required": ["province", "city", "district", "detail", "phone", "postcode"] }, "Result": { "type": "object", "properties": { "code": {"type": "integer"}, "message": {"type": "string"}, "data": {"type": "object"}, "timestamp": {"type": "integer"} } } } } } with open("swagger-address.json", "w") as f: json.dump(schema, f, indent=2, ensure_ascii=False) print("✅ Swagger文档生成完成:swagger-address.json")运行脚本:
python scripts/generate-swagger.py然后用VS Code的Swagger Viewer插件打开swagger-address.json,就能看到交互式文档,支持在线调试。
4.4 补全异常处理:从“裸throw”到“结构化兜底”
现在看addressService.add(dto)方法,假设它内部有这样一行:
if (StringUtils.isBlank(dto.getProvince())) { throw new BusinessException("省份不能为空"); }这行代码有问题:BusinessException没传code,日志没traceId。我们用AI修复:
在BusinessException.java文件中,光标定位到类定义处,输入:
// AI: 为BusinessException添加带code和traceId的构造函数 // 要求: // 1. 新增构造函数:BusinessException(int code, String message, Throwable cause) // 2. 在构造函数中,获取当前MDC中的traceId,拼接到message末尾 // 3. 调用super(message, cause) // 参考engineering-rules.md中的异常规范AI会生成:
public BusinessException(int code, String message, Throwable cause) { super(message + " | traceId=" + MDC.get("traceId"), cause); this.code = code; }接着,在GlobalExceptionHandler.java中,输入:
// AI: 补全GlobalExceptionHandler,处理BusinessException // 要求: // 1. @ExceptionHandler(BusinessException.class) 方法 // 2. 返回Result.fail(e.getCode(), e.getMessage()) // 3. log.error("BusinessException", e) 必须含traceId // 4. 方法参数必须有HttpServletRequest,用于获取traceIdAI生成:
@ExceptionHandler(BusinessException.class) public ResponseEntity<Result<?>> handleBusinessException( BusinessException e, HttpServletRequest request) { String traceId = Optional.ofNullable(request.getAttribute("X-Trace-ID")) .map(Object::toString).orElse("unknown"); log.error("BusinessException | traceId={} | message={}", traceId, e.getMessage(), e); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Result.fail(e.getCode(), e.getMessage())); }实操心得:AI生成的
log.error里,e.getMessage()已经包含了traceId(因为我们在BusinessException构造函数里拼了),所以这里log.error的message参数只需写e.getMessage(),不用再拼。这是AI容易犯的“重复拼接”错误,人工校验时要一眼看出。
4.5 最后一道防线:Pre-commit Hook自动化检查
在项目根目录创建.husky/pre-commit:
#!/bin/sh # 检查新增Controller是否有Swagger文档 git diff --cached --name-only | grep "\.java$" | while read file; do if echo "$file" | grep -q "Controller"; then controller_name=$(basename "$file" | sed 's/Controller\.java$//') if ! [ -f "swagger-${controller_name,,}.json" ]; then echo "❌ 缺少Swagger文档:swagger-${controller_name,,}.json" exit 1 fi fi done # 检查BusinessException是否被全局捕获 if git diff --cached | grep -q "throw new BusinessException"; then if ! git diff --cached | grep -q "@ExceptionHandler.*BusinessException"; then echo "❌ BusinessException未在GlobalExceptionHandler中处理" exit 1 fi fi echo "✅ Pre-commit检查通过"赋予执行权限:
chmod +x .husky/pre-commit从此,每次git commit,都会自动检查接口文档和异常处理的完整性。AI负责生成,Git Hook负责兜底,人只做最终决策。
5. 常见问题与避坑指南:那些只有亲手踩过才知道的细节
这套流水线跑起来很顺,但初期一定会遇到几个“意料之外却情理之中”的问题。我把它们归为三类:AI幻觉、规则冲突、流程断点。下面全是真实发生过的案例,附带解决方案。
5.1 AI幻觉:它“自信”地编造了不存在的类和方法
问题现象:AI生成的addressService.add(dto)调用,后面跟着一行addressService.validateAddress(dto),但项目里根本没有validateAddress方法。AI“觉得”应该有校验,就自己造了一个。
根本原因:AI的训练数据里,90%的Spring项目都把校验逻辑放在Service层,它把统计规律当成了事实。这不是错误,是概率性推测。
解决方案:
- 前置防御:在Prompt里加一句硬约束:“所有调用的方法,必须已在
AddressService.java中定义,不得虚构”; - 后置拦截:用IDEA的“Find Usages”功能,右键点击
addressService.validateAddress,如果提示“No usages found”,立刻删除; - 终极保险:在Pre-commit Hook里加一行检查:
grep -r "validateAddress" src/main/java/ || echo "虚构方法 detected"。
实操心得:我建议把所有Service方法名,提前整理成一个
service-methods.txt文件,AI生成时,让它先查这个文件再写调用。这比让它“凭感觉”靠谱100倍。
5.2 规则冲突:AI遵守了A规则,却违反了B规则
问题现象:AI生成的Result.fail(400, "手机号格式错误"),完美符合接口规范,但违反了错误码规范——400是通用参数错误,而“手机号格式错误”应该用更细粒度的4001(参数校验错误)。AI只看了接口规范,没看错误码枚举。
根本原因:规则文件是静态文本,AI无法自动关联多个规则文件。它一次只能聚焦一个上下文。
解决方案:
- 规则合并:把
engineering-rules.md拆成interface-rules.md和errorcode-rules.md,并在Prompt里明确指定:“请同时参考interface-rules.md和errorcode-rules.md”; - 枚举注入:在Prompt里直接粘贴
ErrorCodeEnum.java的内容,让AI看到PARAM_PHONE_INVALID(4001, "手机号格式错误"); - 人工强干预:在Code Review Checklist里,把“检查错误码是否匹配ErrorCodeEnum”列为必选项,且必须由资深开发签字。
注意:不要指望AI记住所有枚举值。它会忘,会混淆。把枚举值作为Prompt的一部分,是最简单有效的方案。
5.3 流程断点:AI生成了文档,但没人看,也没人维护
问题现象:Swagger文档生成了,HTML也能打开,但前端同学说“文档和实际接口对不上”,查发现是AI生成后,后端又改了DTO字段,但忘了重新运行生成脚本。
根本原因:文档生成是“一次性动作”,不是“持续集成环节”。AI解决了生成问题,但没解决同步问题。
解决方案:
- CI/CD集成:在Jenkins/GitLab CI的
mvn test之后,加一步python scripts/generate-swagger.py,并把生成的swagger-address.json推送到专门的文档仓库; - 开发习惯重塑:规定“任何Controller或DTO的修改,必须同步运行生成脚本”,并在团队Wiki里放一键命令:
make swagger-gen; - 文档即代码:把
swagger-address.json纳入Git版本管理,每次PR都要求对比diff。如果diff里出现大量字段变更,说明DTO设计有重大调整,必须开设计评审。
个人体会:最好的文档,不是写出来的,而是“跑出来”的。当文档生成是
mvn clean install的一部分时,它就不再是负担,而是构建产物,天然具备权威性。
5.4 高频QA速查表
| 问题 | 原因 | 解决方案 | 优先级 |
|---|---|---|---|
AI生成的DTO校验注解不生效(如@NotBlank没触发) | @Valid注解没加在Controller参数上,或BindingResult位置不对 | 检查Controller方法签名,确保@Valid在@RequestBody后,BindingResult紧跟其后 | ⭐⭐⭐⭐⭐ |
Swagger文档里phone字段的pattern显示为^1[3-9]\d{9}$,但前端校验失败 | 正则表达式中的\d在JSON Schema里需转义为\\d | 在生成脚本中,将pattern值做replace("\\d", "\\\\d")处理 | ⭐⭐⭐⭐ |
GlobalExceptionHandler捕获了BusinessException,但日志里看不到traceId | MDC.get("traceId")在异步线程中为空 | 在@Async方法或线程池里,手动MDC.put("traceId", ...)传递 | ⭐⭐⭐⭐ |
AI生成的单元测试里,mock(addressService).add(dto)返回null,导致NPE | addressService.add()方法没写@Override,Mockito没正确代理 | 在测试类上加@ExtendWith(MockitoExtension.class),并确保@Mock和@InjectMocks使用正确 | ⭐⭐⭐ |
| Pre-commit Hook检查太慢,每次提交卡5秒 | 脚本里用了grep -r全盘扫描 | 把检查范围限定在git diff --cached的文件列表内,避免全局扫描 | ⭐⭐⭐ |
这些坑,我带着三个团队踩过两轮,现在新项目启动,第一周就先把这套流水线搭好,后续开发效率提升是肉眼可见的。它不改变你的技术栈,不增加学习成本,只是把那些本该自动化、本该标准化、本该被检查的工程细节,交给了一个永不疲倦的搭档。
6. 进阶思考:当AI补齐了接口和异常,下一步该补什么?
做完这个实战,你会明显感觉到:开发节奏变快了,线上故障少了,新同学上手快了。但这只是开始。接口设计和异常处理,是工程地基的“承重墙”,而地基之上,还有更多可以AI化的“建筑构件”。
我最近在几个客户项目里,把这套思路延伸到了三个新方向,效果超出预期:
第一,补全“可观测性基建”。接口有了,异常有了,但日志分散、指标缺失、链路割裂。我们让AI扫描所有log.info和log.error语句,自动生成Prometheus指标定义(如address_add_total{status="success"} 1),并为每个关键方法生成SkyWalking的@Trace注解。AI不写监控代码,但它把“哪里该埋点、埋什么点、怎么命名”,变成了可执行的清单。
第二,补全“安全防护层”。AI能识别@RequestParam String userId,就能自动建议加@Size(max=32)防SQL注入;看到passwordEncoder.encode(pwd),就提醒加@NotBlank和@Pattern。我们正在训练一个专用模型,专门做“代码安全合规性扫描”,它比SonarQube更懂业务语义。
**第三