swagger优雅显示全部状态码

swagger优雅显示全部状态码

背景: 后端在与前端交流的时候,常常需要用到swagger文档去沟通接口。虽然swagger中有对于枚举类和返回状态码的支持,但是需要在代码中写好返回响应,会带来很多的多余代码。对实际开发并不友好。

引入的依赖

<!--		使用优先级法排除swagger的jar包,为了让没有设定property的example的也可以不报错-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-core -->
        <!--Swagger-->
        <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>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.6</version>
        </dependency>

swagger本身对于状态码的支持

@ApiResponses({
     @ApiResponse(code = 200, message = "成功",response = CustomEnumConstant.class),
     @ApiResponse(code = 500, message = "失败")
})

实际效果如下:

ps: 对于此,其实我是真的不想写这么多响应在代码里面,如果真的要写,不如直接去Apifox里面,测一遍接口保存测试用例和响应示例。

在网上找的对于枚举类的支持方法

基本就是改写ModelPropertyBuilderPlugin或者ExpandedParameterBuilderPlugin。

  • ModelPropertyBuilderPlugin是控制模型属性的构建
  • ExpandedParameterBuilderPlugin是控制参数基本属性的构建,如参数的类型、描述、是否必填等。实现这个接口可以控制参数的基本展示形式

但是都满足不了我的需求,我想把整一个状态码枚举类给前端看。因为单独的在一个模型属性中是显示不了全部后台定义的状态码的。

解决办法

我就想到能不能通过状态码枚举类,动态生成一个类,交给swagger扫描到文档中,这样就可以达到很好的显示效果了.

这个的难点有两个:

  • 1.如何动态生成类
  • 2.如何交给swagger扫描
解决过程

对于如何动态生成类,我借鉴了GPT的说法.可以使用cglib的api或者java原生的javassist.

对于cglib,api有些学习难度.我借鉴了一篇博客
https://developer.aliyun.com/article/1388546
最终用的javassist.

如何交给swagger扫描我也是在上面的博客中找到的办法.

下面直接贴上显示状态码的代码

import com.fasterxml.classmate.TypeResolver;
import io.swagger.annotations.ApiModelProperty;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.StringMemberValue;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import javassist.bytecode.annotation.Annotation;

import java.lang.reflect.Modifier;


/**
 * 用于状态码枚举类的swagger显示
 */
@Configuration
@Order(-19999)
@Slf4j
public class SwaggerModelReader implements ParameterBuilderPlugin {

    @Value("${httpCode}")
    private String httpCode;

    private Integer cnt = 0;

    @Value("${name}")
    private String name;

    @Autowired
    private TypeResolver typeResolver;

    //根据用户自定义的类型拿到该类型所在的包的class位置
    @Override
    public void apply(ParameterContext context) {

        if (cnt == 1) {
            //根据枚举位置获取枚举类
            // 使用反射获取枚举类
            Class<?> enumClass;
            try {
                enumClass = Class.forName(httpCode);
            } catch (ClassNotFoundException e) {
                log.error("找不到枚举类");
                throw new RuntimeException(e);
            }
            //获取枚举类的所有常量
            Object[] enumConstants = enumClass.getEnumConstants();


            //生成一个新的类
            Class newClass = createRefModelIgp(enumConstants);
            //向documentContext的Models中添加我们新生成的Class
            context.getDocumentationContext()
                    .getAdditionalModels()
                    .add(typeResolver.resolve(newClass));
            //修改model参数的ModelRef为我们动态生成的class
            context.parameterBuilder()
                    .parameterType("body")
                    .modelRef(new ModelRef(name))
                    .name(name);
        }

        cnt++;
    }

    /**
     * @return
     */
    private Class createRefModelIgp(Object[] enumConstants) {
        try {

            //创建一个类
            ClassPool pool = ClassPool.getDefault();
            int lastDotIndex = httpCode.lastIndexOf(".");
            String prefix = httpCode.substring(0, lastDotIndex);

            CtClass ctClass = pool.makeClass(prefix + "." + name);
            //创建对象,并把已有的变量添加进去
            createCtFileds(enumConstants, ctClass);
            //返回最终的class
            return ctClass.toClass();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

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

    /**
     * 根据propertys中的值动态生成含有Swagger注解的javaBeen
     */
    public void createCtFileds(Object[] enumConstants, CtClass ctClass) {
        //添加枚举值成为变量
        for (Object object : enumConstants) {
            try {
                // 获取字段名和类型
                String fieldName = ((Enum<?>) object).name();
                // 遍历枚举常量数组
                // 强制转换为 HttpCode 接口类型
                HttpCode httpCode = (HttpCode) object;
                // 使用接口方法获取枚举常量的值
                int code = httpCode.getCode();
                String message = httpCode.getMessage();
                String fieldsInfo = code + "\t" + message;

                // 创建 CtField
                CtField ctField = new CtField(ClassPool.getDefault().get("java.lang.String"), fieldName, ctClass);
                ctField.setModifiers(Modifier.PUBLIC);

                // 设置参数描述
                // 使用枚举值的字符串表示作为描述
                String apiModelPropertyValue = fieldsInfo;
                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);
                }

                // 添加字段到类中
                ctClass.addField(ctField);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

上面的是解析的方法,状态码想要显示,需要实现下面这个接口

public interface HttpCode {
    int getCode();
    String getMessage();
}

然后再yml配置中添加以下

# 你实现HttpCode接口的类的位置
httpCode: com.xxxx.xxxx.xxxx.HttpStatus
# 你希望在swagger文档中状态码显示的名字
name: 状态码

显示效果如下:

遇到的问题

  • 1.如何通过反射拿到枚举的常量
  • 2.动态生成类的CtClass ctClass = pool.makeClass(prefix + “.” + name);报错frozen class

解决

  • 1.写一个接口让用户实现就可以保证反射取得枚举常量通用性
  • 2.开头的cnt就是解决报错frozen class的,具体原因未知
  • 18
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值