本文目录
前言
6月19入的职到现在的7月27号,没错我已经实习了1个多月了,emmm怎么来说呢,拿我在学校敲代码的状态来比喻吧,如果把我在学校敲的代码比喻成一匹矫健驰骋在草原的黑马,那么我在公司敲的代码就更像是正规军,驰骋在赛场的汗血宝马了。
个人所感
以前在学校没怎么重视网络编程这块,即使以前有看过springCloud、duuble相关的视频,但是当时看视频的时候觉着这样开发项目太繁琐了,也没有坚持去系统化的去总结这一块的内容。以前我也刷到过牛客网上那些牛逼简历上写的项目是手写RPC框架、基于scoket开发的实时在线聊天系统相关的项目,当时觉着这些技术简直可以用俩个字“牛逼”来形容。当时本人痴迷于一个集合类、spring框架的源码分析就没有太注意这一块的技术。但是现在在公司开发的是一个微服务项目,核心业务都是涉及到网络编程相关的,迫于压力也是对于技术的一种热爱,于是我现在研究起了微服务相关框架的使用、源码、scoket、netty、dubbo、springCloud这些技术了,后续会陆续更新相关文章。
模块间依赖关系
email:依赖payment模块
gateway:网关负责路由相关到对应的实例(注册中心用的nacos)
payment:单独的模块
email、payment模块添加swagger依赖支持
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.20</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
email、payment模块开启swagger模块配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration;
import springfox.documentation.builders.ApiInfoBuilder;
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;
import java.util.Arrays;
@Configuration
@EnableSwagger2
@Import(BeanValidatorPluginsConfiguration.class)
public class Swagger2Config {
@Bean
public Docket customDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.genericModelSubstitutes(DeferredResult.class)
.useDefaultResponseMessages(false)
.forCodeGeneration(false)
.pathMapping("/")
.apiInfo(apiInfo())
.select()
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("接口文档title")
.description("接口文档说明")
.version("1.0.0")
.build();
}
}
网关模块配置(重点)
添加swagger接口支撑
/**
* zzh
*/
@RestController
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)));
}
@GetMapping("/")
public Mono<ResponseEntity> swaggerResourcesN() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
@GetMapping("/csrf")
public Mono<ResponseEntity> swaggerResourcesCsrf() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
对swagger请求进行过滤(可有可无)
/**
* zzh
*/
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}
全局swagger配置(放在网关模块)
/**
* 全局swagger配置
*/
@Component
@Primary
@AllArgsConstructor
public class SwaggerProvider implements SwaggerResourcesProvider {
//注意这个地址
public static final String API_URI = "/v2/api-docs?urls.primaryName";
private final RouteLocator routeLocator;
private final GatewayProperties gatewayProperties;
/**
* swagger会与返回值中的SwaggerResource进行路径匹配,如果匹配不到那么会出现swagger页面404无法访问的情况
*
* @return
*/
@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(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> ("Path").equalsIgnoreCase(predicateDefinition.getName()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(), predicateDefinition.getArgs().get(NameUtils.GENERATED_NAME_PREFIX + "0")
//swagger API地址
.replace("/**", API_URI + routeDefinition.getId())))));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
网关路由配置
server:
port: 8005
spring:
application:
name: gateway
cloud:
discovery:
locator:
enabled: true
gateway:
routes:
- id: email_id
uri: lb://email
predicates:
- Path=/email/**
filters:
- name: SwaggerHeaderFilter
- StripPrefix=1
- id: payment_id
uri: lb://payment
predicates:
- Path=/payment/**
filters:
- name: SwaggerHeaderFilter
- StripPrefix=1
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
mybatis-plus:
mapper-locations: classpath:mapperXml/*.xml
全局token验证、方式一
在swagger配置文件中加入如下配置,每次请求都会把 name为 X-Auth-Token 的参数放入请求中进行请求
/**
* 对每个接口都添加token认证
*
* @return
*/
private List<Parameter> setHeaderToken() {
ParameterBuilder tokenPar = new ParameterBuilder();
List<Parameter> pars = new ArrayList<>();
tokenPar.name("X-Auth-Token").description("token").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(tokenPar.build());
return pars;
}
加入swagger配置
全局token验证、方式二
一次添加模块下的所有接口下次请求都会携带 token
@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)
.globalOperationParameters(setHeaderToken())
.securityContexts(securityContexts());
}
/**
* 以下代码添加全局token配置
*
* @return
*/
private List<ApiKey> security() {
ArrayList list = new ArrayList();
list.add(new ApiKey("Authorization", "Authorization", "header"));
return list;
}
private List<SecurityContext> securityContexts() {
List<SecurityContext> securityContexts = new ArrayList<>();
securityContexts.add(
SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.regex("^(?!auth).*$"))
.build());
return securityContexts;
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
List<SecurityReference> securityReferences = new ArrayList<>();
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
return securityReferences;
}
规范化入参
我们的post请求尽量使用vo接受参数,不要用map(网上有map接受的方法我也试过,但是放在生产线上会爆bug不推荐)、多个@Requestparam去接受参数,后期不好整理swagger文档!!!
vo接受参数的好处:前端调试可以使用json传值,并且有model注释可以看传的值是什么意思。
坑一 swagger页面404
观察到正确的swagger地址有尾缀
解决办法一:访问swagger的地址加上尾缀
解决办法二:去除前缀这个必须加上
坑二(swagger页面有了、访问接口出现碟名)
一开始我以为是由于网关的配置引起的,也就是下面这种情况:网关配置了路由/a/** , 请求/a/b/c经过网关,由于前缀 /a 匹配上了,/a/b/c的请求会替代 /** 的位置,经过网关转发后的请求实际上是 /a/a/b/c。但是后来想着网关的路由匹配规则也太变态了吧,都已经模糊匹配上了,还给我恶意拼接上去。经过本人实测 gateWay 不是这么个路由匹配规则,出现路径碟名的bug只存在于swagger中,下文有详细描述gateWay路由匹配规则。
gateWay路由匹配
下图对应payment模块controller中的一个方法,返回值包含了请求路径以及端口号
下图对应email模块controller中的一个方法,返回值包含了请求路径以及端口号
gateWay 中 payment 对应的路由配置 注释掉 - StripPrefix=1,而 email 对应的路由配置开启 - StripPrefix=1,我们通过直接走 gateWay 网关来调用我们的接口,来观察经过网关分发后,到达接口的真实请求路径、端口号是多少。并且我们通过 -Dserver.port=7009 指令,分别对email、payment模块开启端口不同的俩个实例,来检测 gateWay 的负载均衡功能是否生效
事实证明: 我们的请求路径为 /payment/getUser/1,经过网关转发之后的请求路径依然为 /payment/getUser/1 而不是/payment/payment/getUser/1 且负载均衡也生效
事实证明: 我们的请求路径为 /email/email/getUser/1、/email/getUser/1,由于配置了去除前缀的规则,经过网关转发之后的请求路径分别为 /email/getUser/1、/getUser/1 因此下图一为404,下图二、三为正常结果
警戒(总结)
不要被swagger蒙蔽了我们的双眼,gateWay路由匹配规则如下
- 去除前缀执行的时机:经过网关分发后去除前缀。例如 /a/a/b/c 到达了网关,经过网关分发后到达接口的请求为 /a/b/c
- 路由匹配规则: /a/** 会匹配以 /a 开头的请求,路由到对应的服务器实例上面去。但是不是这么一种情况, /a/b的请求打到网关 (gateWay) ,匹配上了 /a/** 后,经过网关分发后的请求依然是/a/b , 而不是/a/a/b,至于swagger页面中的碟名这个效果 个人认为是个 bug,上文有证实。
上文为我亲测后推导出来的个人想法的总结,如有不对之处,望各位读者斧正
小咸鱼的技术窝
关注不迷路,日后分享更多技术干货,B站、CSDN、微信公众号同名,名称都是(小咸鱼的技术窝)更多详情在主页