springCloudGateway+nacos自定义负载均衡-通过IP隔离开发环境

先说一下想法,小公司开发项目,参考若依框架使用的spring-cloud-starter-gatewayspring-cloud-starter-alibaba-nacos, 用到了nacos的配置中心和注册中心,有多个模块(每个模块都是一个服务)。
想本地开发,且和其他同事不互相影响的情况下,有同事建议 本地部署 完整的环境(nacos、需要的模块、前端等),除了 数据库和redis共用。这种方案也能用,就是占用自己电脑资源太多了(ps 自己电脑摸鱼的硬件资源就少了)。
我通过看nacos相关的教程,发现可以通过 配置中心 和 注册中心 的namespace命名不同进行隔离,这样可以不用部署nacos服务。
继续分析发现,网关模块 默认的 负载均衡 ReactorServiceInstanceLoadBalancer 的实现 RoundRobinLoadBalancer 是通过轮询 访问服务的,可以 改造 负载均衡 来实现 本地开发环境最小化(只允许开发的服务模块),其他服务用 测试环境提供。
当前改造只适用请求通过网关,如果模块服务之间 直连调用,就需要在每个模块增加类似处理。暂不考虑

(ps 本人小白一枚springCloudGateway使用的少)本文章限定在这个项目里面,如果自己项目环境不一样,不保证能拿来直接用。

实现截图-不想看原理的先食用结果

在这里插入图片描述
在这里插入图片描述

参考

本来想通过搜索和AI找到解决方案,结果代码抄下来每一个能实现的。要么没有引用的类,要么无法实现自己的想法。所以就不贴了。
每个项目的依赖和版本情况都不一样,最好的方法是 找到框架的核心实现类,在跟踪调试和找到引用与初始化的地方。想办法自定义实现注入-这种方式最好,毕竟都是框架给留的口子。实在不行,就只能通过魔改或劫持的方式实现了。

原理图-设想

改造前请求流程

在这里插入图片描述
这种情况,适用于 非开发环境,简单高效

改造后请求流程

在这里插入图片描述
改造后的,适用于 本地开发,通过网关只请求自己的服务,或者自己没有只请求测试环境(简单的测试环境所有服务一台机器上)。尽最大可能不影响 其他同事开发。
图中没有画 锁定请求服务IP的情况(这块代码没测试)。

项目框架主要依赖

参考时候请注意 依赖包版本是否差距过大

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><version>3.1.4</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>2021.0.4.0</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2021.0.4.0</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId><version>2021.0.4.0</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId><version>2021.0.4.0</version>
</dependency>
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId><version>1.8.5</version>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-loadbalancer</artifactId><version>3.1.5</version>
</dependency>

自定义实现

自定义负载均衡类

根据 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer 轮询策略 改造,这块代码倒还好实现。TestLocalLockIpLoadBalancer直接注入spring中,ObjectProvider<ServiceInstanceListSupplier> 获取不到服务列表,看了spring实现,要完全替换需要注入好几个类,有点麻烦。最后改成运行时替换。

自定义路由负载均衡,负载均衡策略:

  • 先根据请求IP查询IP相同的服务
  • 再找和网关IP相同的服务
  • 最后轮询其他IP服务
