栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

解决项目使用swagger频繁创建暴露前台入参问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

解决项目使用swagger频繁创建暴露前台入参问题

当前台每次请求需要的字段不一样,但是我们还不想把多余的字段暴露给前台的时候,我们肯定需要重服创建琐碎的字段,可能因为前台多要了一个字段又要创建一个新的实体类,项目里多余又难看,那该怎么办呢,用用以下方式:

 这是自定义的两个注解我拷贝在下面,我也是自己找的,大家需要自己拷贝吧;

注解@ApiGlobalModel的代码:

package com.dpi.service.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;



@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiGlobalModel {

    
    Class component();

    
    String separator() default ",";

    
    String[] value() default {};
}

处理注解@ApiGlobalModel逻辑的代码:

package com.dpi.service.annotation.impl;


import com.dpi.service.annotation.ApiGlobalModel;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.MemberValue;
import lombok.extern.slf4j.Slf4j;
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.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

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


@Component
@Order
@Slf4j
public class ApiGlobalModelBuilder implements ParameterBuilderPlugin {


    @Autowired
    private TypeResolver typeResolver;
    
    private final static String BASE_PACKAGE = "com.swagger.json.entity.";
    
    private final static String DEFAULT_CLASS_NAME = "ParameterGlobalBO";
    
    private static Integer i = 1;

    @Override
    public void apply(ParameterContext context) {
        try {
            // 从方法或参数上获取指定注解的Optional
            Optional optional = context.getOperationContext().findAnnotation(ApiGlobalModel.class);
            if (!optional.isPresent()) {
                optional = context.resolvedMethodParameter().findAnnotation(ApiGlobalModel.class);
            }
            if (optional.isPresent()) {
                String key = DEFAULT_CLASS_NAME + i++;
                ApiGlobalModel apiAnno = optional.get();
                try {
                    //类名重复将导致swagger识别不准确 主动触发异常
                    Class.forName(BASE_PACKAGE + key);
                } catch (ClassNotFoundException e) {
                    String[] fields = apiAnno.value();
                    String separator = apiAnno.separator();
                    ClassPool pool = ClassPool.getDefault();
                    CtClass ctClass = pool.makeClass(BASE_PACKAGE + key);
                    ctClass.setModifiers(Modifier.ABSTRACT);
                    //处理 javassist.NotFoundException
                    pool.insertClassPath(new ClassClassPath(apiAnno.component()));
                    CtClass globalCtClass = pool.getCtClass(apiAnno.component().getName());

                    //从globalCtClass拷贝指定字段到动态创建的类中
                    for (String field : merge(fields, separator)) {
                        //若指定的字段不存在 throw NotFoundException
                        CtField ctField = globalCtClass.getDeclaredField(field);
                        CtField newCtField = new CtField(ctField, ctClass);
                        handleField(newCtField);
                        ctClass.addField(newCtField);
                    }
                    // 将生成的Class添加到SwaggerModels
                    context.getDocumentationContext().getAdditionalModels()
                            .add(typeResolver.resolve(ctClass.toClass()));
                    // 修改Json参数的ModelRef为动态生成的class
                    context.parameterBuilder()
                            .parameterType("body").modelRef(new ModelRef(key)).name("body").description("body");
                }
            }
        } catch (Exception e) {
            log.error("@ApiGlobalModel Error", e);
        }
    }

