一、背景和目的
Swagger 在目前企业中作为前后端开发对接的技术已经得到了非常广泛的应用,后端开发人员只需要根据 OpenAPI 官方定义的注解就可以把接口文档非常丰富的呈现给前端接口对接人员。并且接口文档是随着代码的变动实时更新,同时提供了在线 HTML 文档辅助开发人员可以进行接口联调测试,这大大省去了技术人员写文档的烦恼,也提升了企业开发的效率,减少沟通成本。
-
代码变,文档变。只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,很好的保证了文档的时效性。
-
跨语言性,支持 40 多种语言。
-
Swagger UI 呈现出来的是一份可交互式的 API 文档,我们可以直接在文档页面尝试 API 的调用,省去了准备复杂的调用参数的过程。
-
还可以将文档规范导入相关的工具(例如 Postman、SoapUI), 这些工具将会为我们自动地创建自动化测试。
Knife4j 是一个 SwaggerUI 的增强工具,同时也提供了一些增强功能,使用 Java+Vue 进行开发,帮助开发者能在编写接口注释时更加完善,基于 OpenAPI 的规范完全重写 UI 界面,左右布局的方式也更加适合国人的习惯。
1.、URI
URI 表示资源,资源一般对应服务器端领域模型中的实体类。
URI规范
不用大写;
用中杠-不用下杠_;
参数列表要encode;
URI中的名词表示资源集合,使用复数形式。
2、Request
GET:查询
GET /zoos
POST:创建单个资源。POST一般向“资源集合”型uri发起
POST /animals //新增动物
PUT:更新单个资源(全量),客户端提供完整的更新后的资源。与之对应的是 PATCH,PATCH 负责部分更新,客户端提供要更新的那些字段。PUT/PATCH一般向“单个资源”型uri发起
PUT /animals/1
DELETE:删除
DELETE /zoos/1/animals //删除id为1的动物园内的所有动物
HEAD / OPTION 用的不多,就不多解释了。
安全性和幂等性
安全性:不会改变资源状态,可以理解为只读的;
幂等性:执行1次和执行N次,对资源状态改变的效果是等价的。
. |
安全性 |
幂等性 |
GET |
√ |
√ |
POST |
× |
× |
PUT |
× |
√ |
DELETE |
× |
√ |
安全性和幂等性均不保证反复请求能拿到相同的response。以 DELETE 为例,第一次DELETE返回200表示删除成功,第二次返回404提示资源不存在,这是允许的。
3、环境
场景1:测试环境
便于开发人员调试,后台和前端开发人员协作,以及对外公布API使用
application.yml
swagger:
show: true
场景2:生产环境
在生产环境也会启用,就会存在一定的安全风险。
application.yml
swagger:
show: false
package com.xiaominfo.swagger.service.user.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfiguration {
@Value("${swagger.show}")
private boolean swaggerShow;
@Bean(value = "userApi")
@Order(value = 1)
public Docket groupRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(groupApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xiaominfo.swagger.service.user.controller"))
.paths(swaggerShow == true ? PathSelectors.any() : PathSelectors.none())
.build();
}
private ApiInfo groupApiInfo() {
return new ApiInfoBuilder()
.title("swagger-bootstrap-ui很棒~~~!!!")
.description("<div style='font-size:14px;color:red;'>swagger-bootstrap-ui-demo RESTful APIs</div>")
.termsOfServiceUrl("http://www.group.com/")
.contact("group@qq.com")
.version("1.0")
.build();
}
}
或者使用knife4j的增强特性
生产环境屏蔽资源
knife4j:
# 开启增强配置
enable: true
# 开启生产环境屏蔽
production: true
访问页面加权控制
knife4j:
# 开启增强配置
enable: true
# 开启Swagger的Basic认证功能,默认是false
basic:
enable: true
# Basic认证用户名
username: test
# Basic认证密码
password: 123
二、API文档自动生成
代码地址:https://gitee.com/xiaoym/swagger-bootstrap-ui-demo.git
整体项目结构如下:
|-knife4j-spring-cloud-gateway
|-----service-doc //文档聚合中心,是所有微服务文档的出口
|-----service-order //订单服务,包含所有与订单业务模块相关的接口
|-----service-server //eureka 注册中心
|-----service-user //用户服务,包含所有的用户接口
eureka注册中心
注册中心几乎没有代码,只是在pom.xml文件中引入了eureka服务的jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
项目的application.yml配置文件如下:
server:
port: 10000
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: knife4j-gateway-server
定义注册中心访问地址,端口号等属性
最后通过注解@EnableEurekaServer来启用注册中心
@EnableEurekaServer
@SpringBootApplication
public class ServiceServerApplication {
服务接口(订单order & 用户User)
由于服务接口订单和用户两个模块其实属性是差不多,只是接口不一样,因此就随便挑一个服务的配置来说吧
service-user:用户服务的接口
每个微服务只需要引入和swagger相关的后端jar包即可,不需要引入swagger的前端Ui包,knife4j为我们提供了微服务项的starter,供开发者使用
当然,作为子服务,还需要引入eureka-client的jar包,所以,pom.xml文件相关配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.6</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
</dependency>
项目的jar包引入完成后,接下来是配置swagger的相关配置,SwaggerConfiguration.java配置如下:
package com.xiaominfo.swagger.service.user.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
@Import(BeanValidatorPluginsConfiguration.class)
public class SwaggerConfiguration {
@Value("${swagger.show}")
private boolean swaggerShow;
@Bean(value = "userApi")
@Order(value = 1)
public Docket groupRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(groupApiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.xiaominfo.swagger.service.user.controller"))
.paths(swaggerShow == true ? PathSelectors.any() : PathSelectors.none())
.build();
}
private ApiInfo groupApiInfo() {
return new ApiInfoBuilder()
.title("swagger-bootstrap-ui很棒~~~!!!")
.description("<div style='font-size:14px;color:red;'>swagger-bootstrap-ui-demo RESTful APIs</div>")
.termsOfServiceUrl("http://www.group.com/")
.contact("group@qq.com")
.version("1.0")
.build();
}
}
配置扫描目录包
通过@EnableSwagger2和@EnableSwaggerBootstrapUi来开启swagger和增强特性
配置项目的application.yml文件,如下:
server:
port: 10001
servlet:
context-path: /aub
spring:
application:
name: service-user
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10000/eureka/
swagger:
show: true
指定注册中心地址即可
最后,启用eureka客户端
@EnableEurekaClient
@SpringBootApplication
public class ServiceUserApplication {
当然,在服务的模块中还有和自己服务相关的业务接口(Controller代码),在这里就不列举了
订单模块(service-order)的代码配置和用户是类似的
文档聚合
有了eureka注册中心,服务模块的接口也已完成,最后一步是把我们所有的微服务都聚合到一个文档,统一输出到前端,供开发者调用了
pom引入相关jar包
service-doc也是一个eureka客户端,首先引入相关的jar包,pom.xml配置文件如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
spring-cloud-starter-netflix-eureka-client:eureka客户端
spring-cloud-starter-gateway:gateway网关
knife4j-spring-boot-starter:knife4j提供的前端ui和后端代码
另外,其实在文档这里,如果没有后端代码编写的话,仅仅引入一个swagger的前端ui模块也是可以的
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-ui</artifactId>
</dependency>
Application文件配置
配置我们的网关属性,路由规则等,application.yml配置文件如下:
server:
port: 10003
spring:
application:
name: service-doc
cloud:
gateway:
discovery:
locator:
# enabled: true
lowerCaseServiceId: true
routes:
- id: service-user
uri: lb://service-user
predicates:
- Path=/user/aub/**
# - Header=Cookie,Set-Cookie
filters:
#- SwaggerHeaderFilter
- StripPrefix=1
- id: service-order
uri: lb://service-order
predicates:
- Path=/order/**
filters:
#- SwaggerHeaderFilter
- StripPrefix=1
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10000/eureka/
logging:
level:
org.springframework:cloud.gateway: debug
文档聚合业务编码
在我们使用Spring Boot等单体架构集成swagger项目时,是通过对包路径进行业务分组,然后在前端进行不同模块的展示,而在微服务架构下,我们的一个服务就类似于原来我们写的一个业务组
springfox-swagger提供的分组接口是swagger-resource,返回的是分组接口名称、地址等信息
在Spring Cloud微服务架构下,我们需要重写该接口,主要是通过网关的注册中心动态发现所有的微服务文档,代码如下:
public class SwaggerResourceConfig implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routes = new ArrayList<>();
routeLocator.getRoutes().subscribe(route -> routes.add(route.getId()));
gatewayProperties.getRoutes().stream().filter(routeDefinition -> routes.contains(routeDefinition.getId())).forEach(route -> {
route.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(route.getId(),
predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
.replace("**", "v2/api-docs"))));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
log.info("name:{},location:{}",name,location);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
接口:
@RestController
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/swagger-resources/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/swagger-resources")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
启动配置
最后,项目启动类添加相关注解,代码如下:
@EnableDiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ServiceDocApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDocApplication.class, args);
}
}
文档展示
最后分别依次启动项目:
service-server
service-user
service-order
service-doc
打开文档地址:http://localhost:10003/doc.html
查看文档效果如下:
三、API 开发规范
1、@Api
@Api注解放在类上面,这里的value是没用的,tags表示该controller的介绍。
但是tags如果有多个值,会生成多个list
@Api(tags = "用户模块")
@RestController
@RequestMapping("/user")
public class UserController {
2、@ApiOperation
@ApiOperation注解用于放在方法上面,其中value是该类的简短的叙述,notes一般是该方法的详细描述。
@ApiOperation(value = "查询用户列表")
@PostMapping(value = "/list")
public Rest<List<User>> list(){
3、@ApiImplicitParams
@ApiImplicitParams:用在请求的方法上,包含一组参数说明
@ApiImplicitParam:对单个参数的说明
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)--> 请求参数的获取:@PathVariable
· body(请求体)--> @RequestBody User user
· form(普通表单提交)
dataType:参数类型,默认String,其它值dataType="int"
defaultValue:参数的默认值
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = false, dataType = "String", paramType = "query", example = "1")
})
@ApiOperation(value = "根据用户id查询用户详情")
@GetMapping("/queryById")
public Rest<User> queryById(@RequestParam(value = "id") String id) {
Rest<User> userRest = new Rest<>();
userRest.setData(new User("user5", "总经理", "公司1"));
return userRest;
}
4、@ApiResponses
@ApiResponses:方法返回对象的说明
@ApiResponse:每个参数的说明
code:数字,例如400
message:信息,例如"请求参数没填好"
response:抛出异常的类
@ApiResponses(value = {
@ApiResponse(code = 200, message = "接口返回成功状态"),
@ApiResponse(code = 500, message = "接口返回未知错误,请联系开发人员调试")
})
@ApiOperation(value = "文件上传 测试接口", notes = "访问此接口,执行文件上传,测试接口")
@PostMapping("upload")
public void upload(@RequestParam("file") MultipartFile file) {
}
5、@ApiModel
这里的Data Type为 Model,此时我们可以在实体类的代码中添加注解,选择我们需要在这里显示的属性。如下:
@ApiModelProperty(hidden = true)表示不需要在swagger页面进行展示,required表示该属性为必需的属性。
@ApiModel(value = "用户")
public class User {
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "年龄")
private Integer age;
@ApiModelProperty(value = "工作")
private String worker;
@ApiModelProperty(value = "单位")
private String company;
四、部署方式
1、前端打包成静态文件夹
将dist文件夹下的内容拷贝到后端项目中resources/static目录下
2、前端页面放行
在springsucurity的拦截器里面加上以下代码,放行这些静态文件
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/login","/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico");
}
3、使用maven的package打包项目
生成jar文件
4、把文件传到服务器主机
运行以下命令
nohup java -jar plms-0.0.1-SNAPSHOT.
5、在浏览器访问
http://服务器主机的ip:8080/index.html
五、在线测试
1、测试环境地址的配置管理
2、测试数据的模拟
Mock数据属性说明:
- 数据类型(MIME):响应结果类型,如JSON、XML等;
- 结果数据:响应结果的数据,如响应结果类型为JSON时可设置响应结果数据为一段JSON数据;
六、授权
授权,是指授予某个APP调用某个API的权限。您的APP需要获得API的授权才能调用该API。
七、监控
API Monitor是一个免费软件,可以让你监视和控制应用程序和服务,取得了API调用。 它是一个强大的工具,看到的应用程序和服务是如何工作的,或跟踪,你在自己的应用程序的问题。
八、统计
Dubbo-Monitor主要用来统计服务的调用次数和调用时间,服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心,监控中心则使用数据绘制图表来显示
点击Statistics,成功的次数,失败的次数,平均花费的时间,最大花费的时间,并发的次数
点击Charts,可以看到请求和响应的图表