package *.*.*.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.*;
import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.http.HttpHeaders;
import reactor.core.publisher.Mono;import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;/*** 自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务** @see  org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer* @author z* @date 2025/1/10*/
public class TestLocalLockIpLoadBalancer implements ReactorServiceInstanceLoadBalancer {private static final Logger log = LoggerFactory.getLogger(TestLocalLockIpLoadBalancer.class);final AtomicInteger position;final String serviceId;ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;private String localIp;public TestLocalLockIpLoadBalancer setLocalIp(String localIp) { this.localIp = localIp; return this; }public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId) {this(provide, serviceId, new Random().nextInt(1000));}public TestLocalLockIpLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provide, String serviceId, int seedPosition) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = provide;this.position = new AtomicInteger(seedPosition);System.out.println("自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务");}@Overridepublic Mono<Response<ServiceInstance>> choose(Request request) {System.out.println("自定义路由负载均衡---->");ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);String ip = getIpAddr(request);return supplier.get(request).next().map(services -> processInstanceResponse(supplier, services, ip));}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier, List<ServiceInstance> services, String reqIp) {Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(services, reqIp);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, String reqIp) {System.out.println("自定义路由负载均衡--- 服务数量:" + instances.size());if (instances.isEmpty()) {log.warn("没有可供服务的服务器: {}, 请求: {}" , serviceId, reqIp);return new EmptyResponse();}if (instances.size() == 1) {System.out.println("自定义路由负载均衡--- 服务只有一个: " + instances.get(0).getHost());return new DefaultResponse(instances.get(0));}if( null != reqIp && !reqIp.isEmpty()){for (ServiceInstance instance : instances) {if(instance.getHost().equals(reqIp)){System.out.println("自定义路由负载均衡--- 策略1: 自己处理 " + reqIp);return new DefaultResponse(instance);}}}for (ServiceInstance instance : instances) {if(instance.getHost().equals(localIp)){System.out.println("自定义路由负载均衡--- 策略2: 测试处理 " + localIp);return new DefaultResponse(instance);}}int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = instances.get(pos % instances.size());System.out.println("自定义路由负载均衡--- 策略3: 默认轮询:" + instance.getHost());return new DefaultResponse(instance);}/** 获取客户端IP */private String getIpAddr(Request request) {DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();RequestData clientRequest = (RequestData) requestContext.getClientRequest();HttpHeaders headers = clientRequest.getHeaders();if(headers.containsKey("sourceIp")){return headers.getFirst("sourceIp");}return null;}
}

自定义配置注入-偷天换日

最麻烦的事,是TestLocalLockIpLoadBalancer注入spring后,ObjectProvider<ServiceInstanceListSupplier> 获取不到服务列表。当前操作是运行时替换。在不考虑性能的情况下,这个只要 重写LoadBalancerClientFactory工厂类,改造获取 负载均衡 实例的方法就行。

注入的全局拦截器LockIpGlobalFilter,主要是获取 当前请求的 来源IP 放到请求头里面,因为 负载均衡的Request好像是获取不到IP信息。
自定义工厂类LockIpLoadBalancerClientFactory实现,重写获取负载均衡方法(替换为自定义类)

