Skip to content

Swagger 使用教程

  • 3.0.0 版本兼容 Spring Boot 2.2.x~2.5.x,不完全支持 Spring Boot 2.6 及以上版本,且社区维护已基本停止。
  • 如果你用的 Spring Boot 2.6 或更高版本,建议考虑换 springdoc-openapi

简介

Swagger是一个规范和完整的API框架,可用于生成、描述、调用Restful风格的Web服务的接口文档。

如果你在SpringBoot中使用的话,在项目启动后可以自动生成在线可调用的API文档,非常方便!

在SpringBoot中集成

使用官方Starter来整合Swagger是非常简单的,我们将采用此种方式。

  • 首先在pom.xml中添加 springfox 官方Swagger依赖;
xml
<!--springfox swagger官方Starter-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>${springfox-swagger.version}</version>
</dependency>
  • 由于Spring默认的路径匹配策略为PATH_PATTERN_PARSER,而SpringFox假设为ANT_PATH_MATCHER,所以需要修改application.yml配置文件,添加如下配置;
yaml
spring:
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER
  • 添加Swagger的Java配置,配置好Api信息和需要生成接口文档的类扫描路径,注意在SpringBoot2.6.x以上版本整合时由于兼容性问题需要配置一个BeanPostProcessor的Bean;
java
/**
 * @auther macrozheng
 * @description Swagger相关配置
 * @date 2022/11/23
 * @github https://github.com/macrozheng
 */
@Configuration
public class Swagger2Config {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.macro.mall.tiny.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("SwaggerUI演示")
                .description("mall-tiny")
                .contact(new Contact("macro", null, null))
                .version("1.0")
                .build();
    }

    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
                List<T> copy = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());
                mappings.clear();
                mappings.addAll(copy);
            }

            @SuppressWarnings("unchecked")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    field.setAccessible(true);
                    return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    throw new IllegalStateException(e);
                }
            }
        };
    }
}

image.png

开始使用

相关注解

由于Swagger主要是通过注解来标注文档内容的,这里我们先来学习下它的常用注解。

注解名称描述常用属性
@Api用于类,标识这个类是Swagger的资源tags:给该类下的接口设置标签
@Tag可用于类或方法,声明一个标签name:标签名称
description:标签描述
@ApiIgnore忽略该类的文档生成value:添加备注
@ApiOperation用于方法,用于描述一个HTTP请求方法value:给方法添加描述
@ApiParam用于参数,用于描述请求参数value:参数描述
name:参数名称
defaultValue:参数默认值
required:参数是否必填
allowableValues:参数允许范围
type:参数类型
@ApiImplicitParam代表一个单个API操作,与@ApiImplicitParams联用paramType:参数请求类型
dataTypeClass:参数值类型
其他类型同@ApiParam
@ApiImplicitParams多个@ApiImplicitParam注解的集合参数为@ApiImplicitParam数组
@ApiModel用于类,声明一个Swagger的模型value:模型名称
description:模型描述
@ApiProperty用于参数,声明Swagger模型的属性或填充数据value:属性描述
name:属性名称
allowableValues:允许值
@ApiResponse用于描述一个可能的返回结果responseCode:返回状态码
message:返回信息
@ApiResponses@ApiResponse的集合参数为@ApiResponse数组

相关配置

下面是Swagger的一些常用配置,在application.yml文件中配置即可。

yaml
springfox:
  documentation:
    # 是否启用Swagger扫描代码生成文档
    enabled: true
    open-api:
      # 是否启用Swagger的open-api
      enabled: false
    swagger-ui:
      # 是否启用Swagger的Web UI
      enabled: true
      # 配置文档基础路径,此时路径为:/doc/swagger-ui/index.html
      base-url: /doc

基本使用

  • 使用时我们只需把Swagger对应的注解添加到接口方法上即可;
java
/**
 * @auther macrozheng
 * @description 品牌管理Controller
 * @date 2019/4/19
 * @github https://github.com/macrozheng
 */
@Controller
@Api(tags = "PmsBrandController")
@Tag(name = "PmsBrandController", description = "商品品牌管理")
@RequestMapping("/brand")
public class PmsBrandController {
    @Autowired
    private PmsBrandService brandService;

    private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);

    @ApiOperation("分页查询品牌列表")
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    @ResponseBody
    @PreAuthorize("hasRole('ADMIN')")
    public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1")
                                                        @ApiParam("页码") Integer pageNum,
                                                        @RequestParam(value = "pageSize", defaultValue = "5")
                                                        @ApiParam("每页数量") Integer pageSize) {
        List<PmsBrand> brandList = brandService.listBrand(pageNum, pageSize);
        return CommonResult.success(CommonPage.restPage(brandList));
    }
}
  • 如果不想在参数上绑定注解,想手动定义的话可以使用@ApiImplicitParam注解;
