前言
前後端分離的項目中,前後端人員靠接口 文檔進行交互,swagger使用比較廣泛,在springboot中使用swagger也很方便,但是swagger存在一些不完善的地方,需要額外的配置才能讓前端更好的理解接口
引入swagger依賴
以gradle爲例:
dependencies {
compile 'io.springfox:springfox-swagger2:2.9.2'
compile 'io.springfox:springfox-swagger-ui:2.9.2'
}
新建swagger配置類
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))//掃描帶@Api註解的接口類
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
Contact contact=new Contact("項目名","","");
return new ApiInfoBuilder().title("“標題”API說明文檔").description("接口API").contact(contact).version("1.0") .build();
}
}
到這裏項目就完成swagger的引入了
如果有實體類的情況下,直接在實體類上通過註解的方式就可以將實體類發佈到接口文檔中去,字段和註釋都可以很方便的定義
但是有些接口只需要一兩個參數,再封裝一下顯得小題大做
此時可以使用map來存放參數,自己定義註解,來解析map中的參數然後聚合到swagger文檔裏
步驟如下:
編寫兩個註解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
ApiJsonProperty[] value(); //對象屬性值
String name(); //對象名稱
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
String key(); // key
String example() default "";// 示例
String type() default "string"; // 支持string、int、double
String description() default "";// 參數描述
boolean required() default true; // 是否必傳
}
編寫一個處理註解的工具類
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.jqyd.yundukao.common.annotion.ApiJsonObject;
import com.jqyd.yundukao.common.annotion.ApiJsonProperty;
import org.apache.ibatis.javassist.*;
import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
import org.apache.ibatis.javassist.bytecode.ConstPool;
import org.apache.ibatis.javassist.bytecode.annotation.Annotation;
import org.apache.ibatis.javassist.bytecode.annotation.IntegerMemberValue;
import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import java.util.Map;
@Component
@Order //plugin加載順序,默認是最後加載
public class MapApiReader implements ParameterBuilderPlugin {
@Autowired
private TypeResolver typeResolver;
@Override
public void apply(ParameterContext parameterContext) {
ResolvedMethodParameter methodParameter = parameterContext.resolvedMethodParameter();
if (methodParameter.getParameterType().canCreateSubtype(Map.class) || methodParameter.getParameterType().canCreateSubtype(String.class)) { //判斷是否需要修改對象ModelRef,這裏我判斷的是Map類型和String類型需要重新修改ModelRef對象
Optional<ApiJsonObject> optional = methodParameter.findAnnotation(ApiJsonObject.class); //根據參數上的ApiJsonObject註解中的參數動態生成Class
if (optional.isPresent()) {
String name = optional.get().name(); //model 名稱
ApiJsonProperty[] properties = optional.get().value();
parameterContext.getDocumentationContext().getAdditionalModels().add(typeResolver.resolve(createRefModel(properties, name))); //像documentContext的Models中添加我們新生成的Class
parameterContext.parameterBuilder() //修改Map參數的ModelRef爲我們動態生成的class
.parameterType("body")
.modelRef(new ModelRef(name))
.name(name);
}
}
}
private final static String basePackage = "com.jqyd.yundukao.swagger."; //動態生成的Class名
/**
* 根據propertys中的值動態生成含有Swagger註解的javaBeen
*/
private Class createRefModel(ApiJsonProperty[] propertys, String name) {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(basePackage + name);
try {
for (ApiJsonProperty property : propertys) {
ctClass.addField(createField(property, ctClass));
}
return ctClass.toClass();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據property的值生成含有swagger apiModelProperty註解的屬性
*/
private CtField createField(ApiJsonProperty property, CtClass ctClass) throws NotFoundException, CannotCompileException {
CtField ctField = new CtField(getFieldType(property.type()), property.key(), ctClass);
ctField.setModifiers(Modifier.PUBLIC);
ConstPool constPool = ctClass.getClassFile().getConstPool();
AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation ann = new Annotation("io.swagger.annotations.ApiModelProperty", constPool);
ann.addMemberValue("value", new StringMemberValue(property.description(), constPool));
if (ctField.getType().subclassOf(ClassPool.getDefault().get(String.class.getName())))
ann.addMemberValue("example", new StringMemberValue(property.example(), constPool));
if (ctField.getType().subclassOf(ClassPool.getDefault().get(Integer.class.getName())))
ann.addMemberValue("example", new IntegerMemberValue(Integer.parseInt(property.example()), constPool));
attr.addAnnotation(ann);
ctField.getFieldInfo().addAttribute(attr);
return ctField;
}
private CtClass getFieldType(String type) throws NotFoundException {
CtClass fileType = null;
switch (type) {
case "string":
fileType = ClassPool.getDefault().get(String.class.getName());
break;
case "int":
fileType = ClassPool.getDefault().get(Integer.class.getName());
break;
}
return fileType;
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
接口中使用方法如下:
@ApiOperation(value = "根據id查看基本信息頁籤")
@PostMapping("/viewBaseInfo")
public Result<ScoreWeightVO> viewBaseInfo(@ApiJsonObject(name = "SWBaseInfoMap", value = {
@ApiJsonProperty(key = "id", example = "1", description = "行標識")
}) @RequestBody Map<String, Integer> map) {
return scoreWeightBiz.viewBaseInfo(map.get("id"));
}
swagger文檔的效果如下:
這裏name必須是唯一的