当前位置: 首页 > news >正文

微服务—Gateway

微服务—Gateway

参考项目路径: D:\Users\lenovo\Desktop\Java学习-代码集\Myself_Practice

网关:就是网络关口,负责请求的路由,转发、身份校验。

配置路由规则

spring:cloud:gateway:routes:— id: item                     # 路由规则id , 自定义 唯一 (最好和微服务名一致)uri: lb://item-service		 # 路由目标微服务,lb 代表负载均衡 predicates:  				# 路由断言,判断请求是否符合规则,符合规则到路由— Path=/items/**		# 以请求路径做判断,以 /items 开头 则符合规则 — id: xxuri: lb://xx-servicepredicates:— Path=/xx/**,/XX/**,/xx/**
  • id:为每条路由规则设定的唯一标识符,建议和微服务名称保持一致。
  • uri:代表路由目标微服务,lb:// 表示使用负载均衡。
  • predicates:属于路由断言,借助特定条件来判定请求是否符合规则,符合的话就进行路由。
  • Path=/items/** 表示请求路径以 /items 开头的请求会被路由到 item-service 微服务。

依赖

        <!--  gateway  应用禁止引入spring-boot-starter-web 依赖,如果引入,当前应用无法启动!!!!!--><!--geateway 依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><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>

快速使用

一:新建一个模块,创建一个网关服务

二:新建一个 yaml 文件,配置 网关路由规则

示例:

spring:application:name: springcloud-alibaba-gatewaycloud:gateway:routes:- id: springcloud-alibaba-consumer #设置iduri: lb://springcloud-alibaba-consumer  #设置服务名predicates:-  Method=GET,POST   #设置请求方法-  Path=/apia/**     #设置匹配路径的网关filters:- StripPrefix=1      #去掉前缀offer- id: springcloud-alibaba-feignconsumeruri: lb://springcloud-alibaba-feignconsumerpredicates:- Method=GET,POST- Path=/apib/**filters:- StripPrefix=1
server:port: 8080logging:level:org.springframework.cloud.gateway: debug

这时,我们通过8080 端口,通过路由匹配规则之后,就可以访问各个模块的接口 (注意,使用的是服务名发现,所以我们要在启动类上面加上 @EnableDiscoveryClient 这个注解)

路由属性

网关路由对应的 Java 类型 是 RouteDefinition, 其中常见的属性有:

  • id: 路由唯一标识
  • uri: 路由目标地址
  • predicates: 路由断言,判断请求是否符合当前路由
  • filters: 路由过滤器,对请求或响应做特殊处理

路由断言 predicates:

Spring 提供了 12种基本的 RoutePredicateFactory 实现

名称说明示例
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权重处理-Weight=group1,2
XForwarded Remote Addr基于请求的来源IP做判断-XForwardedRemoteAddr=192.168.1.1/24

路由过滤器 filter :

参考 Spring Cloud Gateway 官方文档

网关登录校验

思路: 在 网关里面做 JWT 校验(转发之前),响应之后,将登录用户信息传给后面的服务。

网关请求处理流程:

大致流程: 网关拦截判断客户端发送的请求,然后,进行路由匹配,再转发到对应的微服务模块中去

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

零:引出问题:

  • 如何在网关转发之前做登录校验?
  • 网关如何将用户信息传递给微服务?
  • 如何在微服务之间传递用户信息?

所以我们要将 登录校验,放在 pre 阶段,也就是 过滤器之前。因此我们需要在网关内自定义一个过滤器,保证这个过滤器的执行顺序,在 NettyRoutingFilter 之前,并且还要保证在 pre 逻辑里,另外网关还需要将用户信息传递给各个微服务(保存用户到请求头)

一:自定义过滤器:

网关过滤器有两种,分别是:

  • GatewayFilter:路由过滤器,作用于任意指定得路由,默认不生效,要配置到路由后生效。
  • GlobalFilter:全局过滤器,作用氛围是所有路由;声明后自动生效。
public interface GlobalFilter {/*** ServerWebExchange: 请求上下文 包含整个过滤器链内共享数据,例如 request response等* GatewayFilterChain: 过滤器链 当前过滤器执行完之后,要调用过滤器链的下一个过滤器* @param exchange* @param chain* @return*/Mono<Void>  filter(ServerWebExchange exchange, GatewayFilterChain chain);}
自定义 GlobalFilter

