黑马程序员微服务实战开发(网关,nacos配置管理)
1.什么是网关

在springCloud中网关实现有两种:

1.1快速搭建网关
1.1创建项目

1.2引入依赖
在hm-gateway模块的pom.xml文件中引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hmall</artifactId>
<groupId>com.heima</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hm-gateway</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<!--common-->
<dependency>
<groupId>com.heima</groupId>
<artifactId>hm-common</artifactId>
<version>1.0.0</version>
</dependency>
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.3启动类
package com.hmall.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
1.4配置路由
接下来,在hm-gateway模块的resources目录新建一个application.yaml文件:
server:
port: 8080
spring:
application:
name: gateway
cloud:
nacos:
server-addr: 192.168.150.101:8848
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
- Path=/items/**,/search/** # 这里是以请求路径作为判断规则
- id: cart
uri: lb://cart-service
predicates:
- Path=/carts/**
- id: user
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/**
- id: trade
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay
uri: lb://pay-service
predicates:
- Path=/pay-orders/**
1.2路由属性

路由断言
spring提供了12种基本的RoutePredicateFactory实现:

路由过滤器
网关中提供了33种路由过滤器,每种过滤器都有独特的作用:

2.用户登录校验功能
我们可以在网关向后端需要身份校验的服务发送请求前,在网关校验

网关请求流程:

2.1自定义过滤器
网关过滤器有两种:



@Component
public class MyGlobalFiler implements GlobalFilter , Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//todo 模拟登录校验逻辑
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
System.out.println( headers);
//放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
//优先级,过滤值值越小优先级越高,越先执行
return 0;
}
}
2.2实现登录功能
登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service中已经有了,我们直接拷贝过来:

具体作用如下:
- AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
- JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置
- SecurityConfig:工具的自动装配
- JwtTool:JWT工具,其中包含了校验和解析token的功能
- hmall.jks:秘钥文件
接下来,我们定义一个登录校验的过滤器

package com.hmall.gateway.filters;
import cn.hutool.core.text.AntPathMatcher;
import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final AuthProperties authProperties;
private final JwtTool jwtTool;
//spring提供的路径匹配器
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request
ServerHttpRequest request = exchange.getRequest();
//2.判断是否需要拦截
if (isExclude( request.getPath().toString())){
//放行
return chain.filter(exchange);
}
//3.获取token
String token =null;
List<String> authorization = request.getHeaders().get("authorization");
if (authorization != null|| !authorization.isEmpty()){
token=authorization.get(0);
}
//4.校验token
Long userId;
try {
userId = jwtTool.parseToken(token);
}catch (UnauthorizedException e){
//拦截,设置状态码为401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//5.传递用户信息
System.out.println("userId:"+userId);
return chain.filter(exchange);
}
private boolean isExclude(String string) {
for (String path : authProperties.getExcludePaths()) {
if (antPathMatcher.match(path, string)){
return true;
}
}
return false;
}
@Override
public int getOrder() {
return 0;
}
}
3.传递用户信息
3.1网关向微服务传递用户信息

步骤一:

在AuthGlobalFilter自定义过滤器中利用exchange.mutate(),将用户信息存入request并传递给下面微服务。
//5.传递用户信息
System.out.println("userId:"+userId);
String userIdStr = userId.toString();
ServerWebExchange serverWebExchange = exchange.mutate()
.request(builder -> builder.header("user-info", userIdStr))
.build();
return chain.filter(serverWebExchange);
步骤二:

我们每一个微服务都需要接收网关传递给我们的用户信息,所以我们可以解耦,因为每一个微服务项目都引入了hm-common模块,所以把这个通用功能放到hm-common模块实现。
先创建一个用户拦截器
package com.hmall.common.interceptors;
import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import net.bytebuddy.asm.Advice;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserInfoInterceptor implements HandlerInterceptor {
//在请求处理之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取用户信息
String userInfo = request.getHeader("user-info");
//2.判断是否有用户信息,有就存入ThreadLocal
if (StrUtil.isNotBlank(userInfo)){
UserContext.setUser(Long.valueOf(userInfo));
}
//3.放行
return true;
}
//在请求处理之后,视图渲染之前执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清理用户
UserContext.removeUser();
}
}
只是创建用户拦截器不能生效还需要注册拦截器,所以还需要写一个MvcConfig,才能生效
package com.hmall.common.config;
import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnClass(DispatcherServlet.class)//判断DispatcherServlet类是否存在,来做为是否被扫描的标记,因为网关没有web模块,所以网关的配置类不能被扫描
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInfoInterceptor()).addPathPatterns("/**");
}
}
因为用户拦截器和各个微服务不在一个包下,不能被spring自动扫描到,需要我们在spring.factories做相应配置

但是这样,我们启动网关仍然会报错,因为网关微服务也引入hm-common模块,我们只希望MvcConfig只在微服务生效,不在网关生效,我们知道spring的自动装配是可以带条件的,所以我们可以加@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnClass(DispatcherServlet.class)
//判断DispatcherServlet类是否存在,来做为是否被扫描的标记,因为网关没有web模块,所以网关的配置类不能被扫描`
3.2不同微服务之间传递用户信息(OpenFeign传递)
实际业务举例:


我们就可以在hm-api中DefaultFeignConfig注册想对应的拦截器,让不同微服务之间,发起请求时携带用户信息的请求头。
@Bean
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long user = UserContext.getUser();
if (user == null) {
//如果空直接跳过
return;
}
//如果不为空则放入请求头,传递给下游
requestTemplate.header("user-info", user.toString());
}
};
}
整个登录流程拦截器的全过程

4.配置管理

我们的nacos注册中心也可以配置管理服务
4.1配置共享
步骤一:
](https://i-blog.csdnimg.cn/direct/97981453cc264762a1d7db2c64dd70ba.png)
配置了三份配置文件

步骤二:





4.2配置热更新
当修改配置文件中的配置时,微服务无需重启即可生效。
前提条件:

案列:

1.业务代码改造
private final CartProperties cartProperties;
@Override
public void addItem2Cart(CartFormDTO cartFormDTO) {
// 1.获取登录用户
Long userId = UserContext.getUser();
// 2.判断是否已经存在
if (checkItemExists(cartFormDTO.getItemId(), userId)) {
// 2.1.存在,则更新数量
baseMapper.updateNum(cartFormDTO.getItemId(), userId);
return;
}
// 2.2.不存在,判断是否超过购物车数量
checkCartsFull(userId);
// 3.新增购物车条目
// 3.1.转换PO
Cart cart = BeanUtils.copyBean(cartFormDTO, Cart.class);
// 3.2.保存当前用户
cart.setUserId(userId);
// 3.3.保存到数据库
save(cart);
}
private void checkCartsFull(Long userId) {
int count = lambdaQuery().eq(Cart::getUserId, userId).count();
if (count >= cartProperties.getMaxSize()) {
throw new BizIllegalException(StrUtil.format("用户购物车课程不能超过{}", cartProperties.getMaxSize()));
}
}
2.还要创建一个文件读取添加购物车的最大值
创建一个config包

3.nacos配置

更多推荐

所有评论(0)