你是不是也有这样的痛苦?每次前端同事拿着过期的文档来问你:“这个字段怎么变了?”、“这个接口还返回200吗?”,而你只能尴尬地打开IDE,翻代码,手动复制粘贴。等到上线前一周,测试和开发一起对着几千行的Markdown文件头秃,因为没人知道文档到底哪句话是真的。
其实,文档永远不应该由人来维护,文档应该从代码里长出来。今天咱们不聊虚的,直接把这套“Swagger文档自动同步到Jenkins”的流水线给你搭起来,让接口文档变成代码的一部分,自动更新,永不失联。
第一步:为什么你的文档总是“过期”?
在动手之前,得先搞清楚坑在哪。传统的Swagger文档维护通常有两条死路:
- 手写文档:开发写完代码,再手动去Postman或Word里改文档。结果就是代码改了,文档没改;或者文档改了,代码没改。这就是所谓的“文档与实现脱节”。
- 手动生成并上传:开发本地用
swagger-maven-plugin或springfox-swagger生成HTML,然后手动拖进Confluence或Wiki。只要有一次忘记上传,或者生成失败没检查,文档就死了。
我们要做的,是把代码生成文档这一步,嵌进Jenkins的构建流程里。只要代码CI通过了,文档就是最新的。如果代码合并失败,文档也不会更新到生产环境。这才是真正的“单一数据源”。
第二步:技术选型——别踩那些旧时代的雷
现在业界主要有两套玩法,咱们得选对。
- Swagger 2.0 (Springfox):老项目还在用,但Springfox在Spring Boot 3.x上已经不再支持,且性能开销大,配置繁琐。如果你新项目还在用Springfox,建议趁早换掉。
- OpenAPI 3.0 (springdoc-openapi):这是现在的标准答案。它支持Spring Boot 3.x,性能更好,且生成的JSON/YAML结构更标准,兼容Swagger UI 5.x。
我们的方案:基于Spring Boot 3 + springdoc-openapi,在Maven构建阶段自动将生成的JSON文件上传到OSS(如阿里云OSS、AWS S3或腾讯云COS),并触发一个Webhook通知文档系统(如Swagger Editor或内部Wiki)。
第三步:代码层面的准备
假设你有一个标准的Spring Boot项目。首先,你要确保pom.xml里有了正确的依赖。注意,这里我们用springdoc-openapi-maven-plugin,因为它能在构建时直接生成静态文件,而不是靠运行App才能看到。
<dependencies>
<!-- Springdoc OpenAPI for Spring Boot 3 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 这个插件非常关键,它在mvn package时会生成openapi.json -->
<plugin>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-maven-plugin</artifactId>
<version>1.4</version>
<configuration>
<apiDocUrl>http://localhost:8080/v3/api-docs</apiDocUrl>
<outputFileName>openapi.json</outputFileName>
<!-- 输出到target目录 -->
<outputDir>target/classes/static</outputDir>
</configuration>
</plugin>
</plugins>
</build>
但是,光生成还不够。我们需要在代码里暴露一个接口,或者确保生成的JSON能被外部访问。更优雅的做法是,我们在application.yml里配置好Swagger的元数据,这样生成的JSON里会有正确的标题、版本和描述,而不需要我们在CI里硬编码。
# application.yml
springdoc:
api-docs:
path: /v3/api-docs
enabled: true
swagger-ui:
path: /swagger-ui.html
enabled: true
info:
title: 核心业务服务 API 文档
description: 本文档由Jenkins流水线自动生成,请勿手动修改
version: 1.0.0
contact:
name: 后端开发组
email: dev@yourcompany.com
到这里,你在本地执行mvn package,就能在target/classes/static目录下看到一个实打实的openapi.json。这个文件,就是我们的“源数据”。
第四步:Jenkins Pipeline的设计思路
接下来是重头戏。我们的Jenkins流水线需要做三件事:
- 构建代码,并确保测试通过。
- 生成API文档(如果Maven插件没在构建阶段完成,这里再补一刀)。
- 上传文档到对象存储,并返回一个可访问的URL。
很多团队会在这里犯一个错误:把文档存到Jenkins的Workspace里。千万别这么做!Jenkins的Workspace是临时的,每次构建都可能被清理,而且Jenkins服务器磁盘有限,不适合存静态资源。
我们要把文档上传到OSS(对象存储),然后让Nginx或者CDN去托管它。这样,无论Jenkins怎么重启,文档URL永远不变。
4.1 准备OSS工具
我们需要在Jenkins上安装aws-cli或者aliyun-cli,取决于你用的哪家云。假设我们用阿里云OSS。先在Jenkins服务器上安装工具:
# 在Jenkins节点上执行
yum install aliyun-cli -y
# 或者用snap/apt
snap install aliyun-cli
配置好ak/sk,确保Jenkins用户有权限往Bucket里写文件。
4.2 编写Jenkinsfile
这是整个方案的核心。我们用Groovy写一个声明式Pipeline。
pipeline {
agent any
environment {
// 从Jenkins Credential里读取OSS密钥,不要硬编码!
OSS_ACCESS_KEY_ID = credentials('oss-access-key-id')
OSS_ACCESS_KEY_SECRET = credentials('oss-access-key-secret')
// 生成一个唯一的文档版本号,基于Git Commit ID,确保每次更新都有新URL
DOC_VERSION = "${env.BUILD_NUMBER}-${env.GIT_COMMIT}"
DOC_BUCKET = 'your-company-api-docs'
DOC_PATH = "api-docs/${env.JOB_NAME}/${env.BRANCH_NAME}/${DOC_VERSION}"
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build & Generate Docs') {
steps {
sh '''
# 执行Maven构建,同时触发springdoc插件生成openapi.json
mvn clean package -DskipTests
'''
# 确认文件生成成功
sh 'ls -la target/classes/static/openapi.json'
}
}
stage('Upload to OSS') {
steps {
sh '''
# 使用aliyun ossutil命令上传
aliyun oss cp target/classes/static/openapi.json oss://${DOC_BUCKET}/${DOC_PATH}/openapi.json
'''
script {
// 获取上传后的URL
env.DOC_URL = "https://cdn.yourcompany.com/api-docs/${env.JOB_NAME}/${env.BRANCH_NAME}/${DOC_VERSION}/openapi.json"
}
}
}
stage('Notify') {
when {
branch 'master'
}
steps {
// 通知团队文档已更新,比如发送钉钉/飞书/Slack消息
sh '''
curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text", "text": {"content": "🎉 API文档已更新!版本: ${DOC_VERSION} \\n链接: ${DOC_URL}"}}'
'''
}
}
}
post {
always {
// 清理Jenkins Workspace,释放空间
cleanWs()
}
success {
echo "✅ 流水线执行成功,文档已同步至 ${env.DOC_URL}"
}
failure {
echo "❌ 流水线执行失败,文档未更新。请检查构建日志。"
}
}
}
4.3 关键点解析
你可能注意到了DOC_VERSION这个变量。它由BUILD_NUMBER和GIT_COMMIT组成。为什么要这么做?因为如果每次上传都覆盖同一个路径(比如/latest/openapi.json),那么浏览器会缓存旧的JSON文件。用户打开Swagger UI时,看到的还是旧文档。
通过版本号隔离,我们保证了URL的唯一性。前端同事点进去,永远是最新的那个JSON。
但是,用户总不想每次去Jenkins上翻版本号吧?我们需要一个“最新链接”。这里有个小技巧:我们可以额外上传一个latest.json到根目录,每次构建成功后,用cp命令把新的openapi.json复制一份为latest.json。这样,永久链接https://cdn.../latest.json始终指向最新文档。
stage('Upload Latest') {
steps {
sh "aliyun oss cp target/classes/static/openapi.json oss://${DOC_BUCKET}/api-docs/${env.JOB_NAME}/${env.BRANCH_NAME}/latest/openapi.json"
}
}
这样,你就可以把latest/openapi.json的地址发给所有人,这个地址永远有效,永远最新。
第五步:前端Swagger UI的对接
文档JSON有了,怎么展示?你不能让人家打开一个JSON文件看吧?我们需要一个Swagger UI的静态页面来渲染它。
有两种选择:
- 自己搭建一个简单的HTML页:放在OSS里,通过URL参数传入JSON地址。
- 使用Swagger UI的开源镜像。
推荐方案1,简单可控。我们在OSS里放一个index.html,内容如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>API Documentation</title>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css">
<style>
body { margin: 0; padding: 0; }
#swagger-ui { max-width: 1460px; }
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = function() {
// 从URL参数中获取json路径,默认使用latest
const urlParams = new URLSearchParams(window.location.search);
const specUrl = urlParams.get('url') || 'https://your-cdn.com/api-docs/your-project/latest/openapi.json';
SwaggerUIBundle({
url: specUrl,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: 'StandaloneLayout'
});
};
</script>
</body>
</html>
把这个index.html也上传到OSS。然后,你给团队分享的链接就是:
https://your-cdn.com/api-docs/your-project/index.html
这个页面会自动加载latest/openapi.json。每次Jenkins构建成功,latest文件被覆盖,团队打开这个链接,看到的永远都是最新的API文档。
第六步:处理特殊情况——本地调试与权限控制
在实际工作中,你总会遇到一些麻烦事。
场景一:本地开发时,我怎么快速预览?
本地没有Jenkins,但你可以用Maven插件本地生成。在IDEA的Terminal里运行:
mvn springdoc-openapi:generate
生成的文件会在target/classes/static/openapi.json。你可以用VS Code的“OpenAPI (Swagger) Preview”插件直接打开这个JSON文件,预览效果几乎和线上一样。
场景二:文档里有很多敏感字段(比如密码、token),怎么过滤?
Springdoc支持配置屏蔽字段。在你的@Operation或实体类上,加注解:
@Operation(hidden = true) // 整个接口隐藏
// 或者
@Schema(hidden = true) // 某个字段隐藏
private String password;
在Pipeline阶段,你可以配置Maven参数来启用这个过滤,确保生成的JSON不包含敏感信息。
场景三:如果构建失败了,文档会更新吗?
不会。因为我们在Pipeline里是依赖mvn package成功的。如果编译失败,文档生成阶段也不会执行,OSS里的文件保持原样。这保证了文档的一致性——只有能跑起来的代码,对应的文档才会上线。
第七步:进阶技巧——用OpenAPI Generator反向生成客户端代码
这一步虽然不是必须的,但能极大提升团队效率。既然我们有标准的OpenAPI JSON,为什么不让前端和后端都用同一个JSON自动生成代码?
Jenkins里可以加一个阶段,调用openapi-generator-cli:
stage('Generate Client Code') {
steps {
sh '''
docker run --rm \
-v ${PWD}:/local openapi-generator/openapi-generator-cli generate \
-i /local/target/classes/static/openapi.json \
-g typescript-axios \
-o /local/generated-client \
--additional-properties=npmName=my-api-client
'''
// 上传生成的npm包到内部仓库
sh 'npm publish /local/generated-client'
}
}
这样,前端同事直接npm install my-api-client,就能拿到类型安全的API调用方法。接口一变,重新构建Jenkins,前端代码自动同步,连TypeScript的类型错误都能在写代码时暴露出来。这才是真正的“文档驱动开发”。
总结一下这个方案的威力
- 零人工干预:开发者只管写代码,文档自动同步。
- 版本可追溯:每个Commit对应一个文档版本,出了问题可以回溯看当时的接口定义。
- 永久链接:
latest文件机制解决了缓存和URL失效问题。 - 成本极低:OSS存几个JSON文件,几乎不花钱。
我曾经见过一个团队,上线前夜发现文档和代码对不上,测试人员花了3个小时逐行核对接口。如果他们有这套流水线,这事儿根本不会发生。文档过期不是技术问题,是流程问题。把文档变成构建产物,问题就解决了。
你现在就可以打开你的Jenkins,复制上面的Pipeline,花20分钟配置一下OSS,明天开会时,把那个永远最新的Swagger链接甩给产品经理和前端负责人,你会发现他们的眼神都不一样了。
