swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug

先看一下集成后的完整效果:

swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第1张图片
swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第2张图片

依赖

<properties>
        <springfox-swagger.version>2.9.2springfox-swagger.version>
 properties>
 <dependency>
     <groupId>io.springfoxgroupId>
     <artifactId>springfox-swagger2artifactId>
     <version>${springfox-swagger.version}version>
 dependency>
 <dependency>
     <groupId>io.springfoxgroupId>
     <artifactId>springfox-swagger-uiartifactId>
     <version>${springfox-swagger.version}version>
 dependency>

Swagger配置文件

  • 自定义公共Response异常信息
  • 自定义公共Request信息: token
@Slf4j
@Configuration
@Profile({Const.Env.ENV_DEV, Const.Env.ENV_TEST})// 设置 dev test 环境开启
@EnableSwagger2
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {

    @Autowired(required = false) //表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错
    private SwaggerProperties swaggerProperties;

    @Bean
    public Docket createRestApi() {
        //自定义Response异常信息
        List<ResponseMessage> responseMessageList =  new ArrayList<ResponseMessage>() { {
            add(new ResponseMessageBuilder().code(ResultEnum.SUCCESS.getCode())
                .message(ResultEnum.SUCCESS.getMessage()).build());
            add(new ResponseMessageBuilder().code(ResultEnum.PARAM_ERROR.getCode())
                    .message(ResultEnum.PARAM_ERROR.getMessage()).responseModel(new ModelRef("ApiError")).build());
            add(new ResponseMessageBuilder().code(ResultEnum.AUTHOR_EXPIRED.getCode())
                    .message(ResultEnum.AUTHOR_EXPIRED.getMessage()).build());
            add(new ResponseMessageBuilder().code(ResultEnum.NO_PERMITION.getCode())
                    .message(ResultEnum.NO_PERMITION.getMessage()).build());
            add(new ResponseMessageBuilder().code(ResultEnum.BUSSINESS_EXCEPTION.getCode())
                    .message(ResultEnum.BUSSINESS_EXCEPTION.getMessage()).build());
            add(new ResponseMessageBuilder().code(ResultEnum.INTERNAL_SERVER_ERROR.getCode())
                    .message(ResultEnum.INTERNAL_SERVER_ERROR.getMessage()).build());
            }
        };

        Parameter parameter = new ParameterBuilder().name(SecurityConst.TOKEN).description("head或者query带上token")
                // -1是为了当一群都是默认值SWAGGER_PLUGIN_ORDER, TOKEN可以排在最前
                .modelRef(new ModelRef("string")).parameterType("header").order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER - 1).required(true).build();

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo()).select()
                //扫描所有有注解的api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build()
                .useDefaultResponseMessages(false)
                //全局ResponseMessage
                .globalResponseMessage(RequestMethod.GET, responseMessageList)
                .globalResponseMessage(RequestMethod.POST, responseMessageList)
                .globalResponseMessage(RequestMethod.PUT, responseMessageList)
                .globalResponseMessage(RequestMethod.DELETE, responseMessageList)
                //请求,带上token
                .globalOperationParameters(Arrays.asList(parameter))
                //ignoredParameterTypes
                ;
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
                .contact(new Contact(swaggerProperties.getContactName(), "", swaggerProperties.getContactEmail()))
                .version(swaggerProperties.getVersion())
                .build();
    }
}

写一个简单的登录接口

