动态生成swagger在线接口文档

最近公司在弄一个配置化接口平台,以达到低代码,需要为接口提供动态生成的swagger在线文档,接口描述信息记录在数据库内。经过阅读浏览多篇文章,入坑n次,终于得到可行的解决方案,以下分享给各位小伙伴。

方案一:实现ApiListingScannerPlugin,重写apply方法,缺点:每次更新数据库描述信息,需要重启微服务

package com.newpearl.core.service.config;

import com.fasterxml.classmate.TypeResolver;
import com.newpearl.common.dto.Result;
import com.newpearl.common.util.StringUtil;
import com.newpearl.core.api.model.inf.InfApiGroup;
import com.newpearl.core.api.model.inf.InfApiParameter;
import com.newpearl.core.api.model.inf.InfApiSetting;
import com.newpearl.core.biz.dao.inf.InfApiGroupDao;
import com.newpearl.core.biz.dao.inf.InfApiParameterDao;
import com.newpearl.core.biz.dao.inf.InfApiSettingDao;
import org.apache.commons.compress.utils.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.OperationBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ApiListingScannerPlugin;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spring.web.readers.operation.CachingOperationNameGenerator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * 手动将配置化接口注册到Swagger中,在Swagger-UI才能展示,方便调用。
 *
 * @author tao
 * @Date 2022年8月1日16:02:50
 */
@Component
public class OpenPlatformSwaggerApis implements ApiListingScannerPlugin {

    @Autowired
    private InfApiGroupDao apiGroupDao;

    @Autowired
    private InfApiSettingDao apiSettingDao;

    @Autowired
    private InfApiParameterDao apiParameterDao;

