What is a SpringBoot Starter

SpringBoot is a popular development framework that can automate the integration of various frameworks, no disgusting and tedious configuration files, so that the project construction is very fast. For example, SpringBoot empty engineering is like a socket, with countless sockets. When we need to add some tools to the project, we just need to plug the tool (Starter) into the socket. This tool will work perfectly in our projects. For example, Mybatis implements mybatis-spring-boot-starter, making it very easy for SpringBoot to integrate myBatis with almost no configuration files to write.

A small case study of handwritten Starter

1, the background

After SpringBoot integrates Swagger2, Aip cannot generate the standard interface document (Word version). Because of this problem, I plan to write a Starter to convert the JSON interface information generated by Swagger2 into the interface document in Word version. And make it fit any SpringBoot project, plug and play.

2. Detailed steps

1) Create an empty Maven project and introduce spring-boot-autoconfigure and spring-boot-configuration-processor dependencies, which are most critical. The detailed POM file is as follows: \

<? 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. The FBL < / groupId > < artifactId > swagger2word - starter < / artifactId > 1.0 the SNAPSHOT < version > < / version > < dependencies > < the dependency > < groupId > org. Springframework. Boot < / groupId > < artifactId > spring - the boot - autoconfigure < / artifactId > < version > 2.2.4. RELEASE < / version > < / dependency > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> < version > 2.2.4. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework < / groupId > < artifactId > spring - web < / artifactId > < version > 5.2.3 requires. RELEASE < / version > < / dependency > < the 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 > < the dependency > < groupId > org, apache httpcomponents < / groupId > < artifactId > httpclient < / artifactId > < version > 4.5.10 < / version > < / dependency >  </dependencies> </project>Copy the code

2) Create a configuration class for springBoot configuration file (Swagger2Wordprop.java)

package com.fbl.configuration; import org.springframework.boot.context.properties.ConfigurationProperties; The ConfigurationProperties annotation reads the configuration prefixed by Swagger. @ConfigurationProperties(prefix ="swagger"Public class Swagger2WordProp {// If swagger.url is not configured, 123456 is used by default"123456";

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) { this.url = url; }}Copy the code

3) Create a service and Web interface to convert Swagger2 Json into Word. The code I refer to is JMCuixy. This article is not mainly about Thymeleaf template and Word generation technology, interested can go to JMCuixy view)\

Interface code for Servers (wordservice.java)

package com.fbl.services;

import java.util.Map;

public interface WordService {

    Map<String,Object> tableList(String jsonUrl);
}
Copy the code

