After the Dataway integrated swagger interface is updated, it cannot be updated on the swagger web side

Use version

hasor-spring: 4.2.5
hasor-dataway: 4.2.5
springboot 2.5.0
swagger:springfox-boot-starter 3.0.0

Description of the problem

After Dataway configures swagger, the interface document will always display the content published by the Dataway interface for the first time. After modification and re-release, it cannot be updated to the latest record.

Issue Tracking

The interaction between Dataway and swagger is through the public default interface /api/docs/swagger2.json provided by Dataway, and this interface has not been updated after the Dataway interface was updated and re-released. Cause swagger-ui can’t update the document.
Swagger2Controller is the /api/docs/swagger2.json interface provided by Dataway

Dataway provides two tables
interface_release (after release, add a record)
interface_info (record information in real time, save update)

Error location 1: The history table “EntityDef.RELEASE” and “EntityDef.INFO” are queried (since our requirement is to only display the published interface documents, this solution is cancelled. Note: This version does not work, and the As a result, there is no interface data, the reason is sql, and a key field api_schema was missing)
Error location 2: If an interface has multiple duplicate records, it will select the oldest record

Solution ideas

When writing an interface, query the data in the table, and return the specified format, the method is as follows

  1. Write Swagger3Controller like Swagger2Controller
  2. custom interface
  3. Rewrite Swqgger2Query, (but because this class does not understand, so give up this method; if you understand, please let me know)

Therefore, in order to be lazy, choose method 1 as a solution
——–First imitate Swagger2Controller

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import net.hasor.dataql.fx.basic.StringUdfSource;
import net.hasor.dataway.config.DatawayUtils;
import net.hasor.dataway.config.MappingToUrl;
import net.hasor.dataway.dal.EntityDef;
import net.hasor.dataway.dal.FieldDef;
import net.hasor.dataway.web.BasicController;
import net.hasor.dataway.web.Swagger2Query;
import net.hasor.utils.StringUtils;
import net.hasor.web.Invoker;
import net.hasor.web.annotation.Get;
import net.hasor.web.objects.JsonRenderEngine;
import net.hasor.web.render.RenderType;

@MappingToUrl("/api/docs/swagger3.json")
@RenderType(
        value = "json",
        engineType = JsonRenderEngine.class
)
public class Swagger3Controller extends BasicController {<!-- -->
    public Swagger3Controller() {<!-- -->
    }

    @Get
    public Object doSwaggerApi(Invoker invoker) throws IOException {<!-- -->
        HttpServletRequest httpRequest = invoker.getHttpRequest();
        String localName = httpRequest. getHeader("Host");
        if (StringUtils.isBlank(localName)) {<!-- -->
            int localPort = httpRequest. getLocalPort();
            localName = httpRequest.getLocalAddr() + (localPort == 80 ? "" : ":" + localPort);
        }

        List<Map<FieldDef, String>> doList = this.dataAccessLayer.listObjectBy(EntityDef.RELEASE, emptyCondition());
        List<Map<String,Object>> list=new ArrayList<>();
        // Here the acquired data is classified by interface id
        Map<String, List<Map<FieldDef, String>>> map = doList.stream().collect(Collectors.groupingBy(item -> item.get(FieldDef.API_ID)));
        List<Map<FieldDef, String>> doListNew = new ArrayList<>();
        // Select the first in each group, that is, add the newest record to the collection, and then use the newly defined collection to pass down
        map.forEach((k,v)->{<!-- -->
            doListNew.add(v.get(0));
        });
        final List<Map<String, String>> collectList = (List)doListNew.stream().map((defMap) -> {<!-- -->
            Map<String, String> dataMap = new HashMap();
            defMap.forEach((fieldDef, s) -> {<!-- -->
                dataMap.put(StringUdfSource.lineToHump(fieldDef.name()), s);
            });
            return dataMap;
        }).collect(Collectors.toList());
       

        String contextPathProxy = invoker.getHttpRequest().getParameter("DW_CONTEXT_PATH_PROXY");
        final String contextPath = DatawayUtils.getDwContextPath(invoker, contextPathProxy);
        final String locName = localName;
        return (new Swagger2Query()).execute(new HashMap<String, Object>() {<!-- -->
            {<!-- -->
                this.put("apiDataList", collectList);
                this.put("serverHost", locName);
                this.put("serverBasePath", StringUtils.isNotBlank(contextPath) ? contextPath : "/");
            }
        }).getData().unwrap();
    }
}

-Check the source code to know the Swagger2Controller interface loaded in DatawayModule; imitate the loading process,
Since we also define a Module configuration class when integrating Dataway, we can directly load the Swagger3Controller we wrote there

import com.bksx.easy_config.dataway.controller.Swagger3Controller;
import com.bksx.easy_config.dataway.spi.FxSqlCheckChain;
import net.hasor.core.ApiBinder;
import net.hasor.core.DimModule;
import net.hasor.dataql.DimUdf;
import net.hasor.dataql.DimUdfSource;
import net.hasor.dataql.QueryApiBinder;
import net.hasor.dataql.fx.db.FxSqlCheckChainSpi;
import net.hasor.dataway.config.MappingToUrl;
import net.hasor.db.JdbcModule;
import net.hasor.db.Level;
import net.hasor.spring.SpringModule;
import net.hasor.utils.StringUtils;
import net.hasor.web.WebApiBinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.sql.DataSource;

