SpringCloud+Vue+Python人工智能(fastAPI,机器学习,深度学习)前后端架构各功能实现思路——主目录(持续更新):https://blog.csdn.net/grd_java/article/details/144986730 |
---|
文章目录
1. spring boot启动类
gateway网关也是一个springboot项目
package com.yd_oa_java_cloud.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//启动类,springIOC容器加载时 扫描的包范围为com.yd_oa_java_cloud,主要要将你想要spring管理的包都放在一个固定路径下
//我们写代码时,经常创建包搞一个什么com.XXXXX.XXXXX,然后配置时就用com.XXXXX
@SpringBootApplication(scanBasePackages = {"com.yd_oa_java_cloud"})
public class YdOaCloudGatewayRunApplication {
public static void main(String[] args) {
SpringApplication.run(YdOaCloudGatewayRunApplication.class,args);
}
}
2. gateway配置文件,和nacos配置中心的配置文件
1. 代码中,resources文件夹中创建bootstrap.yml文件(注意文件名不可以随便定义,必须是这个),注意配置中,服务名,不可以有下划线
spring:
profiles:
active: dev #定义环境为开发环境
application:
name: yd-oa-java-gateway # 服务名字,服务之间相互调用都依赖这个名字
# spring cloud相关配置
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # localhost:8848为默认的nacos本地端口位置
config:
server-addr: 127.0.0.1:8848 # 配置中心地址
file-extension: yml # 配置中心的文件后缀类型
# 配置中心要加载的yml文件名为yd_oa_java_gateway-dev.yml这样式的,也就是服务名-环境.后缀
shared-configs: ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} # 对应nacos配置中心的Data Id
2. nacos配置中心添加
feign:
okhttp:
enabled: true #feign组件,这里gateway可以不用配置
server:
port: 7071 # 当前服务端口号
# 日志放置的路径和级别
project:
folder: E:/webser/yd_oa_cloud/
log:
root:
level: debug
3. 启动测试
1. 启动gateway网关
2. nacos成功注册项目
3. 用随便一个模块测试一下,例如Spring Security模块,具体构建方式可以参考:https://blog.csdn.net/grd_java/article/details/145013189
4. 写一个测试方法
5. 此时请求7072端口的接口可以请求到,但是我们有网关的目的是统一请求网关所在的7071端口才对,反而,请求7071是无法请求到的
4. 路由配置
添加路由相关配置
spring:
cloud:
gateway:
discovery: # 可以不配置
locator:
enabled: true #使用服务发现路由=true,依赖于openfeign
lower-case-service-id: true #服务名路由可以小写
routes:
- id: yd-oa-java-security #路由id ,随便起,推荐写服务名
uri: lb://yd-oa-java-security #lb是负载均衡,路由url,lb:// 后面 要写在nacos中注册的名字,因为我配置了服务名路由可以小写,所以这里小写也没关系
predicates:
- Path=/security/** # 路径断言匹配,和nginx中配置转发差不多,就是遇到这个路径的请求,转发到yd_oa_java_security这个服务去处理
filters:
- StripPrefix=1 # Path中只过滤第一段路径,也就是我们请求时只需要遇到/security这个路径,就转发
此时我们配置了
/security/**
路径的请求全部通过lb负载均衡的模式,分发到yd-oa-java-security这个服务对应的实例集群中处理
此时我们可以通过请求
gateway网关地址/配置中Path对应路径/具体要请求的路径
,来通过网关分发到具体服务请求接口
当我们某个服务的多个实例,其中有一台挂掉时,并不能立即处理,gateway依旧会将部分请求负载均衡到这个已经挂掉的实例上
通常它需要十几秒的时间才能注意到有一台已经挂掉了,放弃将请求分发到它身上。此时就需要配置熔断,全局的异常处理,后面会介绍到
nginx也可以分发负载均衡,但是它不能渗透到业务中,gateway可以做代码中的健全
所以一般会外层使用nginx,它确实十分强大,然后内层有一层gateway网关,然后才是具体业务模块
5. 过滤器
5.1 全局过滤器配置方法
1. 全局过滤器(全局拦截器),主要用于防xss注入(跨站脚本攻击,通过漏洞注入恶意代码)、以及登录的拦截等,所有的请求都会进行过滤
代码中只是演示了过滤器的配置方法,输出了一个日志。之后如何使用,还取决于具体业务功能需要,目前只是配置一个网关,还不需要编写过滤器,之后进行登录鉴权时,才会使用
package com.yd_oa_java_cloud.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* gateway 全局过滤器,可以调用spring Security微服务进行鉴权
* implements GlobalFilter 实现GlobalFilter接口,实现全局过滤器。可以有多个
* implements Ordered 实现过滤器的先后顺序,如果有多个过滤器,通过此接口规定每个过滤器的先后顺序
*/
@Component//组件组成,交由SpringIOC容器管理
@Slf4j //日志
public class GateWayGlobalRequestFilter implements GlobalFilter , Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String rawPath = exchange.getRequest().getURI().getRawPath();//获取请求路径
log.info("GateWayGlobalRequestFilter:"+rawPath);
//过滤完成,可以向下传递
return chain.filter(exchange);
}
//定义此过滤器为所有过滤器的第0层过滤器
@Override
public int getOrder() {
return 0;
}
}
2. 测试,访问security的测试接口
getaway成功过滤,并放行
5.2 指定过滤器配置方法
1. 只过滤部分请求的过滤器为指定过滤器,只需要配置路由时为其指定过滤器即可。一般应用在包含前后台系统的网站,后台系统需要鉴权,而前台系统很多内容是只要进入网站就可以观看的,无需登录认证。
2. 例如为Security请求添加指定过滤器,只需要在filters配置下添加指定过滤器即可
spring:
cloud:
gateway:
routes:
- id: yd-oa-java-security #路由id ,随便起,推荐写服务名
uri: lb://yd-oa-java-security #lb是负载均衡,路由url,lb:// 后面 要写在nacos中注册的名字,因为我配置了服务名路由可以小写,所以这里小写也没关系
predicates:
- Path=/security/** # 路径断言匹配,和nginx中配置转发差不多,就是遇到这个路径的请求,转发到yd_oa_java_security这个服务去处理
filters:
- StripPrefix=1 # Path中只过滤第一段路径,也就是我们请求时只需要遇到/security这个路径,就转发
- SecurityRequestFilter # 这个路由的指定过滤器
3. 和全局过滤器不同的是,指定过滤器需要继承AbstractGatewayFilterFactory类
package com.yd_oa_java_cloud.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
@Component //交由spring IOC管理
@Slf4j
public class SecurityRequestFilter extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Object config) {
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String rawPath = request.getURI().getRawPath();
log.info("SecurityRequestFilter:@TODO:登录相关认证操作,{}",rawPath);
return chain.filter(exchange);
});
}
}
4. 请求测试
5.3 不推荐使用过滤器的场景
当某个服务中部分路径需要鉴权,而部分路径可以直接放行时,通过过滤器只能通过路径去判断
此时,AOP切面是个更好的选择
6. 全局异常处理(全局拦截器Handler)
注意,全局异常拦截器是针对Gateway的,如果请求成功路由到对应微服务,然后微服务中出错,就不是这个拦截器的管理范围了
我们以404为例,我们的路由配置路径为security路径分发到权限微服务处理。那么如果我们输入security1这个路径,就没有对应可以路由的微服务,一定会报gateway网关层面的404
但是如果我们就是输入security路径,那么gateway根据我们的配置会成功路由到权限服务中处理。此时权限服务中的路径输入错误,和gateway网关就没有什么关系了。下面的例子中,我们路由地址填写正确,但是具体微服务接口填写错误,就不会触发拦截器
实现方法也很简单,只需要实现WebExceptionHandler接口即可,但是要注意,异常拦截器应该在所有过滤器之前执行,也就是说需要额外添加注解
@Order(-1)
(视情况改变,值越小越先执行,如果你的项目中-1不是最小,那么就改为更小的值)
package com.yd_oa_java_cloud.gateway.handler;
import com.yd_oa_java_cloud.base.entity.enums.ResponseCodeEnum;
import com.yd_oa_java_cloud.base.entity.vo.ResponseVO;
import com.yd_oa_java_cloud.base.exception.BusinessException;
import com.yd_oa_java_cloud.base.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
/**
* 全局异常拦截器
*/
@Slf4j
@Component
@Order(-1) //异常拦截器应该在其它过滤器之前执行
public class GatewayExceptionHandler implements WebExceptionHandler {
protected static final String STATUS_ERROR = "error";//错误状态信息
//WebExceptionHandler 拦截处理逻辑
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ResponseVO responseVO = getResponse(ex);//获取异常信息,返回结果为封装好的ResponseVO对象
ServerHttpResponse response = exchange.getResponse();//获取响应体对象
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);//设置响应头上下文类型为Json
//获取异常信息json流
DataBuffer dataBuffer = response.bufferFactory().wrap(JsonUtils.convertObjToJson(responseVO).getBytes(StandardCharsets.UTF_8));
//将其写到响应体中
return response.writeWith(Mono.just(dataBuffer));
}
/**
* 自定义方法,获取异常信息
* @param ex
* @return
*/
private ResponseVO getResponse(Throwable ex){
ResponseVO responseVO = new ResponseVO();
//设置错误状态
responseVO.setStatus(STATUS_ERROR);
//如果响应的错误属于ResponseStatusException的实例
if (ex instanceof ResponseStatusException){
ResponseStatusException responseStatusException = (ResponseStatusException) ex;//错误强转
//如果错误就是404错误的话
if (HttpStatus.NOT_FOUND == responseStatusException.getStatus()){
//将我们封装的响应枚举类中封装的信息进行响应
responseVO.setCode(ResponseCodeEnum.CODE_404.getCode());
responseVO.setInfo(ResponseCodeEnum.CODE_404.getMsg());
return responseVO;
}
//如果是应用服务(微服务)不可用的话。当要路由的微服务挂掉的话,会报此错误
else if (HttpStatus.SERVICE_UNAVAILABLE == responseStatusException.getStatus()){
responseVO.setCode(ResponseCodeEnum.CODE_503.getCode());
responseVO.setInfo(ResponseCodeEnum.CODE_503.getMsg());
return responseVO;
}
//其余异常
else{
responseVO.setCode(responseStatusException.getStatus().value());
responseVO.setInfo(ResponseCodeEnum.CODE_500.getMsg());
return responseVO;
}
}
//如果是我们自定义异常的话
else if(ex instanceof BusinessException){
BusinessException businessException = (BusinessException) ex;
responseVO.setCode(businessException.getCode());
responseVO.setInfo(businessException.getMessage());
return responseVO;
}
//如果是未知错误,报500
responseVO.setCode(ResponseCodeEnum.CODE_500.getCode());
responseVO.setInfo(ResponseCodeEnum.CODE_500.getMsg());
return responseVO;
}
}