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.pswp.cn/web/66106.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66106.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66106.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度解析 React 中 setState 的原理:同步與異步的交織

在 React 框架的核心機制里&#xff0c;setState是實現動態交互與數據驅動視圖更新的關鍵樞紐。深入理解setState的工作原理&#xff0c;尤其是其同步與異步的特性&#xff0c;對于編寫高效、穩定且可預測的 React 應用至關重要。 一、setState 的基礎認知 在 React 組件中&a…

向量數據庫如何助力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;遞歸…

1.2 ThreeJS能力演示——模型導入導出編輯

1、模型導入導出編輯能力 1&#xff09;支持導入基本類型模型 最常用&#xff0c;最適合作為web演示模型的是glb格式的&#xff0c;當前演示glb模型導入 // 1) 支持導入基本類型模型const loader new GLTFLoader();loader.load(./three.js-master/examples/models/gltf/Hors…

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

此次梳理Rocketqa&#xff0c;個人認為該篇文件講述的是段落搜索的改進點&#xff0c;關于其框架&#xff1a;粗檢索 重排序----&#xff08;dual-encoder architecture&#xff09;&#xff0c;講訴不多&#xff0c;那是另外的文章&#xff1b; 之前根據文檔智能功能&#x…

ESP8266 AP模式 網頁配網 arduino ide

ESP8266的AP配網,可以自行配置網絡,一個簡單的demo,文檔最后有所有的代碼,已經測試通過. 查看SPIFFS文件管理系統中的文件 賬號密碼是否存在,如不存在進入AP配網,如存在進入wifi連接模式 // 檢查Wi-Fi憑據if (isWiFiConfigured()) {Serial.println("找到Wi-Fi憑據&#…

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的課程內容. 工…

SK海力士(SK Hynix)是全球領先的半導體制造商之一,其在無錫的工廠主要生產DRAM和NAND閃存等存儲器產品。

SK海力士&#xff08;SK Hynix&#xff09;是全球領先的半導體制造商之一&#xff0c;其在無錫的工廠主要生產DRAM和NAND閃存等存儲器產品。以下是SK海力士的一些主要產品型號和類別&#xff1a; DRAM 產品 DDR4 DRAM 特點: 高速、低功耗&#xff0c;廣泛應用于PC、服務器和移…

WordPress如何配置AJAX以支持點擊加載更多?

WordPress 配置 AJAX 支持點擊加載更多內容通常涉及到前端 JavaScript 和服務器端的配合。以下是基本步驟&#xff1a; 安裝插件&#xff1a;你可以選擇一個現成的插件如 “Advanced Custom Fields” 或者 “WP Infinite Scroll”&#xff0c;它們已經內置了 AJAX 功能&#xf…

【IDEA 2024】學習筆記--文件選項卡

在我們項目的開發過程中&#xff0c;由于項目涉及的類過多&#xff0c;以至于我們會打開很多的窗口。使用IDEA默認的配置&#xff0c;個人覺得十分不便。 目錄 一、設置多個文件選項卡按照文件字母順序排列 二、設置多個文件選項卡分行顯示 一、設置多個文件選項卡按照文件字…

【C】數組和指針的關系

在 C 語言 和 C 中&#xff0c;數組和指針 有非常密切的關系。它們在某些情況下表現類似&#xff0c;但也有重要的區別。理解數組和指針的關系對于掌握低級內存操作和優化程序性能至關重要。 1. 數組和指針的基本關系 數組是一個 連續存儲的元素集合&#xff0c;在內存中占據一…

Maven 配置本地倉庫

步驟 1&#xff1a;修改 Maven 的 settings.xml 文件 找到你的 Maven 配置文件 settings.xml。 Windows: C:\Users\<你的用戶名>\.m2\settings.xmlLinux/macOS: ~/.m2/settings.xml 打開 settings.xml 文件&#xff0c;找到 <localRepository> 標簽。如果沒有該標…

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…

《盤古大模型——鴻蒙NEXT的智慧引擎》

在當今科技飛速發展的時代&#xff0c;華為HarmonyOS NEXT的發布無疑是操作系統領域的一顆重磅炸彈&#xff0c;其將人工智能與操作系統深度融合&#xff0c;開啟了智能新時代。而盤古大模型在其中發揮著至關重要的核心作用。 賦予小藝智能助手超強能力 在鴻蒙NEXT中&#xf…