示例:

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//模拟登录校验逻辑ServerHttpRequest request = exchange.getRequest(); //获取请求HttpHeaders headers = request.getHeaders();//获取请求头System.out.println("headers = " + headers);//放行return chain.filter(exchange);}@Overridepublic int getOrder() {//保证我们的过滤器在 NettyRoutingFilter 之前执行//实现Ordered 接口,返回值越小,优先级越高  NettyRoutingFilter的返回值最大所以我们return 0 就可以在它前面执行return 0;}
}

我们实现 GlobalFilter接口,声明这个类是全局拦截器,并通过 @Component 加到容器当中,然后,获取请求,进行一些登录校验的逻辑

我们还要确保这个自定义的全局拦截器要在 NettyRoutingFilter 拦截器之前运行,所以,实现 Ordered 接口,并返回0 数值越小,优先级越高。

自定义 GatewayFilter

自定义 GatewayFilter 不是直接实现 GatewayFilter , 而是继承 AbstractGatewayFilterFactory

spring:cloud:gateway:routes:- id: user_service                # 路由规则id , 自定义 唯一uri: lb://user-service          # 路由目标微服务,lb 代表负载均衡predicates:                     # 路由断言,判断请求是否符合规则,符合规则到路由- Path=/user/**               # 以请求路径做判断,以 /user 开头 则符合规则- id: post_serviceuri: lb://order-servicepredicates:- Path=/order/**,/shopping/**,/cart/**     # 以请求路径做判断,以 /order  , /shopping  , /cart 开头 则符合规则default-filters:  # 全局过滤器 拦截所有请求- printAny=小新,5,
@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {return new OrderedGatewayFilter( new GatewayFilter(){@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {String name = config.getName();System.out.println("name = " + name);//模拟登录校验逻辑ServerHttpRequest request = exchange.getRequest();//获取请求HttpHeaders headers = request.getHeaders();//获取请求头System.out.println("打印日志");//放行 让下一个过滤器执行return chain.filter(exchange);}}, 1);}// 自定义配置属性 , 成员变量名称很重要@Datapublic static class Config{private String name;private int age;private String sex;}@Overridepublic List<String> shortcutFieldOrder() {//将 name age sex 顺序和配置文件保持一致List<String> list = new ArrayList<>();list.add("name");list.add("age");list.add("sex");return list;}// 构造器 将 config 字节码传递给父类, 父类负责帮我们读取 yaml 的配置public PrintAnyGatewayFilterFactory(){super(Config.class);}
}
  • 首先要注意的是,我们自定义的GatewayFilter的类名要是统一的后缀为 GatewayFilterFactory ,例如 PrintAnyGatewayFilterFactory

  • 这个是自定义了一个有参数的 GatewayFilter 拦截器 我们在 yaml 文件中 写参数。

  • 另外 我们的 GatewayFilter 工厂 new 的是 OrderedGatewayFilter 方便我们进行拦截器的先后拦截顺序。

  • 变量的顺序,就和我们在配置文件中的顺序是一致的。

二:实现登录校验:

需求:在网关中基于 过滤器 实现 登录校验 功能

@ConfigurationProperties 是 Spring Boot 框架中的一个注解,它主要用于将配置文件(如 application.propertiesapplication.yml)中的属性值绑定到 Java Bean 上,方便在代码中使用配置信息。

  • 示例:

  • hm:jwt:location: Haikouname: Xxxage: 16
    
  • @Data
    @Component
    @ConfigurationProperties(prefix="hm.jwt")
    public class JwtProperties{private String location;private String name;private int age;
    }
    
    • 必须为每个需要绑定的字段提供 setter 方法,因为 Spring Boot 通过调用 setter 方法来设置属性值。
    • 组件扫描:使用 @ConfigurationProperties 注解的类需要被 Spring 容器管理,可以使用 @Component 注解或在配置类中使用 @EnableConfigurationProperties 注解来启用。
自定义网关登录校验 过滤器:
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {@Autowiredprivate AuthYaml authYaml;//将 JwtTool 工具类注入@Autowiredprivate JwtTool jwtTool;private final AntPathMatcher antPathMatcher = new AntPathMatcher();// 路径匹配器 spring 内置的 路径匹配器@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1、获取 requestServerHttpRequest request = exchange.getRequest();//2、判断是否需要做登录拦截//判断请求路径,是否和我们在yaml文件中白名单配置的路径一致,如果一致就放行RequestPath path = request.getPath();//通过,我们自定义的 isTrue 方法判断是否需要做登录拦截if (this.isTrue(path.toString())) {//放行return chain.filter(exchange);}//下面是需要进行登录拦截校验的逻辑//3、获取tokenList<String> auth = request.getHeaders().get("authorization");String token = null;if (auth != null && !auth.isEmpty()){System.out.println("auth = " + auth);token = auth.get(0);}//4、校验并解析 tokentry {Long userId = jwtTool.parseToken(token);System.out.println("token = " + token);} catch (Exception e) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete(); // 拦截终止请求}//5、传递用户信息//6、放行 让下一个过滤器执行return chain.filter(exchange);}private boolean isTrue(String path) {// 判断是否需要做登录拦截//判断和我们在yaml文件中白名单配置的路径一致,如果一致就放行,返回truefor (String excludePath : authYaml.getExcludePaths()) {if(antPathMatcher.match(excludePath, path)){// 匹配成功return true;}}return false;}// 优先级 值越小,优先级越高//保证我们的过滤器在 NettyRoutingFilter 之前执行@Overridepublic int getOrder() {return 1;}
}
  • AuthYaml 相关

    xjh:jwt:location: HaiKoualias: xjhpassword: 123456tokenTTL: 30mauth:excludePaths:- /user/login- /user/register
    
    @Data
    @Component
    @ConfigurationProperties(prefix = "xjh.auth")
    public class AuthYaml {private List<String> excludePaths;
    }
    
  • JwtTool 相关

    @Component
    public class JwtTool {private final JWTSigner jwtSigner; //正版public JwtTool(KeyPair keyPair) {this.jwtSigner =  JWTSignerUtil.createSigner("HS256", keyPair);}public String createJwt(String userId, Duration ttl) {return JWT.create().setPayload("userId", userId).setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())) // 设置过期时间.setSigner(jwtSigner)  //正版.sign();}public Long parseToken(String token) {//1、校验token 是否为空if (token == null) {throw new UnauthorizedException("token 为空");}//2、校验并解析 jwtJWT jwt;try {jwt = JWT.of(token).setSigner(jwtSigner); //      正版} catch (Exception e) {throw new UnauthorizedException("token 解析失败");}//3、校验 token 是否有效if (!jwt.verify()) {throw new UnauthorizedException("token 无效");}//4、校验 token 是否过期try {JWTValidator.of(jwt).validateDate();} catch (ValidateException e) {throw new UnauthorizedException("token 过期");}//5、数据格式校验Object userId = jwt.getPayload("userId");if (userId == null) {throw new UnauthorizedException("token 无效");}//6、数据解析try {return Long.valueOf(userId.toString());} catch (NumberFormatException e) {throw new UnauthorizedException("token 无效");}}}
    
SecurityConfig 类相关

SecurityConfig 类的主要作用是为 Spring 应用程序配置与安全相关的 Bean。具体来说: 提供一个密码编码器 PasswordEncoder,用于在用户认证过程中对密码进行加密和验证,保护用户密码的安全性。 从密钥库中获取密钥对 KeyPair,这个密钥对通常用于 JWTJSON Web Token)的签名和验证,确保 JWT 的完整性和真实性,从而实现基于 JWT 的身份验证和授权机制。

@Configuration  // 专门定义配置类 该类可以包含多个 @Bean 注解的方法,这些方法会返回 Spring 容器要管理的 Bean 实例。
@EnableConfigurationProperties(JwtYaml.class)
public class SecurityConfig {  //生成密钥的配置类@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic KeyPair keyPair(JwtYaml jwtYaml){//获取密钥工厂KeyStoreKeyFactory keyStoreKeyFactory =new KeyStoreKeyFactory(jwtYaml.getLocation(),jwtYaml.getPassword().toCharArray());//获取密钥对return keyStoreKeyFactory.getKeyPair(jwtYaml.getAlias(),jwtYaml.getPassword().toCharArray());}}

JwtYaml 相关

xjh:jwt:location: HaiKoualias: xjhpassword: 123456tokenTTL: 30mauth:excludePaths:- /user/login- /user/register
@Data
@Component
@ConfigurationProperties(prefix = "xjh.jwt")
public class JwtYaml {private Resource location;private String alias;private String password;private Duration tokenTTL;
}

三:网关传递用户:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

思路,过滤器经过之后,我们将用户信息保存到请求头当中,然后加一层拦截器,从请求头中拿到信息将用户信息存储到 ThreadLocal 这样就可以不用每个微服务都去进行相关的处理。

3.1 在网关的登录校验过滤器中,把获取到的用户写入请求头

需求:修改 gateway 模块中的 登录校验拦截器,在校验成功之后,将用户 保存到下游请求的请求头当中去。

提示:要修改转发到微服务的请求,需要用到 ServerWebExchange 类下的 API

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {@Autowiredprivate AuthYaml authYaml;//将 JwtTool 工具类注入@Autowiredprivate JwtTool jwtTool;private final AntPathMatcher antPathMatcher = new AntPathMatcher();// 路径匹配器 spring 内置的 路径匹配器@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1、获取 requestServerHttpRequest request = exchange.getRequest();//2、判断是否需要做登录拦截//判断请求路径,是否和我们在yaml文件中白名单配置的路径一致,如果一致就放行RequestPath path = request.getPath();//通过,我们自定义的 isTrue 方法判断是否需要做登录拦截if (this.isTrue(path.toString())) {//放行return chain.filter(exchange);}//下面是需要进行登录拦截校验的逻辑//3、获取tokenList<String> auth = request.getHeaders().get("authorization");String token = null;if (auth != null && !auth.isEmpty()){System.out.println("auth = " + auth);token = auth.get(0);}//4、校验并解析 tokenLong userId;try {userId = jwtTool.parseToken(token);System.out.println("token = " + token);} catch (Exception e) {exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete(); // 拦截终止请求}//5、传递用户信息String userInfo = String.valueOf(userId);ServerWebExchange userExchange =  exchange.mutate() // 对下游的 request 进行修改 就是修改gateway之后的请求.request(builder -> builder.header("user-info", userInfo)).build();//6、放行 让下一个过滤器执行return chain.filter(userExchange);}private boolean isTrue(String path) {// 判断是否需要做登录拦截//判断和我们在yaml文件中白名单配置的路径一致,如果一致就放行,返回truefor (String excludePath : authYaml.getExcludePaths()) {if(antPathMatcher.match(excludePath, path)){// 匹配成功return true;}}return false;}// 优先级 值越小,优先级越高//保证我们的过滤器在 NettyRoutingFilter 之前执行@Overridepublic int getOrder() {return 1;}
}

对过滤器的第五、六步进行修改

        //5、传递用户信息String userInfo = String.valueOf(userId);ServerWebExchange userExchange =  exchange.mutate() // 对下游的 request 进行修改 就是修改gateway之后的请求.request(builder -> builder.header("user-info", userInfo)).build();//6、放行 让下一个过滤器执行return chain.filter(userExchange);
  • mutate() 就是对下游请求进行修改
  • header(param1 , param2) 里面的两个参数:第一个用户信息在请求头中的名字,第二个就是用户信息
  • 将 修改之后的 exchange 交到下一个过滤器进行执行
3.2 在common 模块 中编写 SpringMVC 拦截器,获取登录用户 !!!!(重要)

由于可能有多个模块需要获取到用户信息,我们直接在 common 层 定义拦截器,这样只要各微服务模块引用了 common 的依赖,就可以生效,无需重新编写。

//Spring MVC的拦截器还需要进行配置才可以
public class UserInfoInterceptor implements HandlerInterceptor {/***大致思路:* 在请求到达Controller之前,获取用户信息,然后将用户信息存储到ThreadLocal中。* 在controller 后,将用户信息从ThreadLocal中移除,避免内存泄漏。* HandlerInterceptor 拦截器 会在 请求到达Controller之前执行,返回true表示继续执行,返回false表示请求终止。*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1、获取登录用户信息String userInfo = request.getHeader("user-info");  //这里要和我们在过滤器中设置的一致!!!!!!//2、判断是否获取到了登录用户信息,有就存储到ThreadLocal中if (StrUtil.isNotBlank(userInfo)){//将用户信息存储到ThreadLocal中UserContext.setUserId(Long.valueOf(userInfo));}//3、放行return true;}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清理用户UserContext.clear();}}
  • 写一个 UserContext 工具类,帮助我们进行 用户信息的 管理

    public class UserContext {private static final ThreadLocal<Long> userId = new ThreadLocal<>();public static void setUserId(Long id) {userId.set(id);}public static Long getUserId() {return userId.get();}public static void clear() {userId.remove();}}
    

此时,拦截器还不会生效,我们需要进行如下配置

SpringMVC 的拦截器 配置到 MVC当中,利用 addInterceptors 将我们自定义的拦截器添加到 MVC 的配置当中。

@Configuration
//只要是微服务,就有 SpringMVC 就会有DispatcherServlet
//因为网关没有SpringMVC,就没有 DispatcherServlet 所以这个配置在网关服务中就不会生效
@ConditionalOnClass(DispatcherServlet.class) // 仅在存在 DispatcherServlet 类时才加载配置
public class MvcConfig implements WebMvcConfigurer {//Spring MVC的拦截器还需要进行配置才可以@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 拦截器配置registry.addInterceptor(new UserInfoInterceptor())//添加 UserInfoInterceptor 拦截器.addPathPatterns("/**"); // 拦截所有请求}
}

然后在 rescourse 包下创建 META-INF 文件夹, 在文件夹中新建spring.factories 文件

# 这里将 MvcConfig 配置到 spring.factories 文件中
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.example.common.config.MVCConfig.MvcConfig

此时,我们配置的 webconfig 就可以被扫描到 又因为,各个微服务模块都引用了common 模块,所以说,各个模块的MVC 都可以扫描到,但是,在 Spring Cloud Gateway 里,不存在传统意义上的 Spring MVC 框架,所以可能会扫描不到配置,所以,我们在 MvcConfig 中在配置的时候添加条件

@ConditionalOnClass(DispatcherServlet.class)// 仅在存在 `DispatcherServlet` 类时才加载配置

只要是微服务,就有 SpringMVC 就会有DispatcherServlet
因为网关没有SpringMVC,就没有 DispatcherServlet 所以这个配置在网关服务中就不会生效

四:OpenFeign 传递用户信息

**需求:**在微服务项目中,很多业务需要多个微服务共同合作完成,而这个过程中也需要传递 登陆用户信息

提示: OpenFeign 中提供了一个拦截器接口,所有由OpenFeign 发起的请求都会先调用拦截器处理请求。

问题: 使用 OpenFeign 调用远程接口时默认不会经过你为普通请求配置的拦截器(如 Spring MVC 中的 HandlerInterceptor

解决方法: 依靠OpenFeign 的拦截接口, RequestInterceptor 接口,其中提供了一些方法可以让我们修改请求头

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OpenFeign 在微服务之间互相远程调用接口,而所有的Feign的远程接口都是定义在api模块,所以我们应该将拦截接口也定义在 api模块当中。

这里主要是解决 微服务之间通过 OpenFeign 调用的时候,没有经过拦截器的情况,有下面这个配置之后,在远程接口执行之前就会将用户信息添加到请求头之中

public class FeignConfig {@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取当前登录用户信息Long userId = UserContext.getUserId();if (userId != null) {// 将用户信息添加到请求头中template.header("user-info", String.valueOf(userId));}}};}}

重要!!!!

想要上面的配置类生效,我们需要把这个配置类,加在 feign 微服务的启动类之上

@EnableFeignClients(defaultConfiguration =FeignConfig.class )

而所有的Feign的远程接口都是定义在api模块,所以我们应该将拦截接口也定义在 api模块当中。**

这里主要是解决 微服务之间通过 OpenFeign 调用的时候,没有经过拦截器的情况,有下面这个配置之后,在远程接口执行之前就会将用户信息添加到请求头之中

public class FeignConfig {@Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate template) {// 获取当前登录用户信息Long userId = UserContext.getUserId();if (userId != null) {// 将用户信息添加到请求头中template.header("user-info", String.valueOf(userId));}}};}}

重要!!!!

想要上面的配置类生效,我们需要把这个配置类,加在 feign 微服务的启动类之上

@EnableFeignClients(defaultConfiguration =FeignConfig.class )
http://www.xdnf.cn/news/1247041.html

相关文章:

  • Solidity智能合约基础
  • python学智能算法(三十三)|SVM-构建软边界拉格朗日方程
  • 《零基础入门AI:传统机器学习进阶(从拟合概念到K-Means算法)》
  • 机器学习——集成学习(Ensemble Learning)详解:原理、方法与实战应用
  • 机器学习 集成学习之随机森林
  • python开发环境安装多系统完整版
  • 工作相关: 预刷真值与人工标注的真值之间的关系 以及 真值与原始数据的关系,
  • Vue3 defineAsyncComponent() 函数
  • 【Unity笔记】Unity TextMeshPro 字体显示为方块的终极解决方案(含中文、特殊字符支持)
  • android直连SQLserver的可行性分析
  • TCP协议与UDP协议
  • 智慧能源场景设备缺陷漏检率↓76%:陌讯多模态融合检测方案实战解析
  • Redis备份方案:持久化与外部工具全解析
  • JVM(Java Virtual Machine,Java 虚拟机)超详细总结
  • Spring之【详解FactoryBean】
  • C++ 网络编程入门:TCP 协议下的简易计算器项目
  • 数据结构04 栈和队列
  • 工业级 CAN 与以太网桥梁:串口服务器CAN通讯转换器深度解析(下)
  • Dot1x认证原理详解
  • ChatGPT以及ChatGPT强化学习步骤
  • 数据结构(三)双向链表
  • VSCode中使用Qt
  • 7、Redis队列Stream和单线程及多线程模型
  • Pandas query() 方法详解
  • SpringBoot3.x入门到精通系列:4.2 整合 Kafka 详解
  • 基于deepSeek的流式数据自动化规则清洗案例【数据治理领域AI带来的改变】
  • 2025-08-05Gitee + PicGo + Typora搭建免费图床
  • FPGA设计思想与验证方法学系列学习笔记003
  • springboot + maven 使用资源占位符实现动态加载配置文件
  • 【springcloud的配置文件不生效】