使用 Swagger 的扩展组件Plugin 机制自定义API文
简史
让我们先理一下springfox与swagger的关系。
swagger是一个流行的API开发框架,这个框架以“开放API声明”(OpenAPI Specification,OAS)为基础,对整个API的开发周期都提供了相应的解决方案,是一个非常庞大的项目(包括设计、编码和测试,几乎支持所有语言)。
OAS本身是一个API规范,它用于描述一整套API接口,包括一个接口是GET还是POST请求啊,有哪些参数哪些header啊,都会被包括在这个文件中。它在设计的时候通常是YAML格式,这种格式书写起来比较方便,而在网络中传输时又会以json形式居多,因为json的通用性比较强。
由于Spring的流行,Marty Pitt编写了一个基于Spring的组件swagger-springmvc,用于将swagger集成到springmvc中来。而springfox则是从这个组件发展而来,同时springfox也是一个新的项目,本文仍然是使用其中的一个组件springfox-swagger2。
pringfox-swagger2依然是依赖OSA规范文档,也就是一个描述API的json文件,而这个组件的功能就是帮助我们自动生成这个json文件,我们会用到的另外一个组件springfox-swagger-ui就是将这个json文件解析出来,用一种更友好的方式呈现出来。
SpringFox
Automated JSON API documentation for API's built with Spring.
Getting Started
For new projects
For Maven
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
For Gradle
implementation "io.springfox:springfox-boot-starter:<version>"
Swagger的可扩展组件
在源码中( https://github.com/springfox/springfox ), 可以看到下图所示的一些Plugin结尾的接口文件,我们就是要在这些上面做文章的。
自定义扩展功能的话,只需要实现某个xxxPlugin的接口中的apply方法就可以。apply方法中我们去手动扫描我们自定义的注解,然后加上相关实现的逻辑即可。
代码示例:
/**
* 针对传值的参数自定义注解
* @author zhenghui
* @date 2020年9月13日13:25:18
* @desc 读取自定义的属性并动态生成model
*/
@Configuration
@Order(-19999) //plugin加载顺序,默认是最后加载
public class SwaggerModelReader implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
static final Map<String,String> MAPS = new HashMap<>();
static {
MAPS.put("byte","java.lang.Byte");
MAPS.put("short","java.lang.Short");
MAPS.put("integer","java.lang.Integer");
MAPS.put("long","java.lang.Long");
MAPS.put("float","java.lang.Float");
MAPS.put("double","java.lang.Double");
MAPS.put("char","java.lang.Character");
MAPS.put("string","java.lang.String");
MAPS.put("boolean","java.lang.Boolean");
}
//根据用户自定义的类型拿到该类型所在的包的class位置
static public String getTypePath(String key){
return key==null || !MAPS.containsKey(key.toLowerCase()) ? null : MAPS.get(key.toLowerCase());
}
@Override
public void apply(ParameterContext context) {
ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();
//自定义的注解
Optional<ApiIgp> apiIgp = methodParameter.findAnnotation(ApiIgp.class);
Optional<Apicp> apicp = methodParameter.findAnnotation(Apicp.class);
if (apiIgp.isPresent() || apicp.isPresent()) {
Class originClass = null;
String[] properties = null; //注解传递的参数
Integer annoType = 0;//注解的类型
String name = null + "Model" + 1; //model 名称 //参数名称
String[] noValues = null;
String[] noValueTypes = null;
String[] noVlaueExplains = null;
//拿到自定义注解传递的参数
if (apiIgp.isPresent()){
properties = apiIgp.get().values(); //排除的
originClass = apiIgp.get().classPath();//原始对象的class
name = apiIgp.get().modelName() ; //model 名称 //参数名称
noValues = apiIgp.get().noValues();
noValueTypes = apiIgp.get().noValueTypes();
noVlaueExplains = apiIgp.get().noVlaueExplains();
}else {
properties = apicp.get().values(); //需要的
annoType = 1;
originClass = apicp.get().classPath();//原始对象的class
name = apicp.get().modelName() ;//自定义类的名字
noValues = apicp.get().noValues();
noValueTypes = apicp.get().noValueTypes();
noVlaueExplains = apicp.get().noVlaueExplains();
}
//生成一个新的类
Class newClass = createRefModelIgp(properties, noValues, noValueTypes, noVlaueExplains, name, originClass, annoType);
context.getDocumentationContext()
.getAdditionalModels()
.add(typeResolver.resolve(newClass)); //向documentContext的Models中添加我们新生成的Class
context.parameterBuilder() //修改model参数的ModelRef为我们动态生成的class
.parameterType("body")
.modelRef(new ModelRef(name))
.name(name);
}
}
/**
*
* @param properties annoType=1:需要的 annoType=0:排除的
* @param noValues
* @param noValueTypes
* @param noVlaueExplains
* @param name 创建的mode的名称
* @param origin
* @param annoType 注解的类型
* @return
*/
private Class createRefModelIgp(String[] properties, String[] noValues, String[] noValueTypes, String[] noVlaueExplains, String name, Class origin, Integer annoType) {
try {
//获取原始实体类中所有的变量
Field[] fields = origin.getDeclaredFields();
//转换成List集合,方便使用stream流过滤
List<Field> fieldList = Arrays.asList(fields);
//把传入的参数也转换成List
List<String> dealProperties = Arrays.asList(properties);//去掉空格并用逗号分割
//过滤出来已经存在的
List<Field> dealFileds = fieldList
.stream()
.filter(s ->
annoType==0 ? (!(dealProperties.contains(s.getName()))) //如果注解的类型是0,说明要取反
: dealProperties.contains(s.getName())
).collect(Collectors.toList());
//存储不存在的变量
List<String> noDealFileds = Arrays.asList(noValues);
List<String> noDealFiledTypes = Arrays.asList(noValueTypes);
List<String> noDealFiledExplains = Arrays.asList(noVlaueExplains);
//创建一个类
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(origin.getPackage().getName()+"."+name);
//创建对象,并把已有的变量添加进去
createCtFileds(dealFileds,noDealFileds,noDealFiledTypes,noDealFiledExplains,ctClass,annoType);
//返回最终的class
return ctClass.toClass();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
/**
* 根据propertys中的值动态生成含有Swagger注解的javaBeen
*
* @param dealFileds 原始对象中已经存在的对象属性名字
* @param noDealFileds 原始对象中不存在的对象属性名字
* @param noDealFiledTypes 原始对象中不存在的对象属性的类型,八大基本类型例如:dounle等,还有String
* @param noDealFiledExplains 自定义变量的参数说明
* @param ctClass 源class
* @throws CannotCompileException
* @throws NotFoundException
* @throws ClassNotFoundException
*/
public void createCtFileds(List<Field> dealFileds, List<String> noDealFileds, List<String> noDealFiledTypes,List<String> noDealFiledExplains, CtClass ctClass, Integer annoType) {
//添加原实体类存在的的变量
// if(annoType==1)
for (Field field : dealFileds) {
CtField ctField = null;
try {
ctField = new CtField(ClassPool.getDefault().get(field.getType().getName()), field.getName(), ctClass);
} catch (CannotCompileException e) {
System.out.println("找不到了1:"+e.getMessage());
} catch (NotFoundException e) {
System.out.println("找不到了2:"+e.getMessage());
}
ctField.setModifiers(Modifier.PUBLIC);
ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
String apiModelPropertyValue = java.util.Optional.ofNullable(annotation).map(s -> s.value()).orElse("");
if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
ConstPool constPool = ctClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
attr.addAnnotation(ann);
ctField.getFieldInfo().addAttribute(attr);
}
try {
ctClass.addField(ctField);
} catch (CannotCompileException e) {
System.out.println("无法添加字段1:"+e.getMessage());
}
}
//添加原实体类中不存在的的变量
for (int i = 0; i < noDealFileds.size(); i++) {
String valueName = noDealFileds.get(i);//变量名字
String valueType = noDealFiledTypes.get(i);//变量的类型
valueType=getTypePath(valueType);
//根据变量的类型,变量的名字,变量将要在的类 创建一个变量
CtField ctField = null;
try {
ctField = new CtField(ClassPool.getDefault().get(valueType), valueName, ctClass);
} catch (CannotCompileException e) {
System.out.println("找不到了3:"+e.getMessage());
} catch (NotFoundException e) {
System.out.println("找不到了4:"+e.getMessage());
}
ctField.setModifiers(Modifier.PUBLIC);//设置权限范围是私有的,或者public等
if(noDealFiledExplains.size()!=0){
//参数设置描述
String apiModelPropertyValue = (apiModelPropertyValue=noDealFiledExplains.get(i))==null?"无描述":apiModelPropertyValue;//参数描述
System.out.println(apiModelPropertyValue);
if (StringUtils.isNotBlank(apiModelPropertyValue)) { //添加model属性说明
ConstPool constPool = ctClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
ann.addMemberValue("value", new StringMemberValue(apiModelPropertyValue,constPool));
attr.addAnnotation(ann);
ctField.getFieldInfo().addAttribute(attr);
}
}
//把此变量添加到类中
try {
ctClass.addField(ctField);
} catch (CannotCompileException e) {
System.out.println("无法添加字段2:"+e.getMessage());
}
}
}
}
Swagger 常用注解
@Api
用在类上,说明该类的作用
@Api(value = "UserController", description = "用户相关api")
@ApiOperation
用在方法上,说明方法的作用
@ApiOperation(value = "查找用户", notes = "查找用户", httpMethod = "GET", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ApiImplicitParams
用在方法上包含一组参数说明
@ApiImplicitParam
用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
paramType:参数放在哪个地方
header–>请求参数的获取:@RequestHeader
query–>请求参数的获取:@RequestParam
path(用于restful接口)–>请求参数的获取:@PathVariable
body(不常用)
form(不常用)
name:参数名
dataType:参数类型
required:参数是否必须传
value:参数的意思
defaultValue:参数的默认值
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "唯一id", required = true, dataType = "Long", paramType = "path"),
})
@ApiResponses
用于表示一组响应
@ApiResponse
用在@ApiResponses中,一般用于表达一个错误的响应信息
code:数字,例如400
message:信息,例如”请求参数没填好”
response:抛出异常的类
@ApiResponses(value = {
@ApiResponse(code = 400, message = "No Name Provided")
})
@ApiModel
Swagger-core builds the model definitions based on the references to them throughout the API introspection.
The @ApiModel allows you to manipulate the meta data of a model from a simple description or name change to a definition of polymorphism.
描述一个Model的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候)
@ApiModel(value = "用户实体类")
@ApiModelProperty
描述一个model的属性
@ApiModelProperty(value = "登录用户")
@ApiIgnore //使用这个注解忽略这个接口
参考资料
https://blog.csdn.net/qq_17623363/article/details/109259315
https://blog.csdn.net/wsh900221/article/details/80508548