1.pom配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
<properties>
<skipTests>true</skipTests>
<java.version>1.8</java.version>
<springfox-version>2.6.1</springfox-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.23</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
</dependencies>
<build>
<finalName>SwaggerToWord</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<delimiters>
<delimiter>${*}</delimiter>
<delimiter>@</delimiter>
</delimiters>
<useDefaultDelimiters>false</useDefaultDelimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.1.RELEASE</version>
<executions>
<execution>
<phase>package</phase>
<!--可以把依赖的包都打包到生成的Jar包中-->
<goals>
<goal>repackage</goal>
</goals>
<!--可以生成不含依赖包的不可执行Jar包-->
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
2.application.yml的配置
server:
port: 8080
tomcat:
max-threads: 800
uri-encoding: UTF-8
spring:
application:
name: swaggerToword
thymeleaf:
prefix: classpath:/templates/
suffix: .html
cache: false
servlet:
content-type: text/html
enabled: true
encoding: UTF-8
mode: HTML5
3.实体对象
@Data
public class RequestDto {
//参数名称
private String name;
//参数类型
private String type;
//参数中文说明
private String description;
//是否必填
private Boolean required;
List<RequestDto> modelAttr;
}
@Data
public class ResponseDto {
// 名称
private String name;
// 类型
private String type;
// 子集
private List<ResponseDto> properties;
//参数中文说明
private String description;
}
@Data
public class SwaggerUiDto implements Serializable {
// 标题
private String title;
// 版本
private String version;
private String description;
//每个大类下的接口列表
private Map<String,List<TableDto>> tableMap;
}
@Data
public class TableDto {
// 请求地址
private String url;
// 请求方式
private String method;
// 请求参数举例
private String requestParam;
// 描述
private String description;
// 请求参数
private List<RequestDto> request;
// 响应参数
private List<ResponseDto> response;
// 响应参数举例
private String responseParam;
}
4.controller代码。
@Controller
@Api(tags = "swaggertoWordAPI")
public class Swagger3Controller {
@Autowired
private ISwagger3Service service;
@Autowired
private SpringTemplateEngine springTemplateEngine;
private String fileName = "toWord";
@ApiOperation(value = "将 swaggerv3 文档一键下载为 doc 文档", notes = "", tags = {"Word"})
@ApiResponses(value = {@ApiResponse(code = 200, message = "请求成功。")})
@RequestMapping(value = "/downloadWord3", method = {RequestMethod.GET})
public void wordv3(Model model, @ApiParam(value = "资源地址") @RequestParam(required = true) String url, HttpServletResponse response) {
generateModelData3(model, url, 0);
writeContentToResponse3(model, response);
}
private void generateModelData3(Model model, String url, Integer download) {
SwaggerUiDto dto = service.getSwaggerUiDto(url);
model.addAttribute("url", url);
model.addAttribute("download", download);
model.addAttribute("param",dto);
}
private void writeContentToResponse3(Model model, HttpServletResponse response) {
Context context = new Context();
context.setVariables(model.asMap());
String content = springTemplateEngine.process("word2", context);
response.setContentType("application/octet-stream;charset=utf-8");
response.setCharacterEncoding("utf-8");
try (BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".doc", "utf-8"));
byte[] bytes = content.getBytes();
bos.write(bytes, 0, bytes.length);
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 业务实现接口和类
public interface ISwagger3Service {
/**
* 把swagger3 json解析成swaggerUiDto
* @param swaggerUrl
* @return
*/
SwaggerUiDto getSwaggerUiDto(String swaggerUrl);
}
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.word.dto.RequestDto;
import org.word.dto.ResponseDto;
import org.word.dto.SwaggerUiDto;
import org.word.dto.TableDto;
import org.word.service.ISwagger3Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 把json数据解析成对象
*
* @author lyl
* @version v1.0
* @since 2025/5/8
*/
@Service
@Slf4j
public class Swagger3ServiceImpl implements ISwagger3Service {
@Autowired
private RestTemplate restTemplate;
@Override
public SwaggerUiDto getSwaggerUiDto(String swaggerUrl) {
String jsonStr = restTemplate.getForObject(swaggerUrl, String.class);
JSONObject jsonObject = JSON.parseObject(jsonStr);
return getSwaggerUiDto(jsonObject);
}
/**
* 开始解析json数据
*
* @param jsonObject json数据
* @return 返回SwaggerUiDto对象
*/
private SwaggerUiDto getSwaggerUiDto(JSONObject jsonObject) {
SwaggerUiDto swaggerUiDto = new SwaggerUiDto();
JSONObject info = jsonObject.getJSONObject("info");
swaggerUiDto.setVersion(info.getString("version"));
swaggerUiDto.setTitle(info.getString("title"));
swaggerUiDto.setDescription(info.getString("description"));
swaggerUiDto.setTableMap(getTableMap(jsonObject));
return swaggerUiDto;
}
/**
* 组装接口内容对象
*
* @param jsonObject json数据
* @return 返回接口内容列表
*/
private Map<String, List<TableDto>> getTableMap(JSONObject jsonObject) {
Map<String, List<TableDto>> tableMap = new HashMap<>();
//获取大类标题
JSONArray tags = jsonObject.getJSONArray("tags");
for (int i = 0; i < tags.size(); i++) {
JSONObject tagObject = tags.getJSONObject(i);
String tagName = tagObject.getString("name");
String tagDescription = tagObject.getString("description");
//获取接口内容
JSONObject paths = jsonObject.getJSONObject("paths");
List<TableDto> tableDtoList = new ArrayList<>();
for (String key : paths.keySet()) {
JSONObject pathObject = paths.getJSONObject(key);
for (String methodKey : pathObject.keySet()) {
JSONObject methodObject = pathObject.getJSONObject(methodKey);
//获取请求方法
JSONArray tagNames = methodObject.getJSONArray("tags");
for (int j = 0; j < tagNames.size(); j++) {
if (tagNames.getString(j).equals(tagName)) {
//组装接口内容
TableDto dto = new TableDto();
dto.setUrl(key);
dto.setMethod(methodKey.toUpperCase());
dto.setDescription(methodObject.getString("summary"));
if (methodKey.equals("post")) {
//获取请求参数
JSONObject requestBody = methodObject.getJSONObject("requestBody");
//JSONObject content= requestBody.getJSONObject("content");
dto.setRequest(getPostRequest(requestBody, jsonObject));
dto.setRequestParam(JSON.toJSONString(dto.getRequest()));
} else if (methodKey.equals("get")) {
JSONArray parameters = methodObject.getJSONArray("parameters");
if (null != parameters) {
dto.setRequest(getGetRequest(parameters));
}
if (null != dto.getRequest()) {
//用&拼装参数
String param = dto.getRequest().stream().map(requestDto -> requestDto.getName() + "=").collect(Collectors.joining("&"));
dto.setRequestParam(param);
}
}
JSONObject responses = methodObject.getJSONObject("responses");
dto.setResponse(getResponse(responses, jsonObject));
dto.setResponseParam(JSON.toJSONString(dto.getResponse()));
tableDtoList.add(dto);
break;
}
}
}
tableMap.put(tagName, tableDtoList);
}
}
return tableMap;
}
/**
* 返回参数组装
*
* @param responses
* @param jsonObject "ResponseResult«ServiceItem»": {
* "title": "ResponseResult«ServiceItem»",
* "type": "object",
* "properties": {
* "code": {
* "type": "string",
* "description": "状态码",
* "enum": [
* "AUTHENTICATION_REQUIRED",
* "BAD_REQUEST",
* "DATA_ERROR",
* "INTERNAL_SERVER_ERROR",
* "LICENSE_CHECK_ERROR",
* "MODIFY_PASSWORD_ERROR",
* "PARAMETER_ERROR",
* "SUCCESS",
* "UNAUTHORIZED"
* ]
* },
* "data": {
* "description": "数据",
* "$ref": "#/components/schemas/ServiceItem"
* },
* "message": {
* "type": "string",
* "description": "消息"
* }
* }
* },
* @return
*/
private List<ResponseDto> getResponse(JSONObject responses, JSONObject jsonObject) {
List<ResponseDto> responseDtoList = new ArrayList<>();
for (String key : responses.keySet()) {
ResponseDto responseDto = new ResponseDto();
responseDto.setName(key);
responseDto.setDescription(responses.getJSONObject(key).getString("description"));
if (key.equals("200")) {
JSONObject content = responses.getJSONObject(key).getJSONObject("content");
if (null != content) {
List<ResponseDto> properties = new ArrayList<>();
content.keySet().forEach(k -> {
JSONObject schema = content.getJSONObject(k).getJSONObject("schema");
if (schema.containsKey("$ref")) {
String ref = schema.getString("$ref");
//解析参数路径
String refPath = ref.substring(ref.lastIndexOf("/") + 1);
properties.addAll(getResponse(refPath, jsonObject));
}
});
responseDto.setProperties(properties);
}
}
responseDtoList.add(responseDto);
}
return responseDtoList;
}
/**
* 返回参数组装
*
* @param refPath
* @param jsonObject
* @return
*/
private List<ResponseDto> getResponse(String refPath, JSONObject jsonObject) {
List<ResponseDto> responseDtoList = new ArrayList<>();
jsonObject.getJSONObject("components").getJSONObject("schemas").keySet().forEach(p -> {
if (p.equals(refPath)) {
JSONObject properties = jsonObject.getJSONObject("components").getJSONObject("schemas").getJSONObject(p).getJSONObject("properties");
properties.keySet().forEach(p1 -> {
ResponseDto responseDto = new ResponseDto();
responseDto.setName(p1);
responseDto.setDescription(properties.getJSONObject(p1).getString("description"));
if (p1.equals("data")) {
if (properties.getJSONObject(p1).keySet().contains("items")) {
String ChildRef = properties.getJSONObject(p1).getJSONObject("items").getString("$ref");
if (null != ChildRef) {
String ChildRefPath = ChildRef.substring(ChildRef.lastIndexOf("/") + 1);
responseDto.setProperties(getResponse(ChildRefPath, jsonObject));
}
responseDto.setType("object");
} else if (properties.getJSONObject(p1).keySet().contains("properties")) {
JSONObject propertyList = properties.getJSONObject(p1).getJSONObject("properties");
List<ResponseDto> plist = new ArrayList<>();
for (String key : propertyList.keySet()) {
ResponseDto d = new ResponseDto();
d.setName(key);
d.setDescription(propertyList.getJSONObject(key).getString("description"));
d.setType(propertyList.getJSONObject(key).getString("type"));
if (propertyList.getJSONObject(key).keySet().contains("items")) {
String ChildRef = propertyList.getJSONObject(key).getJSONObject("items").getString("$ref");
if (null != ChildRef) {
String ChildRefPath = ChildRef.substring(ChildRef.lastIndexOf("/") + 1);
d.setProperties(getResponse(ChildRefPath, jsonObject));
}
d.setType("object");
}
plist.add(d);
}
responseDto.setProperties(plist);
responseDto.setType("object");
} else if (properties.getJSONObject(p1).keySet().contains("$ref")) {
String ChildRef = properties.getJSONObject(p1).getString("$ref");
if (null != ChildRef) {
String ChildRefPath = ChildRef.substring(ChildRef.lastIndexOf("/") + 1);
responseDto.setProperties(getResponse(ChildRefPath, jsonObject));
}
responseDto.setType("object");
} else {
responseDto.setType(properties.getJSONObject(p1).getString("type"));
}
}
responseDtoList.add(responseDto);
});
}
});
return responseDtoList;
}
/**
* get请求参数
*
* @param parameters "parameters": [
* {
* "name": "service1Type",
* "in": "query",
* "description": "服务大类",
* "required": true,
* "style": "form",
* "schema": {
* "type": "integer",
* "format": "int32"
* }
* }]
* @return
*/
private List<RequestDto> getGetRequest(JSONArray parameters) {
List<RequestDto> requestDtoList = new ArrayList<>();
parameters.forEach(parameter -> {
RequestDto requestDto = new RequestDto();
JSONObject parameterObject = (JSONObject) parameter;
requestDto.setName(parameterObject.getString("name"));
requestDto.setType(parameterObject.getJSONObject("schema").getString("type"));
requestDto.setDescription(parameterObject.getString("description"));
if (parameterObject.get("required") != null) {
requestDto.setRequired(parameterObject.getBoolean("required"));
} else {
requestDto.setRequired(false);
}
requestDtoList.add(requestDto);
});
return requestDtoList;
}
/**
* post请求参数
*
* @param requestBody 请求参数所在路径
* @param jsonObject 数据
* @return
*/
private List<RequestDto> getPostRequest(JSONObject requestBody, JSONObject jsonObject) {
List<RequestDto> requestDtoList = new ArrayList<>();
if (null == requestBody) {
return null;
}
JSONObject content = requestBody.getJSONObject("content");
content.keySet().forEach(key -> {
JSONObject schema = content.getJSONObject(key).getJSONObject("schema");
if (schema.containsKey("$ref")) {
String ref = schema.getString("$ref");
//解析参数路径
String refPath = ref.substring(ref.lastIndexOf("/") + 1);
jsonObject.getJSONObject("components").getJSONObject("schemas").keySet().forEach(p -> {
if (p.equals(refPath)) {
JSONObject properties = jsonObject.getJSONObject("components").getJSONObject("schemas").getJSONObject(p).getJSONObject("properties");
properties.keySet().forEach(q -> {
JSONObject property = properties.getJSONObject(q);
RequestDto requestDto = new RequestDto();
requestDto.setName(q);
requestDto.setType(property.getString("type"));
requestDto.setDescription(property.getString("description"));
requestDto.setRequired(false);
requestDtoList.add(requestDto);
});
}
});
}
});
return requestDtoList;
}
}
6.html实现。
html文件路径:resources->templates->word2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="application/msword; charset=utf-8"/>
<title>toWord</title>
<style type="text/css">
.bg {
font-size: 18px;
font-weight: bold;
color: #000;
background-color: #D7D7D7;
}
table {
border-width: 1px;
border-style: solid;
border-color: black;
table-layout: fixed;
border-collapse: collapse;
}
tr {
height: 15px;
font-size: 18px;
}
td {
padding-left: 12px;
border-width: 1px;
border-style: solid;
border-color: black;
height: 15px;
overflow: hidden;
word-break: break-all;
word-wrap: break-word;
font-size: 18px;
}
.bg td {
font-size: 18px;
}
tr td {
font-size: 18px;
}
.specialHeight {
height: 40px;
}
.first_title {
height: 60px;
line-height: 60px;
margin: 0;
font-weight: bold;
font-size: 21px;
}
.second_title {
height: 40px;
line-height: 40px;
margin: 0;
font-size: 18.5px;
}
.doc_title {
font-size: 42.5px;
text-align: center;
}
.download_btn {
float: right;
}
body {
font-family: 仿宋;
}
</style>
</head>
<body>
<div style="width:400px; margin: 0 auto">
<div>
<p class="doc_title" th:text="${param.title +'('+ param.version +')'}"></p>
<a class="download_btn" th:if="${download == 1}" th:href="${'/downloadWord?url='+ url}">下载文档</a>
<br>
</div>
<div th:each="tableMap:${param.tableMap}" style="margin-bottom:20px;">
<!--这个是类的说明-->
<h2 class="first_title" th:text="${tableMap.key}"></h2>
<div th:each="table,tableStat:${tableMap.value}">
<!--这个是每个请求的说明,方便生成文档后进行整理-->
<h3 class="second_title" th:text="${tableStat.count} + ')' + ${table.description}"></h3>
<!-- <div th:text="接口描述"></div>-->
<!-- <div th:text="${table.description}"></div>-->
<!-- <div th:text="URL"></div>-->
<!-- <div th:text="${table.url}"></div>-->
<table border="1" cellspacing="0" cellpadding="0" width="100%">
<tr>
<td class="bg">名称</td>
<td colspan="5" th:text="${table.description}"></td>
</tr>
<tr>
<td class="bg">URL样式</td>
<td colspan="5" th:text="${table.url}">http://ip:端口</td>
</tr>
<tr>
<td class="bg">提交方式</td>
<td colspan="5" th:text="${table.method}"></td>
</tr>
<tr>
<td class="bg">接口协议</td>
<td colspan="5">HTTP+JSON</td>
</tr>
<tr>
<td class="bg">内容类型</td>
<td>名称</td>
<td>是否必填</td>
<td>类型</td>
<td>长度</td>
<td>说明</td>
</tr>
<th:block th:each="request, c:${table.request}">
<tr >
<td rowspan="${c.count}"></td>
<td th:text="${request.name}"></td>
<td th:if="${request.required}" th:text="是"></td>
<td th:if="${!request.required}" th:text="否"></td>
<td th:text="${request.type}"></td>
<td></td>
<td th:text="${request.description}"></td>
</tr>
<th:block th:if="${request.modelAttr}">
<tbody th:include="this::request(${request.modelAttr},${c.count} + '.', 1)"/>
</th:block>
</th:block>
<tr>
<td class="bg">提交数据举例</td>
<td colspan="5" th:text="${table.requestParam}">
</td>
</tr>
<tr>
<td class="bg">返回状态</td>
<td colspan="5">200,401,403,404</td>
</tr>
<tr>
<td class="bg">返回数据参数</td>
<td colspan="2">名称</td>
<td>类型</td>
<td>长度</td>
<td>说明</td>
</tr>
<tr>
<th:block th:if="${table.response}">
<tbody th:include="this::response(${table.response},'', 1)"/>
</th:block>
</tr>
<tr>
<td class="bg" >返回数据举例</td>
<td colspan="5" th:text="${table.responseParam}"></td>
</tr>
</table>
</div>
</div>
</div>
<th:block th:fragment="request(properties,count, lv)">
<th:block th:each="p,c : ${properties}">
<tr>
<td rowspan="${c.count}"></td>
<td th:text="${p.name}"></td>
<td th:if="${p.required}" th:text="是"></td>
<td th:if="${!p.required}" th:text="否"></td>
<td th:text="${p.type}"></td>
<td></td>
<td th:text="${p.description}"></td>
</tr>
<th:block th:unless="${#lists.isEmpty(p.modelAttr)}"
th:include="this::request(${p.modelAttr},${count} + '' + ${c.count} + '.',${lv+1})"/>
</th:block>
</th:block>
<th:block th:fragment="response(properties,count, lv)">
<th:block th:each="p,c : ${properties}">
<tr>
<td th:text="${count} + '' + ${c.count} "> </td>
<td th:text="${p.name}" colspan="2"></td>
<td th:text="${p.type}"></td>
<td ></td>
<td th:text="${p.description}"></td>
</tr>
<th:block th:unless="${#lists.isEmpty(p.properties)}"
th:include="this::response(${p.properties},${count} + '' + ${c.count} + '.',${lv+1})"/>
</th:block>
</th:block>
</body>
</html>
7.运行
启动springboot项目,打开: http://localhost:8080/swagger-ui.html
点try it out. 运行成功返回下载文档路径。
下载结果:导出word格式接口文档。