Servers implementation code (wordServiceImp.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); DefinitinMap = parseDefinitions(Map); // Parse paths Map<String, Map<String, Object>> paths = (Map<String, Map<String, Object>>)"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(); String url = path.getKey(); // 1. String requestType = stringutils.join (path.getValue().keyset ()),","); // 3. Parse the first Entry<String, Object> firstRequest = it2.next(); Map<String, Object> content = (Map<String, Object>)firstRequest.getValue(); String title = string.valueof (((List) content.get())"tags")).get(0)); String tag = string.valueof (content.get())"summary")); // 6. Interface description String description = String.valueof (content.get("summary")); RequestForm = multipart/form-data String requestForm ="";
                    List<String> consumes = (List) content.get("consumes");
                    if(consumes ! = null && consumes.size() > 0) { requestForm = StringUtils.join(consumes,","); } // 8. Return a parameter format similar to application/json String responseForm ="";
                    List<String> produces = (List) content.get("produces");
                    if(produces ! = null && produces.size() > 0) { responseForm = StringUtils.join(produces,","); Parameters = (ArrayList) content.get("parameters"); Return body Map<String, Object> responses = (LinkedHashMap) content.get("responses"); 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)); Map<String, Object> obj = (Map<String, Object>) response.get ("200");
                    if(obj ! = null && obj.get("schema")! =null) { table.setResponseModeAttrList(processResponseModelAttrs(obj, definitinMap)); } // example 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) {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);
        }
        returnresultMap; } /** * process the request parameter list * @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"); // The array case is handled separatelyif (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); }}returnrequestList; } /** * process the list of return codes * @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(); // Status code 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);
        }
		returnresponseList; } /** * the process returns a list of properties * @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// array 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")! // object 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"));
        }
    	returnattrList; } /** * 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<>(); // Parse attributeswhile (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); }}returndefinitinMap; } /** * process the return value * @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// array 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);
	                returnarrayNode.toString(); }}else if (schema.get("$ref") != null) {
	            ref = (String)schema.get("$ref");
	            
	            ObjectNode objectNode = parseRef(ref, map);
	            returnobjectNode.toString(); }}returnStringUtils.EMPTY; } /** * parses the specified ref ** @param ref ref link from the map"Issue # / definitions/PageInfoBT Customer"* @param map is the entire Swagger JSON converted into a map object * @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; // select start from reffor (String tmp : refs) {
                if (!"#".equals(tmp)) { tmpMap = (Map<String, Object>) tmpMap.get(tmp); }} // select end from refif (tmpMap == null) {
                return objectNode;
            }
            Object properties = tmpMap.get("properties");
            if (properties == null) {
                returnobjectNode; } Map<String, Object> propertiesMap = (Map<String, Object>) properties; Set<String> keys = propertiesMap.keySet(); / / traverse the keyfor (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"); // The object is self-contained, skipping parsingif (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")! String sonRef = (String) keymap.get ("$ref"); // The object is self-contained, skipping parsingif (ref.equals(sonRef)) {
                        continue;
                    }
                    ObjectNode object = parseRef(sonRef, map);
                    objectNode.set(key, object);
                } else{// How to handle other arguments, string, int string STR ="";
                    if (keyMap.get("description") != null) {
                        str = str + keyMap.get("description");
                    }
                    if (keyMap.get("format") != null) {
                        str = str + String.format("Format (%s)", keyMap.get("format")); } objectNode.put(key, str); }}}returnobjectNode; } /** * encapsulates the post request body ** @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; }}}returnparamMap; }}Copy the code

Controller implementation code (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; /** * To convert swagger document to HTML document, you can convert swagger document to Word document by right-saving XXX. Doc on the web page ** @param model * @param URL to convert resource address to Word document * @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 ? Zero:type);
        model.addAttribute("info", result.get("info"));
        model.addAttribute("tables", result.get("tables"));
        return "word"; } /** * @param url = @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(); }}}Copy the code

4) Create an automatic configuration class and publish the service and Web interface we wrote into the Spring container. After joining the Starter, it can automatically read the configuration and successfully add its service to the new project.

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 annotations must be added, Otherwise will be an error in Swagger2WordProp @ EnableConfigurationProperties (Swagger2WordProp. Class) public class Swagger2WordAutoConfiguration { @Autowired Swagger2WordProp swagger2WordProp; // Add the WordController to @bean@conditionalonmissingBean (wordController.class) public if there are no WordController beans in the container WordController wordController(WordService wordService, RestTemplate restTemplate) {returnnew WordController(wordService, restTemplate); } // Add WordServiceImpl to @bean@conditionAlonmissingBean (wordService.class) public if there is no WordService type Bean in the container 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));
        returnrestTemplate; }}Copy the code

5) Create a Spring.factories file under Resources/meta-INF/and specify the Starter auto-configured classes.

org.springframework.boot.autoconfigure.EnableAutoConfiguration = com.fbl.Swagger2WordAutoConfiguration
Copy the code

6) The coding process is almost complete, and now it’s time to package and install the project into a local Maven repository. Run the following command:

mvn clean install
Copy the code

Use the Starter in your project

1) Add swagger2Word-starter dependency in new project

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> The < version > 2.0.0 < / version > < / dependency >Copy the code

2) Since word generation needs to support Thymeleaf, related dependencies should also be added

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Copy the code

3) Add the Starter configuration to application.properties

# swagger2Word -starterSwagger. Url = HTTP: / / http://127.0.0.1:8088/v2/api-docs# thymeleaf required configuration
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
Copy the code

4) Add templates in Resources /templates/ user generated Word.

<! 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="The ${! isSame}" th:text="${isSame ? '' : #strings.concat(iterStat.count,'. ', table.title)}"></h4> <! -- This is a description of each request, which is easy to organize after generating the document --> <h5 class="second_title" th:text="${count + ') '+ table. The 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 > Request mode </ TD >< TD colspan="4" th:text="${table.requestType}"></ TD ></ tr> <tr> < TD > Request type </ TD >< TD colSPAN ="4" th:text="${table.requestForm}"></td> </tr> <tr> < TD > Return type </ TD >< TD colspan="4" th:text="${table.responseForm}"></td>
                </tr>
    
                <tr class="bg" align="center"Parameter name > < td > < / td > < / td > < td > data type parameter type < td > < / td > < td > if required < / td > < td > description < / 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="The ${! request.require}" th:text="N"></td>
                    <td th:text="${request.remark}"></td>
                </tr>
    
                <tr class="bg" align="center"> < TD > status code </ TD > < TD colspan="2"> Description </ 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 > returns the name of the class < / td > < td > returns the attribute name < / td > < td > type < / 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"> Example </td> </tr> <tr class="specialHeight">
                    <td class="bg"> Request parameter </ TD > < TD colspan="4" th:text="${table.requestParam}"></td>
                </tr>
                <tr class="specialHeight">
                    <td class="bg"> Return value </td> < TD colspan="4" th:text="${table.responseParam}"></td>
                </tr>
                
            </table>
        </div>
    </div>
</div>
</body>
</html>
Copy the code

5) start the project, access interface at http://127.0.0.1:8088/toWord

Word file effect:

conclusion

The above code is in my Git repository: github.com/1315977663/…

The steps to implement a Starter are basically like this, of course there are a lot of things that I do badly, for example: after introducing a Starter, I also need to add templates to the application. No complete plug and play, there is a lot of room for optimization, like friends can go to github.com/1315977663/… Commit your code so that you can document the interface much less in the future.

There are few steps to implementing a Starter, but being a good Starter requires a good design that is completely decoupled from the client’s engineering. Want to design good or to master solid basic knowledge, can not rely too much on the upper framework, so the foundation is very important, only know how to use the upper framework is difficult to become an excellent program ape.

Mybatis – Spring-boot-starter will automatically register key classes such as SqlSessionFactory and SqlSessionTemplate into the Spring container. \

Start with the spring.factories file from mybatis-spring-boot-starter

In see MybatisAutoConfiguration. Class