拓展Swagger功能,支持JsonView视图

SSM+Vue影院售票系统:高并发选座与支付实战解析 在Web应用开发领域,分层架构与数据库设计是构建稳定系统的基石。通过MVC模式,开发者可以将复杂的业务逻辑分解为模型、视图和控制器,实现代码的解耦与可维护性。数据库设计则直接决定了数据操作的效率与一致性,合理的表结构与索引是支撑高并发场景的关键技术。这些技术的核心价值在于,它们能够将现实世界的业务流程(如在线购票)转化为可靠、高效的软件系统。在典型的在线售票应用场景中,高并发下的数据一致性是首要挑战,例如处理多用户同时选座和支付时,需要引入并发控制机制。本文以影院在线售票系统为例,深入剖析了如何利用**乐观 阅读详情

拓展Swagger功能,支持JsonView视图,全网首发


对象使用jackson进行json序列化和反序列化时,能通过@JsonView设置要屏蔽的字段,但swagger不能识别@JsonView,在接口文档中,应该被屏蔽的字段仍会展示出来,此代码作用就是让swagger能识别JsonView注解,正确渲染字段。

功能一年前就写好了,当时找遍全网也没有解决方案,只好自己开发。
过年在家太无聊,写成博客分享出来。

下面贴出代码片举例说明

也可以从仓库拉取代码,测试效果
gitee上的Demo工程

实体类代码
包含username、birthday、guardian三个属性
其中username和guardian属于UserSimpleView视图,birthday属于UserDetailView视图

import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import java.util.List;

/**
 * @ApiModel 注解中必须用description,不能用value
 * 否则无法识别
 *
 * 支持JsonView的继承关系
 * 支持泛型类、泛型字段、继承的字段、嵌套对象
 */
@ApiModel(description = "用户对象")
public class User {

    public interface UserSimpleView {
    }

    public interface UserDetailView extends UserSimpleView {
    }

    @ApiModelProperty(value = "姓名", required = true)
    @JsonView(UserSimpleView.class)
    private String username;

    @ApiModelProperty(value = "生日", required = true)
    @JsonView(UserDetailView.class)
    private String birthday;

    @ApiModelProperty(value = "监护人", required = true)
    @JsonView(UserSimpleView.class)
    private List<User> guardian;

	// getter、setter...
}

接口代码
接口参数的User使用@JsonView(User.UserDetailView.class)修饰,三个字段都参与json反序列化;
接口返回值的User使用@JsonView(User.UserSimpleView.class)修饰,json序列化时birthday字段会被屏蔽;

    /**
     * 使用@RequestMapping时,一定要设置method参数
     *
     * @param user
     * @return
     */
    @ApiOperation("注册人员信息")
    @RequestMapping(value = "/addUser", method = RequestMethod.POST)
    @JsonView(User.UserSimpleView.class)
    public User addUser(@RequestBody @JsonView(User.UserDetailView.class) User user) {
        return user;
    }

请求参数示例

{
  "username": "小鸟",
  "birthday": "2022-01-29",
  "guardian": [
    {
      "username": "鸟爸爸",
      "birthday": "2021-06-18",
      "guardian": null
    },
    {
      "username": "鸟妈妈",
      "birthday": "2021-11-11",
      "guardian": null
    }
  ]
}

返回值示例
返回的json对象中,birthday字段消失了

{
  "username": "小鸟",
  "guardian": [
    {
      "username": "鸟爸爸",
      "guardian": null
    },
    {
      "username": "鸟妈妈",
      "guardian": null
    }
  ]
}

swagger文档
返回值的字段仍然包含birthday字段
(右边是swagger-bootstrap-ui的界面)
请添加图片描述

增强后的swagger文档
返回值的birthday字段消失了
(右边是swagger-bootstrap-ui的界面)
请添加图片描述

原理
在swagger扫描接口时,用javassist复制原本的参数和返回值的Class生成新的Class,跳过要忽略的字段,然后把新的Class注册到swagger中。

注意事项
1、接口使用@RequestMapping时,一定要设置method参数
2、@ApiModel 中必须用description,用value会导致无法识别
3、能识别泛型类、泛型字段、继承的字段、嵌套对象
4、支持JsonView视图的继承关系

参考文章
Swagger 自定义Model、Enum(SpringFox源码分析)
springfox 源码分析(一) 程序入口(整个专栏)

用到的依赖

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>

功能实现
把下面三个类复制到项目中即可生效

OperationJsonViewModelProvider.java

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeBindings;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.classmate.types.ResolvedInterfaceType;
import com.fasterxml.classmate.types.ResolvedObjectType;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.BeanSerializer;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.google.common.base.Optional;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.Extension;
import io.swagger.annotations.ExtensionProperty;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.SignatureAttribute.ClassSignature;
import javassist.bytecode.SignatureAttribute.ClassType;
import javassist.bytecode.SignatureAttribute.TypeArgument;
import javassist.bytecode.SignatureAttribute.TypeParameter;
import javassist.bytecode.SignatureAttribute.TypeVariable;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.Types;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationModelsProviderPlugin;
import springfox.documentation.spi.service.contexts.RequestMappingContext;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 *
 * 对象使用jackson进行json序列化和反序列化时,能通过@JsonView设置要屏蔽的字段
 * 用@JsonView修饰接口的参数和返回值,在接口请求和响应时,相关字段会被屏蔽
 * 但swagger不能识别@JsonView,在接口文档中,应该被屏蔽的字段仍会展示出来
 * 此代码作用就是让swagger能识别JsonView注解,正确渲染字段
 *
 * 原理:在swagger扫描接口时,用javassist复制原本的参数和返回值的Class生成
 * 新的Class,跳过要忽略的字段,然后把新的Class注册到swagger中
 *
 * 复杂的字段也能复制,支持泛型类、泛型字段、继承的字段、嵌套对象
 *
 * 举个例子:被@JsonView(UserSimpleView.class)修饰的User类有username、
 * birthday、guardian三个字段,其中birthday字段不参与json序列化,就生成
 * 一个新Class叫UserSimpleView_User,UserSimpleView_User只有username、
 * guardian两个字段,用UserSimpleView_User代替User
 *
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 依赖中的OperationDynamicModelProvider类
 *
 * @author honeypie
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 12)
public class OperationJsonViewModelProvider implements OperationModelsProviderPlugin {

