目录
- 一、Swagger 简介
- 1. 什么是 Swagger?
- 2. 如何使用 Swagger
- 3. Springboot 中swagger的使用示例
- 1. maven 引入安装
- 2. java配置
- 二、Swagger UI存在的缺点
- 1.不够方便直观
- 2.请求的参数没有缓存
- 3.不够美观
- 4.如果是JWT 无状态登录,Swagger使用起来就没有那么丝滑了
- 三、封装以及讨论
- 封装组件
- 代码封装
- 解决无状态登录问题
- 1. 我们往swagger-bootstrap-ui的右上角加一个按钮,点击出现弹框来配置自动获取登录token
- 2.javascript 脚本部分实现:
- 总结
一、Swagger 简介
Swagger 是一款用于 API 设计、构建、文档化和测试的开源工具。它提供了一整套的解决方案,帮助开发者更好地构建和管理 RESTful APIs。以下是对 Swagger 的简要介绍:
1. 什么是 Swagger?
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful Web 服务。它基于 OpenAPI 规范(以前称为 Swagger 规范),该规范定义了 API 的结构。
2. 如何使用 Swagger
- 定义 API:使用 Swagger Editor 编写 OpenAPI 规范文件,描述 API 的端点、请求参数、响应格式等。
- 生成文档:通过 Swagger UI 或 Swagger Codegen,生成可视化的 API 文档。
- 集成到项目:在项目中集成 Swagger 的相关工具,比如在 Spring Boot 项目中使用
springfox-swagger2
和springfox-swagger-ui
依赖,自动生成和托管 API 文档。
3. Springboot 中swagger的使用示例
以下是一个使用 Swagger 注解的简单示例,展示了如何在 Spring Boot 应用中集成 Swagger:
1. maven 引入安装
<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>
2. java配置
@EnableSwagger2
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.efficientnotes.boot.web"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Efficient notes api platform")
.description("apis")
.termsOfServiceUrl("http://localhost:9000/")
.contact("yh4494@sina.com")
.version("1.0")
.build();
}
}
title
title就是swagger页面上展示的title,比如交ERP api平台
description
详情介绍
termsOfServiceUrl
机构首页地址
contact
联系方式
version
版本号
Spring security 排除拦截:
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/v2/*").permitAll()
.antMatchers("/csrf").permitAll()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
控制器示例:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@RestController
@Api(value = "示例API", description = "这是一个示例API")
public class ExampleController {
@GetMapping("/hello")
@ApiOperation(value = "问候接口", notes = "通过传递名字来获得问候语")
public String hello(@ApiParam(value = "用户的名字", required = true) @RequestParam String name) {
return "Hello, " + name + "!";
}
}
在完成上述步骤后,启动 Spring Boot 应用并访问 http://localhost:8080/swagger-ui.html
,即可查看自动生成的 API 文档。
Swagger 是一个强大且灵活的工具集,为开发者提供了方便、高效的 API 管理解决方案。通过标准化和自动化,它极大地简化了 API 开发和维护的过程。
图 ①
二、Swagger UI存在的缺点
1.不够方便直观
swagger ui 布局是上下瀑布式的,比如我访问完A接口,想访问B接口,访问完B接口想继续访问A接口就必须往上翻,接口少还好操作。接口多的话来回就很烦。
2.请求的参数没有缓存
比如我想掉一个post接口来伪造一条数据,第一次访问完成之后,刷新页面后第二次还要重新造数据,就很麻烦,命名我只需要改部分字段重新请求就行,结果每次都要重新填写报文。字段躲起来兼职就是折磨。
3.不够美观
不用多说,当然一个工具类产品美观并不重要,但是美观的产品还是能给人带来心情愉悦的体验,就像你旁边坐着一位漂亮女孩,你整天心情都会好很多。不管如何对我来说工具颜值还是挺重要的。
4.如果是JWT 无状态登录,Swagger使用起来就没有那么丝滑了
因为JWT无状态登录这种需要每次在请求的Header中带上TOKEN,Swagger可没那么只能给你登录接口返回的token带过去,这样就导致无状态session的情况下Swagger的调试功能等于瘫痪状态。
三、封装以及讨论
我们来讨论下如何解决上述一些缺陷给Swagger换一层皮,并且将Swagger封装成组件
很久以前我就关注过一些swaggerui的项目了,比较优秀的是swagger-bootstrap-ui。现在好像升级过不叫这个名字了,但我保存了一份之前的swagger-bootstrap-ui的代码。github的地址是:https://github.com/xiaoymin/swagger-bootstrap-ui,直接可以下载的地址: swagger-bootstrap-ui。
这份UI解决了上述中前三个问题不够方便直观、请求的参数没有缓存、不够美观,虽然不是太好看,但也还好吧!如果对外观还是不够满意的话可以自己修改下样式,盖起来也很方便,我会在下面的文章中进行讨论和介绍。那么我们先看下具体的ui长什么样子。
图②
看起来是不是比图 ①要舒服多了。那么我们在下面异步异步的讨论如何将这个ui封装成一个组件可以直接引用以及如何解决缺陷的第四点:JWT无状态登录如何自动拼装token。
封装组件
现在我们有了前端代码,可以参考swagger原生的ui就是做成的一个jar包,引入的这个jar包就拥有了swagger-ui。我们也参考下这种方式,前后端代码结合自动配置,项目引入我们的依赖就能直接拥有提供接口文档的能力。
代码封装
我们想让我们的组件提供完整的swagger能力,那么首先我们自己的封装的swagger组件中要引入所有的swagger的依赖。
<dependencies>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
<exclusions>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-aop</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>jackson-annotations</artifactId>
<groupId>com.fasterxml.jackson.core</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>spring-beans</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
swagger2是通过EnableSwagger2来启用swagger,我们也可以写一个注解来做这件事情,引用我们自己定义的注解就可以自动配置启用我们的swagger组件。
EnableAllensSwagger
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(SwaggerConfig.class)
public @interface EnableAllensSwagger {
}
然后我们需要将所有的配置都配置好,提供我们自己自定义的配置出去,然需要引入的应用项目配置我们提供的配置来自定义Swagger。
SwaggerProperties
/**
* SwaggerProperties
*
* @author allens
* @since 2024/5/21
*/
@Component
@ConfigurationProperties(prefix = "allens.swagger")
@Setter
@Getter
public class SwaggerProperties {
private String packages;
private String title;
private String description;
private String termsOfServiceUrl;
private String version;
private String contact;
}
SwaggerConfig
通过这个类来做组件的autoconfig,当Springboot中启用@EnableAllensSwagger注解的时候,@Import(SwaggerConfig.class)就会自动加载注入 SwaggerConfig
,SwaggerConfig回去扫描com.allens.swagger
下的所有bean来自动加载组件。
@EnableSwagger2
@Configuration
@ComponentScan("com.allens.swagger")
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {
@Resource
SwaggerProperties swaggerProperties;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage(swaggerProperties.getPackages()))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title(swaggerProperties.getTitle())
.description(swaggerProperties.getDescription())
.termsOfServiceUrl(swaggerProperties.getTermsOfServiceUrl())
.contact(swaggerProperties.getContact())
.version(swaggerProperties.getVersion())
.build();
}
}
接着我们需要把前端的代码copy到项目中,同时项目打包的时候可以把swagger-bootstrap-ui打包到jar包中,同时我们需要访问/${servletContextPath}/doc.html的时候可以正常访问到前端界面。
① 拷贝swagger-boot-ui 到项目中
② 配置路由映射规则,如果是SpringSecurity也是要忽略doc.html
//webjars/**
这两个地址。
@Configuration
public class SwaggerWebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/resources/webjars/");
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(" "/doc.html",
"/webjars/**",
"/v2/api-docs/**",
"/swagger-resources/**")
.anonymous()
.anyRequest().authenticated();
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
http.addFilterBefore(corsFilter, LogoutFilter.class);
}
然后我们就可以直接到项目中引入我们的组件并且配置了
<dependency>
<groupId>com.efficientnotes</groupId>
<artifactId>allens-swagger</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
yml文件配置:
allens:
swagger:
packages: com.efficientnotes.boot.web
title: Efficient notes api platform
description: apis
termsOfServiceUrl: http://localhost:9000/
version: 1.0
contact: allens@sina.com
启动服务并访问: http://localhost:9000/${servletContextPath}/doc.html
解决无状态登录问题
如果说你的项目是Session模式的方案,那么到这里就结束了。但假如说你是无状态登录的方案,那么就要继续往下看了。无状态登录我们需要把token塞进header中进行请求,那么我们就需要拦截截取登录接口的报文中的token。而且每次请求的时候都要把header中的token拼接上去。
注意只有无状态JWT模式才需要这样做!!!
1. 我们往swagger-bootstrap-ui的右上角加一个按钮,点击出现弹框来配置自动获取登录token
点击之后是一个弹窗,这个弹窗会要求输入登录接口路径、登录token提取路径以及Token名称。如果走自动获取HeaderJSON是不需要填的。如果想手动塞Header,那么可以使用JSON格式塞TOKEN塞进行去,这种方式不需要配置登录接口路径和登录token提取路径以及token名称。
登录接口路径
你系统的登录接口路径登录接口提取路径
json path,比如登录接口返回的是{“data”: {token: “xxx”, “other”: “ok”},“msg”: “”},那么登录接口提取路径为:data.token
TOKEN名称
接口请求需要携带的Header Token名称Header JSON
有两种情况,如果不填上述三个配置,header JSON就可以手动设置,设置完成之后,每次请求都会自动携带你配置的Header上去。如果配置了上述三个配置,那么每次自动获取完登录的TOKEN之后会自动带出来。
来看看代码怎么实现的:
doc.html
<div class="sbu-header-right" style="margin-top:12px">
<div class="col-sm-6">
<button style="background: #0d5aa7" onclick="showPromoteDefaultHeader()" type="button" class="btn btn-info">通用头设置</button>
</div>
</div>
2.javascript 脚本部分实现:
function showPromoteDefaultHeader() {
var test = layer.open({
type: 1, // page 层类型
area: ['700px', '500px'],
title: '配置头信息',
shade: 0.6, // 遮罩透明度
shadeClose: true, // 点击遮罩区域,关闭弹层
maxmin: true, // 允许全屏最小化
anim: 0, // 0-6 的动画形式,-1 不开启
content:
`
<div class="allens-dialog">
<div class="element">
<span>登录接口路径</span>
<input id="allens-dialog-login-interface-name" name="loginInterfaceName" />
</div>
<div class="element">
<span>登录token提取路径</span>
<input id="allens-dialog-json-path" name="jsonPath" />
</div>
<div class="element">
<span>TOKEN名称</span>
<input id="allens-dialog-token-name" name="tokenName" />
</div>
<div class="element">
<span>Header JSON</span>
<textarea id="allens-dialog-header-json" style="width: 100%;" cols="10" rows="8" name="" id=""></textarea>
</div>
<button class="btn btn-primary btn-lg" id="allens-dialog-submit">提交</button>
</div>
`
});
function notEmpty (variable) {
if (variable === null || variable === undefined || variable === '') {
// 变量为空
return false;
}
return true;
}
var button = document.querySelector('#layui-layer' + test + ' #allens-dialog-submit');
var loginInterFaceName = document.querySelector('#layui-layer' + test + ' #allens-dialog-login-interface-name');
var jsonPath = document.querySelector('#layui-layer' + test + ' #allens-dialog-json-path');
var headerJson = document.querySelector('#layui-layer' + test + ' #allens-dialog-header-json');
var tokenName = document.querySelector('#layui-layer' + test + ' #allens-dialog-token-name');
button.onclick = () => {
if (notEmpty(loginInterFaceName)) {
localStorage.setItem('LOGININTERFACENAME', loginInterFaceName.value)
}
if (notEmpty(jsonPath)) {
localStorage.setItem('JSONPATH', jsonPath.value)
}
if (notEmpty(headerJson)) {
localStorage.setItem('HEADERJSON', headerJson.value)
}
if (notEmpty(tokenName)) {
localStorage.setItem('TOKENNAME', tokenName.value)
}
layer.close(test);
}
// text.setAttribute('placeholder', '格式为JSON:{"headerName": "headerValue"}')
var data = localStorage.getItem('HEADERJSON');
if (data) {
const jsonObject = JSON.parse(data);
const jsonString = JSON.stringify(jsonObject, null, 2);
headerJson.value = jsonString;
}
loginInterFaceName.value = localStorage.getItem('LOGININTERFACENAME')
jsonPath.value = localStorage.getItem('JSONPATH')
tokenName.value = localStorage.getItem('TOKENNAME')
}
可以下载下源码看下具体的实现,暂时只支持CSDN下载,后续我会传到github。https://download.csdn.net/download/yh4494/89335880
总结
至此我们讨论的四个swagger的缺点就全部解决了,当然你也可以自己去修改样式,源码都有的怎么该都可以,只要不要有大的bug就行。甚至可以上传到中央仓库给大家使用。