Swagger集成Jenkins自动化工作流从API文档生成到CI流水线部署完整实操指南
咱们先聊聊这个组合为啥这么香
你有没有经历过这种崩溃场景:后端同学API改了三版,前端还在对着三个月前的文档对接接口,最后上线前才发现字段对不上,两个人吵得脸红脖子粗。我有个朋友叫小张,他之前就在这样的项目里坑了快半年。
后来他们引入了Swagger+Jenkins这套组合拳,整个人都变温柔了。今天我就把这个实操路径给你掰开揉碎了讲清楚,保证你看完就能上手。
Swagger到底在帮咱们解决啥
Swagger本质上就是一套API文档标准,它让你能用代码直接描述接口长啥样——请求方式、路径、参数、返回结构,全都能标准化表达。
关键点在于代码即文档。你不用专门写Markdown或者Word,接口定义在代码里,文档自动跟着跑。前端开发拿着这个文档直接用,后端改了代码,文档第二天就更新了,再也不用追着程序员问”这个接口是不是变了”。
Jenkins那边又扮演什么角色
Jenkins是个开源的自动化服务器,能干的事情特别多,但核心就一件事:让你写一遍流程,它帮你自动跑。代码提交之后,该编译编译,该测试测试,该打包打包,该部署部署,全程不用你动手。
把Swagger和Jenkins串起来,意思就是:你改完接口代码→Jenkins自动跑测试→自动生成最新的Swagger文档→部署到服务器上→文档随时能看。这一整套流程全自动,人就是坐在旁边喝茶的。
实操第一步:Swagger配置环境
咱们先建个项目目录,假设你用的是Java+Spring Boot,这是最常见的场景。
在pom.xml里加这几个依赖:
<!-- Swagger 3.x 核心依赖 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 如果需要JSON/YAML输出支持 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.3.0</version>
</dependency>
然后在application.yml里做基本配置:
spring:
doc:
openapi:
info:
title: 用户服务API文档
version: 1.0.0
description: 用户中心相关接口,包含登录、注册、信息管理
contact:
name: 技术团队
email: dev@company.com
servers:
- url: http://localhost:8080
description: 本地开发环境
- url: https://api.production.com
description: 生产环境
paths:
- /api/**
default-produces-media-type: application/json
接着写个配置类,告诉Swagger你项目里哪些包需要扫描:
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("用户服务API")
.version("v1.0")
.description("这个服务负责处理用户注册、登录和基本信息管理")
.contact(new Contact()
.name("后端开发组")
.email("backend@company.com")
)
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")
)
);
}
}
接口代码里加上注解,Swagger就能识别出来了:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Tag(name = "用户管理", description = "用户相关的增删改查接口")
@RestController
@RequestMapping("/api/users")
public class UserController {
@Operation(
summary = "获取用户列表",
description = "分页查询用户信息,支持按状态筛选"
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "查询成功"),
@ApiResponse(responseCode = "400", description = "请求参数错误",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
@GetMapping
public List<User> getUsers(
@Parameter(description = "页码,从1开始", example = "1")
@RequestParam(defaultValue = "1") int page,
@Parameter(description = "每页数量", example = "10")
@RequestParam(defaultValue = "10") int size
) {
// 业务逻辑
return userService.listUsers(page, size);
}
@Operation(summary = "创建新用户", description = "注册新用户账号")
@PostMapping
public User createUser(@RequestBody @Schema(description = "用户信息", example = "{\"name\":\"张三\",\"email\":\"zhangsan@example.com\"}") User user) {
return userService.createUser(user);
}
}
这样写完之后,访问http://localhost:8080/swagger-ui/index.html,你就会看到一个漂亮的交互界面,接口列表、参数说明、甚至可以直接在页面上发起请求测试。
让Swagger文档自动生成导出
文档生成了,但要是每次部署完还要手动去访问UI界面才能拿到JSON或YAML文件,那就太麻烦了。咱们需要让它在构建阶段自动导出。
加一个Maven插件配置:
<plugin>
<groupId>io.github.swagger2markup</groupId>
<artifactId>swagger2markup-maven-plugin</artifactId>
<version>1.3.3</version>
<configuration>
<swaggerInput>http://localhost:8080/v3/api-docs</swaggerInput>
<outputFile>src/docs/asciidoc/generated</outputFile>
<config>
<swagger2markup.markupLanguage>ASCIIDOC</swagger2markup.markupLanguage>
<swagger2markup.pathsGroupedBy>TAGS</swagger2markup.pathsGroupedBy>
</config>
</configuration>
</plugin>
不过说实话,我见过更多人直接用springdoc提供的Actuator端点来导出JSON:
management:
endpoints:
web:
exposure:
include: health,info,metrics,openapi
然后在CI流程里直接调http://host:port/v3/api-docs就能拿到原始JSON,再转换成Markdown或者HTML。
Jenkins环境搭起来
Jenkins的安装比较简单,下载WAR包扔到Tomcat里跑,或者直接用Docker:
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v /var/jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts-jdk17
安装完成后,去插件管理页面,把这几个必装的装上:
- Git Plugin — 拉代码用
- Maven Integration Plugin — 构建Java项目
- Pipeline Utility Steps — 处理JSON/YAML文件
- Publish Over SSH — 把产物推到服务器
- Blue Ocean — 界面更好看(可选)
创建第一个Pipeline脚本
在项目根目录建一个Jenkinsfile,这是整个自动化的核心:
pipeline {
agent any
environment {
APP_NAME = 'user-service'
APP_VERSION = "${env.BUILD_NUMBER}"
DOCKER_REGISTRY = 'registry.company.com'
SWAGGER_JSON_PATH = 'target/swagger.json'
}
stages {
stage('拉取代码') {
steps {
checkout scm
echo "当前分支:${env.BRANCH_NAME}"
echo "提交哈希:${env.GIT_COMMIT}"
}
}
stage('编译构建') {
steps {
sh 'mvn clean package -DskipTests -q'
}
post {
success {
echo '构建成功,产物在target目录下'
}
failure {
error '构建失败,请检查编译日志'
}
}
}
stage('单元测试') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/**/*.xml'
}
}
}
stage('生成Swagger文档') {
steps {
// 启动服务并导出Swagger JSON
script {
def swaggerPort = 9090
sh """
nohup java -jar target/${APP_NAME}-${APP_VERSION}.jar \
--server.port=${swaggerPort} > /tmp/app.log 2>&1 &
echo $! > /tmp/app.pid
sleep 15
"""
}
sh "curl -s http://localhost:9090/v3/api-docs > ${SWAGGER_JSON_PATH}"
script {
sh 'kill $(cat /tmp/app.pid) || true'
}
}
}
stage('文档格式化与验证') {
steps {
sh 'python scripts/validate_swagger.py ${SWAGGER_JSON_PATH}'
sh 'npx @redocly/cli build-docs ${SWAGGER_JSON_PATH} --output docs/index.html'
}
}
stage('打包镜像') {
when {
branch 'main'
}
steps {
sh '''
docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${APP_VERSION} .
docker push ${DOCKER_REGISTRY}/${APP_NAME}:${APP_VERSION}
'''
}
}
stage('部署到测试环境') {
when {
branch 'main'
}
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'test-server',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: """
cd /opt/apps/${APP_NAME}
docker-compose pull
docker-compose up -d
""",
sourceFiles: ''
)
]
)
]
)
}
}
stage('部署API文档服务器') {
when {
branch 'main'
}
steps {
sh '''
docker run -d \
--name swagger-ui \
-p 8888:8080 \
-e SWAGGER_JSON=/data/swagger.json \
-v ${WORKSPACE}/docs:/data \
swaggerapi/swagger-ui
'''
}
}
}
post {
always {
cleanWs()
echo "任务完成,构建编号:${env.BUILD_NUMBER}"
}
success {
echo '🎉 全流程成功,API文档已更新'
slackSend(
channel: '#deployments',
message: "✅ ${APP_NAME} 构建${env.BUILD_NUMBER}成功,文档已更新",
color: 'good'
)
}
failure {
echo '❌ 构建失败,请查看日志'
slackSend(
channel: '#deployments',
message: "❌ ${APP_NAME} 构建${env.BUILD_NUMBER}失败",
color: 'danger'
)
}
}
}
文档校验脚本
那个validate_swagger.py是咱们自己写的一个小工具,用来检查导出的JSON是不是合法的OpenAPI规范:
#!/usr/bin/env python3
"""
Swagger JSON 校验脚本
检查导出的API文档是否符合OpenAPI 3.0规范
"""
import json
import sys
import re
def validate_swagger(json_path: str) -> bool:
"""校验Swagger JSON文件"""
# 读取文件
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
except json.JSONDecodeError as e:
print(f"❌ JSON解析失败: {e}")
return False
except FileNotFoundError:
print(f"❌ 文件不存在: {json_path}")
return False
# 检查必填字段
required_fields = ['openapi', 'info', 'paths']
missing = [f for f in required_fields if f not in data]
if missing:
print(f"❌ 缺少必填字段: {missing}")
return False
# 检查info字段
info = data.get('info', {})
if not info.get('title'):
print("❌ info.title 不能为空")
return False
if not info.get('version'):
print("❌ info.version 不能为空")
return False
# 检查paths不为空
paths = data.get('paths', {})
if not paths:
print("⚠️ 警告: 没有定义任何API路径")
else:
print(f"✅ 检测到 {len(paths)} 个API路径")
# 检查每个路径的操作
valid_methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head']
path_issues = []
for path, methods in paths.items():
for method, spec in methods.items():
if method.lower() not in valid_methods:
path_issues.append(f"未知方法: {method} on {path}")
if 'summary' not in spec:
path_issues.append(f"缺少summary: {method} on {path}")
if path_issues:
print(f"⚠️ 路径检查发现 {len(path_issues)} 个问题:")
for issue in path_issues[:10]: # 最多显示10个
print(f" - {issue}")
else:
print("✅ 所有路径格式检查通过")
# 统计接口数量
total_operations = sum(
len([m for m in ops if m in valid_methods])
for ops in paths.values()
)
print(f"📊 统计: 共 {len(paths)} 个路径,{total_operations} 个接口操作")
# 输出关键信息摘要
print(f"\n📋 文档摘要:")
print(f" 标题: {info.get('title')}")
print(f" 版本: {info.get('version')}")
print(f" 描述: {info.get('description', '无')}")
if info.get('contact'):
print(f" 联系人: {info['contact'].get('name') or info['contact'].get('email')}")
print("\n✅ Swagger JSON 校验通过")
return True
if __name__ == '__main__':
if len(sys.argv) < 2:
print("用法: python validate_swagger.py <swagger.json路径>")
sys.exit(1)
success = validate_swagger(sys.argv[1])
sys.exit(0 if success else 1)
把整个流程串起来看一遍
咱们从头到尾过一遍,你提交代码的那一刻会发生什么:
T+0秒 — 你push代码到Git仓库,Jenkins收到webhook通知,触发流水线。
T+5秒 — Jenkins从Git拉取最新代码,确认分支和commit信息。
T+30秒 — Maven编译打包,跳过测试快速构建。
T+60秒 — 单元测试跑完,生成测试报告,Jenkins展示通过率。
T+90秒 — 临时启动服务,访问/v3/api-docs端点,把Swagger JSON抓下来存到target/swagger.json,然后关掉临时服务。
T+120秒 — Python脚本检查JSON格式是否合法,redocly把JSON转成好看的HTML文档。
T+150秒 — 构建Docker镜像并推到私有仓库,打上当前构建号的标签。
T+180秒 — 通过SSH连接到测试服务器,拉取新镜像并重启容器。
T+200秒 — 启动Swagger UI容器,访问对应端口就能看到最新文档。
T+210秒 — 整条流水线结束,Slack发送通知,所有人都知道这次部署成功还是失败了。
整个过程中,没人需要手动操作任何东西。前端同学要查接口,直接打开文档URL;运维同学要看部署状态,去Jenkins页面点一下就行。
几个实际踩过的坑
坑一:端口冲突
之前我写那个临时启动服务的脚本时,忘了换端口,结果测试服务器上已经有个服务在跑8080,导致抓不到文档。后来改成动态分配端口,或者用0让系统自动分配:
script {
def swaggerPort = sh(script: 'cat /proc/sys/net/ipv4/ip_local_port_range | awk \'{print $2}\'', returnStdout: true).trim() as int
swaggerPort = swaggerPort - 100 // 留点余量
sh "java -jar target/app.jar --server.port=${swaggerPort} &"
}
坑二:Swagger文档导出时机
有时候服务启动慢,curl太早就发了,拿到的是空文档。加个重试逻辑比较稳:
stage('生成Swagger文档') {
steps {
script {
def maxRetries = 5
def retryCount = 0
def swaggerJson = ''
while (retryCount < maxRetries) {
try {
swaggerJson = sh(script: "curl -s --max-time 10 http://localhost:9090/v3/api-docs", returnStdout: true)
if (swaggerJson && swaggerJson.contains('openapi')) {
break
}
} catch (Exception e) {
// 忽略超时
}
retryCount++
echo "第${retryCount}次重试获取Swagger文档..."
sleep(5)
}
if (!swaggerJson || !swaggerJson.contains('openapi')) {
error '无法获取Swagger文档,请检查服务是否正常启动'
}
swaggerJson.save 'target/swagger.json'
}
}
}
坑三:大型项目的文档生成时间
接口多的项目,服务启动加文档导出可能要一两分钟,整个流水线看着就很慢。解决办法是把文档生成和其他不依赖的步骤并行做:
stage('并行处理') {
parallel {
stage('单元测试') {
steps { sh 'mvn test' }
}
stage('代码扫描') {
steps { sh 'mvn sonar:sonar' }
}
}
}
stage('生成文档') {
// 等上面并行阶段完成后再执行
when { expression { return true } }
steps { /* 文档生成逻辑 */ }
}
进阶玩法:文档版本管理和变更通知
文档更新了,但谁也不知道改了啥。咱们可以加个版本对比的功能:
stage('文档变更检测') {
steps {
script {
// 拉取上一次构建的文档
def lastBuild = currentBuild.getPreviousSuccessfulBuild()
if (lastBuild != null) {
def lastSwagger = lastBuild.getEnvVar('SWAGGER_JSON_PATH') ?: 'target/swagger.json'
sh '''
if [ -f ${lastSwagger} ]; then
npx swagger-diff ${lastSwagger} target/swagger.json --summary > docs/changelog.md
else
echo "# 首次构建,无变更记录" > docs/changelog.md
fi
'''
} else {
sh 'echo "# 首次构建" > docs/changelog.md'
}
}
}
}
这样每次部署完,文档里都附带着变更日志,前端开发一看就知道哪个接口加了什么字段,哪个字段被废弃了。
生产环境的部署建议
测试环境跑通了,上生产要注意几件事:
一、文档服务独立部署
不要把Swagger UI和你的业务服务混在一起。单独起一个文档服务器,域名独立,权限控制独立。这样即使业务服务挂了,文档还能看。
二、加访问控制
生产环境的API文档可能包含敏感信息,加个简单的认证:
# docker-compose.yml 示例
swagger-docs:
image: swaggerapi/swagger-ui
ports:
- "8888:8080"
environment:
- SWAGGER_JSON=/data/swagger.json
- API_URL=https://api.company.com
volumes:
- ./docs:/data
# 配合Nginx做基础认证
Nginx配置加个Basic Auth:
location /swagger {
auth_basic "API文档";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://swagger-ui:8080;
}
三、CI/CD触发时机控制
不是每次commit都要重新生成文档。可以加个判断,只有改了接口定义相关的代码才触发文档生成:
stage('变更检测') {
steps {
script {
def changedFiles = sh(
script: "git diff --name-only ${lastSuccessfulBuild} ${env.GIT_COMMIT}",
returnStdout: true
).trim().split('\n')
def hasApiChanges = changedFiles.any {
it =~ /src\/main\/java\/.*Controller|\.java.*swagger|.*OpenAPI/
}
if (!hasApiChanges) {
echo '未检测到API变更,跳过文档重新生成'
currentBuild.result = 'SUCCESS'
return
}
}
}
}
这个工作流带来的实际收益
我当初把这个流程搭起来之后,团队里的变化还挺明显的:
前端同学不再天天在群里@后端问”这个字段叫啥”,文档就在那里,随时能查。后端同学也不用专门抽空写接口文档了,代码写对了文档自然就有了。运维同学部署完不用手动去更新文档,流水线自动搞定。
以前一个版本发布,光是协调文档更新时间就能耗掉大半天。现在只要提交代码,一切自动发生,大家的时间都省下来了。
要是你现在的项目还没有这套流程,强烈建议花一个周末搭起来。前期配置确实需要花点时间,但后面每一天的维护成本都降下来了。
有啥具体问题或者遇到坑了,随时交流。