package *.*.*.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClientSpecification;
import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.Arrays;
import java.util.Collections;
import java.util.List;/*** 路由过滤 优先 请求ip对应的服务实例** @see org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter* @author z.* @date 2025/1/10*/
@Configuration
@ConditionalOnProperty(value = "spring.cloud.lockIp", havingValue = "true")
public class TestLocalLockIpConfig {private static final Logger log = LoggerFactory.getLogger(TestLocalLockIpConfig.class);@Autowiredprivate InetUtils inetUtils;@Beanpublic LockIpGlobalFilter lockIpGlobalFilter(){System.out.println(">>>>>>>>>自定义全局过滤器-注册");return new LockIpGlobalFilter();}@Beanpublic LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties,ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {System.out.println(">>>>>>>>>自定义路由工厂-注册");LockIpLoadBalancerClientFactory clientFactory = new LockIpLoadBalancerClientFactory(properties);clientFactory.setConfigurations(configurations.getIfAvailable(Collections::emptyList));return clientFactory.setLocalIp(inetUtils.findFirstNonLoopbackHostInfo().getIpAddress());}public static class LockIpGlobalFilter implements GlobalFilter, Ordered{@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();System.out.println("------->自定义全局过滤器: " + request.getURI().getPath());String ip = remoteIp(request);if(ip != null && !UNKNOWN.equals(ip)){ServerHttpRequest.Builder mutate = request.mutate();mutate.header("sourceIp", ip);}System.out.println("自定义全局过滤器-获取IP: " + ip);return chain.filter(exchange);}@Overridepublic int getOrder() { return 0; }private static final String UNKNOWN = "unknown";public String remoteIp(ServerHttpRequest request){HttpHeaders headers = request.getHeaders();String def = null != request.getRemoteAddress() ? request.getRemoteAddress().getHostString() : UNKNOWN;String[] keys = {"x-forwarded-for","Proxy-Client-IP","X-Forwarded-For","WL-Proxy-Client-IP","X-Real-IP"};String ip = Arrays.stream(keys).map(headers::getFirst).filter(v-> v != null && !v.isEmpty() && !UNKNOWN.equalsIgnoreCase(v)).findFirst().orElse(def);//本机请求 负载均衡 第二个策略 优先使用本机服务if( "0:0:0:0:0:0:0:1".equals(ip) || "127.0.0.1".equals(ip) ){ return null; }//从多级反向代理中获得第一个非unknown IP地址if (ip.indexOf(",") <= 0) {return Arrays.stream(ip.trim().split(",")).filter(subIp -> !UNKNOWN.equalsIgnoreCase(subIp)).findFirst().orElse(ip);}return ip;}}public static class LockIpLoadBalancerClientFactory extends LoadBalancerClientFactory{/** 本网关服务 的内网IP */private String localIp;public LockIpLoadBalancerClientFactory setLocalIp(String localIp) { this.localIp = localIp; return this; }public LockIpLoadBalancerClientFactory(LoadBalancerClientsProperties properties){ super(properties); }@Overridepublic <T> T getInstance(String name, Class<T> type) {AnnotationConfigApplicationContext context = getContext(name);try {T t = context.getBean(type);if(t instanceof TestLocalLockIpLoadBalancer){ return t; }if(t instanceof RoundRobinLoadBalancer){System.out.println("自定义路由工厂-路由负载均衡策略: 默认轮询 "+t);String[] beanName = context.getBeanNamesForType(type);System.out.println("自定义路由工厂-路由负载均衡策略: 默认 删除第一个: "+ Arrays.toString(beanName));context.removeBeanDefinition(beanName[0]);System.out.println("自定义路由工厂-路由负载均衡策略: 自定义 创建");TestLocalLockIpLoadBalancer balancer = new TestLocalLockIpLoadBalancer(this.getLazyProvider(name, ServiceInstanceListSupplier.class), name).setLocalIp(localIp);System.out.println("自定义路由工厂-路由负载均衡策略: 自定义 注入");context.getBeanFactory().registerSingleton(beanName[0],balancer);t = context.getBean(type);System.out.println("自定义路由工厂-路由负载均衡策略: 注入后获取 " + t);}return t;} catch (Exception e) {log.error("自定义路由工厂", e);return null;}}}
}

配置文件-开关

通过配置,控制是否开启 自定义负载均衡

spring:cloud:lockIp: true

Spring网关源码分析

通过搜索和咨询AI,找到springCloudGateway处理服务负载均衡的拦截器org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter,核心代码是

	private final LoadBalancerClientFactory clientFactory;private final GatewayLoadBalancerProperties properties;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);//判断是不是注册中心的服务地址if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {return chain.filter(exchange);}//其他代码略...URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);//微服务模块IDString serviceId = requestUri.getHost();//存活的服务列表Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),RequestDataContext.class, ResponseData.class, ServiceInstance.class);//创建负载均衡函数choose入参的RequestDefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId)));//通过 负载均衡客户端工厂 获取 负载均衡配置LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {//通过负载均衡获取到服务后的处理代码略...}).then(chain.filter(exchange)).doOnError(/*异常处理略*/).doOnSuccess(/*成功处理略*/);}private Mono<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {//本次核心代码,通过 负载均衡客户端工厂 获取 负载均衡ReactorLoadBalancer<ServiceInstance> loadBalancer = this.clientFactory.getInstance(serviceId,ReactorServiceInstanceLoadBalancer.class);if (loadBalancer == null) {throw new NotFoundException("No loadbalancer available for " + serviceId);}supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));return loadBalancer.choose(lbRequest);}

