使用thymeleaf模版导出swagger3的word格式接口文档
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: 8080tomcat:max-threads: 800uri-encoding: UTF-8spring:application:name: swaggerTowordthymeleaf:prefix: classpath:/templates/suffix: .htmlcache: falseservlet:content-type: text/htmlenabled: trueencoding: UTF-8mode: 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 {@Autowiredprivate ISwagger3Service service;@Autowiredprivate 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 {@Autowiredprivate RestTemplate restTemplate;@Overridepublic 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格式接口文档。