    /**
     * 缓存处理后的数据类型,key是接口的url,value是参数和返回值的ResolvedType
     * key的生成规则在下面的代码中描述
     * 在JsonViewRequestModelReader和JsonViewResponseModelReader中读取缓存
     */
    static HashMap<String, ResolvedType> urlModelRefCache = new HashMap<>();

    /**
     * 把Class转换为ResolvedType
     */
    private final TypeResolver typeResolver;
    /**
     * 把Class转换为BeanDescription,BeanDescription能处理多态,整合类的视图和父类属性
     */
    private final SerializationConfig serializationConfig;
    /**
     * 缓存,key是Class,value是Class对应的BeanDescription
     */
    private final HashMap<Class, BeanDescription> beanDescriptionCache;
    /**
     * 分析对象的json serializer
     */
    private final DefaultSerializerProvider serializerProvider;
    /**
     * javassist的classPool
     */
    private final ClassPool classPool;
    /**
     * 缓存,key是新Class的类全名,value是ResolvedType
     * key的生成规则在下面的代码中描述
     */
    private final HashMap<String, ResolvedType> resolvedTypeCache;

    @Autowired
    public OperationJsonViewModelProvider(TypeResolver typeResolver, ObjectMapper objectMapper) {
        urlModelRefCache = new HashMap<>();
        this.typeResolver = typeResolver;
        this.serializationConfig = objectMapper.getSerializationConfig();
        serializerProvider = ((DefaultSerializerProvider) objectMapper.getSerializerProvider()).createInstance(this.serializationConfig, objectMapper.getSerializerFactory());
        classPool = new ClassPool(null);
        classPool.appendSystemPath();
        beanDescriptionCache = new HashMap<>();
        resolvedTypeCache = new HashMap<>();
    }

    @Override
    public void apply(RequestMappingContext context) {
        // pattern是接口url,比如:/addUser
        String pattern = context.getPatternsCondition().getPatterns().toArray()[0].toString();
        // method是请求类型,比如:[POST]
        String method = context.getMethodsCondition().toString();
        // 拼接在一起就是/addUser[POST]
        String requestMappingPattern = pattern + method;

        // 处理返回值
        collectFromReturnType(context, requestMappingPattern);
        // 处理请求参数
        collectParameters(context, requestMappingPattern);
    }

    /**
     * 处理返回值类型
     * @param context context
     * @param requestMappingPattern 接口url
     */
    private void collectFromReturnType(RequestMappingContext context, String requestMappingPattern) {
        ResolvedType returnType = context.getReturnType();
        Optional<JsonView> jsonViewOptional = context.findAnnotation(JsonView.class);
        if (!Types.isVoid(returnType) && jsonViewOptional.isPresent()) {
            // value是JsonView注解的value,是Class对象,相当于视图名称
            Class[] value = jsonViewOptional.get().value();
            if (value.length > 0) {
                // 生成新的返回值类型
                ResolvedType resolvedType = applyJsonViewModelType(returnType, value[0]);
                // 把新的返回值类型添加到context中
                context.operationModelsBuilder().addReturn(resolvedType);
                // 添加缓存,直接使用requestMappingPattern作为key,比如:/addUser[POST]
                urlModelRefCache.put(requestMappingPattern, resolvedType);
            }
        }
    }

    /**
     * 处理参数类型
     * @param context context
     * @param requestMappingPattern 接口url
     */
    private void collectParameters(RequestMappingContext context, String requestMappingPattern) {
        List<ResolvedMethodParameter> parameters = context.getParameters();
        int paramNum = parameters.size();
        // 遍历接口的参数
        for (int i = 0; i < paramNum; i++) {
            ResolvedMethodParameter parameterType = parameters.get(i);
            Optional<JsonView> jsonViewOptional = parameterType.findAnnotation(JsonView.class);
            if (jsonViewOptional.isPresent()) {
                // value是JsonView注解的value,是Class对象,相当于视图名称
                Class[] value = jsonViewOptional.get().value();
                if (value.length > 0) {
                    // 生成新的参数类型
                    ResolvedType resolvedType = applyJsonViewModelType(parameterType.getParameterType(), value[0]);
                    // 把新的参数类型添加到context中
                    context.operationModelsBuilder().addInputParam(resolvedType);
                    // 添加缓存,用url拼接参数index作为key,比如:/addUser[POST]_P[0]
                    String resolvedTypeKey = requestMappingPattern + "_P[" + i + "]";
                    urlModelRefCache.put(resolvedTypeKey, resolvedType);
                }
            }
        }
    }

