springboot2集成knife4j(swagger2)

springboot2集成knife4j(swagger2)

  • springboot2集成knife4j(swagger2)
    • 环境说明
    • 集成knife4j
      • 第一步:引入依赖
      • 第二步:编写配置类
      • 第三步:放行相关资源 & 保证启动了knife4j
      • 第四步:测试一下
        • 第一小步:编写controller
        • 第二小步:启动项目,访问api文档
    • 相关资料

环境说明

  • springboot:2.6.4
  • knife4j-openapi2-spring-boot-starter:4.0.0

集成knife4j

第一步:引入依赖

<dependency>
    <groupId>com.github.xiaoymingroupId>
    <artifactId>knife4j-openapi2-spring-boot-starterartifactId>
    <version>4.0.0version>
dependency>

第二步:编写配置类

提示:可以借助配置文件,进一步改造

import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.ideaaedi.demo.support.EnumDescriptor;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spi.service.ParameterBuilderPlugin;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Knife4j配置
 *
 * @author JustryDeng 
 * @since 1.0.0
 */
@Slf4j
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig implements WebMvcConfigurer {
    
    // 对于管控了权限的应用,应放行以下资源
    public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v2/api-docs", "/doc.html"};
    
    @Value("${spring.application.name:default}")
    private String applicationName;
    
    @Bean
    public Docket docket() {
        // 指定使用Swagger2规范
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        // 简介(支持Markdown语法)
                        .description("# 我是API简介")
                        // 服务地址
                        .termsOfServiceUrl("http://local.idea-aedi.com/")
                        // 作者及联系信息
                        .contact(new Contact("JustryDeng", "https://gitee.com/JustryDeng", "[email protected]"))
                        // api版本
                        .version("1.0.0")
                        .build())
                //分组名称(微服务项目可以用微服务名分组)
                .groupName(applicationName)
                .select()
                // 定位api
                .apis(
                        RequestHandlerSelectors.basePackage(getProjectBasePackage())
                                .and(RequestHandlerSelectors.withClassAnnotation(RestController.class)
                                        .or(RequestHandlerSelectors.withClassAnnotation(Controller.class))
                                )
                )
                .paths(PathSelectors.any())
                .build();
        
        docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
        return docket;
    }
    
    private List<SecurityScheme> securitySchemes() {
        // 设置请求头信息
        List<SecurityScheme> result = new ArrayList<>();
        // 第一个参数name,自定义即可。 在配置securityContexts时,通过此name对应到apiKey即可
        // 第二个参数,header name自定义即可。 如:JWT_TOKEN_KEY=Auth-Token,然后在代码里request.getHeader(JWT_TOKEN_KEY)取值
        String JWT_TOKEN_KEY="Auth-Token";
        ApiKey apiKey = new ApiKey("JustryDengApiKey", JWT_TOKEN_KEY, "header");
        result.add(apiKey);
        return result;
    }
    
    private List<SecurityContext> securityContexts() {
        // 设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        List<SecurityReference> securityReferences = defaultAuth();
        result.add(
                SecurityContext.builder().securityReferences(securityReferences).forPaths(
                        // 当直接使用swagger文档发送请求时,这些api需要满足securityReferences认证要求
                        PathSelectors.regex("/.*")
                                .and(
                                        // 当直接使用swagger文档发送请求时,这些api不需要满足securityReferences认证要求. '.*'表示匹配所有
                                        PathSelectors.regex("/hello.*").or(PathSelectors.regex("/hi.*"))
                                                .negate()
                                )
                ).build()
        );
        return result;
    }
    
    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        // 这里指定使用哪个apiKey进行认证鉴权. 这里指定使用上面名为JustryDengApiKey的apiKey
        result.add(new SecurityReference("JustryDengApiKey", authorizationScopes));
        return result;
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
    
    /**
     * 获取项目包前缀
     */
    private String getProjectBasePackage() {
        String projectBasePackage;
        String currPackageName = this.getClass().getPackage().getName();
        String[] packageItemArr = currPackageName.split("\\.");
        if (packageItemArr.length > 3) {
            projectBasePackage = String.join(".", packageItemArr[0], packageItemArr[1], packageItemArr[2]);
        } else {
            projectBasePackage = currPackageName;
        }
        log.info("Base package to scan api is -> {}", projectBasePackage);
        return projectBasePackage;
    }
    
    /**
     * 显示自定义枚举类型注释
     * 

*
参考here */ @Component @SuppressWarnings("unchecked") public static class Knife4jSwaggerEnumPlugin implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin { private static final Field parameterDescriptionField; private static final Field modelPropertyBuilderDescriptionField; static { // swagger3用: parameterDescriptionField = ReflectionUtils.findField(RequestParameterBuilder.class, "description"); parameterDescriptionField = ReflectionUtils.findField(ParameterBuilder.class, "description"); Objects.requireNonNull(parameterDescriptionField, "parameterDescriptionField should noe be null."); ReflectionUtils.makeAccessible(parameterDescriptionField); // swagger3用: modelPropertyBuilderDescriptionField = ReflectionUtils.findField(PropertySpecificationBuilder.class, "description"); modelPropertyBuilderDescriptionField = ReflectionUtils.findField(ModelPropertyBuilder.class, "description"); Objects.requireNonNull(modelPropertyBuilderDescriptionField, "ModelPropertyBuilder_descriptionField should noe be null."); ReflectionUtils.makeAccessible(modelPropertyBuilderDescriptionField); } /** * {@link ApiModelProperty}相关 *

* 主要处理枚举对象直接作为方法参数的内部字段的情况. 如: *

         *     @Data
         *     public class LoginTokenRespVO {
         *   
         *         @ApiModelProperty("用户类型")
         *         private UserTypeEnum userType;
         *     }
         * 
*/
@Override public void apply(ModelPropertyContext context) { Optional<BeanPropertyDefinition> optional = context.getBeanPropertyDefinition(); if (!optional.isPresent()) { return; } // 对应被@ApiModelProperty标注的字段 BeanPropertyDefinition beanPropertyDefinition = optional.get(); Class<?> fieldType = beanPropertyDefinition.getField().getRawType(); if (!Enum.class.isAssignableFrom(fieldType)) { return; } Class<Enum<?>> enumType = (Class<Enum<?>>) fieldType; Enum<?>[] enumConstants = enumType.getEnumConstants(); // swagger3用: PropertySpecificationBuilder modelPropertyBuilder = context.getSpecificationBuilder(); ModelPropertyBuilder modelPropertyBuilder = context.getBuilder(); Object oldValue = ReflectionUtils.getField(modelPropertyBuilderDescriptionField, modelPropertyBuilder); // 解析枚举 List<String> enumDescList = Arrays.stream(enumConstants).map(this::obtainEnumDescription).collect(Collectors.toList()); /* * swagger3用: * modelPropertyBuilder.description((oldValue == null ? BaseConstant.EMPTY : oldValue) + buildHtmlUnOrderList(enumDescList)) * .type(new ModelSpecificationBuilder().scalarModel(ScalarType.UUID).build()); */ modelPropertyBuilder.description((oldValue == null ? "" : oldValue) + buildHtmlUnOrderList(enumDescList)) .type(context.getResolver().resolve(Enum.class)); } /** * {@link ApiParam}、{@link io.swagger.v3.oas.annotations.Parameter}相关. *

主要处理:枚举对象直接作为方法参数的情况. 如: *

         *     @PostMapping("/test1")
         *     @ApiOperation(value = "测试1")
         *     public void test1(@ApiParam(value = "用户类型", required = true) UserTypeEnum userTypeEnum)
         * 
*/
@Override public void apply(ParameterContext context) { Class<?> type = context.resolvedMethodParameter().getParameterType().getErasedType(); // swagger3用: RequestParameterBuilder parameterBuilder = context.requestParameterBuilder(); ParameterBuilder parameterBuilder = context.parameterBuilder(); if (!Enum.class.isAssignableFrom(type)) { return; } Class<Enum<?>> enumType = (Class<Enum<?>>) type; Enum<?>[] enumConstants = enumType.getEnumConstants(); // 解析枚举 List<String> enumDescList = Arrays.stream(enumConstants).map(this::obtainEnumDescription).collect(Collectors.toList()); Object oldValue = ReflectionUtils.getField(parameterDescriptionField, parameterBuilder); parameterBuilder.description((oldValue == null ? "" : oldValue) + buildHtmlUnOrderList(enumDescList)); } /** * 此插件是否支持处理该DocumentationType */ @Override public boolean supports(DocumentationType documentationType) { return true; } /** * 获取枚举描述 * * @param enumObj 枚举对象 * * @return 枚举描述 */ private String obtainEnumDescription(@NonNull Enum<?> enumObj) { String name = enumObj.name(); /* * 枚举说明器示例: * * public interface EnumDescriptor { * // 获取枚举项说明 * String obtainDescription(); * } */ if (enumObj instanceof EnumDescriptor) { return name + ":" + ((EnumDescriptor) enumObj).obtainDescription(); } return name; } /** * 构建无序列表html * * @param itemList 列表元素 * * @return 无序列表html */ private String buildHtmlUnOrderList(@Nullable List<String> itemList) { if (CollectionUtils.isEmpty(itemList)) { return ""; } StringBuilder sb = new StringBuilder(); sb.append("
    "); for (String item : itemList) { sb.append("
  • "); sb.append(item); sb.append("
  • "
    ); } sb.append("
"
); return sb.toString(); } } }