    @Override
    public List apply(DocumentationContext documentationContext) {
        String tagName = "配置化接口开放平台";
//        documentationContext.getTags().add(new Tag(tagName, "Open Platform Controller"));
        List apiSettingList = apiSettingDao.findAll(false);
        List apiDescriptionList = new ArrayList<>();
        //接口配置
        for (InfApiSetting apiInfo : apiSettingList) {
            List parameterList = apiParameterDao.findByApiCode(apiInfo.getApiCode());
            List apiParaList = new ArrayList<>();
            //接口参数
            for (InfApiParameter parameter : parameterList) {
                if (parameter.getOutput() != null && parameter.isOutput()) {
                    continue;
                }
                Parameter para = new ParameterBuilder()
                        .name(StringUtil.isEmpty(parameter.getParaFieldOtherName()) ? parameter.getParaField() : parameter.getParaFieldOtherName())
                        .description(StringUtil.isEmpty(parameter.getMemo()) ? parameter.getParaField() : parameter.getMemo())
                        .type(new TypeResolver().resolve(String.class))
                        .modelRef(new ModelRef("string"))
                        .parameterType("query")
                        .required(true)
                        .defaultValue(parameter.getParaDefaultValue())
                        .build();
                apiParaList.add(para);
            }
            //如果分页增加分页参数
            if (apiInfo.getPaging() != null && apiInfo.isPaging()) {
                Parameter para = new ParameterBuilder()
                        .name("currPage")
                        .description("当前页")
                        .type(new TypeResolver().resolve(String.class))
                        .modelRef(new ModelRef("string"))
                        .parameterType("query")
                        .defaultValue("1")
                        .required(false)
                        .build();
                apiParaList.add(para);
                para = new ParameterBuilder()
                        .name("pageSize")
                        .description("每页大小")
                        .type(new TypeResolver().resolve(String.class))
                        .modelRef(new ModelRef("string"))
                        .parameterType("query")
                        .defaultValue("20")
                        .required(false)
                        .build();
                apiParaList.add(para);
            }
            //每个接口增加报文类型参数
            Parameter para = new ParameterBuilder()
                    .name("rtnType")
                    .description("报文类型(json/xml,默认json)")
                    .type(new TypeResolver().resolve(String.class))
                    .modelRef(new ModelRef("string"))
                    .parameterType("query")
                    .defaultValue("json")
                    .required(false)
                    .build();
            apiParaList.add(para);
            //每个接口增加appId类型参数
            Parameter appIdPara = new ParameterBuilder()
                    .name("appId")
                    .description("应用ID")
                    .type(new TypeResolver().resolve(String.class))
                    .modelRef(new ModelRef("string"))
                    .parameterType("query")
                    .required(false)
                    .build();
            apiParaList.add(appIdPara);
            //接口
            HashSet responseMessagesSets = new HashSet();
            responseMessagesSets.add(new ResponseMessageBuilder().code(200).message("OK").responseModel(new ModelRef("Result")).build());
            responseMessagesSets.add(new ResponseMessageBuilder().code(201).message("Created").build());
            responseMessagesSets.add(new ResponseMessageBuilder().code(401).message("Unauthorized").build());
            responseMessagesSets.add(new ResponseMessageBuilder().code(403).message("Forbidden").build());
            responseMessagesSets.add(new ResponseMessageBuilder().code(404).message("Not Found").build());
            documentationContext.getTags().add(new Tag(tagName + "-" + apiInfo.getApiGroupName(), "Open Platform Controller"));
            Operation operation = new OperationBuilder(new CachingOperationNameGenerator())
                    .method(HttpMethod.POST)
                    .uniqueId(apiInfo.getApiCode())
                    .responseModel(new ModelRef("*/*"))
                    .summary(apiInfo.getApiCode() + " " + apiInfo.getApiName())
//                    .tags(Sets.newHashSet(tagName))
                    .tags(Sets.newHashSet(tagName + "-" + apiInfo.getApiGroupName()))
                    .responseMessages(responseMessagesSets)
                    .consumes(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
                    .produces(Sets.newHashSet(MediaType.ALL_VALUE))
                    .parameters(apiParaList)
                    .build();

            //接口描述
            ApiDescription description = new ApiDescription(apiInfo.getApiGroupName(), "/open/inf/base/" + apiInfo.getApiUrlMethod(), apiInfo.getApiName(), Arrays.asList(operation), false);
            apiDescriptionList.add(description);
            description.getPath();
        }
        return apiDescriptionList;
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
//        return DocumentationType.SWAGGER_2.equals(documentationType);
        return true;
    }

}

方案二:使用spring aop(面向切面编程)

需要知道:

1、swagger的接口描述信息是通过请求访问http://199.188.188.12:8889/v2/api-docs得到的,重点关注端口号后面那段/v2/api-docs

2、那有请求地址,那肯定有映射到的方法,重点就是这个方法,查看源码,找到Swagger2Controller,重点关注的就是getDocumentation这方法(请求就是映射到该方法),该方法主要做一件这样的事:将接口描述信息从缓存取出,然后转换为json响应返回

动态生成swagger在线接口文档_第1张图片

ok,实战,利用aop 环绕通知,在取swagger对象时,将swagger对象重新封装,切点就是mapDocumentation这个方法。

package com.newpearl.core.service.config;

import com.newpearl.common.util.StringUtil;
import com.newpearl.core.api.model.inf.InfApiParameter;
import com.newpearl.core.api.model.inf.InfApiSetting;
import com.newpearl.core.biz.dao.inf.InfApiParameterDao;
import com.newpearl.core.biz.dao.inf.InfApiSettingDao;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Response;
import io.swagger.models.Swagger;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.QueryParameter;
import io.swagger.models.properties.RefProperty;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 拦截swagger,重新封装
 *
 * @author tao
 * @Date 2022年8月1日16:02:50
 */
@Aspect
@Component
public class OpenPlatformSwaggerAop {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private InfApiParameterDao apiParameterDao;

    @Autowired
    private InfApiSettingDao apiSettingDao;

    /**
     * 切点
     */
    @Pointcut(value = "execution(public * springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl.mapDocumentation(..))")
    public void point(){

    }

    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Swagger swagger = (Swagger) proceedingJoinPoint.proceed();

        Map paths = swagger.getPaths();
        ListkeyList=new ArrayList<>();
        for(String key:paths.keySet()){
            if(key.toLowerCase().indexOf("/open/inf/base/")>-1&&!(key.toLowerCase().indexOf("/open/inf/base/*")>-1)){
                keyList.add(key);
            }
        }
        for(String key:keyList){
            paths.remove(key);
        }
        createOpenPlatformApiPath(paths);
        return swagger;
    }