    private void handleField(CtField field) {
        //防止private又没有getter
        field.setModifiers(Modifier.PUBLIC);
        //有name的把字段名改为name
        //因为JSON格式化的原因,ApiModelProperty的name属性无效 所以如果有name,直接更改字段名为name
        AnnotationsAttribute annos = ((AnnotationsAttribute) field.getFieldInfo().getAttribute("RuntimeVisibleAnnotations"));
        if (annos != null) {
            Annotation anno = annos.getAnnotation(ApiModelProperty.class.getTypeName());
            if (anno != null) {
                MemberValue name = anno.getMemberValue("name");
                if (name != null) {
                    //这里返回的name会以引号包裹
                    String fName = name.toString().replace(""", "").trim();
                    if (fName.length() > 0) {
                        field.setName(fName);
                    }
                }
            }
        }

    }

    
    private List merge(String[] arr, String separator) {
        List tmp = new ArrayList<>();
        Arrays.stream(arr).forEach(s -> {
            if (s.contains(separator)) {
                tmp.addAll(Arrays.asList(s.split(separator)));
            } else {
                tmp.add(s);
            }
        });
        return tmp;
    }


    @Override
    public boolean supports(DocumentationType documentationType) {
        return true;
    }
}

注解@ApiJsonModel的代码:

package com.dpi.service.annotation;

import io.swagger.annotations.ApiModelProperty;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonModel {
    
    ApiModelProperty[] value() default {};
}

处理@ApiJsonModel的代码:

package com.dpi.service.annotation.impl;


import com.dpi.service.annotation.ApiJsonModel;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModelProperty;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
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.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;

import java.lang.reflect.Method;
import java.lang.reflect.Type;


@Component
@Order
@Slf4j
public class ApiJsonModelBuilder implements ParameterBuilderPlugin {

    @Autowired
    private TypeResolver typeResolver;
    
    private final static String BASE_PACKAGE = "com.swagger.json.entity.";
    
    private final static String DEFAULT_CLASS_NAME = "ParameterJsonBO";
    
    private static Integer i = 1;

    @Override
    public void apply(ParameterContext context) {
        try {
            // 从方法或参数上获取指定注解的Optional
            Optional optional = context.getOperationContext().findAnnotation(ApiJsonModel.class);
            if (!optional.isPresent()) {
                optional = context.resolvedMethodParameter().findAnnotation(ApiJsonModel.class);
            }
            if (optional.isPresent()) {
                String key = DEFAULT_CLASS_NAME + i++;
                ApiJsonModel apiAnno = optional.get();
                try {
                    Class.forName(BASE_PACKAGE + key);
                } catch (ClassNotFoundException e) {
                    ApiModelProperty[] properties = apiAnno.value();
                    // 将生成的Class添加到SwaggerModels
                    context.getDocumentationContext().getAdditionalModels()
                            .add(typeResolver.resolve(createRefModel(properties, key)));
                    // 修改Json参数的ModelRef为动态生成的class
                    context.parameterBuilder()
                            .parameterType("body").modelRef(new ModelRef(key)).name("JSON").description("body");
                }
            }
        } catch (Exception e) {
            log.error("@ApiJsonModel Error", e);
        }
    }

    
    private Class createRefModel(ApiModelProperty[] propertys, String key) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass(BASE_PACKAGE + key);
        ctClass.setModifiers(Modifier.ABSTRACT);
        for (ApiModelProperty property : propertys) {
            ctClass.addField(createField(property, ctClass));
        }
        return ctClass.toClass();
    }

    
    private CtField createField(ApiModelProperty property, CtClass ctClass) throws Exception {
        //此处默认字段类型为String 如果不是 swagger也是取注解的dataType 字段类型就不重要了
        CtField ctField = new CtField(ClassPool.getDefault().get(String.class.getName()), property.name(), ctClass);
        ctField.setModifiers(Modifier.PUBLIC);
        ConstPool constPool = ctClass.getClassFile().getConstPool();
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        //不知道是否有直接把property转为对应Annotation的方法 它们本质是一样的
        Annotation anno = new Annotation(ApiModelProperty.class.getTypeName(), constPool);
        Method[] members = ApiModelProperty.class.getDeclaredMethods();
        for (Method member : members) {
            Object value = member.invoke(property);
            //使用默认值的就不重复赋值了
            if (!value.equals(member.getDefaultValue())) {
                //由于只拷贝了String,int,boolean等返回值的属性,所以诸如accessMode,extensions这样的属性设置将无效
                Type type = member.getReturnType();
                if (type == String.class) {
                    anno.addMemberValue(member.getName(), new StringMemberValue(String.valueOf(member.invoke(property)), constPool));
                } else if (type == int.class) {
                    anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
                } else if (type == Long.class) {
                    anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
                }else if (type == Integer.class) {
                    anno.addMemberValue(member.getName(), new IntegerMemberValue((Integer) member.invoke(property), constPool));
                }else if (type == boolean.class) {
                    anno.addMemberValue(member.getName(), new BooleanMemberValue((Boolean) member.invoke(property), constPool));
                }
            }
        }
        attr.addAnnotation(anno);
        ctField.getFieldInfo().addAttribute(attr);
        return ctField;
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

以下是使用方法:

@ApiGlobalModel注解说明:对入参json形式   注解用于从一个已有的实体类中抽取接口所需的参数字

使用方法:

需要什么暴露什么字段,value后面写好参数以逗号分割对应实体类,注意大小写;

接下来swagger页面就是这样:

 

@ApiJsonModel注解说明:对入参json形式 对入参进行说明

使用方法:

 这是我们需要暴露给前台的字段,控制层我们用JSONObject,后期我们后台代码处理随意转换对象类型;

接下来swagger页面就是这样:

 

 

以下注意事项:

我这里创建了一个空的控制层以Z字母开头的,大家以后创建控制层不要以Z开头命名,因为这三个注解按a-z顺序加载,最后一个加载不到,这个是swagger的bug,所以要建一个空控制器以Z开头命名,自己业务代码控制器最好就别以z开头命名,就这一个要求;

 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1041023.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号