最近公司在弄一个配置化接口平台,以达到低代码,需要为接口提供动态生成的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响应返回
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);
}
}
}