    /**
     * 根据原类型originType和JsonView视图名称targetView生成新的类型
     *
     * @param originType 原类型
     * @param targetView 视图名称
     * @return 新类型
     */
    private ResolvedType applyJsonViewModelType(ResolvedType originType, Class targetView) {

        // 生成新Class的类名
        String className = getClassNameWithTypeBindings(originType, targetView);
        Class newClass = null;

        Class erasedClass = originType.getErasedType();
        if (checkIgnorableType(erasedClass)) {
            // 先检查缓存
            if (resolvedTypeCache.containsKey(className)) {
                return resolvedTypeCache.get(className);
            }

            try {
                // 生成类名并创建Class
                String erasedClassName = getClassNameWithoutTypeBindings(erasedClass, targetView);
                newClass = generateModelClass(erasedClassName, erasedClass, targetView);
            } catch (CannotCompileException | NotFoundException | NullPointerException e) {
                e.printStackTrace();
            }
        }

        // 检查原类型的泛型,比如:List<User>,List不用管,只需处理User类
        List<ResolvedType> typeParameters = new ArrayList<>();
        for (ResolvedType typeParameter : originType.getTypeBindings().getTypeParameters()) {
            typeParameters.add(applyJsonViewModelType(typeParameter, targetView));
        }

        // 如果原类型是基本类型,则newClass为null,泛型也为空
        if (newClass == null && typeParameters.isEmpty()) {
            return originType;
        }

        // 检查原类型实现的接口,用于生成ResolvedType
        List<ResolvedType> superInterfaces = new ArrayList<>();
        for (ResolvedType anInterface : originType.getImplementedInterfaces()) {
            superInterfaces.add(applyJsonViewModelType(anInterface, targetView));
        }

        TypeBindings typeBindings = TypeBindings.create(erasedClass, typeParameters);
        Class erased = newClass == null ? erasedClass : newClass;

        ResolvedType newType;
        if (originType.findSupertype(Object.class) == null) {
            // 比如originType是List<User>,会走true的逻辑
            newType = new ResolvedInterfaceType(erased, typeBindings, superInterfaces.toArray(new ResolvedType[0]));
        } else {
            // 比如originType是User,会走false的逻辑
            newType = ResolvedObjectType.create(erased, typeBindings, typeResolver.resolve(typeBindings, erased.getSuperclass()), superInterfaces);
        }

        // 生成ResolvedType并填入缓存
        resolvedTypeCache.put(className, newType);
        return newType;
    }

