关于swagger配置及踩坑@Api参数postion无效解决接口排序问题
作者:王火火
这篇文章主要介绍了关于swagger配置及踩坑@Api参数postion无效解决接口排序问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
添加maven依赖
<!-- 集成swagger2 --> <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> <!--google很好用的一个类库--> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.0.1-jre</version> </dependency>
添加配置类
package top.lidaoyuan.hamster.api.config.swagger; import java.util.Arrays; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.bind.annotation.RequestMethod; import io.swagger.annotations.ApiOperation; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.builders.ResponseMessageBuilder; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ResponseMessage; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class Swagger2Config { @Bean public Docket customDocket() { // 配置全局参数返回状态 java.util.List<ResponseMessage> resMsgList = Arrays.asList( new ResponseMessageBuilder().code(200).message("成功!").build(), new ResponseMessageBuilder().code(-1).message("失败!").build(), new ResponseMessageBuilder().code(401).message("参数校验错误!").build(), new ResponseMessageBuilder().code(403).message("没有权限操作,请后台添加相应权限!").build(), new ResponseMessageBuilder().code(500).message("服务器内部异常,请稍后重试!").build(), new ResponseMessageBuilder().code(501).message("请登录!").build()); return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build() .globalResponseMessage(RequestMethod.GET, resMsgList) .globalResponseMessage(RequestMethod.POST, resMsgList) .globalResponseMessage(RequestMethod.PUT, resMsgList) .globalResponseMessage(RequestMethod.DELETE, resMsgList); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("Hamster接口文档") .description("接口文档说明") .version("1.0.0") .build(); } }
在application.properties中添加配置
logging.level.io.swagger.models.parameters.AbstractSerializableParameter=ERROR
添加控制类UserController
package top.lidaoyuan.hamster.api.web; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.extern.slf4j.Slf4j; import top.lidaoyuan.hamster.api.dto.JsonResultDTO; import top.lidaoyuan.hamster.api.dto.PageInputDTO; import top.lidaoyuan.hamster.api.dto.UserInputDTO; import top.lidaoyuan.hamster.api.entity.User; import top.lidaoyuan.hamster.api.service.IUserService; /** * 用户 前端控制器 * @author Lidy * @since 2019-03-06 */ @Api(tags = {"用户接口"}) @RestController @RequestMapping("/v1/user") @Slf4j public class UserController { @Autowired private IUserService userService; @ApiOperation(value = "新增用户") @PostMapping public JsonResultDTO create(@RequestBody @Valid UserInputDTO inputDTO, BindingResult bindingResult) { log.info("create.inputDTO:" + inputDTO); boolean hasError = bindingResult.hasErrors(); log.info("hasError:" + hasError); if(hasError) { List<String> errMsgList = bindingResult.getAllErrors().stream() .map(ObjectError::getDefaultMessage) .collect(Collectors.toList()); return JsonResultDTO.errorArgument(errMsgList.toString()); } User userDb = userService.save(inputDTO.convertToEntity()); log.info("create.userDb:" + userDb ); return JsonResultDTO.ok(inputDTO.convertFor(userDb)); } @ApiOperation(value = "获取单个用户") @GetMapping("{id}") public JsonResultDTO getOne(@ApiParam(value = "用户Id", example = "1", required = true) @PathVariable Integer id) throws Exception { log.info("getOne.id:" + id); User userDb = userService.getOne(id); log.info("getOne.userDB:" + userDb); return JsonResultDTO.ok(new UserInputDTO().convertFor(userDb)); } @ApiOperation(value = "更新单个用户") @PutMapping("{id}") public JsonResultDTO update(@ApiParam(value = "用户Id", example = "1", required = true) @PathVariable Integer id, @RequestBody UserInputDTO inputDTO) { log.info("update.id:" + id + " inputDTO:" + inputDTO ); User userDb = userService.update(inputDTO.convertToEntity(), id); log.info("update.userDb:" + userDb ); return JsonResultDTO.ok(inputDTO.convertFor(userDb)); } @ApiOperation(value = "获取用户列表") @GetMapping("listExample") public JsonResultDTO listExample(UserInputDTO inputDTO) { log.info("listExample.inputDTO:" + inputDTO); List<User> listDb = null; if(inputDTO == null) { listDb = userService.list(); }else { listDb = userService.list(inputDTO.convertToEntity()); } log.info("listExample.listDb:" + listDb); if(listDb == null || listDb.size() ==0) return JsonResultDTO.ok(); List<UserInputDTO> inputDTOList = new ArrayList<>(); for(User user: listDb) { inputDTOList.add(inputDTO.convertFor(user)); } return JsonResultDTO.ok(inputDTOList); } @ApiOperation(value = "分页获取用户列表") @GetMapping("listPageExample") public JsonResultDTO listPageExample(UserInputDTO inputDTO, PageInputDTO pageDTO) { log.info("listPageExample.inputDTO:" + inputDTO + " pageDTO:" + pageDTO); Page<User> pageDb = null; if(inputDTO == null) { pageDb = userService.page(pageDTO); }else { pageDb = userService.page(inputDTO.convertToEntity(), pageDTO); } log.info("listPageExample.pageDb:" + pageDb); if(pageDb == null || pageDb.getSize() ==0) { return JsonResultDTO.ok(); } List<UserInputDTO> inputDTOList = new ArrayList<>(); for(User user: pageDb) { inputDTOList.add(inputDTO.convertFor(user)); } return JsonResultDTO.page(inputDTOList,pageDb.getTotalElements()); } @ApiOperation(value = "删除用户") @DeleteMapping("{id}") public JsonResultDTO deleteById(@ApiParam(value = "用户Id", example = "1", required = true) @PathVariable Integer id) { log.info("del.id:" + id); userService.deleteById(id); return JsonResultDTO.ok(); } }
请求对象类DTO
package top.lidaoyuan.hamster.api.dto; import java.io.Serializable; import com.google.common.base.Converter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.*; import lombok.experimental.Accessors; import javax.validation.constraints.*; import top.lidaoyuan.hamster.api.entity.User; import top.lidaoyuan.hamster.utils.BeanUtils; import java.util.Date; import org.springframework.format.annotation.DateTimeFormat; import com.fasterxml.jackson.annotation.JsonFormat; /** * 用户DTO * @author Lidy * @since 2019-03-06 */ @ApiModel(value = "用户Model") @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @ToString @JsonInclude(Include.NON_NULL) public class UserInputDTO implements Serializable { private static final long serialVersionUID = 1L; /** ID */ @ApiModelProperty(hidden = true) private Integer id; /** 是否激活 */ @ApiModelProperty(value = "是否激活", position = 1) private Boolean active; /** 年龄 */ @ApiModelProperty(value = "年龄", position = 2) private Integer age; /** email */ @ApiModelProperty(value = "email", position = 3) @Email(message = "邮箱格式不正确") @NotBlank(message = "【email】不能为空") @Size(max = 255, message = "【email】字段长度应<255") private String email; /** 姓 */ @ApiModelProperty(value = "姓", position = 4) @NotBlank(message = "【姓】不能为空") @Size(max = 10, message = "【姓】字段长度应<10") private String firstname; /** 名 */ @ApiModelProperty(value = "名", position = 5) @Size(max = 20, message = "【名】字段长度应<20") private String lastname; /** 开始时间 */ @ApiModelProperty(value = "开始时间", example = "2019-03-06 17:09:10", position = 6) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date startDate; /** 结束时间 */ @ApiModelProperty(value = "结束时间", example = "2019-03-06 17:09:10", position = 7) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date endDate; /** 转换成实体类 */ public User convertToEntity() { return new UserInputDTOConver().convert(this); } /** 转换成InputDTO */ public UserInputDTO convertFor(User bean) { return new UserInputDTOConver().reverse().convert(bean); } /** Conver转换类 */ private static class UserInputDTOConver extends Converter<UserInputDTO, User> { @Override protected User doForward(UserInputDTO dto) { User bean = new User(); BeanUtils.copyProperties(dto, bean); return bean; } @Override protected UserInputDTO doBackward(User bean) { UserInputDTO dto = new UserInputDTO(); BeanUtils.copyProperties(bean, dto); return dto; } } }
响应对象类
package top.lidaoyuan.hamster.api.dto; import java.util.Arrays; import java.util.List; import org.springframework.data.domain.Page; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * Json结果DTO * @author ex-lidy001 * */ @ApiModel(value = "返回JSON对象") @Data @ToString @NoArgsConstructor @AllArgsConstructor @JsonInclude(Include.NON_NULL) public class JsonResultDTO { @ApiModelProperty(value = "状态码;200:成功") private Integer code; @ApiModelProperty(value = "状态说明") private String msg; @ApiModelProperty(value = "总记录数") private Long total; @ApiModelProperty(value = "返回数据") private Object data; public JsonResultDTO(Integer code, String msg) { this.code = code; this.msg = msg; this.data = Arrays.asList(); } public JsonResultDTO(Integer code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; if(data == null) { this.data = Arrays.asList(); } } public static JsonResultDTO ok() { return new JsonResultDTO(200, "成功"); } public static JsonResultDTO ok(String msg) { return new JsonResultDTO(200, msg); } public static JsonResultDTO ok(Object data) { return new JsonResultDTO(200, "成功", data); } public static JsonResultDTO page(Page<?> page) { JsonResultDTO jrDTO = new JsonResultDTO(200, "成功", page.getContent()); jrDTO.setTotal(page.getTotalElements()); return jrDTO; } public static JsonResultDTO page(List<?> list, Long total) { JsonResultDTO jrDTO = new JsonResultDTO(200, "成功", list); jrDTO.setTotal(total); return jrDTO; } public static JsonResultDTO error() { return new JsonResultDTO(-1, "失败!"); } public static JsonResultDTO error(String msg) { return new JsonResultDTO(-1, msg); } public static JsonResultDTO errorArgument(String msg) { return new JsonResultDTO(401, "参数校验:" + msg); } public static JsonResultDTO unAuth() { return new JsonResultDTO(403, "没有权限!"); } public static JsonResultDTO unlogin() { return new JsonResultDTO(501, "请登录!"); } public static JsonResultDTO exception(String msg) { return new JsonResultDTO(500, "系统内部错误:" + msg); } }
最后,上效果图
请求路径http://localhost:8080/swagger-ui.html
请求
响应
最后还有个坑
在使用最新版的springfox-swagger2 2.9.2``position排序的时候没有生效,后来在网上找了相关资料,需要重写swagger的两个类,具体如下。
package top.lidaoyuan.hamster.api.config.swagger; import java.util.List; import java.util.stream.Collectors; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import io.swagger.models.parameters.Parameter; import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl; /** * Created by wujie on 2019/2/16. * 重写 将Document转换成Swagger 类, 根据order进行排序 */ @Primary //同一个接口,可能会有几种不同的实现类,而默认只会采取其中一种的情况下 @Component("ServiceModelToSwagger2Mapper") @Order(Ordered.HIGHEST_PRECEDENCE) 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); } }
package top.lidaoyuan.hamster.api.config.swagger; import static springfox.documentation.swagger.common.SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER; import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Lists; import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiParam; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.service.AllowableListValues; import springfox.documentation.service.AllowableValues; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.schema.EnumTypeDeterminer; import springfox.documentation.spi.service.ExpandedParameterBuilderPlugin; import springfox.documentation.spi.service.contexts.ParameterExpansionContext; import springfox.documentation.spring.web.DescriptionResolver; import springfox.documentation.swagger.common.SwaggerPluginSupport; import springfox.documentation.swagger.readers.parameter.Examples; import springfox.documentation.swagger.schema.ApiModelProperties; /** * 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 = Strings.emptyToNull(apiParam.allowableValues()); AllowableValues allowable = allowableValues( Optional.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.examples(apiParam.examples())) .order(SWAGGER_PLUGIN_ORDER) .build(); } private void fromApiModelProperty(ParameterExpansionContext context, ApiModelProperty apiModelProperty) { String allowableProperty = Strings.emptyToNull(apiModelProperty.allowableValues()); AllowableValues allowable = allowableValues( Optional.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 Lists.transform(Arrays.asList(subject.getEnumConstants()), new Function<Object, String>() { @Override public String apply(final Object input) { return input.toString(); } }); } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。