    public void createOpenPlatformApiPath(Map paths) {
        String tagName = "配置化接口开放平台";
        List apiSettingList = apiSettingDao.findAll(false);
        //接口配置
        for (InfApiSetting apiInfo : apiSettingList) {
            List parameterList = apiParameterDao.findByApiCode(apiInfo.getApiCode());
            List apiParaList = new ArrayList<>();
            //接口参数
            for (InfApiParameter parameter : parameterList) {
                if (parameter.getOutput() != null && parameter.isOutput()) {
                    continue;
                }
                QueryParameter queryParameter=new QueryParameter();
                queryParameter.setName(StringUtil.isEmpty(parameter.getParaFieldOtherName()) ? parameter.getParaField() : parameter.getParaFieldOtherName());
                queryParameter.description(StringUtil.isEmpty(parameter.getMemo()) ? parameter.getParaField() : parameter.getMemo());
                queryParameter.setType("string");
                queryParameter.setIn("query");
                queryParameter.required(true);
                queryParameter.setDefaultValue(parameter.getParaDefaultValue());
                apiParaList.add(queryParameter);
            }
            //如果分页增加分页参数
            if(apiInfo.getPaging()!=null&&apiInfo.isPaging()){
                QueryParameter queryParameter=new QueryParameter();
                queryParameter.setName("currPage");
                queryParameter.description("当前页");
                queryParameter.setType("string");
                queryParameter.setIn("query");
                queryParameter.setDefaultValue("1");
                queryParameter.required(false);
                apiParaList.add(queryParameter);
                queryParameter=new QueryParameter();
                queryParameter.setName("pageSize");
                queryParameter.description("每页大小");
                queryParameter.setType("string");
                queryParameter.setIn("query");
                queryParameter.setDefaultValue("20");
                queryParameter.required(false);
                apiParaList.add(queryParameter);
            }

            //每个接口增加报文类型参数
            QueryParameter queryParameter=new QueryParameter();
            queryParameter.setName("rtnType");
            queryParameter.description("报文类型(json/xml,默认json)");
            queryParameter.setType("string");
            queryParameter.setIn("query");
            queryParameter.setDefaultValue("json");
            queryParameter.required(false);
            apiParaList.add(queryParameter);
            //每个接口增加appId类型参数
            QueryParameter appIdPara=new QueryParameter();
            appIdPara.setName("appId");
            appIdPara.description("应用ID");
            appIdPara.setType("string");
            appIdPara.setIn("query");
            appIdPara.setDefaultValue("*");
            appIdPara.required(false);
            apiParaList.add(appIdPara);

            Operation operation=new Operation();
            operation.setParameters(apiParaList);
            Listproduces=new ArrayList<>();
            produces.add(MediaType.ALL_VALUE);
            operation.setProduces(produces);
            Listconsumes=new ArrayList<>();
            consumes.add(MediaType.APPLICATION_JSON_VALUE);
            operation.setConsumes(consumes);
            Map responses=new HashMap<>();

            Response response=new Response();
            response.setDescription("OK");
            response.setSchema(new RefProperty().asDefault("Result"));
            responses.put("200",response);

            response=new Response();
            response.setDescription("Created");
            response.setSchema(new RefProperty().asDefault("Result"));
            responses.put("201",response);

            response=new Response();
            response.setDescription("Unauthorized");
            response.setSchema(new RefProperty().asDefault("Result"));
            responses.put("401",response);

            response=new Response();
            response.setDescription("Forbidden");
            response.setSchema(new RefProperty().asDefault("Result"));
            responses.put("403",response);

            response=new Response();
            response.setDescription("Not Found");
            response.setSchema(new RefProperty().asDefault("Result"));
            responses.put("404",response);
            operation.setResponses(responses);
            Listtags=new ArrayList<>();
            tags.add(tagName+"-"+apiInfo.getApiGroupName());
            operation.setTags(tags);
            operation.setSummary(apiInfo.getApiCode()+" "+apiInfo.getApiName());
            operation.setOperationId(apiInfo.getApiCode());
            //接口响应设置
            operation.setResponses(responses);
            Path path=new Path();
            path.set("post",operation);
            paths.put("/open/inf/base/"+apiInfo.getApiUrlMethod(),path);
        }
    }


}

你可能感兴趣的:(java,spring,spring,boot)