/**
 * 

* 后台用户表 前端控制器 *

* * @author wujie * @since 2019-01-30 */
@RestController @RequestMapping(value = "/user", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @Api(tags = "后台用户管理") @Slf4j public class AdminController { @Autowired private AdminService adminService; @PostMapping("/login") @ApiOperation(value = "用户登录", notes = "根据用户名和密码登录,返回token") @ApiResponses({ @io.swagger.annotations.ApiResponse(code = 200, message = "登录成功"), @io.swagger.annotations.ApiResponse(code = 4014, message = "用户不存在", response = ApiError.class), @io.swagger.annotations.ApiResponse(code = 4018, message = "账号密码错误"), @io.swagger.annotations.ApiResponse(code = 4015, message = "账号被冻结,请联系管理员") }) public ApiResponse login(@ModelAttribute LoginParam param) { //校验参数 BeanValidator.check(param); String token = adminService.login(param); HashMap map = Maps.newHashMap(); map.put("token", token); return ApiResponse.success("登录成功", map); } }

LoginParam

/**
 * Created by wujie on 2019/2/1.
 * 用户登录表单参数
 */
@Data
@ApiModel(value = "LoginParam", description = "用户登录请求参数描述")
public class LoginParam {

    @ApiModelProperty(value = "用户名(4-50个字符)", required = true, position = 1)
    @NotEmpty(message = "用户名不能为空")
    @Length(min = 4, max = 50, message = "用户名不能超过50个字符")
    private String username;

    @ApiModelProperty(value = "密码(不小于6个字符)", required = true, position = 2)
    @NotEmpty(message = "密码不能为空")
    @Length(min = 6, message = "密码不能小于6个字符")
    private String password;

    @ApiModelProperty(value = "是否自动登录", required = false, example = "false", position = 3)
    private Boolean isAutoLogin = false;

}

效果:

swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第3张图片

发现这里并没有按照原先LoginParam申明的顺序排列,翻看一下swagger的源码查找一下原因:

在springfox.documentation.service.Operation发现原来parameters被按照字母进行了升序

 this.parameters = FluentIterable.from(parameters)
        .toSortedList(byParameterName());
 
 private Comparator<Parameter> byParameterName() {
    return new Comparator<Parameter>() {
      @Override
      public int compare(Parameter first, Parameter second) {
        return first.getName().compareTo(second.getName());
      }
    };
  }

解决思路:需要从@ApiModelProperty读取position,将position赋值给Parameter的order, 然后将List根据order输出

主要是涉及:ExpandedParameterBuilderPlugin和ServiceModelToSwagger2Mapper的重现

重写ExpandedParameterBuilderPlugin.class

/**
 * Created by wujie on 2019/2/16.
 * 自定义ExpandedParameterBuilderPlugin,主要是修正源码query传入请求参数postion无效
 * 这里,将postion赋值给order
 */
@Primary
@Component
public class CustomSwaggerParameterBuilder implements ExpandedParameterBuilderPlugin {

    private final DescriptionResolver descriptions;
    private final EnumTypeDeterminer enumTypeDeterminer;

    @Autowired
    public CustomSwaggerParameterBuilder(
            DescriptionResolver descriptions,
            EnumTypeDeterminer enumTypeDeterminer) {
        this.descriptions = descriptions;
        this.enumTypeDeterminer = enumTypeDeterminer;
    }

    @Override
    public void apply(ParameterExpansionContext context) {
        Optional<ApiModelProperty> apiModelPropertyOptional = context.findAnnotation(ApiModelProperty.class);
        if (apiModelPropertyOptional.isPresent()) {
            fromApiModelProperty(context, apiModelPropertyOptional.get());
        }
        Optional<ApiParam> apiParamOptional = context.findAnnotation(ApiParam.class);
        if (apiParamOptional.isPresent()) {
            fromApiParam(context, apiParamOptional.get());
        }
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }

    private void fromApiParam(ParameterExpansionContext context, ApiParam apiParam) {
        String allowableProperty = emptyToNull(apiParam.allowableValues());
        AllowableValues allowable = allowableValues(
                fromNullable(allowableProperty),
                context.getFieldType().getErasedType());

        maybeSetParameterName(context, apiParam.name())
                .description(descriptions.resolve(apiParam.value()))
                .defaultValue(apiParam.defaultValue())
                .required(apiParam.required())
                .allowMultiple(apiParam.allowMultiple())
                .allowableValues(allowable)
                .parameterAccess(apiParam.access())
                .hidden(apiParam.hidden())
                .scalarExample(apiParam.example())
                .complexExamples(examples(apiParam.examples()))
                .order(SWAGGER_PLUGIN_ORDER)
                .build();
    }

    private void fromApiModelProperty(ParameterExpansionContext context, ApiModelProperty apiModelProperty) {
        String allowableProperty = emptyToNull(apiModelProperty.allowableValues());
        AllowableValues allowable = allowableValues(
                fromNullable(allowableProperty),
                context.getFieldType().getErasedType());

        maybeSetParameterName(context, apiModelProperty.name())
                .description(descriptions.resolve(apiModelProperty.value()))
                .required(apiModelProperty.required())
                .allowableValues(allowable)
                .parameterAccess(apiModelProperty.access())
                .hidden(apiModelProperty.hidden())
                .scalarExample(apiModelProperty.example())
                .order(apiModelProperty.position()) //源码这里是: SWAGGER_PLUGIN_ORDER,需要修正
                .build();
    }

    private ParameterBuilder maybeSetParameterName(ParameterExpansionContext context, String parameterName) {
        if (!Strings.isNullOrEmpty(parameterName)) {
            context.getParameterBuilder().name(parameterName);
        }
        return context.getParameterBuilder();
    }

    private AllowableValues allowableValues(final Optional<String> optionalAllowable, Class<?> fieldType) {

        AllowableValues allowable = null;
        if (enumTypeDeterminer.isEnum(fieldType)) {
            allowable = new AllowableListValues(getEnumValues(fieldType), "LIST");
        } else if (optionalAllowable.isPresent()) {
            allowable = ApiModelProperties.allowableValueFromString(optionalAllowable.get());
        }
        return allowable;
    }

    private List<String> getEnumValues(final Class<?> subject) {
        return transform(Arrays.asList(subject.getEnumConstants()), new Function<Object, String>() {
            @Override
            public String apply(final Object input) {
                return input.toString();
            }
        });
    }
}

重写ServiceModelToSwagger2MapperImpl.class

/**
 * Created by wujie on 2019/2/16.
 * 重写 将Document转换成Swagger 类, 根据order进行排序
 */
@Primary //同一个接口,可能会有几种不同的实现类,而默认只会采取其中一种的情况下
@Component("ServiceModelToSwagger2Mapper")
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class CustomModelToSwaggerMapper extends ServiceModelToSwagger2MapperImpl {

    @Override
    protected List<Parameter> parameterListToParameterList(List<springfox.documentation.service.Parameter> list) {
        //list需要根据order|postion排序
        list = list.stream().sorted((p1, p2) -> Integer.compare(p1.getOrder(), p2.getOrder())).collect(Collectors.toList());
//        log.debug("************************************list:{}", list.toString());
        return super.parameterListToParameterList(list);
    }
}

swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第4张图片

排序问题是解决了,但新问题:由于在swagger配置文件中,定义了全局的请求参数token,但登录接口不需要传token,而@ApiImplicitParam注解并没有hidden相关属性。

解决方式: 自定义规则——access=“hidden”,就需要隐藏该ApiImplicitParam

swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第5张图片
swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第6张图片

加了这几行代码后,终于解决了

swagger最新2.9.2使用,以及重写相关插件修复ApiModel作为query请求参数无法根据postion排序的bug_第7张图片

你可能感兴趣的:(java)