SpringCloud
注册中心(Consul)
作用:存放和调度服务
1.添加依赖
<!-- Actuator 监控端点:提供健康检查、指标等信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Consul 服务发现:用于注册中心,服务注册与发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
2.配置application.yaml文件
cloud:
consul:
host: 127.0.0.1 # Consul 服务器地址
port: 8500 # Consul 默认端口
# 服务发现配置
discovery:
# 服务注册
register: true
# 使用IP注册而不是主机名(容器化部署推荐)
prefer-ip-address: true
# 服务名称
service-name: ${spring.application.name}
# 实例ID(确保唯一性)
instance-id: ${spring.application.name}-${spring.cloud.client.ip-address}-${server.port}
# 健康检查配置
health-check-path: /actuator/health
health-check-interval: 15s
health-check-timeout: 10s
health-check-critical-timeout: 30s
# 心跳配置
heartbeat:
enabled: true
3.开启服务发现服务注册开关
@EnableDiscoveryClient
,从SpringCloud Edgware开始,该注解可以省略。
4.启动注册中心Consul
启动应用程序后访问consul的图形化界面,地址127.0.0.1:8500
服务调用(Feign/OpenFeign)
一个业务场景的应用需要多个服务之间共同写作才能完成整个链路的请求,比如查询客户A的信息以及该客户下面的其他信息。客户端->网关(服务)->服务A->服务B->客户端
1.引入依赖
<!-- OpenFeign:声明式 HTTP 客户端,用于微服务间调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.配置application.yaml文件
和consul配置并列
服务A:
server:
port: 8081 # 应用服务端口号
servlet:
context-path: /${spring.application.name}
spring:
application:
name: manager-service # 应用名称
cloud:
consul:
openfeign:
client:
config:
default:
connect-timeout: 5000 # 连接超时 5s
read-timeout: 10000 # 读取超时 10s
服务B:
server:
port: 8082 # 应用服务端口号
servlet:
context-path: /${spring.application.name}
spring:
application:
name: department-service # 应用名称
cloud:
consul:
openfeign:
client:
config:
default:
connect-timeout: 5000 # 连接超时 5s
read-timeout: 10000 # 读取超时 10s
3.创建FeignClient
在com.example.work3.feign包下
NOTE
注意访问路径:ip:端口号/服务名称/servlet的path/服务B的拦截路径
@FeignClient(value = "department-service", path = "/department-service/departments")
public interface DepartFeignClient {
@GetMapping("/{departNo}")
Result queryDepartById(@PathVariable String departNo);
}
@FeignClient
:代表该接口为feign接口value
:调用对应的服务path
:拦截路径,类似Controller
中的@RequestMapping
,
4.Application开启feign接口
@SpringBootApplication
@EnableFeignClients
@MapperScan("com.example.work3.mapper")
public class ManagerApplication {
public static void main(String[] args) {
SpringApplication.run(ManagerApplication.class, args);
}
}
5.调用FeignClient
controller
中调用
@PostMapping("/queryByDepartments")
public Result queryManagersByDepartments(@RequestBody ManagerRequestDTO dto) {
PageResult<Manager> pageResult = managerService.queryByDepartments(dto);
Result department = departFeignClient.queryDepartById(dto.getDepartmentId());
System.out.println(department.getData());
return Result.ok(pageResult);
}
服务网关(Gateway)
网关是整个微服务API请求的入口,负责拦截所有请求,分发到服务上去。可以实现日志拦截、权限控制、解决跨域问题、限流、熔断、负载均衡,隐藏服务端的ip,黑名单与白名单拦截、授权
等功能,常用的网关有spring cloud gateway
- 路由(Route):网关的基本构成块,有一个ID, 一个目标
URI
,一组断言和自定义过滤器定义,如果断言为真,则匹配路由。 - 断言(Predicate):输入类型为个
ServerWebExchange
,我们可以使用他来匹配来自http请求的任何内容,例如headers。 - 过滤器(filter):
gateway
中的过滤器默认分为GatewayFilter
和GlobalFilter
,过滤器可以对请求和响应进行修改处理。
Nginx
先将客户端的请求负载均衡到SpringGateway
,然后SpringGateway
再通过服务发现,将请求负载均衡到各个业务微服务上。
1.创建一个gateway服务完成网关的转发
结构:
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── gateway/
│ │ │ ├── GatewayApplication.java
│ │ │ ├── config/
│ │ │ │ └── RouteConfig.java
│ │ │ ├── filter/
│ │ │ │ ├── UserFilter.java
│ │ │ │ └── UserGatewayFilterFactory.java
│ │ │ └── service/
│ │ │ └── SecurityService.java
│ │ └── resources
│ │
│ └── test/
│
└── pom.xml
1.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
NOTE
gateway
中不能引入starter-web
, 若二者同时引入会产生冲突
2.配置yaml 或者 自定义转发规则
user服务
server:
port: 8082 # 应用服务端口号
servlet:
context-path: /${spring.application.name}
spring:
application:
name: user-service # 应用名称
card服务
server:
port: 8081 # 应用服务端口号
servlet:
context-path: /${spring.application.name}
spring:
application:
name: card-service # 应用名称
# 服务器配置
server:
port: 8083 # 服务端口设置为8083
# Spring应用配置
spring:
application:
name: gateway # 应用名称设置为gateway
# Spring Cloud Gateway配置
cloud:
gateway:
enabled: true # 启用网关功能
discovery:
locator:
lower-case-service-id: true # 服务ID使用小写格式
# 路由配置
routes:
# 卡片服务路由
- id: card_route # 路由ID
uri: lb://card-service # 使用负载均衡指向card-service服务
order: 0 # 路由优先级为0(数字越小优先级越高)
predicates:
- Path=/card-service/** # 路径匹配规则:所有/card-service开头的请求
# 用户服务路由
- id: user_route # 路由ID
uri: lb://user-service # 使用负载均衡指向user-service服务
predicates:
- Path=/user-service/** # 路径匹配规则:所有/user-service开头的请求
# Consul服务发现配置
consul:
discovery:
instance-id: ${spring.application.name}-${spring.cloud.client.ip-address}-${server.port} # 实例ID格式:应用名-IP-端口
prefer-ip-address: true # 注册服务时优先使用IP地址
host: 127.0.0.1 # Consul服务器地址
port: 8500 # Consul服务器端口
3.自定义转发规则(更灵活)
/**
* 路由配置类,用于定义Spring Cloud Gateway的路由规则
*/
@Configuration
public class RouteConfig {
private static final String CARD = "lb://card-service";
private static final String USER = "lb://user-service";
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 配置卡服务路由
.route("card_route", r -> r.order(0).path("/card-service/**")
.uri(CARD)
)
// 配置用户服务路由
.route("user_route", r -> r.order(0).path("/user-service/**")
.uri(USER)
).build();
}
}
4.代码解读
- 常量定义
private static final String CARD = "lb://card-service";
private static final String USER = "lb://user-service";
lb://
是Load Balancer的协议前缀,表示使用负载均衡card-service
和user-service
是在服务注册中心(如Consul)中注册的服务名
- 这样配置后,Gateway会自动从服务注册中心发现这些服务的实例
- 路由配置方法
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder)
@Bean
:将返回值注册为Spring容器中的BeanRouteLocator
:Spring Cloud Gateway的路由定位器接口RouteLocatorBuilder
:用于构建路由的建造者模式工具
- 具体路由规则
3.1 Card服务路由
.route("card_route", r -> r.order(0).path("/card-service/**").uri(CARD))
"card"
:路由的唯一标识符order(0)
:路由优先级,数字越小优先级越高path("/card-service/**")
:匹配所有以/card-service/
开头的请求路径uri(CARD)
:将匹配的请求转发到card服务
3.2 User服务路由
.route("user_route", r -> r.order(0).path("/user-service/**").uri(USER))
- 同样的配置模式,匹配
/user-service/**
的请求转发到user-service服务
- 工作原理
当请求到达Gateway时:
4.1 如果请求路径是/card-service/anything
,会被转发到card-service服务
4.2 如果请求路径是/user-service/anything
,会被转发到user-service服务
4.3 Gateway会自动进行负载均衡,在多个服务实例间分发请求
4.4 实际效果
- 客户端请求:
http://gateway:8080/card-service/list
- Gateway转发到:
http://card-service-instance/card-service/list
- 客户端请求:
http://gateway:8080/user-service/profile
- Gateway转发到:
http://user-service-instance/user-service/profile
这种配置方式实现了统一的API网关入口,客户端只需要知道Gateway的地址,就能访问所有后端微服务。
5.测试
http://localhost:8083/user-service/user/OP002
2.网关层自定义过滤器
需求:创建自定义过滤器给
指定的路由
添加Header信息
- 实现
GatewayFilter接口
和Ordered接口
,编写过滤器处理逻辑
/**
* 用户自定义网关过滤器,用于处理请求并添加自定义头部信息
* 实现了GatewayFilter和Ordered接口,可以控制过滤器的执行顺序
*/
@Slf4j
@Component
public class UserFilter implements GatewayFilter, Ordered {
/**
* 过滤器核心方法,处理请求并传递给下一个过滤器
* @param exchange 服务器网络交换对象,包含请求和响应信息
* @param chain 网关过滤器链,用于继续处理请求
* @return Mono<Void> 异步处理结果
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取当前请求路径
String path = exchange.getRequest().getURI().getPath();
// 修改请求头,添加sessionId信息
ServerHttpRequest build = exchange.getRequest().mutate()
.headers(it -> it.set("sessionId", "766876"))
.build();
// 构建新的exchange对象
exchange.mutate().request(build).build();
// 记录请求路径日志
log.info("自定义拦截器当前请求的path:" + path);
// 继续执行过滤器链
return chain.filter(exchange);
}
/**
* 获取过滤器执行顺序
* @return int 过滤器执行顺序,数值越小优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}
- 路由转发应用
userFilter
@Configuration
public class RouteConfig {
private static final String CARD = "lb://card-service";
private static final String USER = "lb://user-service";
@Autowired
private UserFilter userFilter;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("card_route", r -> r.order(0).path("/card-service/**")
.filters(f -> f.filter(userFilter))
.uri(CARD)
).route("user_route", r -> r.order(0).path("/user-service/**")
.uri(USER)
).build();
}
}
- 测试
@RestController
@RequestMapping("/card")
public class CardController {
@Autowired
private HttpServletRequest request;
@GetMapping(value = "/gateway")
public String getTest() {
return request.getHeader("sessionId");
}
}
也可以用yaml实现
GatewayFilterFactory
类(用于 YAML 配置)
@Component
public class UserGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new UserFilter;
}
}
NOTE
注意:YAML 中引用的名字是类名去掉 GatewayFilterFactory
→ User
application.yml
示例
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: user_route
uri: lb://user-service
predicates:
- Path=/user-service/**
filters:
- User # 这里会匹配 UserGatewayFilterFactory
3.网关层全局过滤器
需求:创建
自定义全局过滤器
给指定的路由添加Header信息
- 实现
GlobalFilte接口
和Ordered接口
,编写过滤器处理逻辑
/**
* 用户全局过滤器,实现GlobalFilter和Ordered接口
* 用于对所有经过网关的请求进行统一处理
*/
@Component
@Slf4j
public class UserGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在响应头中添加操作密钥
exchange.getResponse().getHeaders().set("operKey", "766876");
// 获取当前请求路径
String path = exchange.getRequest().getURI().getPath();
// 记录请求路径日志
log.info("当前请求的path:" + path);
// 继续执行过滤器链
return chain.filter(exchange);
}
/**
* 获取过滤器执行顺序
* @return int 过滤器执行顺序,数值越小优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}
NOTE
当order的数值相同时候:
- 全局过滤器最后执行
- 优先出发默认过滤器,然后才是自定义过滤器
- order相同时,默认过滤器->自定义过略器->全局过滤器
4.网关层修改body体
需求:为了保证互联网层数据传输的安全性,需要在网关层对微服务层响应的数据进行加密操作,同样前端请求的数据也是密文,网关在收到请求后需要将数据进行解密,验证数据的合法性,通过后,将对应的请求转发到对应的微服务进行请求处理。
- 定义加解密服务类
@Service
@Slf4j
public class SecurityService {
private final SymmetricCrypto sm4 = SmUtil.sm4(Const.EN_KEY.getBytes());
/**
* @description: 对请求的数据进行解密操作, 解密使用国密的sm4进行加解密
* @param: s 入参请求体(解密之前的数据)
**/
public Mono<String> modifyRequest(ServerWebExchange exchange, String s) {
log.info("解密之前的数据是:" + s);
//获取到加密串
//{"value": "加密数据"}
JSONObject entries = JSONUtil.parseObj(s);
Object value = entries.get("value");
String s1 = sm4.decryptStr(value.toString(), Charset.defaultCharset());
exchange.getResponse().getHeaders().set("decrpt", "1");
log.info("解密之后的数据是:" + s1);
return Mono.just(s1);
}
/**
* @description: 对响应的数据进行加密 使用hutool的国密加密算法进行导出
* @param: s 入参, 响应体(未加密前的数据)
**/
public Mono<String> modifyResponse(ServerWebExchange exchange, String s) {
log.info("加密之前的数据是:" + s);
String s1 = sm4.encryptHex(s.getBytes());
log.info("加密之后的数据为:" + s1);
exchange.getResponse().getHeaders().set("decrpt", "1");
return Mono.just(s1);
}
}
- 路由添加处理逻辑
@Configuration
@RequiredArgsConstructor
public class RouteConfig {
private static final String CARD = "lb://card-service";
private static final String USER = "lb://user-service";
private final UserFilter userFilter;
private final SecurityService securityService;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("card_route", r -> r.order(0).path("/card-service/**")
.filters(f -> f.filter(userFilter))
.uri(CARD)
).route("user_route", r -> r.order(0).path("/user-service/**")
.filters(f -> f.modifyResponseBody(String.class, String.class,
((exchange, s) -> securityService.modifyResponse(exchange, s))))
.uri(USER)
).build();
}
}
总结常用的类和方法
在 Spring Cloud Gateway 中实现转发与拦截功能时,涉及的常用类和方法主要分为以下几类:
1. Filter 相关类
类 | 说明 | 关键方法 |
---|---|---|
GatewayFilter | 自定义局部过滤器(作用于特定路由)。 | Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) |
GlobalFilter | 全局过滤器(作用于所有路由)。 | 同上 |
AbstractGatewayFilterFactory<T> | 定义工厂类,支持在 application.yml 中配置路由过滤器。 | GatewayFilter apply(T config) |
Ordered | 定义过滤器执行顺序。 | int getOrder() |
2. Web 交互类
类 | 说明 | 关键方法 |
---|---|---|
ServerWebExchange | 封装 HTTP 请求和响应的上下文。 | getRequest() , getResponse() , mutate() |
ServerHttpRequest | 表示 HTTP 请求,可通过 mutate() 修改 header 或 path。 | mutate().headers() , getURI() |
ServerHttpResponse | 表示 HTTP 响应,可设置状态码、header、body。 | getHeaders().set() , writeWith() |
3. Reactor 响应式编程类
类 | 说明 | 常用方法 |
---|---|---|
Mono<T> | 0 或 1 个元素的异步流(用于返回值)。 | Mono.just() , Mono.defer() , flatMap() |
Flux<T> | 0-N 个元素的异步流(主要用于请求体和响应体)。 | Flux.from() , map() , flatMap() |
4. 其他常用类
类 | 说明 |
---|---|
RouteLocator / RouteLocatorBuilder | 用于配置路由规则。 |
@Component , @Configuration , @Service | 组件注册注解,用于管理 Bean 生命周期。 |
SmUtil , SymmetricCrypto | Hutool 提供的国密加密解密工具。 |
5. 常用方法示例
可以用UserFilter implements GatewayFilter, Ordered {}
- 修改请求头:
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(h -> h.set("sessionId", "123456"))
.build();
exchange.mutate().request(request).build();
- 读取请求体并解密:
exchange.getRequest().getBody()
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
})
.flatMap(body -> securityService.modifyRequest(exchange, body));
- 设置响应头:
exchange.getResponse().getHeaders().set("decrpt", "1");
- 加密响应数据:
securityService.modifyResponse(exchange, responseBody)
.map(encrypted -> bufferFactory.wrap(encrypted.getBytes()));
修改请求体
只能用RouteConfig
,不能用UserFilter implements GatewayFilter, Ordered {}
/**
* 自定义路由定位器,配置修改请求体的路由规则
*
* @param builder 路由构建器,用于定义路由规则
* @return RouteLocator 路由定位器实例
*/
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 定义一个名为"modify_body_route"的路由规则
.route("modify_body_route", r -> r.path("/user-service/**")
// 添加过滤器,修改请求体内容
.filters(f -> f.modifyRequestBody(
String.class, // 原始请求体类型
String.class, // 修改后的请求体类型
MediaType.APPLICATION_JSON_VALUE, // 媒体类型
(exchange, body) -> modifyRequestBodyService.modifyJsonBody(exchange, body) // 请求体修改逻辑
))
// 路由目标地址,使用服务发现中的user-service服务
.uri(USER))
.build();
}
两种方式的核心区别
ModifyRequestBodyGatewayFilterFactory
- 适合对 请求体内容(body) 进行修改,例如 JSON 入参。
- 由于请求体是流式的(Reactive),必须使用
ModifyRequestBody
这样的工厂类来安全地读取和修改 body。 - Reactive 流式数据在网关中只能安全地通过
ModifyRequestBody
过滤器读取并重新写入。
自定义 GatewayFilter(如 UserFilter)
- 适合对 请求头、请求路径、查询参数 进行修改。
- 直接在
filter
方法中通过exchange.getRequest().mutate()
修改即可。 - 不适合直接修改请求体,因为 body 读取后不能再次读取,容易导致下游无法获取数据。