拦截器获取 负载均衡ReactorLoadBalancer,是通过LoadBalancerClientFactory类的函数getInstance,这个类是 ReactiveLoadBalancerClientFilter构造入参。找到拦截器初始化的代码org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,是spring的自动注入配置类

	@Bean@ConditionalOnBean(LoadBalancerClientFactory.class)@ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)@ConditionalOnEnabledGlobalFilterpublic ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory factory,GatewayLoadBalancerProperties properties) {return new ReactiveLoadBalancerClientFilter(factory, properties);}

那就找到注入LoadBalancerClientFactory类的代码。也没看到初始化负载均衡ReactorLoadBalancer<ServiceInstance>的代码

	@ConditionalOnMissingBean@Beanpublic LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {LoadBalancerClientFactory factory = new LoadBalancerClientFactory(properties);factory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));return factory;}

下面就看到,挠头的配置类初始化方式

进入LoadBalancerClientFactory的构造函数,发现LoadBalancerClientConfiguration负载均衡客户端配置类。

	public LoadBalancerClientFactory(LoadBalancerClientsProperties properties) {super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);this.properties = properties;}

LoadBalancerClientConfiguration负载均衡客户端配置类,请留意这里,这个配置类不是项目启动自动注入。也就是说,参考reactorServiceInstanceLoadBalancer函数自定义注入spring是不对的。就因为这个情况,卡了好几天,因为它是运行时初始化的。

//当设置为 false 时,Spring 不会为配置类生成代理对象。也就是不会自动注入配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment ev,LoadBalancerClientFactory factory) {//自定义ReactorLoadBalancer注入spring,name获取为null。实际这个应该是serviceId//获取配置参数 loadbalancer.client.nameString name = ev.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RoundRobinLoadBalancer( factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
//其他略
}