第三步:放行相关资源 & 保证启动了knife4j

  • 放行相关资源

    对于管控了权限的应用,应放行以下资源

    // 需要放行的资源已经定义进上面编写的Knife4jConfig中
    public static String[] RESOURCE_URLS = new String[]{"/webjars/**", "/swagger**", "/v2/api-docs", "/doc.html"};
    
  • 保证启动了knife4j

    # 启动knife4j(注:有时,如果我们不进行此配置,knife4j不会开启)
    # 其实核心是:保证com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration生效即可。如果knife4j怎么也没启动,请debug此类,确保其被加载
    knife4j.enable=true
    

第四步:测试一下

第一小步:编写controller
import com.ideaaedi.demo.controller.model.UserAddReqVO;
import com.ideaaedi.demo.controller.model.UserDetailRespVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用于测试knife4j的controller
 *
 * @author JustryDeng 
 * @since 1.0.0
 */
@RestController
@Api(tags = "我是DemoController")
public class TestController {

    @GetMapping("/hello")
    @ApiOperation(value = "哈喽")
    public String hello(@ApiParam(name = "name", value = "姓名", required = true)@RequestParam String name) {
        return "hello " + name;
    }
    
    @PostMapping("/user/add")
    @ApiOperation(value = "新增用户")
    public UserDetailRespVO addUser(@RequestBody UserAddReqVO req) {
        UserDetailRespVO resp = new UserDetailRespVO();
        resp.setId(9527L);
        resp.setName(req.getName());
        resp.setAge(req.getAge());
        return resp;
    }
    
