使用thymeleaf模版导出swagger3的word格式接口文档

发布于:2025-05-09 ⋅ 阅读:(13) ⋅ 点赞:(0)

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


网站公告

今日签到

点亮在社区的每一天
去签到