换个方向,分析函数getInstance,实现是在org.springframework.cloud.context.named.NamedContextFactory类里。看到这里,就能发现有一个map字段contexts,存着每个 微服务模块 的上下文AnnotationConfigApplicationContext。只有请求到对应 微服务模块 才初始化对应的 负载均衡类。
代码做了简化处理

    public <T> T getInstance(String serviceId, Class<T> type) {return (T)this.getContext(serviceId).getBean(type);}protected AnnotationConfigApplicationContext getContext(String serviceId) {if (!this.contexts.containsKey(serviceId)) {this.contexts.put(serviceId, this.createContext(serviceId));}return (AnnotationConfigApplicationContext)this.contexts.get(serviceId);}protected AnnotationConfigApplicationContext createContext(String serviceId) {AnnotationConfigApplicationContext context /*上下文获取 代码略...*/;//上下文配置注册 代码略...//-----核心代码,注入配置类 LoadBalancerClientConfigurationcontext.register(new Class[]{PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType});//-----核心代码,注入配置类 需要的 Environment 配置参数 loadbalancer.client.namecontext.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName, Collections.singletonMap(this.propertyName, serviceId)));//其他 代码略...//上下文 刷新并启动,类似初始化-只能调用一次context.refresh();return context;}

通过上面的代码分析,spring通过运行时注入负载均衡,可能是考虑 网关无法感知到有多少微服务模块。
如果自定义负载均衡,正常的启动注入 每个微服务模块的负载均衡器,代码开发是比较麻烦的,需要明确列出所有的微服务,还需要考虑让负载均衡获取到存活的 服务列表。
所以最后 通过 覆盖函数getInstance,替换为自定义 负载均衡,这种代码量最简单粗暴(在不考虑性能的情况下)

测试日志

>>>>>>>>>自定义全局过滤器-注册
>>>>>>>>>自定义路由工厂-注册------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: null
自定义路由工厂-路由负载均衡策略: 默认轮询 org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer@1fc654d2
自定义路由工厂-路由负载均衡策略: 默认 删除第一个: [reactorServiceInstanceLoadBalancer]
自定义路由工厂-路由负载均衡策略: 自定义 创建
自定义路由负载均衡->初始化,负载均衡策略: 先找请求IP查询IP相同的服务、再找和网关IP相同的服务、最后轮询其他IP服务
自定义路由工厂-路由负载均衡策略: 自定义 注入
自定义路由工厂-路由负载均衡策略: 注入后获取 *.*.*.config.TestLocalLockIpLoadBalancer@72e4d1fe
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:1
自定义路由负载均衡--- 服务只有一个: 192.168.1.10------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: null
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:2
自定义路由负载均衡--- 策略2: 测试处理 192.168.1.17------->自定义全局过滤器: /a/login
自定义全局过滤器-获取IP: 192.168.1.10
自定义路由负载均衡---->
自定义路由负载均衡--- 服务数量:2
自定义路由负载均衡--- 策略1: 自己处理 192.168.1.10

测试截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

END

用的公司项目测试,抱歉内容做了脱敏处理。
仅供参考-请结合实际情况使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/66106.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

向量数据库如何助力Text2SQL处理高基数类别数据

01. 导语 Agent工作流和 LLMs &#xff08;大语言模型&#xff09;的出现&#xff0c;让我们能够以自然语言交互的模式执行复杂的SQL查询&#xff0c;并彻底改变Text2SQL系统的运行方式。其典型代表是如何处理High-Cardinality Categorical Data &#xff08;高基数类别数据&am…

qBittorent访问webui时提示unauthorized解决方法

现象描述 QNAP使用Container Station运行容器&#xff0c;使用Docker封装qBittorrent时&#xff0c;访问IP:PORT的方式后无法访问到webui&#xff0c;而是提示unauthorized&#xff0c;如图&#xff1a; 原因分析 此时通常是由于设备IP与qBittorrent的ip地址不在同一个网段导致…

工程水印相机结合图纸,真实现场时间地点,如何使用水印相机,超简单方法只教一次!

在工程管理领域&#xff0c;精准记录现场信息至关重要。水印相机拍照功能&#xff0c;为工程人员提供了强大的现场信息记录工具&#xff0c;助力工程管理和统计工程量&#xff0c;更可以将图片分享到电脑、分享给同事&#xff0c;协同工作。 一、打开图纸 打开手机版CAD快速看图…

GO语言实现KMP算法

前言 本文结合朱战立教授编著的《数据结构—使用c语言&#xff08;第五版&#xff09;》&#xff08;以下简称为《数据结构&#xff08;第五版&#xff09;朱站立》&#xff09;中4.4.2章节内容编写&#xff0c;KMP的相关概念可参考此书4.4.2章节内容。原文中代码是C语言&…

LeetCode 热题 100_从前序与中序遍历序列构造二叉树(47_105_中等_C++)(二叉树;递归)

LeetCode 热题 100_从前序与中序遍历序列构造二叉树&#xff08;47_105&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;递归&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;递归…

文档智能:OCR+Rocketqa+layoutxlm <Rocketqa>

此次梳理Rocketqa&#xff0c;个人认为该篇文件讲述的是段落搜索的改进点&#xff0c;关于其框架&#xff1a;粗检索 重排序----&#xff08;dual-encoder architecture&#xff09;&#xff0c;讲诉不多&#xff0c;那是另外的文章&#xff1b; 之前根据文档智能功能&#x…

ubuntu官方软件包网站 字体设置

在https://ubuntu.pkgs.org/22.04/ubuntu-universe-amd64/xl2tpd_1.3.16-1_amd64.deb.html搜索找到需要的软件后&#xff0c;点击&#xff0c;下滑&#xff0c; 即可在Links和Download找到相关链接&#xff0c;下载即可&#xff0c; 但是找不到ros的安装包&#xff0c; 字体设…

使用 WPF 和 C# 绘制覆盖网格的 3D 表面

此示例展示了如何使用 C# 代码和 XAML 绘制覆盖有网格的 3D 表面。示例使用 WPF 和 C# 将纹理应用于三角形展示了如何将纹理应用于三角形。此示例只是使用该技术将包含大网格的位图应用于表面。 在类级别&#xff0c;程序使用以下代码来定义将点的 X 和 Z 坐标映射到 0.0 - 1.…

[Do374]Ansible一键搭建sftp实现用户批量增删

[Do374]Ansible一键搭建sftp实现用户批量增删 1. 前言2. 思路3. sftp搭建及用户批量新增3.1 配置文件内容3.2 执行测试3.3 登录测试3.4 确认sftp服务器配置文件 4. 测试删除用户 1. 前言 最近准备搞一下RHCA LV V,外加2.9之后的ansible有较大变化于是练习下Do374的课程内容. 工…

【IDEA 2024】学习笔记--文件选项卡

在我们项目的开发过程中&#xff0c;由于项目涉及的类过多&#xff0c;以至于我们会打开很多的窗口。使用IDEA默认的配置&#xff0c;个人觉得十分不便。 目录 一、设置多个文件选项卡按照文件字母顺序排列 二、设置多个文件选项卡分行显示 一、设置多个文件选项卡按照文件字…

Docker save load 镜像 tag 为 <none>

一、场景分析 我从 docker hub 上拉了这么一个镜像。 docker pull tomcat:8.5-jre8-alpine 我用 docker save 命令想把它导出成 tar 文件以便拷贝到内网机器上使用。 docker save -o tomcat-8.5-jre8-alpine.tar.gz 镜像ID 当我把这个镜像传到别的机器&#xff0c;并用 dock…

O2O同城系统架构与功能分析

2015工作至今&#xff0c;10年资深全栈工程师&#xff0c;CTO&#xff0c;擅长带团队、攻克各种技术难题、研发各类软件产品&#xff0c;我的代码态度&#xff1a;代码虐我千百遍&#xff0c;我待代码如初恋&#xff0c;我的工作态度&#xff1a;极致&#xff0c;责任&#xff…

走出实验室的人形机器人,将复刻ChatGPT之路?

1月7日&#xff0c;在2025年CES电子展现场&#xff0c;黄仁勋不仅展示了他全新的皮衣和采用Blackwell架构的RTX 50系列显卡&#xff0c;更进一步展现了他对于机器人技术领域&#xff0c;特别是人形机器人和通用机器人技术的笃信。黄仁勋认为机器人即将迎来ChatGPT般的突破&…

EF Core执行原生SQL语句

目录 EFCore执行非查询原生SQL语句 为什么要写原生SQL语句 执行非查询SQL语句 有SQL注入漏洞 ExecuteSqlInterpolatedAsync 其他方法 执行实体相关查询原生SQL语句 FromSqlInterpolated 局限性 执行任意原生SQL查询语句 什么时候用ADO.NET 执行任意SQL Dapper 总…

Java中网络编程的学习

目录 网络编程概述 网络模型 网络通信三要素: IP 端口号 通信协议 IP地址&#xff08;Internet Protocol Address&#xff09; 端口号 网络通信协议 TCP 三次握手 四次挥手 UDP TCP编程 客户端Socket的工作过程包含以下四个基本的步骤&#xff1a; 服务器程序…

HarmonyOS NEXT开发进阶(七):页面跳转

文章目录 一、前言二、页面跳转三、页面返回四、页面返回前增加确认对话框4.1 系统的默认询问框4.2 自定义询问框 五、拓展阅读 一、前言 APP开发过程中&#xff0c;多页面跳转场景十分常见&#xff0c;例如&#xff0c;登录 -> 首页 -> 个人中心。在鸿蒙开发中&#xf…

【Python】第一弹---解锁编程新世界:深入理解计算机基础与Python入门指南

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】【Python】 目录 1、计算机基础概念 1.1、什么是计算机 1.2、什么是编程 1.3、编程语言有哪些 2、Python 背景知识 2.…

学习threejs,使用FlyControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.FlyControls 相机控制…

隐私计算,构建安全的未来数据空间

大数据产业创新服务媒体 ——聚焦数据 改变商业 在医疗领域&#xff0c;不同医院之间需要共享患者数据&#xff0c;以提供更全面准确的诊断和治疗方案。 传统的数据处理方式通常是数据经过转换隐藏重要数据后再进行分析&#xff0c;虽然可以保护数据隐私&#xff0c;但在数据源…