    /**
     * 判断原类型erasedClass是否需要处理
     * 比如:String类型不用管,User类型需要处理
     * @param erasedClass 原类型Class
     * @return 需要处理就返回true,否则返回false
     */
    private boolean checkIgnorableType(Class erasedClass) {
        try {
            JavaType javaType = serializationConfig.constructType(erasedClass);
            if (!(javaType instanceof SimpleType)) {
                return false;
            }
            JsonSerializer<Object> typedValueSerializer = serializerProvider.findTypedValueSerializer(javaType, true, null);
            return typedValueSerializer instanceof BeanSerializer;
        } catch (JsonMappingException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据类名className判断Class是否存在
     * @param className 类名
     * @param erasedClass 原类型
     * @param targetView 视图名称
     * @throws CannotCompileException 异常
     * @throws NotFoundException 异常
     */
    private void prepareModelClass(String className, Class erasedClass, Class targetView) throws CannotCompileException, NotFoundException {
        try {
            // 如果当前线程的classPool中存在,就不用生成
            Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            try {
                // 如果javassist的classPool中存在,就不用生成
                classPool.get(className);
            } catch (NotFoundException e2) {
                // 生成Class
                generateModelClass(className, erasedClass, targetView);
            }
        }
    }

    /**
     * 根据原类型erasedClass和视图名称targetView生成新Class
     * @param className 类全名
     * @param erasedClass 原类型Class
     * @param targetView 视图名称Class
     * @return 新Class
     * @throws CannotCompileException 异常
     * @throws NotFoundException 异常
     */
    private Class generateModelClass(String className, Class erasedClass, Class targetView) throws CannotCompileException, NotFoundException {
        CtClass ctClass;
        try {
            // 判断当前线程的classPool中是否存在
            return Thread.currentThread().getContextClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            // 使用javassist生成新Class
            ctClass = classPool.makeClass(className);
        }

        // 复制原类型的泛型参数
        java.lang.reflect.TypeVariable[] sourceArr = erasedClass.getTypeParameters();
        TypeParameter[] targetArr = new TypeParameter[sourceArr.length];
        for (int i = 0; i < sourceArr.length; i++) {
            targetArr[i] = new TypeParameter(sourceArr[i].getName());
        }
        ctClass.setGenericSignature(new ClassSignature(targetArr).encode());

        // 获取原类型的BeanDescription
        BeanDescription beanDesc = beanDescriptionCache.get(erasedClass);
        if(beanDesc == null) {
            beanDesc = serializationConfig.introspect(serializationConfig.constructType(erasedClass));
            beanDescriptionCache.put(erasedClass, beanDesc);
        }

        ClassFile classFile = ctClass.getClassFile();
        ConstPool constPool = classFile.getConstPool();
        // 复制原类型的ApiModel注解以及参数
        ApiModel apiModel = beanDesc.getClassAnnotations().get(ApiModel.class);
        if (apiModel != null) {
            AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
            Annotation ann = new Annotation(ApiModel.class.getName(), constPool);
            ann.addMemberValue("value", new StringMemberValue(apiModel.value(), constPool));
            ann.addMemberValue("description", new StringMemberValue(apiModel.description(), constPool));
            ann.addMemberValue("parent", new ClassMemberValue(apiModel.parent().getName(), constPool));
            ann.addMemberValue("discriminator", new StringMemberValue(apiModel.discriminator(), constPool));
            ArrayMemberValue targetSubTypes = new ArrayMemberValue(constPool);
            Class<?>[] classes = apiModel.subTypes();
            ClassMemberValue[] sourceSubTypes = new ClassMemberValue[classes.length];
            for (int i = 0; i < classes.length; i++) {
                sourceSubTypes[i] = new ClassMemberValue(classes[i].getName(), constPool);
            }
            targetSubTypes.setValue(sourceSubTypes);
            ann.addMemberValue("subTypes", targetSubTypes);
            ann.addMemberValue("reference", new StringMemberValue(apiModel.reference(), constPool));
            attr.addAnnotation(ann);
            classFile.addAttribute(attr);
        }

        // defaultView是指声明在类上的@JsonView视图名称
        Class<?>[] defaultViews = beanDesc.findDefaultViews();
        boolean hasDefaultView = defaultViews != null && defaultViews.length > 0;

        for (BeanPropertyDefinition property : beanDesc.findProperties()) {
            // 遍历原类型的每一个property,根据视图关系判断是否展示

            boolean propertyVisible = false;

            JsonView JsonViewAnn = property.getAccessor().getAnnotation(JsonView.class);
            if (JsonViewAnn != null) {
                for (Class<?> fieldView : JsonViewAnn.value()) {
                    if (fieldView.isAssignableFrom(targetView)) {
                        propertyVisible = true;
                        break;
                    }
                }
            } else if (hasDefaultView) {
                for (Class<?> defaultView : defaultViews) {
                    if (defaultView.isAssignableFrom(targetView)) {
                        propertyVisible = true;
                        break;
                    }
                }
            }

            if (propertyVisible) {
                // 如果property需要展示,就移植到新Class

                Class fieldType = property.getRawPrimaryType();
                CtClass fieldCtClass;

                if (checkIgnorableType(fieldType)) {
                    String fieldTypeName = getClassNameWithoutTypeBindings(fieldType, targetView);
                    prepareModelClass(fieldTypeName, fieldType, targetView);
                    fieldCtClass = classPool.getCtClass(fieldTypeName);
                } else {
                    fieldCtClass = classPool.getCtClass(fieldType.getName());
                }

                CtField ctField = new CtField(fieldCtClass, property.getName(), ctClass);
                ctField.setModifiers(Modifier.PUBLIC);

                Type fieldGenericType;
                AnnotatedMember member = property.getPrimaryMember();
                if (member instanceof AnnotatedField) {
                    fieldGenericType = ((AnnotatedField) member).getAnnotated().getGenericType();
                } else if (member instanceof AnnotatedMethod) {
                    fieldGenericType = ((AnnotatedMethod) member).getAnnotated().getGenericReturnType();
                } else {
                    continue;
                }

                // 如果字段类型是基本类型,则fieldType.isPrimitive()为true
                if (!fieldType.isPrimitive()) {
                    // 不是基本类型的字段,需要设置GenericSignature
                    ctField.setGenericSignature(getTypeGenericSignature(fieldGenericType, targetView).getType().encode());
                }

                // 复制原字段的ApiModelProperty注解以及参数
                ApiModelProperty apiModelProperty = property.getPrimaryMember().getAnnotation(ApiModelProperty.class);
                if (apiModelProperty != null) {
                    AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                    Annotation ann = new Annotation(ApiModelProperty.class.getName(), constPool);
                    ann.addMemberValue("value", new StringMemberValue(apiModelProperty.value(), constPool));
                    ann.addMemberValue("name", new StringMemberValue(apiModelProperty.name(), constPool));
                    ann.addMemberValue("allowableValues", new StringMemberValue(apiModelProperty.allowableValues(), constPool));
                    ann.addMemberValue("access", new StringMemberValue(apiModelProperty.access(), constPool));
                    ann.addMemberValue("notes", new StringMemberValue(apiModelProperty.notes(), constPool));
                    ann.addMemberValue("dataType", new StringMemberValue(apiModelProperty.dataType(), constPool));
                    ann.addMemberValue("required", new BooleanMemberValue(apiModelProperty.required(), constPool));
                    IntegerMemberValue position = new IntegerMemberValue(constPool);
                    position.setValue(apiModelProperty.position());
                    ann.addMemberValue("position", position);
                    ann.addMemberValue("hidden", new BooleanMemberValue(apiModelProperty.hidden(), constPool));
                    ann.addMemberValue("example", new StringMemberValue(apiModelProperty.example(), constPool));
                    ann.addMemberValue("readOnly", new BooleanMemberValue(apiModelProperty.readOnly(), constPool));
                    EnumMemberValue enumMemberValue = new EnumMemberValue(constPool);
                    enumMemberValue.setValue(apiModelProperty.accessMode().name());
                    enumMemberValue.setType(ApiModelProperty.AccessMode.class.getName());
                    ann.addMemberValue("accessMode", enumMemberValue);
                    ann.addMemberValue("reference", new StringMemberValue(apiModelProperty.reference(), constPool));
                    ann.addMemberValue("allowEmptyValue", new BooleanMemberValue(apiModelProperty.allowEmptyValue(), constPool));
                    ArrayMemberValue targetExtensions = new ArrayMemberValue(constPool);
                    Extension[] extensions = apiModelProperty.extensions();
                    AnnotationMemberValue[] sourceExtensions = new AnnotationMemberValue[extensions.length];
                    for (int i = 0; i < extensions.length; i++) {
                        Extension extension = extensions[i];
                        Annotation ann1 = new Annotation(Extension.class.getName(), constPool);
                        ann1.addMemberValue("name", new StringMemberValue(extension.name(), constPool));
                        ArrayMemberValue targetProperties = new ArrayMemberValue(constPool);
                        ExtensionProperty[] properties = extension.properties();
                        AnnotationMemberValue[] sourceProperties = new AnnotationMemberValue[properties.length];
                        for (int i1 = 0; i1 < properties.length; i1++) {
                            ExtensionProperty extensionProperty = properties[i];
                            Annotation ann2 = new Annotation(ExtensionProperty.class.getName(), constPool);
                            ann2.addMemberValue("name", new StringMemberValue(extensionProperty.name(), constPool));
                            ann2.addMemberValue("value", new StringMemberValue(extensionProperty.value(), constPool));
                            sourceProperties[i] = new AnnotationMemberValue(ann2, constPool);
                        }
                        targetProperties.setValue(sourceProperties);
                        ann1.addMemberValue("properties", targetProperties);
                        sourceExtensions[i] = new AnnotationMemberValue(ann1, constPool);
                    }
                    targetExtensions.setValue(sourceExtensions);
                    ann.addMemberValue("extensions", targetExtensions);
                    attr.addAnnotation(ann);
                    ctField.getFieldInfo().addAttribute(attr);
                }

                ctClass.addField(ctField);
            }
        }

        return ctClass.toClass();
    }

    /**
     * 用javassist生成CtClass时,需要设置CtField类型
     * 根据字段类型type和视图名称targetView生成字段的GenericSignature
     *
     * @param type 对象的字段类型
     * @param targetView 视图名称
     * @return TypeArgument CtField的GenericSignature
     * @throws CannotCompileException
     * @throws NotFoundException
     */
    private TypeArgument getTypeGenericSignature(Type type, Class targetView) throws CannotCompileException, NotFoundException {
        // 字段类型分三种情况处理

        if (type instanceof java.lang.reflect.TypeVariable) {
            // 泛型字段,比如:private T t;
            TypeVariable ctFieldTypeVariable = new TypeVariable(type.getTypeName());
            return new TypeArgument(ctFieldTypeVariable);
        } else if (type instanceof ParameterizedType) {
            // 字段类型包含泛型,比如:private List<User> list;
            ParameterizedType parameterizedType = (ParameterizedType) type;
            TypeArgument rawType = getTypeGenericSignature(parameterizedType.getRawType(), targetView);

            Type[] sourceArr = parameterizedType.getActualTypeArguments();
            TypeArgument[] targetArr = new TypeArgument[sourceArr.length];
            for (int i = 0; i < sourceArr.length; i++) {
                targetArr[i] = getTypeGenericSignature(sourceArr[i], targetView);
            }
            return new TypeArgument(new ClassType(((ClassType) rawType.getType()).getName(), targetArr));
        } else if (type instanceof Class) {
            // 普通类型字段,比如:private User user;
            Class classType = (Class) type;

            String rowTypeName;
            if (checkIgnorableType(classType)) {
                rowTypeName = getClassNameWithoutTypeBindings(classType, targetView);
                prepareModelClass(rowTypeName, classType, targetView);
            } else {
                rowTypeName = classType.getTypeName();
            }

            return new TypeArgument(new ClassType(rowTypeName));
        } else {
            throw new RuntimeException("unknown field type : " + type.getTypeName());
        }
    }

    /**
     * 根据原类型type和视图名称targetView,生成新类型名称,会忽略泛型
     * 比如原类型是com.swagger.demo.entity.User<String>,视图名称是UserSimpleView
     * 新类型名称就是com.swagger.demo.entity.UserSimpleView_User
     *
     * @param type 原类型
     * @param targetView 视图名称
     * @return 新类型名称
     */
    private String getClassNameWithoutTypeBindings(Class type, Class targetView) {
        return type.getPackage().getName() + "." + targetView.getSimpleName() + "_" + type.getSimpleName();
    }

    /**
     * 根据原类型type和视图名称targetView,生成新类型名称,会保留泛型
     * 比如原类型是com.swagger.demo.entity.User<String>,视图名称是UserSimpleView
     * 新类型名称就是com.swagger.demo.entity.UserSimpleView_User<String>
     *
     * @param type 原类型
     * @param targetView 视图名称
     * @return 新类型名称
     */
    private String getClassNameWithTypeBindings(ResolvedType type, Class<?> targetView) {
        StringBuilder className = new StringBuilder();
        className.append(type.getErasedType().getPackage().getName())
                .append(".")
                .append(targetView.getSimpleName())
                .append("_");

        appendTypeName(type, className);
        return className.toString();
    }

    private void appendTypeName(ResolvedType type, StringBuilder className) {
        className.append(type.getErasedType().getSimpleName());
        TypeBindings typeBindings = type.getTypeBindings();
        if (typeBindings.size() > 0) {
            className.append("<");
            int size = typeBindings.size();
            for (int i = 0; i < size; ) {
                appendTypeName(typeBindings.getBoundType(i), className);
                if (++i < size) {
                    className.append(",");
                }
            }
            className.append(">");
        }
    }

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

JsonViewRequestModelReader.java

import com.fasterxml.classmate.ResolvedType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;

import static springfox.documentation.schema.ResolvedTypes.modelRefFactory;
import static springfox.documentation.spi.schema.contexts.ModelContext.inputParam;

/**
 * @author honeypie
 * 把解析后的请求值类型填充到swagger文档中
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 此依赖中的DynamicParameterBuilderPlugin类
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class JsonViewRequestModelReader implements ParameterBuilderPlugin {


    private final TypeNameExtractor typeNameExtractor;

    @Autowired
    public JsonViewRequestModelReader(TypeNameExtractor typeNameExtractor) {
        this.typeNameExtractor = typeNameExtractor;
    }

    @Override
    public void apply(ParameterContext context) {
        ResolvedMethodParameter methodParameter = context.resolvedMethodParameter();

        OperationContext operationContext = context.getOperationContext();
        String pattern = operationContext.requestMappingPattern();
        String method = "[" + operationContext.httpMethod().name() + "]";
        String requestMappingPattern = pattern + method;
        // 拼接url、method和参数index得到缓存的key,比如:/addUser[POST]_P[0]
        requestMappingPattern = requestMappingPattern + "_P[" + methodParameter.getParameterIndex() + "]";

        if (OperationJsonViewModelProvider.urlModelRefCache.containsKey(requestMappingPattern)) {
            ResolvedType parameterType = OperationJsonViewModelProvider.urlModelRefCache.get(requestMappingPattern);
            ModelContext modelContext = inputParam(
                    context.getGroupName(),
                    parameterType,
                    context.getDocumentationType(),
                    context.getAlternateTypeProvider(),
                    context.getGenericNamingStrategy(),
                    context.getIgnorableParameterTypes());
            ModelReference modelRef = modelRefFactory(modelContext, typeNameExtractor).apply(parameterType);

            context.parameterBuilder()
                    .type(parameterType)
                    .modelRef(modelRef);
        }

    }

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

}

JsonViewResponseModelReader.java

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.jackson.annotation.JsonView;
import com.google.common.base.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.schema.Types;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import static com.google.common.collect.Sets.newHashSet;
import static springfox.documentation.schema.ResolvedTypes.modelRefFactory;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.httpStatusCode;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.message;

/**
 * @author honeypie
 * 把解析后的response返回值类型填充到swagger文档中
 *
 * 参考代码:
 * <dependency>
 *     <groupId>com.github.xiaoymin</groupId>
 *     <artifactId>swagger-bootstrap-ui</artifactId>
 *     <version>1.9.6</version>
 * </dependency>
 * 此依赖中的DynamicResponseModelReader类
 *
 */
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1051)
public class JsonViewResponseModelReader implements OperationBuilderPlugin {

    private final TypeNameExtractor typeNameExtractor;

    @Autowired
    public JsonViewResponseModelReader(TypeNameExtractor typeNameExtractor) {
        this.typeNameExtractor = typeNameExtractor;
    }

    @Override
    public void apply(OperationContext context) {
        ResolvedType returnType = context.getReturnType();
        if (Types.isVoid(returnType)) return;

        Optional<JsonView> jsonViewOptional = context.findAnnotation(JsonView.class);
        if (jsonViewOptional.isPresent()) {
            Class<?>[] value = jsonViewOptional.get().value();
            if (value.length > 0) {

                String requestMappingPattern = context.requestMappingPattern();
                String method = "[" + context.httpMethod().name() + "]";
                // 拼接url和method得到缓存的key,比如:/addUser[POST]
                returnType = OperationJsonViewModelProvider.urlModelRefCache.get(requestMappingPattern + method);
                if (returnType == null) {
                    return;
                }

                int httpStatusCode = httpStatusCode(context);
                String message = message(context);

                ModelContext modelContext = ModelContext.returnValue(
                        context.getGroupName(),
                        returnType,
                        context.getDocumentationType(),
                        context.getAlternateTypeProvider(),
                        context.getGenericsNamingStrategy(),
                        context.getIgnorableParameterTypes());

                ModelReference modelRef = modelRefFactory(modelContext, typeNameExtractor).apply(returnType);

                ResponseMessage built = new ResponseMessageBuilder()
                        .code(httpStatusCode)
                        .message(message)
                        .responseModel(modelRef)
                        .build();

                context.operationBuilder().responseMessages(newHashSet(built));
            }
        }
    }

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

Cadence 中Cutout、Design_Outline 和 Outline的区别 摘要: 在PCB设计中,Cutout、Design_Outline和Outline都与电路板外形相关但用途不同。Cutout用于定义切割区域(如孔、槽),直接影响制造;Design_Outline仅标记设计边界,作为参考不影响切割;Outline则是实际切割轮廓层,用于制造。三者区别在于:Cutout针对具体切割区域,Design_Outline是设计参考,Outline是最终切割依据。设计时需根据需求选择合适层:外形参考用Design_Outline/Outline,切割区域用Cutout。 阅读详情

相关推荐

详解ReID的各部分组成及Trick——基于FastReID

这一系列博客将基于京东开源的FastReID进行扩充,详细介绍了ReID的各个组成部分,一些有用的Trick,评价指标,常用数据集等,详细内容见下方的链接,总结不易,如有理解不正确之处,麻烦各位批评指正。 一、FastReID的中的baseline文件配置 二、Training strategy 三、Pre-processing 四、Backbone 五、Aggregation 六、Head 七、Loss 八、Distance Metric 九、Post-processing 十、Evaluation 十一

藏晖的博客 9165

通过swagger生成接口的.json文件

springboot项目集成了swagger,那么我们可以在ui页面测试接口,如果想要接口的json文件该怎么办呢? 首先确定需要的是某个接口还是所有接口的json文件,如果是只要某个接口的,那么将其他接口屏蔽调,屏蔽的方式就是在其他的controller类上添加注释@ApiIgnore,这样在ui页面就看不到该接口了,然后启动项目。 获取json文件有两种方式: 1、从浏览器页面访问http://localhost:端口/服务/v2/api-docs, 这样就能获取到该接口的json文件,如图所示: 2

qq_41955582的博客 2万+

基于YOLOv11的工业零件表面缺陷检测系统:从原理到实现

工业质量控制是制造业的核心环节,表面缺陷检测作为其中重要组成部分,直接关系到产品质量和企业效益。本文详细介绍了一种基于YOLOv11深度学习算法的工业零件表面缺陷检测系统,包含完整的算法原理分析、数据集构建方法、模型训练策略以及用户界面设计。通过大量实验验证,该系统能够高效准确地检测零件表面的划痕、凹陷、锈蚀等多种缺陷,检测精度达到96.2%,满足工业实时检测需求。本文还提供了完整的代码实现和详细的技术文档,为相关领域的研究者和工程师提供参考。关键词:表面缺陷检测;YOLOv11;深度学习;工业视觉;

YOLO 567

Swagger json file

用swagger json file,导入到soap ui里,再写用例。 1.让开发生成json(将swagger里的内容生成json)后,打开swagger (https://dfsunlock-dev.azurewebsites.net/swagger/ ),可以看头部的Url https://dfsunlock-dev.azurewebsites.net/swagger/v1215/swag...

小卷子的笔记~ 880

YOLOv11改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 RepViT 轻量级的Vision Transformers架构

本文记录的是基于RepVit的YOLOv11轻量化改进方法研究。RepVit的网络结构借鉴ViT的设计理念,通过分离的token mixe和减少推理时的计算和内存成本,同时减少扩展比率并增加宽度,降低延迟,并通过来弥补参数大幅减少的问题,提高了准确性。本文在替换骨干网络中配置了原论文中的和五种模型,以满足不同的需求。

Limiiiing的博客 2976

C#的GDI+绘图详解

/消除锯齿 或 AntiAlias。// 1.在窗体或控件的Paint事件中,用PaintEventArgs e 创建绘图对象 ---- 控件或窗体重绘时。//为控件创建绘图对象。//--------------------以上方法三选一。//2.创建绘图对象 panel1中绘图。

weixin_50920631的博客 3457

在swagger上生成关于json数据类型返回结果的描述

在swagger上生成关于json数据类型返回结果的描述 注解ApiReturnJsonObject表示返回结果的map结构 package com.test.swagger2; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Re...

qq_31601531的博客 9674

机器人轨迹规划——三次样条轨迹1(基于速度的求解方法)

三次样条轨迹是利用三次样条曲线来对给定的一系列点进行插值,从而得到一个光滑的轨迹。三次样条因为每段的多项式次数最高都是三阶的,所以可以得到2阶导数连续,也就是加速度连续的轨迹。假设给定了一系列的点q0q1q2qnq0​q1​q2​...qn​,以及每个点对应的参数t0t1t2tnt0​t1​t2​...tn​,计算一条三次样条曲线sts(t)st,该曲线通过给定的点q0q1q2qnq0​q1。

出门搔白首,若负平生志 1471

swagger对json数据的处理

就用@JsonIgnore给注掉了(当时这个我不知道,😫),问题出现的场景是在调用另一个接口的时候,impl类里需要查询该字段,并向这个类传递这个属性。开始看的时候我也懵了,调试都有数据,最终发现了是该属性加上了@JsonIgnore注解。在实习中遇到了一个不寻常的事情,今天和同事讨论一个小问题,同事使用swagger,想要调用一个接口,这个接口要传递一个json对象,对应java的一个实体类,但是。他用的是@JSONField注解,而我之前没有用过这个注解,我决定是尝试使用一下。

qq_63918780的博客 3055

【推荐100个unity插件】unity易于使用模块化设计的天空、体积云和天气系统——Enviro 3

Enviro 3是一款功能强大的Unity天空与天气系统插件,提供动态天空、体积云、天气模拟等丰富功能。安装时需将预制体拖入场景并配置主相机,URP支持需激活深度纹理和添加渲染特性。模块化设计允许自由添加/移除时间、光照、雾效等模块,并通过API控制参数。推荐配合色调映射(如ACES模式)获得最佳视觉效果。插件支持季节、温湿度模拟及闪电风暴等特效,适用于需要逼真环境渲染的Unity项目。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 2094

我的世界模组开发——前言

避坑:在开发过程中一定不要过分依赖AI,虽说现在的人工智能很厉害,但是也有很大的弊端,很多AI生成的代码错误百出,还得自己修改,而且像deepseek这种AI的训练数据还停留在一年前,给出的maven仓库地址都是过时的甚至是错误的,开发模组就是要多学习,多看源码,这样开发模组的水平才能越来越高。总而言之,build.gradle文件在我的世界Forge模组开发中扮演了重要的角色,它定义了项目的构建过程、依赖关系和编译选项,为开发者提供了灵活而强大的构建工具。开发工具推荐使用idea,因为可以使用通灵义码。

qq_24992377的博客 2594

Swagger.json 完全手册:从规范解析到工程实践

如果你使用的是 Swagger 相关工具,一般只需要按照相应的配置和注解在代码中描述 API,然后启动服务,就可以通过特定的 URL 访问到生成的 `swagger.json` 文件。例如,如果你的 API 路径是 `https://abc.com/apidoc/`,那么通常可以通过访问 `https://abc.com/apidoc-json` 来获取对应的 `swagger.json` 文件。通过使用 `swagger.json` 文件,开发人员可以更清晰地了解 API 的结构和功能,

m0_57836225的博客 1397

WPF实战项目十七(客户端):数据等待加载弹框动画

1、在Common文件夹下新建文件夹Events,新建扩展类UpdateLoadingEvent。2、新建一个静态扩展类DialogExtensions来编写注册和推送等待消息弹框方法。3、在ViewModel中添加实现类NavigationViewModel。5、在View文件夹下新建用户控件ProgressView.xmal。4、在主窗体MainView.xmal中给弹窗界面命名。6、在MainView.xmal.cs中注册消息。

yigu4011的博客 2348

SpringMVC视图解析与RESTful接口设计实战

视图解析是Java Web开发中的核心技术,SpringMVC通过ViewResolver链实现多视图技术的统一处理。其工作原理涉及逻辑视图名转换、模板定位和内容渲染等关键环节,支持JSP、Thymeleaf、Freemarker等多种模板引擎。在前后端分离架构中,JSON视图与RESTful风格的结合成为主流方案,通过HTTP动词明确操作语义,URI定位资源实体。这种设计模式显著提升接口规范性和可维护性,特别适用于电商平台等需要同时支持Web页面和移动API的场景。本文通过用户管理API的完整案例,详解如

weixin_33957648的博客 301

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

JSONVue 项目教程

JSONVue 项目教程 1. 项目介绍 JSONVue 是一个基于 Chromium 浏览器的 JSON 视图插件,它是 JSONView 的一个分支。JSONVue 允许用户在浏览器中以更友好的方式查看 JSON 数据,提供了语法高亮、折叠和展开等功能,使得 JSON 数据的阅读和调试变得更加直观和高效。 2. 项目快速启动 2.1 安装 首先,你需要克隆项目到本地: git clone ht...

gitblog_00434的博客 440

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

直观易用,这两款JSON可视化工具,配合Swagger使用真香~

经常使用Swagger的小伙伴应该有所体会,Swagger对于JSON的支持真的很不友好!最近发现了两款颜值很不错的JSON可视化工具,可以优雅地展示JSON数据从而提高开发效率,推荐给大家!

macrozheng的专栏 1725

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

颜值爆表,推荐两款JSON可视化工具,配合Swagger使用真香

经常使用Swagger的小伙伴应该有所体会,Swagger对于JSON的支持真的很不友好!最近发现了两款颜值很不错的JSON可视化工具,可以优雅地展示JSON数据从而提高开发效率,推荐给大家! 聊聊Swagger 我们先来聊聊Swagger对JSON支持有哪些不友好的地方,我们为什么需要JSON可视化工具! 当我们使用Swagger提交POST请求,输入JSON请求参数时,它既不支持JSON格式校验,也不支持格式化,用起来很不方便; 当我们使用Swagger获取到的JSON数据比较长时,S..

uuqaz的博客 1835

springboot集成swagger并统一返回json格式

Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。 作用: 接口的文档在线自动生成。 功能测试。 配置 添加依赖 <!--swagger 文档注释 https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <g

二十一克阳光!的博客 6250

记:通过Swagger-ui 生成json

自己搭建了一个springboot项目,用swagger生成api文档。结果显示如下: 但是此时需要json格式的内容。我发现了两种方法。 1.替换访问路径 此时访问的url为http://localhost:8080/swagger-ui.html# ,只需要将url中swagger-ui.html#替换成右上角的/v2/api-docs即可。 访问http://localhost:8080/v2/api-docs,得到json 2.使用F12打开开发工具。 点击network,选择.

C18298182575的博客 8757

swagger 返回json字符串_解析和增量导出 Swagger 的 JSON 文件

用了 Swagger 的程序在运行时会扫描类和方法里的注解,生成 JSON 文档,默认地址为接口访问地址后面加 /v2/api-docs本地启动的服务默认为 http://localhost:8080/v2/api-docsSwagger UI 生成的网页的数据源就是这 JSON 文件缺点:只有服务启动了才能查看,没有离线版只支持全量导出,有些由于历史遗留问题没细分的服务可能有几千个接口,不方便...

weixin_35711852的博客 7074

SpringBoot集成Swagger UI显示的接口可以显示Json格式的信息说明

本篇分享了Springboot集成Swagger。实现OperationBuilderPlugin接口,自定义扩展Swagger的能力,使得Swagger UI显示的接口可以显示json格式的信息说明

JingAi_jia917的博客 3699

Swagger返回嵌套json数据

文章目录一、接口展示中的问题二、BaseResult工具类三、使用BaseResult1. 对应一个url该怎么返回数据2. swagger中的数据展示四、多层嵌套1. 嵌入一层列表2. 返回分页信息3. 对象里有另外的对象五、总结 一、接口展示中的问题   现在大部分前后端的数据交互都是采用json格式,如我们经常用一个大的json包裹着数据发送的,但是在Swagger中,如果直接返回这个类对象...

lonely喆的博客 7796

Swagger入门和实战(概念和java例子及解决Gson兼容问题)

本文讲解了Swagger的特性以及如何在java项目中使用Swagger。另外如果你的项目使用Gson作为接口的json转换工具,那么本文还告诉你如何让Swagger兼容Gson。最后也介绍了Swagger的局限性。 目录 前言 Swagger概述 如何引入Swagger 解决Gson和springfox的兼容性 Swagger其它功能概述 Swagger的问题 总结 前言 随着...

稀有气体的技术博客 7740

Swagger接口返回JSON,Map的字段注释比较优雅的实现

看很多小伙伴写的接口,既想要Map的灵活扩展性,又苦于生成的接口文档没办法把已知的字段解释清楚;导致接口文档效果打折扣,还得另行约定文档,或者直接嘴上沟通了事;目前使用Swagger2形成接口文档时,当系统设计的接口返回的类型不是实体对象时,Swagger2无法在接口文档页面中显示返回结果字段说明,比如返回json、map等可以存储key-val形式的类型;均无法在接口文档页面上显示返回的字段备注说明;所以怎么才能像实体对象一样显示正常的model字段说明是我们这次需要解决的问题;...

austindev的博客 4351

Swagger扩展 - 同一个接口生成多份Swagger API文档

为同一个`@ApiOperation`生成多份不同Swagger API文档

夫礼者的专栏 2077

如何把swagger项目导出成json或者yaml文件

2.按F12,查看网络,先择Fetch/XHR,根据swagger版本的不同,文件也可能不同,一般情况是:v1、swagger.json,swagger.yaml,api-docs等等类似文件。1.先进去swagger/UI界面,然后看下左下角的BASE URL有没有完整连接,如果有直接访问就行。json或者swagger.yaml文件即可。3.提取到返回的json数据后,进入。网页,在线生成对应的swagger.

ff1834453983的博客 7471
下一篇: 根据邻接表数据生成闭包表,树结构表设计,支持MySQL,Oracle,DB2
honey_pie
博客等级 码龄9年 8粉丝 8原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值