    @DeleteMapping("/user/delete/{id}")
    @ApiOperation(value = "删除用户")
    public Boolean addUser(@ApiParam(name = "id", value = "数据id", required = true) @PathVariable Long id) {
        return true;
    }
    
    /**
     * 测试 @RequestBody、@RequestParam、@PathVariable并存
     */
    @PostMapping("/multi-anno/{id}")
    @ApiOperation(value = "组合使用测试")
    public UserDetailRespVO testMultiAnno(@RequestBody UserAddReqVO req,
                                          @ApiParam(name = "name", value = "姓名", required = true)
                                          @RequestParam String name,
                                          @ApiParam(name = "id", value = "数据id", required = true)
                                          @PathVariable Long id) {
        UserDetailRespVO resp = new UserDetailRespVO();
        resp.setId(9527L);
        resp.setName(req.getName());
        resp.setAge(req.getAge());
        return resp;
    }
    
}

此controller中用到的相关模型

  • UserAddReqVO

    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    import javax.validation.constraints.NotBlank;
    
    /**
     * 用户新增req模型
     */
    @Data
    public class UserAddReqVO {
        
        @ApiModelProperty(value = "姓名",required = true)
        @NotBlank(message = "姓名不能为空")
        private String name;
        
        @ApiModelProperty("年龄")
        private Integer age;
    }
    
  • UserDetailRespVO

    import io.swagger.annotations.ApiModelProperty;
    import lombok.Data;
    
    /**
     * 用户详情resp模型
     */
    @Data
    public class UserDetailRespVO {
        
        @ApiModelProperty("id")
        private Long id;
        
        @ApiModelProperty("姓名")
        private String name;
        
        @ApiModelProperty("年龄")
        private Integer age;
    }
    
第二小步:启动项目,访问api文档

启动项目后,直接访问http://{ip}:{端口}/doc.html即可

springboot2集成knife4j(swagger2)_第1张图片

springboot2集成knife4j(swagger2)_第2张图片

说明:

  • 文档分组:可切换观察其余分组下的api
  • 主页:概览
  • Swagger Models:可以查看所有请求模型的信息
  • 文档管理:可以导出文档、进行高级设置(如设置后处理脚本等)、进行全局参数设置、查看api信息
  • 点击进入文档后,会展示api的详细信息,也可以进行调试,还可以打开api json数据等等

相关资料

  • demo代码下载(knife4j-swagger2-springboot2分支)
  • springboot2集成knife4j(swagger3)
  • springcloud-gateway集成knife4j(swagger2)
  • springcloud-gateway集成knife4j(swagger3)
  • knife4j官网
  • 本文已被收录进《程序员成长笔记》 ,笔者JustryDeng

你可能感兴趣的:(Java知识大杂烩,knife4j,knife4j,swagger,api文档,api,knife4j集成)