SpringFox 提供给我们了一个ParameterBuilderPlugin接口,通过这个接口我们可以在SpringFox构造Map参数映射的ModelRef时使用javassist动态的生成类,并把这个map参数的modelRef对象指向我们动态生成的具体Class对象(通过自定义注解在Map参数上生成可表示JSON结构的类)
1、ApiJsonProperty注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Administrator
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty {
//key
String key();
String example() default "";
//支持string 和 int
String type() default "string";
String description() default "";
}
2、ApiJsonObject注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Administrator
*/
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
//对象属性值
ApiJsonProperty[] value();
//对象名称
String name();
}
3、MapApiReader类
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import 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;
/**
* @author Administrator
* plugin加载顺序,默认是最后加载
*/
@Component
@Order
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 BASE_PACKAGE = "com.xx.in.swagger.model.";
//动态生成的Class名
/**
* 根据propertys中的值动态生成含有Swagger注解的javaBeen
*/
private Class createRefModel(ApiJsonProperty[] propertys, String name) {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(BASE_PACKAGE + 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;
default:
fileType = ClassPool.getDefault().get(String.class.getName());
break;
}
return fileType;
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
4、Controller控制器
/**
* 会员登录(通过短信或账号密码)
*/
@ApiOperation(value = "通过短信或账号密码登录")
@RequestMapping(value = "/signIn", method = RequestMethod.POST)
@CrossOrigin
public ResponseObject signIn(@ApiJsonObject(name = "param", value = {
@ApiJsonProperty(key = "mobile", example = "15677888899", description = "通过短信验证码登录:手机号"),
@ApiJsonProperty(key = "verifyCode", example = "123456", description = "通过短信验证码登录:短信验证码"),
@ApiJsonProperty(key = "account", example = "qiaochu", description = "通过账号密码登录:登录账号"),
@ApiJsonProperty(key = "password", example = "123456", description = "通过账号密码登录:登录密码"),
@ApiJsonProperty(key = "captchaCode", example = "57ma", description = "通过账号密码登录:图形验证码"),
@ApiJsonProperty(key = "uuid", example = "b65d461893a14f2899e1287643391981", description = "通过账号密码登录:图形验证码uuid")
}) @RequestBody Map<String, Object> param) {
// TODO::接收参数处理业务逻辑
// String mobile = param.get("mobile") == null ? "" : param.get("mobile").toString();
// String verifyCode = param.get("verifyCode") == null ? "" : param.get("verifyCode").toString();
// String account = param.get("account") == null ? "" : param.get("account").toString();
// String password = param.get("password") == null ? "" : param.get("password").toString();
// String captchaCode = param.get("captchaCode") == null ? "" : param.get("captchaCode").toString();
// String uuid = param.get("uuid") == null ? "" : param.get("uuid").toString();
}
注意:整个工程中ApiJsonObject种的name值不能相同,否则会报错
如果整个工程中ApiJsonObject种的name值有多个相同,升级ApiJsonProperty注解和MapApiReader类,方案如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Administrator
*/
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject {
//对象属性值
ApiJsonProperty[] value();
//对象名称
String name();
//对象方法
String method();
}
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.IntegerMemberValue;
import 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;
/**
* @author Administrator
* plugin加载顺序,默认是最后加载
*/
@Component
@Order
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 = String.format("%s_%s", optional.get().name(), optional.get().method());
//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(optional.get().name());
}
}
}
/**
* 动态生成的Class名
*/
private final static String BASE_PACKAGE = "com.fuint.in.swagger.model.";
/**
* 根据propertys中的值动态生成含有Swagger注解的javaBeen
*/
private Class createRefModel(ApiJsonProperty[] propertys, String name) {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass(BASE_PACKAGE + 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;
default:
fileType = ClassPool.getDefault().get(String.class.getName());
break;
}
return fileType;
}
@Override
public boolean supports(DocumentationType delimiter) {
return true;
}
}
/**
* 会员登录(通过短信或账号密码)
*/
@ApiOperation(value = "通过短信或账号密码登录")
@RequestMapping(value = "/signIn", method = RequestMethod.POST)
@CrossOrigin
public ResponseObject signIn(@ApiJsonObject(method = "signIn", name = "param", value = {
@ApiJsonProperty(key = "mobile", example = "15677888899", description = "通过短信验证码登录:手机号"),
@ApiJsonProperty(key = "verifyCode", example = "123456", description = "通过短信验证码登录:短信验证码"),
@ApiJsonProperty(key = "account", example = "qiaochu", description = "通过账号密码登录:登录账号"),
@ApiJsonProperty(key = "password", example = "123456", description = "通过账号密码登录:登录密码"),
@ApiJsonProperty(key = "captchaCode", example = "57ma", description = "通过账号密码登录:图形验证码"),
@ApiJsonProperty(key = "uuid", example = "b65d461893a14f2899e1287643391981", description = "通过账号密码登录:图形验证码uuid")
}) @RequestBody Map<String, Object> param) {
// TODO::接收参数处理业务逻辑
// String mobile = param.get("mobile") == null ? "" : param.get("mobile").toString();
// String verifyCode = param.get("verifyCode") == null ? "" : param.get("verifyCode").toString();
// String account = param.get("account") == null ? "" : param.get("account").toString();
// String password = param.get("password") == null ? "" : param.get("password").toString();
// String captchaCode = param.get("captchaCode") == null ? "" : param.get("captchaCode").toString();
// String uuid = param.get("uuid") == null ? "" : param.get("uuid").toString();
}
注意: 整个工程中ApiJsonObject种的name_method值不能相同,否则会报错