java
/**
 * @auther macrozheng
 * @description 后台用户管理
 * @date 2018/4/26
 * @github https://github.com/macrozheng
 */
@Controller
@Api(tags = "UmsAdminController")
@Tag(name = "UmsAdminController", description = "后台用户管理")
@RequestMapping("/admin")
public class UmsAdminController {
    @Autowired
    private UmsAdminService adminService;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ApiOperation(value = "登录以后返回token")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "username", value = "用户名", required = true, dataTypeClass = String.class),
            @ApiImplicitParam(name = "password", value = "密码", required = true, dataTypeClass = String.class)
    })
    @ApiResponses({
            @ApiResponse(responseCode = "401", description = "暂未登录或token已经过期"),
            @ApiResponse(responseCode = "403", description = "没有相关权限"),
    })
    @ResponseBody
    public CommonResult login(@RequestParam String username, @RequestParam String password) {
        String token = adminService.login(username, password);
        if (token == null) {
            return CommonResult.validateFailed("用户名或密码错误");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return CommonResult.success(tokenMap);
    }
}
  • 实体类上的注解,可以采用如下方式定义。
java
@ApiModel(value = "PmsBrand",description = "商品品牌")
public class PmsBrand implements Serializable {
    @ApiModelProperty(value = "主键ID")
    private Long id;

    @ApiModelProperty(value = "名称")
    private String name;

    @ApiModelProperty(value = "首字母")
    private String firstLetter;

    @ApiModelProperty(value = "排序")
    private Integer sort;

    @ApiModelProperty(value = "是否为品牌制造商:0->不是;1->是")
    private Integer factoryStatus;

    @ApiModelProperty(value = "是否显示")
    private Integer showStatus;

    @ApiModelProperty(value = "产品数量")
    private Integer productCount;

    @ApiModelProperty(value = "产品评论数量")
    private Integer productCommentCount;

    @ApiModelProperty(value = "品牌logo")
    private String logo;

    @ApiModelProperty(value = "专区大图")
    private String bigPic;

    @ApiModelProperty(value = "品牌故事")
    private String brandStory;
}

结合Spring Security使用

我们经常会在项目中使用Spring Security实现登录认证,接下来我们来讲下如何使用Swagger整合Spring Security,实现访问需要登录认证的接口。

  • 如何访问需要登录认证的接口?只需在访问接口时添加一个合法的Authorization请求头即可,下面是Swagger相关配置;
java
/**
 * @auther macrozheng
 * @description Swagger相关配置(带认证)
 * @date 2022/11/23
 * @github https://github.com/macrozheng
 */
@Configuration
public class Swagger2Config {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.macro.mall.tiny.controller"))
                .paths(PathSelectors.any())
                .build()
                //添加登录认证
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
    }

    private List<SecurityScheme> securitySchemes() {
        //设置请求头信息
        List<SecurityScheme> result = new ArrayList<>();
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
        result.add(apiKey);
        return result;
    }

    private List<SecurityContext> securityContexts() {
        //设置需要登录认证的路径
        List<SecurityContext> result = new ArrayList<>();
        result.add(getContextByPath("/brand/.*"));
        return result;
    }

    private SecurityContext getContextByPath(String pathRegex) {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex(pathRegex))
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        result.add(new SecurityReference("Authorization", authorizationScopes));
        return result;
    }
}
  • 我们需要在Spring Security中配置好Swagger静态资源的无授权访问,比如首页访问路径/swagger-ui/
java
/**
 * @auther macrozheng
 * @description SpringSecurity的配置
 * @date 2018/4/26
 * @github https://github.com/macrozheng
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    @Lazy
    @Autowired
    private UmsAdminService adminService;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                .disable()
                .sessionManagement()// 基于token,所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问
                        "/",
                        "/swagger-ui/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger-resources/**",
                        "/v2/api-docs/**"
                )
                .permitAll()
                .antMatchers("/admin/login")// 对登录注册要允许匿名访问
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                .permitAll()
                .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                .authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();
        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
        return httpSecurity.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        //获取登录用户信息
        return username -> {
            AdminUserDetails admin = adminService.getAdminByUsername(username);
            if (admin != null) {
                return admin;
            }
            throw new UsernameNotFoundException("用户名或密码错误");
        };
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
        return new JwtAuthenticationTokenFilter();
    }
}
  • 调用登录接口获取token,账号密码为admin:123456

image.png

  • 点击Authorize按钮后输入Authorization请求头,之后就可以访问需要登录认证的接口了。

image.png