Что такое SpringBoot Starter
SpringBoot — популярный фреймворк для разработки, он может автоматически интегрировать различные фреймворки без отвратительных и громоздких конфигурационных файлов, что делает сборку проекта очень быстрой. Герой для достижения всей этой «автоматизации»Starter, например, пустой проект SpringBoot подобен сокету с бесчисленным количеством разъемов на нем.Когда нам нужно добавить в проект какие-то инструменты, нам нужно только вставить вилку (стартер) этого инструмента в розетку, этот инструмент работает отлично в нашем проекте. Например: mybatis реализует mybatis-spring-boot-starter, что позволяет SpringBoot очень легко интегрировать MyBatis и почти не требует написания каких-либо файлов конфигурации.
Небольшой футляр почерка Starter
1. Предпосылки
После того, как SpringBoot интегрирует Swagger2, Aip не может сгенерировать стандартный документ интерфейса (версию в формате Word).Из-за этой проблемы я планирую написать Starter для преобразования json информации об интерфейсе, сгенерированной Swagger2, в версию документа в формате word. И пусть он применяется к любому проекту SpringBoot, подключи и играй.
2. Подробные шаги
1) Создайте пустой проект maven и введите зависимости spring-boot-autoconfigure и spring-boot-configuration-processor, эти два являются наиболее важными. Подробный файл pom выглядит следующим образом:\
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fbl</groupId>
<artifactId>swagger2word-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
</dependencies>
</project>
2) Создайте класс конфигурации, который получает файл конфигурации SpringBoot.
Код класса конфигурации (Swagger2WordProp.java)
package com.fbl.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
// ConfigurationProperties注解是读取swagger为前缀的配置。
@ConfigurationProperties(prefix = "swagger")
public class Swagger2WordProp {
// 如果没有写swagger.url配置,就默认使用123456(此地址就是Swagger生成Json的接口地址)
private String url = "123456";
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
3) Создайте сервис и веб-интерфейс, который преобразует Json Swagger2 в Word, код является моей ссылкойJMCuixy из. (Эта статья в основном не о шаблонах Thymeleaf и технологии генерации Word, если вам интересно, вы можете перейти наJMCuixy Проверить)\
Код интерфейса сервера (WordService.java)
package com.fbl.services;
import java.util.Map;
public interface WordService {
Map<String,Object> tableList(String jsonUrl);
}
Код реализации серверов (WordServiceImpl.java)
package com.fbl.services.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fbl.configuration.Swagger2WordProp;
import com.fbl.services.WordService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import com.fbl.model.Request;
import com.fbl.model.Response;
import com.fbl.model.ResponseModelAttr;
import com.fbl.model.Table;
import com.fbl.utils.JsonUtils;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
/**
* @Author fangbl
* @Date 2020/2/11
**/
@SuppressWarnings({"unchecked", "rawtypes"})
public class WordServiceImpl implements WordService {
private RestTemplate restTemplate;
private Swagger2WordProp swagger2WordProp;
public WordServiceImpl(RestTemplate restTemplate, Swagger2WordProp swagger2WordProp) {
this.restTemplate = restTemplate;
this.swagger2WordProp = swagger2WordProp;
}
@Override
public Map<String, Object> tableList(String jsonUrl) {
jsonUrl = StringUtils.defaultIfBlank(jsonUrl, swagger2WordProp.getUrl());
Map<String, Object> resultMap = new HashMap<>();
List<Table> result = new ArrayList<>();
try {
String jsonStr = restTemplate.getForObject(jsonUrl, String.class);
// convert JSON string to Map
Map<String, Object> map = JsonUtils.readValue(jsonStr, HashMap.class);
//解析model
Map<String, Object> definitinMap = parseDefinitions(map);
//解析paths
Map<String, Map<String, Object>> paths = (Map<String, Map<String, Object>>) map.get("paths");
if (paths != null) {
Iterator<Entry<String, Map<String, Object>>> it = paths.entrySet().iterator();
while (it.hasNext()) {
Entry<String, Map<String, Object>> path = it.next();
Iterator<Entry<String, Object>> it2 = path.getValue().entrySet().iterator();
// 1.请求路径
String url = path.getKey();
// 2.请求方式,类似为 get,post,delete,put 这样
String requestType = StringUtils.join(path.getValue().keySet(), ",");
// 3. 不管有几种请求方式,都只解析第一种
Entry<String, Object> firstRequest = it2.next();
Map<String, Object> content = (Map<String, Object>)firstRequest.getValue();
// 4. 大标题(类说明)
String title = String.valueOf(((List) content.get("tags")).get(0));
// 5.小标题 (方法说明)
String tag = String.valueOf(content.get("summary"));
// 6.接口描述
String description = String.valueOf(content.get("summary"));
// 7.请求参数格式,类似于 multipart/form-data
String requestForm = "";
List<String> consumes = (List) content.get("consumes");
if (consumes != null && consumes.size() > 0) {
requestForm = StringUtils.join(consumes, ",");
}
// 8.返回参数格式,类似于 application/json
String responseForm = "";
List<String> produces = (List) content.get("produces");
if (produces != null && produces.size() > 0) {
responseForm = StringUtils.join(produces, ",");
}
// 9. 请求体
List<LinkedHashMap> parameters = (ArrayList) content.get("parameters");
// 10.返回体
Map<String, Object> responses = (LinkedHashMap) content.get("responses");
//封装Table
Table table = new Table();
table.setTitle(title);
table.setUrl(url);
table.setTag(tag);
table.setDescription(description);
table.setRequestForm(requestForm);
table.setResponseForm(responseForm);
table.setRequestType(requestType);
table.setRequestList(processRequestList(parameters));
table.setResponseList(processResponseCodeList(responses));
// 取出来状态是200时的返回值
Map<String, Object> obj = (Map<String, Object>)responses.get("200");
if (obj != null && obj.get("schema")!=null) {
table.setResponseModeAttrList(processResponseModelAttrs(obj, definitinMap));
}
//示例
table.setRequestParam(JsonUtils.writeJsonStr(buildParamMap(table.getRequestList(), map)));
table.setResponseParam(processResponseParam(obj, map));
result.add(table);
}
//排序,同类别的接口归并在一起
Collections.sort(result, new Comparator<Table>() {
public int compare(Table o1, Table o2) {
return o1.getTitle().compareTo(o2.getTitle());
};
});
}
resultMap.put("tables", result);
resultMap.put("info", map.get("info"));
System.out.println(JsonUtils.writeJsonStr(resultMap));
} catch (Exception e) {
System.out.println("parse error" + e);
}
return resultMap;
}
/**
* 处理请求参数列表
* @param parameters
* @return
*/
private List<Request> processRequestList(List<LinkedHashMap> parameters){
List<Request> requestList = new ArrayList<>();
if (!CollectionUtils.isEmpty(parameters)) {
for (Map<String, Object> param : parameters) {
Request request = new Request();
request.setName(String.valueOf(param.get("name")));
Object in = param.get("in");
if (in != null && "body".equals(in)) {
request.setType(String.valueOf(in));
Map<String, Object> schema = (Map) param.get("schema");
Object ref = schema.get("$ref");
// 数组情况另外处理
if (schema.get("type") != null && "array".equals(schema.get("type"))) {
ref = ((Map) schema.get("items")).get("$ref");
}
request.setParamType(ref == null ? "{}" : ref.toString());
} else {
request.setType(param.get("type") == null ? "Object" : param.get("type").toString());
request.setParamType(String.valueOf(in));
}
if (param.get("required") != null) {
request.setRequire((Boolean) param.get("required"));
} else {
request.setRequire(false);
}
request.setRemark(String.valueOf(param.get("description")));
request.setParamType(request.getParamType().replaceAll("#/definitions/", ""));
requestList.add(request);
}
}
return requestList;
}
/**
* 处理返回码列表
* @param responses
* @return
*/
private List<Response> processResponseCodeList(Map<String, Object> responses){
List<Response> responseList = new ArrayList<>();
Iterator<Entry<String, Object>> it3 = responses.entrySet().iterator();
while (it3.hasNext()) {
Response response = new Response();
Entry<String, Object> entry = it3.next();
// 状态码 200 201 401 403 404 这样
response.setName(entry.getKey());
LinkedHashMap<String, Object> statusCodeInfo = (LinkedHashMap) entry.getValue();
response.setDescription(String.valueOf(statusCodeInfo.get("description")));
// response.setRemark(String.valueOf(statusCodeInfo.get("description")));
responseList.add(response);
}
return responseList;
}
/**
* 处理返回属性列表
* @param responseObj
* @param definitinMap
* @return
*/
private List<ResponseModelAttr> processResponseModelAttrs(Map<String, Object> responseObj, Map<String, Object> definitinMap){
List<ResponseModelAttr> attrList=new ArrayList<>();
Map<String, Object> schema = (Map<String, Object>)responseObj.get("schema");
String type=(String)schema.get("type");
String ref = null;
if("array".equals(type)) {//数组
Map<String, Object> items = (Map<String, Object>)schema.get("items");
if (items != null && items.get("$ref") != null) {
ref = (String) items.get("$ref");
}
}else {
if (schema.get("$ref") != null) {//对象
ref = (String)schema.get("$ref");
}else {//其他类型
ResponseModelAttr attr=new ResponseModelAttr();
attr.setType(type);
attrList.add(attr);
}
}
if(StringUtils.isNotBlank(ref)) {
Map<String, Object> mode = (Map<String,Object>)definitinMap.get(ref);
ResponseModelAttr attr=new ResponseModelAttr();
attr.setClassName((String)mode.get("title"));
attr.setName((String)mode.get("description"));
attr.setType(StringUtils.defaultIfBlank(type, StringUtils.EMPTY));
attrList.add(attr);
attrList.addAll((List<ResponseModelAttr>)mode.get("properties"));
}
return attrList;
}
/**
* 解析Definition
* @param map
* @return
*/
private Map<String, Object> parseDefinitions(Map<String, Object> map){
Map<String, Map<String, Object>> definitions = (Map<String, Map<String, Object>>) map.get("definitions");
Map<String, Object> definitinMap = new HashMap<String, Object>();
if(definitions!=null) {
Iterator<String> modelNameIt=definitions.keySet().iterator();
String modeName = null;
Entry<String, Object> pEntry=null;
ResponseModelAttr modeAttr=null;
Map<String, Object> attrInfoMap=null;
while (modelNameIt.hasNext()) {
modeName = modelNameIt.next();
Map<String, Object> modeProperties=(Map<String, Object>)definitions.get(modeName).get("properties");
Iterator<Entry<String, Object>> pIt= modeProperties.entrySet().iterator();
List<ResponseModelAttr> attrList=new ArrayList<>();
//解析属性
while (pIt.hasNext()) {
pEntry=pIt.next();
modeAttr=new ResponseModelAttr();
modeAttr.setValue(pEntry.getKey());
attrInfoMap=(Map<String, Object>)pEntry.getValue();
modeAttr.setName((String)attrInfoMap.get("description"));
modeAttr.setType((String)attrInfoMap.get("type"));
if(attrInfoMap.get("format")!=null) {
modeAttr.setType(modeAttr.getType()+"("+(String)attrInfoMap.get("format")+")");
}
attrList.add(modeAttr);
}
Map<String, Object> mode=new HashMap<>();
mode.put("title", definitions.get(modeName).get("title"));
mode.put("description", definitions.get(modeName).get("description"));
mode.put("properties", attrList);
definitinMap.put("#/definitions/"+modeName, mode);
}
}
return definitinMap;
}
/**
* 处理返回值
* @param responseObj
* @param map
* @return
*/
private String processResponseParam(Map<String, Object> responseObj, Map<String, Object> map){
if (responseObj != null && responseObj.get("schema")!=null) {
Map<String, Object> schema = (Map<String, Object>)responseObj.get("schema");
String type=(String)schema.get("type");
String ref = null;
if("array".equals(type)) {//数组
Map<String, Object> items = (Map<String, Object>)schema.get("items");
if (items != null && items.get("$ref") != null) {
ref = (String) items.get("$ref");
ObjectNode objectNode = parseRef(ref, map);
ArrayNode arrayNode = JsonUtils.createArrayNode();
arrayNode.add(objectNode);
return arrayNode.toString();
}
}else if (schema.get("$ref") != null) {
ref = (String)schema.get("$ref");
ObjectNode objectNode = parseRef(ref, map);
return objectNode.toString();
}
}
return StringUtils.EMPTY;
}
/**
* 从map中解析出指定的ref
*
* @param ref ref链接 例如:"#/definitions/PageInfoBT«Customer»"
* @param map 是整个swagger json转成map对象
* @return
* @author fpzhan
*/
private ObjectNode parseRef(String ref, Map<String, Object> map) {
ObjectNode objectNode = JsonUtils.createObjectNode();
if (StringUtils.isNotEmpty(ref) && ref.startsWith("#")) {
String[] refs = ref.split("/");
Map<String, Object> tmpMap = map;
//取出ref最后一个参数 start
for (String tmp : refs) {
if (!"#".equals(tmp)) {
tmpMap = (Map<String, Object>) tmpMap.get(tmp);
}
}
//取出ref最后一个参数 end
//取出参数
if (tmpMap == null) {
return objectNode;
}
Object properties = tmpMap.get("properties");
if (properties == null) {
return objectNode;
}
Map<String, Object> propertiesMap = (Map<String, Object>) properties;
Set<String> keys = propertiesMap.keySet();
//遍历key
for (String key : keys) {
Map<String, Object> keyMap = (Map) propertiesMap.get(key);
if ("array".equals(keyMap.get("type"))) {
//数组的处理方式
String sonRef = (String) ((Map) keyMap.get("items")).get("$ref");
//对象自包含,跳过解析
if (ref.equals(sonRef)) {
continue;
}
JsonNode jsonNode = parseRef(sonRef, map);
ArrayNode arrayNode = JsonUtils.createArrayNode();
arrayNode.add(jsonNode);
objectNode.set(key, arrayNode);
} else if (keyMap.get("$ref") != null) {
//对象的处理方式
String sonRef = (String) keyMap.get("$ref");
//对象自包含,跳过解析
if (ref.equals(sonRef)) {
continue;
}
ObjectNode object = parseRef(sonRef, map);
objectNode.set(key, object);
} else {
//其他参数的处理方式,string、int
String str = "";
if (keyMap.get("description") != null) {
str = str + keyMap.get("description");
}
if (keyMap.get("format") != null) {
str = str + String.format("格式为(%s)", keyMap.get("format"));
}
objectNode.put(key, str);
}
}
}
return objectNode;
}
/**
* 封装post请求体
*
* @param list
* @param map
* @return
*/
private Map<String, Object> buildParamMap(List<Request> list, Map<String, Object> map) throws IOException {
Map<String, Object> paramMap = new HashMap<>(8);
if (list != null && list.size() > 0) {
for (Request request : list) {
String name = request.getName();
String type = request.getType();
switch (type) {
case "string":
paramMap.put(name, "string");
break;
case "integer":
paramMap.put(name, 0);
break;
case "number":
paramMap.put(name, 0.0);
break;
case "boolean":
paramMap.put(name, true);
break;
case "body":
String paramType = request.getParamType();
ObjectNode objectNode = parseRef(paramType, map);
paramMap = JsonUtils.readValue(objectNode.toString(), Map.class);
break;
default:
paramMap.put(name, null);
break;
}
}
}
return paramMap;
}
}
Код реализации контроллера (WordController.java)
package com.fbl.web;
import com.fbl.services.WordService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Map;
@Controller
public class WordController {
private WordService tableService;
private RestTemplate restTemplate;
public WordController(WordService tableService, RestTemplate restTemplate) {
this.restTemplate = restTemplate;
this.tableService = tableService;
}
@Value("${server.port}")
private Integer port;
/**
* 将 swagger 文档转换成 html 文档,可通过在网页上右键另存为 xxx.doc 的方式转换为 word 文档
*
* @param model
* @param url 需要转换成 word 文档的资源地址
* @return
*/
@Deprecated
@RequestMapping("/toWord")
public String getWord(Model model, @RequestParam(value = "url", required = false) String url, Integer type) {
Map<String, Object> result = tableService.tableList(url);
model.addAttribute("url", StringUtils.defaultIfBlank(url, StringUtils.EMPTY));
model.addAttribute("type", type==null ? 0 : type);
model.addAttribute("info", result.get("info"));
model.addAttribute("tables", result.get("tables"));
return "word";
}
/**
* 将 swagger 文档一键下载为 doc 文档
*
* @param url 需要转换成 word 文档的资源地址
* @param response
*/
@RequestMapping("/downloadWord")
public void word(@RequestParam(required = false) String url, HttpServletResponse response) {
ResponseEntity<String> forEntity = restTemplate.getForEntity("http://localhost:" + port + "/toWord?type=1&url=" + StringUtils.defaultIfBlank(url, StringUtils.EMPTY), String.class);
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("toWord.doc", "utf-8"));
byte[] bytes = forEntity.getBody().getBytes();
bos.write(bytes, 0, bytes.length);
bos.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4) Создайте класс автоматической конфигурации, опубликуйте службу и веб-интерфейс, которые мы написали, в контейнере Spring, добавьте этот Starter для автоматического чтения конфигурации и успешно добавьте свою собственную службу в новый проект.
package com.fbl;
import com.fbl.configuration.Swagger2WordProp;
import com.fbl.services.WordService;
import com.fbl.services.impl.WordServiceImpl;
import com.fbl.web.TestApi;
import com.fbl.web.WordController;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fbl.services.TestService;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
@Configuration
// EnableConfigurationProperties注解必须加,否则Swagger2WordProp里就会报错
@EnableConfigurationProperties(Swagger2WordProp.class)
public class Swagger2WordAutoConfiguration {
@Autowired
Swagger2WordProp swagger2WordProp;
// 如果容器中不存在WordController类型的Bean就添加WordController到容器
@Bean
@ConditionalOnMissingBean(WordController.class)
public WordController wordController(WordService wordService, RestTemplate restTemplate) {
return new WordController(wordService, restTemplate);
}
// 如果容器中不存在WordService类型的Bean就添加WordServiceImpl到容器
@Bean
@ConditionalOnMissingBean(WordService.class)
public WordService wordService(RestTemplate restTemplate) {
return new WordServiceImpl(restTemplate, swagger2WordProp);
}
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
//60s
requestFactory.setConnectTimeout(60 * 1000);
requestFactory.setReadTimeout(60 * 1000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}
5) Создайте файл Spring.factories в папке resources/META-INF/ и укажите класс, автоматически настроенный Starter.
org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.fbl.Swagger2WordAutoConfiguration
6) Процесс кодирования в основном завершен, и теперь проект упакован и установлен в локальный репозиторий maven.
Выполните следующую команду:
mvn clean install
Используйте этот Starter в своем проекте
1) Добавьте зависимость swagger2word-starter в новый проект.
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
2) Поскольку генерация слов должна поддерживать тимелеаф, следует также добавить связанные зависимости
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3) Добавьте конфигурацию Starter в application.properties
# swagger2word-starter需要的配置(获取Swagger文档json数据的接口)
swagger.url=http://127.0.0.1:8088/v2/api-docs
# thymeleaf需要的配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
4) Добавьте шаблон в resources/templates/, и пользователь сгенерирует слово.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="application/msword"/>
<title>toWord</title>
<style type="text/css">
.bg {
color: #fff;
background-color: #559e68;
}
table {
border: 1px solid #dbe3e4;
table-layout: fixed;
}
tr {
height: 32px;
font-size: 12px;
}
td {
padding: 0px 5px 0px 5px;
border: 1px solid #dbe3e4;
height: 32px;
overflow: hidden;
word-break: break-all;
word-wrap: break-word;
font-size: 14px;
}
.specialHeight {
height: 40px;
}
.first_title{
height: 60px;
line-height: 60px;
margin: 0;
font-weight: bold;
font-size:20px;
}
.second_title{
height: 40px;
line-height: 40px;
margin: 0;
font-weight: bold;
font-size:16px;
}
.doc_title{
font-size:24px;
text-align:center;
}
.download_btn{
float:right;
}
</style>
</head>
<body>
<div style="width:800px; margin: 0 auto" th:with="count=1">
<div>
<p class="doc_title" th:text="${info.title+'('+info.version+')'}"></p>
<a class="download_btn" th:href="${'/downloadWord?url='+url}" th:if="${type!=1}">下载文档</a>
<br>
</div>
<div th:each="table,iterStat:${tables}" style="margin-bottom:20px;">
<div th:with="preTitle=${iterStat.index>0 ? tables[iterStat.index-1].title : ''}, isSame=${table.title == preTitle}, count=${ table.title == preTitle ? count+1 : 1}">
<!--这个是类的说明-->
<h4 class="first_title" th:if="${!isSame}" th:text="${isSame ? '' : #strings.concat(iterStat.count,'. ', table.title)}"></h4>
<!--这个是每个请求的说明,方便生成文档后进行整理-->
<h5 class="second_title" th:text="${count+')'+table.tag}"></h5>
<table border="1" cellspacing="0" cellpadding="0" width="100%">
<tr class="bg">
<td colspan="5" th:text="${table.tag}"></td>
</tr>
<tr>
<td width="25%">接口描述</td>
<td colspan="4" th:text="${table.description}"></td>
</tr>
<tr>
<td>URL</td>
<td colspan="4" th:text="${table.url}"></td>
</tr>
<tr>
<td>请求方式</td>
<td colspan="4" th:text="${table.requestType}"></td>
</tr>
<tr>
<td>请求类型</td>
<td colspan="4" th:text="${table.requestForm}"></td>
</tr>
<tr>
<td>返回类型</td>
<td colspan="4" th:text="${table.responseForm}"></td>
</tr>
<tr class="bg" align="center">
<td>参数名</td>
<td>数据类型</td>
<td>参数类型</td>
<td>是否必填</td>
<td>说明</td>
</tr>
<tr align="center" th:each="request:${table.requestList}">
<td th:text="${request.name}"></td>
<td th:text="${request.type}"></td>
<td th:text="${request.paramType}"></td>
<td th:if="${request.require}" th:text="Y"></td>
<td th:if="${!request.require}" th:text="N"></td>
<td th:text="${request.remark}"></td>
</tr>
<tr class="bg" align="center">
<td>状态码</td>
<td colspan="2">描述</td>
<td colspan="2">说明</td>
</tr>
<tr align="center" th:each="response:${table.responseList}">
<td th:text="${response.name}"></td>
<td colspan="2" th:text="${response.description}"></td>
<td colspan="2" th:text="${response.remark}"></td>
</tr>
<tr class="bg" align="center">
<td>返回类名</td>
<td>返回属性名</td>
<td>类型</td>
<td colspan="2">说明</td>
</tr>
<tr align="center" th:each="response:${table.responseModeAttrList}">
<td th:text="${response.className}"></td>
<td th:text="${response.value}"></td>
<td th:text="${response.type}"></td>
<td colspan="2" th:text="${response.name}"></td>
</tr>
<tr class="bg">
<td colspan="5">示例</td>
</tr>
<tr class="specialHeight">
<td class="bg">请求参数</td>
<td colspan="4" th:text="${table.requestParam}"></td>
</tr>
<tr class="specialHeight">
<td class="bg">返回值</td>
<td colspan="4" th:text="${table.responseParam}"></td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>
5) Запускаем проект и заходим на интерфейс http://127.0.0.1:8088/toWord
эффект файла слова:
Суммировать
Приведенный выше код находится в моем репозитории git:GitHub.com/1315977663/…
Шаги по внедрению Стартера примерно такие.Конечно, в моем еще много плохого.Например, после внедрения Стартера нужно добавить шаблон для применения. Полноценного plug and play нет, много места для оптимизации, и друзья, которым нравится, могут перейти наGitHub.com/1315977663/…Зафиксируйте свой код, чтобы значительно сократить рабочую нагрузку по написанию документации по интерфейсу в будущем.
На самом деле, для реализации Starter не так много шагов, но для создания отличного Starter требуется хороший дизайн, чтобы его можно было полностью отделить от проекта клиента. Если вы хотите хорошо проектировать, вам все равно необходимо овладеть прочными базовыми знаниями. Нельзя слишком полагаться на структуру верхнего уровня, поэтому фундамент очень важен. Трудно стать отличным программистом, только используя верхний уровень. каркас уровня.
Здесь вы знаете основной рабочий процесс Starter.Вы можете перейти к исходному коду mybatis-spring-boot-starter, чтобы увидеть, как он автоматически регистрирует ключевые классы, такие как SqlSessionFactory и SqlSessionTemplate, в контейнере Spring. \
Сначала посмотрите на файл spring.factories mybatis-spring-boot-starter.
Взгляните на Mybatisautoconfiguration.class