04-微服务02
我们将黑马商城拆分为5个微服务:
-
用户服务
-
商品服务
-
购物车服务
-
交易服务
-
支付服务
由于每个微服务都有不同的地址或端口,相信大家在与前端联调的时候发现了一些问题:
-
请求不同数据时要访问不同的入口,需要维护多个入口地址,麻烦
-
前端无法调用nacos,无法实时更新服务列表
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,这就存在一些问题:
-
每个微服务都需要编写登录校验、用户信息获取的功能吗?
-
当微服务之间调用时,该如何传递用户信息?
接下来我们会通过网关技术解决上述问题。今天的内容会分为3章:
-
第一章:网关路由,解决前端请求入口的问题。
-
第二章:网关鉴权,解决统一登录校验和用户信息获取的问题。
-
第三章:统一配置管理,解决微服务的配置文件重复和配置热更新问题。
通过今天的学习你将掌握下列能力:
-
会利用微服务网关做请求路由
-
会利用微服务网关做登录身份校验
-
会利用Nacos实现统一配置管理
-
会利用Nacos实现配置热更新
好了,接下来我们就一起进入今天的学习吧。
1.网关路由
1.1.认识网关
网关:网关就是网络的关口,负责请求的路由、请求的转发、身份校验。
更通俗的来讲,网关就像是以前园区传达室的大爷。
-
外面的人要想进入园区,必须经过大爷的认可,如果你是不怀好意的人,肯定被直接拦截。
-
外面的人要传话或送信,要找大爷。大爷帮你带给目标人。
微服务网关起到同样的作用:前端请求不能直接访问微服务,而是要请求网关:
-
网关可以做登录身份校验,校验通过才放行
-
通过认证后,网关再根据请求去判断访问哪个微服务,将请求转发过去
-
网关其实也是一个微服务,它启动之后可以去注册中拉去所有的微服务地址,将来微服务地址有变更也会推送回给网关
- 前端所有的请求都去请求网关,然后网关 根据前端发来的请求 判断 这个请求要转发给哪个微服务处理(请求的路由)
- 然后网关就会把请求转发给具体的微服务(请求的转发)
- 网关在做请求转发之前还需要对请求的用户进行身份校验(身份校验),身份校验通过后解析jwt,得到用户的信息,然后再把用户的信息传给下游微服务
在SpringCloud中,提供了两种网关的实现方案:
-
Netflix Zuul:早期实现,目前已经淘汰
-
SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强
课堂中我们以SpringCloudGateway为例来讲解,官方网站:
Spring Cloud GatewayLevel up your Java code and explore what Spring can do for you.https://spring.io/projects/spring-cloud-gateway#learn
1.2.快速入门
接下来,我们先看下如何利用网关实现请求路由。由于网关本身也是一个独立的微服务,但没有业务逻辑,所以也要创建一个模块开发功能。大概步骤如下:
-
创建网关微服务
-
引入SpringCloudGateway、NacosDiscovery依赖
-
编写启动类
-
配置网关路由
1.2.1.创建项目
首先,我们要在hmall下创建一个新的module,命名为hm-gateway,作为网关微服务:
1.2.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.2.3.启动类
在hm-gateway
模块的com.hmall.gateway
包下新建一个启动类:
代码如下:
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.2.4.配置路由
接下来,在hm-gateway
模块的resources
目录新建一个application.yaml
文件,内容如下:
server: port: 8080 # 网关服务的端口号
spring:application:name: gateway # 网关服务的名称cloud:nacos:server-addr: 192.168.150.101:8848 # 配置nacos服务的地址gateway:routes: # 在routes属性下配置路由规则,可以配置多个路由规则- id: item # 路由规则的id(唯一标识),建议和要路由的微服务名一致uri: lb://item-service # 要路由到的目标服务,lb代表负载均衡,会从注册中心拉取服务列表,然后负载均衡挑一个实例predicates: # 路由断言,判断请求是否符合这个路由断言,符合则按此条路由规则走- Path=/items/**,/search/** # 这里是以请求路径作为判断规则,等号前面是路由断言的名字,右边是规则,有多个规则就用逗号隔开- id: carturi: lb://cart-servicepredicates:- Path=/carts/**- id: useruri: lb://user-servicepredicates:- Path=/users/**,/addresses/**- id: tradeuri: lb://trade-servicepredicates:- Path=/orders/**- id: payuri: lb://pay-servicepredicates:- Path=/pay-orders/**
例如下图:
前端发来请求,请求路径是/items/list,然后就符合了由路断言,然后就会按此条路由规则走,路由到到uri后面的微服务
1.2.5.测试
启动GatewayApplication,以 http://localhost:8080 拼接微服务接口路径来测试。例如:
http://localhost:8080/items/page?pageNo=1&pageSize=1
此时,启动UserApplication、CartApplication,然后打开前端页面,发现相关功能都可以正常访问了:
1.3.路由过滤
先复习下路由规则的定义语法:
spring:cloud:gateway:routes:- id: itemuri: lb://item-servicepredicates:- Path=/items/**,/search/**
其中routes对应的Java类型如下:
是一个集合,也就是说可以定义很多路由规则。集合中的RouteDefinition
就是路由规则定义,其中常见的属性如下:
四个属性含义如下:
-
id
:路由的唯一标示 -
predicates
:路由断言,其实就是匹配条件,是一个集合,可以配置多个 -
filters
:路由过滤规则,是一个集合,可以配置多个 -
uri
:路由目标地址,lb://
代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
这里我们重点关注predicates
,也就是路由断言。SpringCloudGateway中提供的断言类型有很多:
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=**.somehost.org,**.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
weight | 权重处理 |
2.网关做登录校验
单体架构时我们只需要完成一次用户登录、身份校验,就可以在所有业务中获取到用户信息。而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然不可取。
2.1.鉴权思路分析
我们的登录是基于JWT来实现的,校验JWT的算法复杂,而且需要用到秘钥。如果每个微服务都去做登录校验,这就存在着两大问题:
-
每个微服务都需要知道JWT的秘钥,不安全
-
每个微服务重复编写登录校验代码、权限校验代码,麻烦
因为网关是所有微服务的入口,一切请求都需要先经过网关。所以可以把登录校验放到网关去做,这样之前说的问题就解决了:
-
只需要在网关和用户服务保存秘钥
-
只需要在网关开发登录校验功能
此时,登录校验的流程如图:
不过,这里存在几个问题:
-
网关路由是配置的,请求转发是Gateway内部代码,我们如何在转发之前做登录校验?
-
网关校验JWT之后,如何将用户信息传递给微服务?
-
如何在微服务之间传递用户信息?
这些问题将在接下来几节一一解决。
2.2.网关过滤器
登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway
内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway
内部工作的基本原理。
如图所示:
-
客户端请求进入网关后由
HandlerMapping
对发送请求做判断,基于路由断言,找到与当前请求匹配的路由规则(Route
),然后将请求交给WebHandler
去处理。 -
WebHandler
会加载当前路由下需要执行的过滤器链(Filter chain
),然后按照顺序逐一执行(后面称为Filter
)。 -
NettyRoutingFilter过滤器负责将请求转发到微服务,当微服务返回结果后存入上下文,然后依次返回给其他过滤器,最终返回给用户
-
最终请求转发是由
NettyRoutingFilter
的过滤器来执行的,这个过滤器是整个过滤器链中最后一个,所以只要定义一个过滤器,并在其中实现登录校验的逻辑,并且将过滤器的执行顺序定义到NettyRoutingFilter
之前,这样网关就可以在转发请求之前去做登录校验了
-
-
图中
Filter
被虚线分为左右两部分,是因为Filter
内部的逻辑分为pre
和post
两部分,分别会在请求路由到微服务之前和之后被执行。 -
只有所有
Filter
的pre
逻辑都依次顺序执行通过后,请求才会被路由到微服务。 -
微服务返回结果后,再倒序执行
Filter
的post
逻辑。 -
最终把响应结果返回。
那么,该如何实现一个网关过滤器呢?
网关过滤器链中的过滤器有两种:
-
GatewayFilter
:路由过滤器,作用范围比较灵活,可以是任意指定的路由Route
.-
在yml文件中用filters属性配置的那种就是路由过滤器,SpringCloudGateway已经提供好的,默认不生效,要配置到路由后才生效
-
-
GlobalFilter
:全局过滤器,作用范围是所有路由,声明后自动生效。
其实GatewayFilter
和GlobalFilter
这两种过滤器的方法签名完全一致:
/*** 处理请求并将其传递给下一个过滤器* @param exchange 当前请求的上下文,其中包含request、response等各种数据* @param chain 过滤器链,基于它向下传递请求* @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
- ServerWebExchange:网关内部的上下文对象,可以保存网关内部的共享数据,如request、response、session、或自定义的共享属性。将来在网关的整个过滤器链中都可以从它里面读取数据或存数据
- chain:过滤器链,当过滤器执行完后,调用过滤器链中的下一个过滤器
FilteringWebHandler
在处理请求时,会将GlobalFilter
装饰为GatewayFilter
,然后放到同一个过滤器链中,排序以后依次执行。
Gateway
中内置了很多的GatewayFilter
,详情可以参考官方文档:
Docshttps://b11et3un53m.feishu.cn/wiki/UMgpwmmQKisWBIkaABbcwAPonVf#CbiqdfAlNoTXFCxAJeDcyiwenuc
Gateway
内置的GatewayFilter
过滤器使用起来非常简单,无需编码,只需在yaml文件中简单配置即可。而且其作用范围也很灵活,配置在哪个Route
下,就作用于哪个Route
.
GatewayFilter
过滤器的使用,有一个过滤器叫做AddRequestHeaderGatewayFilterFacotry
,顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。
使用的使用只需要在application.yaml中这样配置:
spring:cloud:gateway:routes:- id: test_routeuri: lb://test-servicepredicates:-Path=/test/**filters:- AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是请求头的value
如果想要让过滤器作用于所有的路由,则可以这样配置:
spring:cloud:gateway:default-filters: - AddRequestHeader=key, valueroutes:- id: test_routeuri: lb://test-servicepredicates:-Path=/test/**
2.3.自定义过滤器
无论是GatewayFilter
还是GlobalFilter
都支持自定义,只不过编码方式、使用方式略有差别。
2.3.1.自定义GatewayFilter(先跳过)
自定义GatewayFilter
不是直接实现GatewayFilter接口
,而是实现AbstractGatewayFilterFactory
。最简单的方式是这样的:
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求ServerHttpRequest request = exchange.getRequest();// 编写过滤器逻辑System.out.println("过滤器执行了");// 放行return chain.filter(exchange);}};}
}
注意:该类的名称一定要以GatewayFilterFactory
为后缀!
然后在yaml配置中这样使用:
spring:cloud:gateway:default-filters:- PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器
另外,这种过滤器还可以支持动态配置参数,不过实现起来比较复杂,示例:
@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {// OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:// - GatewayFilter:过滤器// - int order值:值越小,过滤器执行优先级越高return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取config值String a = config.getA();String b = config.getB();String c = config.getC();// 编写过滤器逻辑System.out.println("a = " + a);System.out.println("b = " + b);System.out.println("c = " + c);// 放行return chain.filter(exchange);}}, 100);}// 自定义配置属性,成员变量名称很重要,下面会用到@Datastatic class Config{private String a;private String b;private String c;}// 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取@Overridepublic List<String> shortcutFieldOrder() {return List.of("a", "b", "c");}// 返回当前配置类的类型,也就是内部的Config@Overridepublic Class<Config> getConfigClass() {return Config.class;}}
然后在yaml文件中使用:
spring:cloud:gateway:default-filters:- PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制
上面这种配置方式参数必须严格按照shortcutFieldOrder()方法的返回参数名顺序来赋值。
还有一种用法,无需按照这个顺序,就是手动指定参数名:
spring:cloud:gateway:default-filters:- name: PrintAnyargs: # 手动指定参数名,无需按照参数顺序a: 1b: 2c: 3
2.3.2.自定义GlobalFilter
自定义GlobalFilter过滤器很简单,直接实现GlobalFilter接口即可,而且也无法设置动态参数:
- 同时实现Ordered接口并重写其中的getOrder方法是为了将这个自定义过滤器的执行顺序放在
NettyRoutingFilter
之前
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//获取http请求信息ServerHttpRequest request = exchange.getRequest();// 编写过滤器逻辑System.out.println("过滤逻辑模拟");// 放行return chain.filter(exchange);}@Overridepublic int getOrder() {// 过滤器执行顺序,值越小,优先级越高return 0;}
}
2.4.登录校验
接下来,我们就利用自定义GlobalFilter
来完成登录校验。
2.4.1.JWT工具
登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service
中已经有了,我们直接拷贝过来:
具体作用如下:
-
AuthProperties类
:读取yaml文件中配置的登录校验需要拦截的路径(从application.yaml文件中获取),因为不是所有的路径都需要登录才能访问 -
JwtProperties
:定义与JWT工具有关的属性,比如秘钥文件位置 -
SecurityConfig
:工具的自动装配 -
JwtTool
:JWT工具,其中包含了校验和解析token
的功能 -
hmall.jks
:秘钥文件
其中AuthProperties
和JwtProperties
所需的属性要在application.yaml
中配置:
hm:jwt:location: classpath:hmall.jks # 秘钥地址alias: hmall # 秘钥别名password: hmall123 # 秘钥文件密码tokenTTL: 30m # 登录有效期auth:excludePaths: # 无需登录校验的路径,碰到哪些路径就直接放行- /search/**- /users/login- /items/**
2.4.2.编写登录校验的过滤器
接下来,我们定义一个登录校验的过滤器:
代码如下:
package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;//spring提供的AntPathMatcher类,一个匹配器private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断 发过来的请求 是否需要做登录拦截if(isExclude(request.getPath().toString())){// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.使用工具类校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete(); //调用这个方法后,后面所有的过滤器就不会执行了}// TODO 5.如果有效,传递用户信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {//判断指定的路径和路径的通配符是否匹配if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
重启测试,会发现访问/items开头的路径,未登录状态下不会被拦截:
访问其他路径则,未登录状态下请求会被拦截,并且返回401
状态码:
2.5.微服务获取用户
现在,网关已经可以完成登录校验并获取到登录用户的信息。但是网关将请求转发到微服务时,微服务如何获取到用户信息呢?
由于网关发送请求到微服务依然采用的是Http
请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
据图流程图如下:
因此,接下来我们要做的事情有:
-
改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
-
编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行
2.5.1.保存用户信息到请求头
首先,我们修改登录校验过滤器的处理逻辑,保存用户信息到请求头中:
- .mutate():对下游的请求做改变
- .request():对请求做处理
- .build():构造出新的ServerWebExchange对象
到这里可以在controller获取信息:
2.5.2.拦截器获取用户
在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:
其中已经提供了保存和获取用户的方法:
接下来,我们只需要编写拦截器,获取用户信息并保存到UserContext
,然后放行即可。
由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common
中,并写好自动装配。这样微服务只需要引入hm-common
就可以直接具备拦截器功能,无需重复编写。
我们在hm-common
模块下定义一个拦截器:
具体代码如下:
package com.hmall.common.interceptor;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的用户信息String userInfo = request.getHeader("user-info");// 2.判断是否为空if (StrUtil.isNotBlank(userInfo)) {// 不为空,保存到ThreadLocalUserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserContext.removeUser();}
}
接着在hm-common
模块下编写SpringMVC
的配置类,配置登录拦截器:
具体代码如下:
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)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config
,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources
目录下的META-INF/spring.factories
文件中:
内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig
2.5.3.恢复购物车代码
之前我们无法获取登录用户,所以把购物车服务的登录用户写死了,现在需要恢复到原来的样子。
找到cart-service
模块的com.hmall.cart.service.impl.CartServiceImpl
:
修改其中的queryMyCarts
方法:
2.6.OpenFeign传递用户信息
前端发起的请求都会经过网关再到微服务,由于我们之前编写的过滤器和拦截器功能,微服务可以获取到登录用户的信息。
但有些业务是比较复杂的,请求到达微服务后还需要调用其它多个微服务。比如下单业务,流程如下:
下单的过程中,需要调用商品服务扣减库存,调用购物车服务清理用户购物车。而清理购物车时必须知道当前登录的用户身份。但是,订单服务调用购物车时并没有传递用户信息,购物车服务无法知道当前用户是谁!
由于微服务获取用户信息是通过拦截器在请求头中读取,因此想要实现微服务之间的用户信息传递,就必须在微服务 发起调用时 把用户信息存入请求头。
微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢?
OpenFeign中提供了一个拦截器接口:feign.RequestInterceptor,
用于在Feign调用发起前,对请求进行处理,在所有的OpenFeign发起请求前,都会先调用这个拦截器中的apply方法来处理请求。
public interface RequestInterceptor {/*** Called for every request. * Add data using methods on the supplied {@link RequestTemplate}.*/void apply(RequestTemplate template);
}
我们只需要实现这个接口,然后实现其apply方法,再利用RequestTemplate对象
来添加请求头,将用户信息保存到请求头中,之后每次OpenFeign发起请求时都会调用该方法,传递用户信息。
- 其中RequestTemplate类中提供了一些方法可以修改请求头,具体自行查阅
更多的用法参考:使用OpenFeing远程调用时为请求添加请求头_openfeign添加请求头-CSDN博客
因为FeignClient
全部都是在hm-api
模块,因此我们在hm-api
模块的com.hmall.api.config.DefaultFeignConfig
中编写这个拦截器,然后任何引入了hm-api模块的微服务在发起远程调用的时候都会生效:
在com.hmall.api.config.DefaultFeignConfig配置类
中添加一个Bean:
@Bean
public RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取登录用户Long userId = UserContext.getUser();if(userId == null) {// 如果为空则直接跳过return;}// 如果不为空则放入请求头中,传递给下游微服务template.header("user-info", userId.toString());}};
}
现在微服务之间通过OpenFeign调用时也会传递登录用户信息了。
3.配置管理
到目前为止我们已经解决了微服务相关的几个问题:
-
微服务远程调用
-
微服务注册、发现
-
微服务请求路由、负载均衡
-
微服务登录用户信息传递
不过,现在依然还有几个问题需要解决(学习此部分,更加方便开发微服务):
-
网关路由在配置文件中写死了,如果变更必须重启微服务
-
某些业务配置在配置文件中写死了,每次修改都要重启服务
-
每个微服务都有很多重复的配置,维护成本高(yml文件中有很多重复的配置)
这些问题可以通过统一的配置管理器服务解决。而Nacos不仅仅具备注册中心功能,还具备配置管理的功能:
所以微服务共享的配置可以统一交给Nacos保存和管理,在Nacos控制台修改配置后,Nacos会将配置变更 推送 给相关的微服务,而且无需重启即可生效,实现配置的热更新。
网关的路由同样是配置,因此同样可以基于这个功能实现动态路由功能,无需重启网关即可修改路由配置。
3.1.配置共享
把微服务共享的配置抽取到Nacos中统一管理,这样就不需要每个微服务都重复配置了。分为两步:
-
在Nacos中添加共享配置
-
微服务拉取配置
3.1.1.配置管理中添加共享配置
分析重复的配置
以cart-service为例,我们看看有哪些配置是重复的,可以抽取的:
首先是jdbc相关配置:
然后是日志配置:
然后是swagger以及OpenFeign的配置:
我们在nacos控制台分别添加这些配置。
jdbc相关配置
首先是jdbc相关配置,在配置管理
->配置列表
中点击+
新建一个配置:
在弹出的表单中填写信息:
其中详细的配置如下:
spring:datasource:url: jdbc:mysql://${hm.db.host:192.168.150.101}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: ${hm.db.un:root}password: ${hm.db.pw:123}
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandlerglobal-config:db-config:update-strategy: not_nullid-type: auto
注意这里的jdbc的相关参数并没有写死,例如:
-
数据库ip
:通过${hm.db.host:192.168.150.101}
配置了默认值为192.168.150.101
,同时允许通过${hm.db.host}
来覆盖默认值 -
数据库端口
:通过${hm.db.port:3306}
配置了默认值为3306
,同时允许通过${hm.db.port}
来覆盖默认值 -
数据库database
:可以通过${hm.db.database}
来设定,无默认值
日志配置
然后是统一的日志配置,命名为shared-log.
yaml
,配置内容如下:
logging:level:com.hmall: debugpattern:dateformat: HH:mm:ss:SSSfile:path: "logs/${spring.application.name}"
swagger配置
然后是统一的swagger配置,命名为shared-swagger.yaml
,配置内容如下:
knife4j:enable: trueopenapi:title: ${hm.swagger.title:黑马商城接口文档}description: ${hm.swagger.description:黑马商城接口文档}email: ${hm.swagger.email:zhanghuyi@itcast.cn}concat: ${hm.swagger.concat:虎哥}url: https://www.itcast.cnversion: v1.0.0group:default:group-name: defaultapi-rule: packageapi-rule-resources:- ${hm.swagger.package}
注意,这里的swagger相关配置我们没有写死,例如:
-
title
:接口文档标题,我们用了${hm.swagger.title}
来代替,将来可以有用户手动指定 -
email
:联系人邮箱,我们用了${hm.swagger.email:
zhanghuyi@itcast.cn
}
,默认值是zhanghuyi@itcast.cn
,同时允许用户利用${hm.swagger.email}
来覆盖。
3.1.2.拉取共享配置
接下来,我们要在微服务拉取共享配置,将其与本地的application.yaml
配置合并,完成项目上下文的初始化。
不过,需要注意的是,读取Nacos配置是在SpringCloud上下文(ApplicationContext
)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml
。
也就是说引导阶段,application.yaml
文件尚未读取,根本不知道nacos 地址,该如何去加载nacos中的配置文件呢?
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml
(或者bootstrap.properties
)的文件,如果我们将nacos地址配置到bootstrap.yaml
中,那么在项目引导阶段就可以读取nacos中的配置了。
拉去共享配置的流程如下:
其中:
- 拉去Nacos配置:拉去Nacos中的共享配置文件
- 初始化ApplicationContext:基于从Nacos拉去到的配置完成SpringCloud上下文的初始化
- 之后加载SpringBoot配置文件,以及上下文初始化
因此,微服务整合Nacos配置管理的步骤如下:
1)引入依赖:
在cart-service模块引入依赖:
<!--nacos配置管理:做配置管理的依赖,帮助我们去完成拉去配置的操作--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--作用:读取bootstrap.yaml文件,知道nacos的地址后,才能去拉取共享配置文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>
2)新建bootstrap.yaml
在cart-service中的resources目录新建一个bootstrap.yaml文件:
内容如下:
spring:application:name: cart-service # 服务名称profiles:active: devcloud:nacos:server-addr: 192.168.150.101 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置
3)修改application.yaml
由于一些配置挪到了bootstrap.yaml,因此application.yaml需要修改为:
server:port: 8082
feign:okhttp:enabled: true # 开启OKHttp连接池支持
hm:swagger:title: 购物车服务接口文档package: com.hmall.cart.controllerdb:database: hm-cart
重启服务,发现所有配置都生效了。
3.2.配置热更新(到这)
相关文章:
04-微服务02
我们将黑马商城拆分为5个微服务: 用户服务 商品服务 购物车服务 交易服务 支付服务 由于每个微服务都有不同的地址或端口,相信大家在与前端联调的时候发现了一些问题: 请求不同数据时要访问不同的入口,需要维护多个入口地址…...
离线语音识别+青云客语音机器人(幼儿园级别教程)
1、使用步骤 确保已安装以下库: pip install vosk sounddevice requests pyttsx3 2、下载 Vosk 模型: 下载适合的中文模型,如 vosk-model-small-cn-0.22。 下载地址: https://alphacephei.com/vosk/models 将模型解压后放置在…...
Llama 3 后训练(三)
目录 4. 后训练 4.1 建模 图表解读 4.1.1 聊天对话格式 4.1.2 奖励建模 4.1.3 监督微调(Supervised Finetuning) 4.1.4 直接偏好优化(Direct Preference Optimization) 4.1.5 模型平均(Model Averaging&#x…...
Cobbler+kickstart实现批量全自动装机
cobbler简介 cobbler 是一个系统启动服务boot server,可以通过pxe得方式用来快速安装,重装系统,支持安装不同linux发行版和windows。这个工具是用python开发,方便小巧,15k行代码,使用简单得命令完成pxe网络安装环境…...
【Pandas】pandas Series at
Pandas2.2 Series Indexing, iteration 方法描述Series.get()用于根据键(索引标签)从 Series 中获取值Series.at用于快速访问标量值(单个元素)的访问器Series.iat用于快速访问标量值(单个元素)的访问器 …...
optimum-habana 安装 optimum安装
目录 git地址: 运行必须参数设置 git地址: https://github.com/huggingface/optimum-habana git clone 以后 cd optimum-habana pip install . 运行必须参数设置 hpu改为 cuda, output = subprocess.run("pip list | grep habana-torch-plugin",shell=True…...
AutoDL服务器深度学习使用过程
前期准备 Xshell,Xftp,Pycharm专业版 step 1:实例开机(无卡or有卡),Xshell连接 新建xshell会话: 登录指令格式为: ssh -p 38076 rootregion-1.autodl.com 在ssh -p 38076 rootregion-1.autodl.com命令中࿰…...
微信小程序:定义页面标题,动态设置页面标题,json
1、常规设置页面标题 正常微信小程序中,设置页面标题再json页面中进行设置,例如 {"usingComponents": {},"navigationBarTitleText": "标题","navigationBarBackgroundColor": "#78b7f7","navi…...
LeetCode算法题——有序数组的平方
题目描述 给你一个按非递减顺序排序的整数数组nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。 题解 解法一:暴力解法 思路: 该题目可通过暴力解法解决,即利用for循环遍历数组,对数组每…...
【MyBatis-Plus 核心接口】BaseMapper 和 IService 深度解析
在使用 MyBatis-Plus(简称 MP)进行开发时,BaseMapper 和 IService 接口是我们老朋友了,不知道你会不会跟我一样好奇:为什么实现了 BaseMapper 或 IService 接口,我们就能轻松操作数据库?这背后有…...
SQL 建表语句详解
SQL 建表语句详解 在 SQL 中,创建表(Table)是数据库设计的基础。表是存储数据的基本单位,每个表由行和列组成。创建表的过程涉及到定义表的结构,包括列名、数据类型、约束等。本文将详细介绍 SQL 中的建表语句&#x…...
数据可视化-16. 日历图
目录 1. 日历图的概念 2. 日历图的适用场景 2.1 事件或活动的频率分析 2.2 数据的时间周期性分析 2.3 异常值检测 2.4 绩效监控 3. 日历图的缺陷 3.1 粒度受限于天数 3.2 数据密度过高时候难以解读 3.3 难以比较多个数据集 3.4 周期性较长的数据不易展示 4. 日历图…...
Docker 安装与常用命令
Docker 安装与常用命令 安装 Docker 如果您的系统尚未安装 Docker,可以使用以下命令安装 docker.io: 1. 安装命令: sudo apt install -y docker.io2、安装完成后,使用以下命令检查 Docker 是否成功安装: docker -…...
logback之自定义pattern使用的转换器
目录 (1)场景介绍 (2)定义转换器BizCallerConverter (3)logback配置conversionRule (4)测试效果 前文《logback之pattern详解以及源码分析》已经介绍了pattern,以及…...
Spring Cloud Alibaba2022之Sentinel总结
Spring Cloud Alibaba2022之Sentinel学习 Sentinel介绍 Sentinel是一个面向云原生微服务的流量控制、熔断降级组件。 Sentinel 分为两个部分: 核心库:(Java 客户端)不依赖任何框架/库,能够运行于所有 Java运行时环 …...
期末速成C++【知识点汇总完】
目录 第一章 C特点 命名空间-命名冲突 引用 new和delete 堆和栈 缺省参数 重载/隐藏/覆盖 初始化方式 第二章 面向对象的三大特征 成员变量 成员函数:构造函数和析构函数 访问权限和继承方式 空类 常const 静态static 友元friend 第三章 重…...
从 ELK Stack 到简单 — Elastic Cloud Serverless 上的 Elastic 可观察性
作者:来自 Elastic Bahubali Shetti, Chris DiStasio 宣布 Elastic Cloud Serverless 上的 Elastic Observability 正式发布 — 一款完全托管的可观察性解决方案。 随着组织规模的扩大,一个能够处理分布式云环境的复杂性并提供实时洞察的可观察性解决方…...
手机h5加桌面图标
手机h5应用1,网址浏览器添加到桌面,修改图标 关键代码 <!-- 手机h5加桌面图标 --> <!-- 安卓平台 chrome --> <link relapple-touch-icon-precomposed href<% BASE_URL %>logonew.png> <meta name"mobile-web-app-capab…...
Vue3,什么情况下数据会丢失响应式呢?
一、使用 reactive 定义的数据重新赋值 <template><h1>{{ foo.a }}</h1><h1>{{ bar.a }}</h1><button click"handleClick">点我</button> </template> <script setup> import { ref, reactive } from vuele…...
【Vim Masterclass 笔记03】S03L10 + S03L11:Vim 中的文本删除操作以及 Vim 思维习惯的培养(含 DIY 拓展知识点)
文章目录 Section 3:Vim Essentials(Vim 核心知识)S03L10 Vim 核心浏览命令同步练习点评课S03L11 Deleting Text and "Thinking in Vim" 文本的删除及 Vim 思维习惯的培养1 删除单个字符2 删除一个单词2.1 推广1:D HJK…...
《Java核心技术II》流中的filter、map和flatMap方法
filter、map和flatMap方法 filter filter通过转换产生过滤后的新流,将字符串流转化为只包含长单词的另一个流。 List words ...; Stream longWords words.stream().filter(w->w.length()>12) filter类型是Predicate(谓词,表示动作)类型对象,…...
logback之自定义过滤器
logback有两种过滤器,一种是context中的过滤器叫TurboFilter,是一个全局的过滤器,会影响所有的日志记录。另一种是Appender中的过滤器,只对所在的append有效。两者大同小异,这里我们以Appender的过滤器为例。 &#x…...
【论文阅读笔记】IceNet算法与代码 | 低照度图像增强 | IEEE | 2021.12.25
目录 1 导言 2 相关工作 A 传统方法 B 基于CNN的方法 C 交互方式 3 算法 A 交互对比度增强 1)Gamma estimation 2)颜色恢复 3)个性化初始η B 损失函数 1)交互式亮度控制损失 2)熵损失 3)平滑损失 4)总损失 C 实现细节 4 实验 5 IceNet环境配置和运行 1 下载…...
查看 GitHub 仓库的创建时间
查看 GitHub 仓库的创建时间 1. https://api.github.com/repos/{owner}/{repository}2. curl -s https://api.github.com/repos/{owner}/{repository} | jq .created_atReferences 1. https://api.github.com/repos/{owner}/{repository} REST API endpoints for repositories…...
五种被低估的非常规统计检验方法:数学原理剖析与多领域应用价值研究
在当前的数据分析实践中,研究人员往往过度依赖t检验和方差分析(ANOVA)等传统统计方法。但是还存在多种具有重要应用价值但未受到足够重视的统计检验方法,这些方法在处理复杂的实际数据时具有独特优势。本文将详细介绍五种具有重要…...
mysql重置root密码(适用于5.7和8.0)
今天出一期重置mysql root密码的教程,适用于5.7和8.0,在网上搜索了很多的教程发现都没有效果,浪费了很多时间,尝试了多次之后发现这种方式是最稳妥的,那么废话不多说,往下看: 目录 第一步&…...
【AIGC-ChatGPT职业提示词指令】职业发展的航海指南:在人生的十字路口做出明智抉择
引言 在职业发展的海洋中,每个人都会遇到需要重要抉择的时刻。这些关键节点就像航海中的分岔路口,选择不同的航线可能驶向截然不同的目的地。如何在这些关键时刻做出明智的选择,需要我们既要着眼当下的风向,也要洞察远方的航程。…...
【从零开始入门unity游戏开发之——C#篇39】C#反射使用——Type 类、Assembly 类、Activator 类操作程序集
文章目录 前言一、前置知识1、编译器2、程序集(Assembly)3、元数据(Metadata) 二、反射1、反射的概念2、反射的作用3、反射的核心Type 类3.1 Type 类介绍3.2 不同方法获取 Type3.3 获取type类型所在的程序集的相关信息 4、反射的常…...
如何启动CentOS6远程服务器和进行ssh远程登录?
如何启动CentOS 6远程服务器? 在CentOS 6中,启动远程服务器通常涉及到配置SSH服务和VNC服务,以下是详细的步骤: 配置SSH服务 确认是否安装SSH 首先需要检查系统中是否已经安装了SSH服务,打开终端并输入以下命令&am…...
面向机器学习的Java库与平台
学习Java语言中与机器学习相关的各种库与平台,了解每个库的功能,以及可以用它 们解决的问题。 实现机器学习应用时需要具备的Java环境 Weka:一个通用的机器学习平台 Java机器学习库:一系列机器学习算法 Apache Mah…...
AI大模型语音识别转文字
提取音频 本项目作用在于将常见的会议录音文件、各种语种音频文件进行转录成相应的文字,也可从特定视频中提取对应音频进行转录成文字保存在本地。最原始的从所给网址下载对应视频和音频进行处理。下载ffmpeg(https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-…...
GAN对抗生成网络(一)——基本原理及数学推导
1 背景 GAN(Generative Adversarial Networks)对抗生成网络是一个很巧妙的模型,它可以用于文字、图像或视频的生成。 例如,以下就是GAN所生成的人脸图像。 2 算法思想 假如你是《古董局中局》的文物造假者(Generator,生成器)&a…...
LeetCode - 初级算法 数组(旋转数组)
旋转数组 这篇文章讨论如何通过编程实现数组元素的旋转操作。 免责声明:本文来源于个人知识与公开资料,仅用于学术交流。 描述 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例: 输入: nums = [1,2,3,...
目标检测入门指南:从原理到实践
目录 1. 数据准备与预处理 2. 模型架构设计 2.1 特征提取网络原理 2.2 区域提议网络(RPN)原理 2.3 特征金字塔网络(FPN)原理 2.4 边界框回归原理 2.5 非极大值抑制(NMS)原理 2.6 多尺度训练与测试原理 2.7 损失函数设计原理 3. 损失函数设计 4. 训练策略优化 5. 后…...
连接github和ai的桥梁:GitIngest
Git ingest GitIngest - 将任何 Github 仓库转变为适合 LLM 的友好型提示文本 (https://github.com/cyclotruc/gitingest) 输入 Github 地址或者名称,GitIngest 就会提供该仓库的总结、目录结构、仓库内容的文本内容 你可以复制这些文本与 AI 大模型更好地对话...
百度贴吧的ip属地什么意思?怎么看ip属地
在数字化时代,IP地址不仅是网络设备的唯一标识符,更承载着用户的网络身份与位置信息。百度贴吧作为广受欢迎的社交平台,也遵循相关规定,在用户个人主页等位置展示账号IP属地信息。那么,百度贴吧的IP属地究竟意味着什么…...
5.系统学习-PyTorch与多层感知机
PyTorch与多层感知机 前言PyTroch 简介张量(Tensor)张量创建张量的类型数据类型和 dtype 对应表张量的维度变换:张量的常用操作矩阵或张量计算 Dataset and DataLoaderPyTorch下逻辑回归与反向传播数据表格 DNN(全连结网络&#x…...
wpf 基于Behavior库 的行为模块
Microsoft.Xaml.Behaviors 是一个用于WPF(Windows Presentation Foundation)的行为库,它的主要作用是允许开发者在不修改控件源代码的情况下,为控件添加自定义的行为和交互逻辑。行为库的核心思想是通过定义可重用的行为组件&…...
【一文解析】新能源汽车VCU电控开发——能量回收模块
一、概述 VCU(Vehicle Control Unit,整车控制器)能量回收功能是新能源汽车(如纯电动汽车和混合动力汽车)中非常重要的一个环节。它主要是在车辆减速或制动过程中,将车辆的部分动能转化为电能,并…...
鸿蒙TCPSocket通信模拟智能家居模拟案例
效果图 一、智能家居热潮下的鸿蒙契机 在当下科技飞速发展的时代,智能家居已如浪潮般席卷而来,深刻地改变着我们的生活方式。从能依据环境光线自动调节亮度的智能灯具,到可远程操控、精准控温的智能空调,再到实时监测健康数据的智…...
【Spring Boot 实现 PDF 导出】
Spring Boot 实现 PDF 导出 在Spring Boot应用程序中实现PDF导出功能,可以选择多种库和技术栈。每种方法都有其优缺点,适用于不同的场景。以下是四种常见的方式:iText、Apache PDFBox、JasperReports 和 Thymeleaf Flying Saucer。我将详细…...
【Python】selenium结合js模拟鼠标点击、拦截弹窗、鼠标悬停方法汇总(使用 execute_script 执行点击的方法)
我们在写selenium获取网络信息的时候,有时候我们会受到对方浏览器的监控,对方通过分析用户行为模式,如点击、滚动、停留时间等,网站可以识别出异常行为,进而对Selenium爬虫进行限制。 这里我们可以加入JavaScript的使…...
leetcode hot 100 前k个高平元素
347. 前 K 个高频元素 已解答 中等 相关标签 相关企业 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 class Solution(object):def topKFrequent(self, nums, k):""":type nums: Lis…...
数据结构漫游记:静态双向链表
嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的pa…...
Object.defineProperty() 完整指南
Object.defineProperty() 完整指南 1. 基本概念 Object.defineProperty() 方法允许精确地添加或修改对象的属性。默认情况下,使用此方法添加的属性是不可修改的。 1.1 基本语法 Object.defineProperty(obj, prop, descriptor)参数说明: obj: 要定义…...
1Panel自建RustDesk服务器方案实现Windows远程macOS
文章目录 缘起RustDesk 基本信息实现原理中继服务器的配置建议 中继服务器自建指南准备服务器安装1Panel安装和配置 RustDesk 中继服务防火墙配置和安全组配置查看key下载&安装&配置客户端设置永久密码测试连接 macOS安装客户端提示finder写入失败hbbs和hbbr说明**hbbs…...
nginx学习之路-windows系统安装nginx
文章目录 1. 下载2. 启动3. 验证参考文档 1. 下载 官方下载地址:https://nginx.org/en/download.html 可以下载windows版本,如nginx-1.26.2.zip。解压后,加入系统变量。 2. 启动 可以使用命令行启动(windows系统自带的cmd可能…...
Paimon_01_241020
1. 概述 1.1. 核心特点 统一批处理和流处理(流和批同一套代码)数据湖能力多种引擎平权变更日志生成丰富的表类型(主键表、append-only,有序的流式读取来代替消息队列)模式演化(schema变更) 1…...
人工智能:变革时代的核心驱动力
求各位观众老爷看一看 先声明一下,该内容由于篇幅过长,可能会有一些地方存在一些小问题请大家谅解 观众老爷们,点个免费的赞和关注呗,您们的支持就是我最大的动力~ 人工智能:变革时代的核心驱动力 一、引言 在当今…...
【机器学习】工业 4.0 下机器学习如何驱动智能制造升级
我的个人主页 我的领域:人工智能篇,希望能帮助到大家!!!👍点赞 收藏❤ 随着科技的飞速发展,工业 4.0 浪潮正席卷全球制造业,而机器学习作为这一变革中的关键技术,正以前…...