最近主包的考试有点多,六级,期末,所以最近有点偷懒
废话不多说,回归正题
服务注册和发现
首先我们需要先去知道为什么要进行服务注册和发现,因为我们在使用微服务的时候每个项目都是独立的对不对,那么我要是比如去购买东西的话,是不是我的用户信息需要去共享,这样的话我创建订单就能知道是谁购买的对不对?
nacos
这里改成自己的虚拟机的ip地址,然后需要去映射nacos
docker run-d \--name nacos \--env-file./nacos/custom.env \-p 8848:8848 \-p 9848:9848 \-p 9849:9849 \--restart=always \ nacos/nacos-server:v2.1.0-slim这样就能映射到8848端口了
但是要是想要去使用的话,我们还需要再nacos当中去注册一下
<!--nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>这样的话我们在启动的时候就能扫描到了
spring: application: name: item-service# 服务名称cloud: nacos: server-addr:自己虚拟机的ip地址:8848# nacos地址这里需要配置一下nacos的地址
最后配置出来是这个样子的,这样就能被nacos扫描到,但是话又说回来了,我只有服务注册的话还是不够啊,我光注册有啥用,我最终肯定是需要去调用的对不对,那我去调用的话就需要使用服务发现了
<!--nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>可以发现,这里Nacos的依赖于服务注册时一致,这个依赖中同时包含了服务注册和发现的功能。因为任何一个微服务都可以调用别人,也可以被别人调用,即可以是调用者,也可以是提供者。
openfeign
现在我们需要去使用远程服务的话就需要使用openfeign来进行调用方法,
首先还是需要去引入依赖
<!--openFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--负载均衡器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency>这里使用的openfeign和负载均衡的依赖
在配置完成依赖之后还需要在启动类上面加上@enablefeignclients的注解里面的value指的是需要扫描到的包,在后面我们会重新解释一下的
接下来就需要去编写client的客户端来进行调用
这里需要先去声明一下关于feignclient的客户端扫描的模块是哪个,后面的配置注解也是在后面会讲到的
在这里大家需要注意到的是,这里使用的是代理的方式来进行唤醒方法,然后去转发请求,但是这里面的路径我们需要写全,注意是写全,就是locakhost:后面的都需要写上去,不然会出现找不到的异常,写完这个时候我们打底的就完成了,剩下的就是去调用这个方法
这里就是去调用的方法
接下来就是去开启连接池
<!--OK http 的依赖--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-okhttp</artifactId> </dependency>feign: okhttp: enabled: true# 开启OKHttp功能这里需要先去引入okhttp的依赖,然后再去配置文件里面去开启okhttp的功能
最后就是去配置日志
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
首先
这里面需要去配置一下日志,这里我们将日志等级设置成全部记录,这里还需要回顾一下前面的知识,我们在配置日志的时候默认
这里配置的是全局变量,然后要是需要配置的是局部变量的话,需要在client上面去配置
这里指定的就是我需要关联的是哪个模块,然后给这个模块配置一下
网关
首先大家需要知道我为什么需要去设置网关,网关就相当于是大门,我们去创建订单的时候还是需要去获取用户的id但是这个用户的id我是从哪里获取这是个问题,所以我需要网关来去拦截,要是没有人的话我就需要拦截请求
什么是网关?
顾明思议,网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发以及数据安全的校验。
更通俗的来讲,网关就像是以前园区传达室的大爷。
- 外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。
- 外面的人要传话或送信,要找大爷。大爷帮你带给目标人。
现在,微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关: - 网关可以做安全控制,也就是登录身份校验,校验通过才放行
- 通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去
这里我们去创建网关的模块
<?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>然后去配置路由
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/**这里给大家透个底,后面会优化的
网关过滤器
这里定义网关过滤器就是为了去拦截请求,不然的话要网关的意义是就没有了
最后是nettyroutingFilter来发送的请求,所以我的网关要是需要过滤的话是不是就需要正在这个请求之前,所以这里取使用的是globalfilter来进行拦截
前端不是发送的请求吗,携带的请求头里面是不是我的jwt来进行加密的吗,所以这里我就需要使用jwt来进行解析出来用户的id,要是存在用户的id的话,我是不是就直接放行,要是不存在的话,我就直接返回给前端一个404的错误
但是这时候我的最初目的是什么,我是不是需要将用户的id传递下去,所以这里我是不是需要将id传递,这时候就需要使用
ServerWebExchange build = exchange.mutate().request(a-> a.header("user-info",userInfo)).build();我调用mutate将请求写到里面去,这样的话用户id就会随着网关进行转发转发到每个子模块当中,但是这时候又存在问题了,我现在是给子模块了,但是子模块怎么进行获取呢,所以下载乃我需要去定义一个全局的拦截器去将用户id拿出来,然后存起来,所以这时候就需要使用common下面的模块了
package com.hmall.common.interceptor;import com.ctc.wstx.util.StringUtil;import com.hmall.common.utils.UserContext;import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;publicclassuserInfoInterceptor implements org.springframework.web.servlet.HandlerInterceptor{@Override public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler)throws Exception{String header = request.getHeader("user-info");if(!StringUtils.isEmpty(header)){Long userId = Long.valueOf(header);UserContext.setUser(userId);}returntrue;}@Override public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex)throws Exception{UserContext.removeUser();}然后去添加拦截器
package com.hmall.common.config;import com.hmall.common.interceptor.userInfoInterceptor;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Conditional;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration @ConditionalOnClass(name ="org.springframework.web.servlet.DispatcherServlet")publicclassmvcConfig implements org.springframework.web.servlet.config.annotation.WebMvcConfigurer{@Override public void addInterceptors(InterceptorRegistry registry){registry.addInterceptor(new userInfoInterceptor()).excludePathPatterns("/users/login").excludePathPatterns("/search/**").excludePathPatterns("/items/**");}}这里面需要去除我在网关里面的,因为,我在网关里面引用了common模块,common模块是mvc的,但是我的网关是webflux的,没有mvc的配置,所以你要是不想让这个拦截器在网关里面生效的话需要将这个排除在外,同时因为是定义在common模块下面的,没法被其他的微服务扫描到,所以这里需要去自动装配
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.hmall.common.config.MyBatisConfig,\ com.hmall.common.config.MvcConfig我去解析请求头,这样的话我的用户id就能在每个子模块之间传递对不对?
现在我们的网关到微服务之间的用户的信息是不是已经完成了,但是问题又来了,我要是用户信息在微服务之间进行传递的话,我还是会报错,所以我们需要将用户的信息随着我去向另一个微服务发送请求的时候是不是就需要一块传递过去,所以这时候我们就用到了openfeign的拦截器
@Bean public RequestInterceptor userInfoRequestInterceptor(){returnnew RequestInterceptor(){@Override public void apply(RequestTemplate template){//获取登录用户 Long userId = UserContext.getUser();if(userId == null){//如果为空则直接跳过return;}//如果不为空则放入请求头中,传递给下游微服务 template.header("user-info",userId.toString());}};}这里直接去创建一个拦截器,然后将用户的信息存放到请求头里面传递给下游微服务,这样的话又会被common拦截到,这样又能存到usercontext里面去,这样的话我的用户信息就能在微服务之间进行传递了
配置管理
这里我们要是需要在线上微调的话,我的配置还是需要变动,但是我又不想直接在服务里面写死,这样我每次调试都需要重启服务,所以我们来介绍一下配置
首先来讲一下配置共享,
就是去里面创建
这里面的配置项不要写的太死了,这里面就相当于是我又卸载resources下面的,直接把这个当成yaml文件就行
然后就是去拉去配置
<!--nacos配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>还是需要先去引入依赖,然后去创建bootstrap的yaml文件
接下来就是热部署
package com.hmall.cart.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Data@Component @ConfigurationProperties(prefix ="hm.cart")publicclassCartProperties{private Integer maxAmount;}其实和yaml一样定义在nacos里面就行
这样我们在去修改的时候就能直接修改nacos里面的文件实现热部署,就不需要重新启动服务了
最后就是动态路由
<!--统一配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--加载bootstrap--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>首先还是先去引入依赖
spring: application: name: gateway cloud: nacos: server-addr: 192.168.150.101 config: file-extension: yaml shared-configs:-dataId: shared-log.yaml# 共享日志配置然后去引入共享配置,最后就是去动态更新路由
package com.heima.gateway;import ch.qos.logback.classic.spi.EventArgUtil;import cn.hutool.core.collection.CollectionUtil;import cn.hutool.json.JSONUtil;import com.alibaba.cloud.nacos.NacosConfigManager;import com.alibaba.nacos.api.config.listener.Listener;import com.alibaba.nacos.api.exception.NacosException;import com.heima.config.AuthProperties;import com.heima.config.gatewayConfig;import lombok.Data;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.route.RouteDefinition;import org.springframework.cloud.gateway.route.RouteDefinitionWriter;import org.springframework.stereotype.Component;import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;import java.util.ArrayList;import java.util.List;import java.util.concurrent.Executor;@Slf4j @Data@Component @RequiredArgsConstructor publicclassDynamicRoureLoader{private final NacosConfigManager nacosConfigManager;private final RouteDefinitionWriter routeDefinitionWriter;private final gatewayConfig GatewayConfig;private final List <String> routeIds = new ArrayList<>();@PostConstruct public void init()throws NacosException{//获取nacos中的配置 String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(GatewayConfig.getDataId(),GatewayConfig.getGroup(),5000,new Listener(){@Override public Executor getExecutor(){returnnull;}@Override public void receiveConfigInfo(String configInfo){updateRouteDefinition(configInfo);}});updateRouteDefinition(configInfo);}private void updateRouteDefinition(String configInfo){log.debug("监听到路由配置变更,{}",configInfo);//1.删除之前存在过的路由信息 routeIds.forEach(id-> routeDefinitionWriter.delete(Mono.just(id)).subscribe());routeIds.clear();//2.添加新的路由信息 List<RouteDefinition> list = JSONUtil.toList(configInfo,RouteDefinition.class);//3.判断是不是需要添加新的路由信息if(CollectionUtil.isEmpty(list)){return;}for(RouteDefinition routeDefinition : list){routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();routeIds.add(routeDefinition.getId());}}}这里我们需要注入的是nacosconfig和routeDefinitionWriter至于gateway那是我自己定义的一个,大家不需要在意,然后就是在类加载完毕的时候去初始化路由,增加监听器,更新路由配置,恒心路由配置的时候需要注意的是我们要去订阅,因为mono是懒惰式的,需要调用才能执行