@DimModule
@Component
public class HasorDataModule implements SpringModule {<!-- -->
    @Autowired
    private DataSource dataSource = null;

    @Autowired
    private Environment environment;

    @Override
    public void loadModule(ApiBinder apiBinder) throws Throwable {<!-- -->
        // .DataSource form Spring boot into Hasor
        apiBinder.installModule(new JdbcModule(Level.Full, this.dataSource));
        //apiBinder.loadModule(LoadSwagger3Module.class);
        //((WebApiBinder) apiBinder).loadMappingTo(Swagger3Controller.class);
        // load custom query method
        // .custom DataQL
        apiBinder.tryCast(QueryApiBinder.class).loadUdfSource(apiBinder.findClass(DimUdf.class)); // register tool class and query class
        apiBinder.tryCast(QueryApiBinder.class).loadUdfSource(apiBinder.findClass(DimUdfSource.class)); // register tool class and query class
        //apiBinder.tryCast(QueryApiBinder.class).bindFragment("sql", SqlFragment.class);//Register custom syntax
        //apiBinder.bindSpiListener(AuthorizationChainSpi.class,AuthorizationChain.class);// Register background user verification interceptor
        //apiBinder.bindSpiListener(CompilerSpiListener.class,CompilerListener.class);// Register compilation interceptor, you can rewrite the script
        apiBinder.bindSpiListener(FxSqlCheckChainSpi.class, FxSqlCheckChain.class);// register sql verification interceptor
        //apiBinder.bindSpiListener(LoginPerformChainSpi.class, LoginPerformChain.class);// Register background user authentication interceptor
        //apiBinder.bindSpiListener(LoginTokenChainSpi.class, LoginTokenChain.class);// Register background user authentication interceptor
        //apiBinder.bindSpiListener(LookupConnectionListener.class,LookupConnectionListeners.class);// Register data source interceptor, customize dynamic data source
        //apiBinder.bindSpiListener(LookupDataSourceListener.class, LookupDataSourceListeners.class);// Register data source interceptor
        //apiBinder.bindSpiListener(PreExecuteChain.class,PreExecuteChainSpi.class);// Register request interceptor
        //apiBinder.bindSpiListener(ResultProcessChain.class,ResultProcessChainSpi.class);// Register return result interceptor (success, failure)
        //apiBinder.bindSpiListener(SerializationChain.class,SerializationChainSpi.class);// Register serializer

       // Add Dataway interface public method
        addControllers(apiBinder, Swagger3Controller. class);

    }

    /**
     * Add to
     * @param apiBinder
     * @param aClasss
     */
    private void addControllers(ApiBinder apiBinder,Class... aClasss){<!-- -->
        String enable = environment. getProperty("HASOR_DATAQL_DATAWAY");
        if(StringUtils.isBlank(enable)||"false".equals(enable)){<!-- -->
            return;
        }
        String adminUrl=environment.getProperty("HASOR_DATAQL_DATAWAY_UI_URL");
        Assert.notNull(adminUrl,"Dataway is enabled, you must specify the management access path: HASOR_DATAQL_DATAWAY_UI_URL");

        String apiUrl = environment. getProperty("HASOR_DATAQL_DATAWAY_API_URL");
        Assert.notNull(apiUrl,"Dataway is enabled, API access path must be specified: HASOR_DATAQL_DATAWAY_API_URL");

        WebApiBinder webApiBinder = (WebApiBinder)apiBinder. tryCast(WebApiBinder. class);
        if (webApiBinder != null & amp; & amp;aClasss!=null & amp; & amp;aClasss.length>0) {<!-- -->
            for (Class aClass : aClasss) {<!-- -->
                ApiBinder.MetaDataBindingBuilder<?> metaDataBinder = apiBinder.bindType(aClass).asEagerSingleton();
                metaDataBinder.metaData("KEY_DATAWAY_UI_BASE_URI", adminUrl);
                metaDataBinder.metaData("KEY_DATAWAY_API_BASE_URI", apiUrl);
                MappingToUrl toUrl = (MappingToUrl)aClass.getAnnotation(MappingToUrl.class);
                String url = (adminUrl + toUrl. value()). replaceAll("/ + ", "/");
                webApiBinder.mappingTo(url, new String[0]).with(metaDataBinder.toInfo());
            }

        }
    }

}

Modify the Dataway interface path of the swagger configuration

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.ArrayList;
import java.util.List;

@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {<!-- -->
    @Override
    public List<SwaggerResource> get() {<!-- -->
        List<SwaggerResource> resources = new ArrayList<>();
        resources.add(swaggerResource("Application Interface", "/v3/api-docs", "1.0"));
        // Modify the new path
        resources.add(swaggerResource("Dataway interface", "/interface-ui/api/docs/swagger3.json", "1.0"));
        return resources;
    }

    private SwaggerResource swaggerResource(String name, String location, String version) {<!-- -->
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location);
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}


The reason for encapsulating it into a method is to load multiple custom DataWay interfaces at the same time
Access path: http://ip:port/data/interface-ui/api/docs/swagger3.json