当前位置: 首页 > news >正文

使用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格式接口文档。

http://www.xdnf.cn/news/337519.html

相关文章:

  • 【每天学习一点点】使用Python的pathlib模块分割文件路径
  • HBuilderX安卓真机运行安装失败解决汇总
  • Git实战经验分享:深入掌握git commit --amend的进阶技巧
  • zookeeper实现分布式获取全局唯一自增ID的案例。
  • 论文速读《DARE:基于扩散模型的自主机器人探索新范式》
  • 【Linux网络】网络命令
  • 基于LSTM与SHAP可解释性分析的神经网络回归预测模型【MATLAB】
  • 基于vueflow可拖拽元素的示例(基于官网示例的单文件示例)
  • 深入解析 C# 常用数据结构:特点、区别与优缺点分析
  • C/C++内存分布
  • JVM——Java虚拟机是怎么实现synchronized的?
  • 力扣刷题Day 43:矩阵置零(73)
  • 【随笔】Google学术:but your computer or network may be sending automated queries.
  • 红黑树的应用场景 —— 进程调度 CFS 与内存管理
  • 基于SpringBoot的校园周边美食探索及分享平台的设计与实现
  • Linux系统下使用Kafka和Zookeeper
  • C++ | 常用语法笔记
  • 宝塔面板部署 springboot + mysql 项目
  • CMake笔记(简易教程)
  • 【探寻C++之旅】第十三章:红黑树
  • 第8章-3 查询性能优化1
  • kotlin @JvmStatic注解的作用和使用场景
  • 《信息论与编码课程笔记》——信源编码(1)
  • 动态SQL与静态SQL
  • threejs 添加css3d标签 vue3
  • [数据处理] 6. 数据可视化
  • 商业中的人工智能 (AI) 是什么?
  • 从0到1:用Lask/Django框架搭建个人博客系统(4/10)
  • 每日学习:DAY24
  • 第三节第一部分:Static修饰类变量、成员变量