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

【从0到1设计一个网关】整合Nacos-服务注册与服务订阅的实现

文章目录

  • Nacos
  • 定义服务注册与订阅方法
  • 服务信息加载与配置
  • 实现将网关注册到注册中心
  • 实现服务的订阅

Nacos

Nacos提供了许多强大的功能:
比如服务发现、健康检测。
Nacos支持基于DNS和基于RPC的服务发现。
同时Nacos提供对服务的实时的健康检查,阻止向不健康的主机活服务发送请求。
并且Nacos也提供了一个可视化的控制台方便我们对实例等信息进行管理。
同时Nacos提供了动态配置服务,可以让我们以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置

在这里插入图片描述

Nacos是我在开发自己项目过程中用的最多的一个注册中心和配置中心,并且Nacos的社区相对其他的来说更加活跃,代码也更加容易阅读。
如下是Nacos官网:Nacos官网
我就不再这篇文章里面过多的讲解Nacos的一些特性了。
在这一章节中,我将使用Nacos暴露出来的接口,来完成项目的服务注册功能以及服务发现功能。
完成这一章的学习也会让你更加深入的了解到Nacos的底层运行原理,注册中心原理。
下面是一些我曾经学习Nacos过程中编写的一些文章,有兴趣可以看看。
使用Nacos实现动态线程池技术以及Nacos配置文件更新监听事件
【源码分析】Nacos如何使用AP协议完成服务端之间的数据同步?
【源码分析】Nacos服务端如何更新以及保存注册表信息?
Nacos自动注册原理实现以及服务注册更新并如何保存到注册表

为什么选择Nacos之前的文章简单讲解过,这里我将详细的列举出几个原因:

  • Nacos 提供了让我从微服务平台建设的视角管理数据中心的所有服务及元数据,具体原因可以看看上面我对Nacos源码的分析,Nacos将服务细粒度的划分为了各自实例,并且我们可以管理这些实例的信息
  • Nacos支持基于DNS和基于RPC的服务发现,这也就意味着提供给我们较强的服务发现的选择能力
  • Nacos提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求,也就是安全
  • 动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置,我之前也已经利用过这一点来实现对线程池的动态配置具体可以查看这篇文章

定义服务注册与订阅方法

在这一步,我们将需要定义一些网关项目用于连接到Nacos这个注册中心的接口,来实现会将我们的项目链接到注册中心。
要将一个服务注册到注册中心,大概需要初始化、注册、取消注册、服务订阅等方法,也就是我们需要编写一个如下的接口来提供这样子的一个接口并在后面的具体注册中心实例中去实现这个接口方法。

public interface RegisterCenter {/***   初始化* @param registerAddress  注册中心地址* @param env  要注册到的环境*/void init(String registerAddress, String env);/*** 注册* @param serviceDefinition 服务定义信息* @param serviceInstance 服务实例信息*/void register(ServiceDefinition serviceDefinition, ServiceInstance serviceInstance);/*** 注销* @param serviceDefinition* @param serviceInstance*/void deregister(ServiceDefinition serviceDefinition, ServiceInstance serviceInstance);/*** 订阅所有服务变更* @param registerCenterListener*/void subscribeAllServices(RegisterCenterListener registerCenterListener);
}

实现完毕接口之后,我们还需要提供一个方法,它的作用是用于监听注册中心的配置的变更。
这也是Nacos作为注册中心和配置中心特别重要的一个功能,接口定义如下:

public interface RegisterCenterListener {void onChange(ServiceDefinition serviceDefinition,Set<ServiceInstance> serviceInstanceSet);
}

服务信息加载与配置

基于上面的服务注册与订阅接口,我们就可以大致编写出来如何将我们的网关注册到Nacos中了。当然,我们还没有具体实现如何注册到Nacos注册中心的方法,但是我们可以先编写出来其大致的一个调用方法。


@Slf4j
public class Bootstrap
{public static void main( String[] args ){//加载网关核心静态配置Config config = ConfigLoader.getInstance().load(args);System.out.println(config.getPort());//插件初始化//配置中心管理器初始化,连接配置中心,监听配置的新增、修改、删除//启动容器Container container = new Container(config);container.start();//连接注册中心,将注册中心的实例加载到本地final RegisterCenter registerCenter = registerAndSubscribe(config);//服务优雅关机//进程收到kill信号的时候进行一个注销操作Runtime.getRuntime().addShutdownHook(new Thread(){/*** 下线操作*/@Overridepublic void run(){registerCenter.deregister(buildGatewayServiceDefinition(config),buildGatewayServiceInstance(config));}});}/*** 当前方法用于提供注册和订阅服务信息变更通知* @param config* @return*/private static RegisterCenter registerAndSubscribe(Config config) {//加载服务提供者  具体这里的作用可以 查看我的博客ServiceLoader<RegisterCenter> serviceLoader = ServiceLoader.load(RegisterCenter.class);final RegisterCenter registerCenter = serviceLoader.findFirst().orElseThrow(() -> {log.error("not found RegisterCenter impl");return new RuntimeException("not found RegisterCenter impl");});//初始化注册中心信息registerCenter.init(config.getRegistryAddress(), config.getEnv());//构造网关服务定义和服务实例ServiceDefinition serviceDefinition = buildGatewayServiceDefinition(config);ServiceInstance serviceInstance = buildGatewayServiceInstance(config);//注册registerCenter.register(serviceDefinition, serviceInstance);//订阅registerCenter.subscribeAllServices(new RegisterCenterListener() {@Overridepublic void onChange(ServiceDefinition serviceDefinition, Set<ServiceInstance> serviceInstanceSet) {log.info("refresh service and instance: {} {}", serviceDefinition.getId(),JSON.toJSON(serviceInstanceSet));DynamicConfigManager manager = DynamicConfigManager.getInstance();manager.addServiceInstance(serviceDefinition.getId(), serviceInstanceSet);}});return registerCenter;}/*** 构建网关服务实例* @param config* @return*/private static ServiceInstance buildGatewayServiceInstance(Config config) {String localIp = NetUtils.getLocalIp();int port = config.getPort();ServiceInstance serviceInstance = new ServiceInstance();serviceInstance.setServiceInstanceId(localIp + COLON_SEPARATOR + port);serviceInstance.setIp(localIp);serviceInstance.setPort(port);serviceInstance.setRegisterTime(TimeUtil.currentTimeMillis());return serviceInstance;}/*** 构建网关服务定义信息* @param config* @return*/private static ServiceDefinition buildGatewayServiceDefinition(Config config) {ServiceDefinition serviceDefinition = new ServiceDefinition();serviceDefinition.setInvokerMap(Map.of());serviceDefinition.setId(config.getApplicationName());serviceDefinition.setServiceId(config.getApplicationName());serviceDefinition.setEnvType(config.getEnv());return serviceDefinition;}}

其中比较重要的就是这一行代码,也就是加载服务提供者

ServiceLoader.load(RegisterCenter.class) 

ServiceLoader是 Java 中用于加载服务提供者的工具,通常用于实现服务提供者框架。它的作用是查找和加载指定接口或抽象类的服务提供者实现类,这些实现类在运行时动态注册到系统中,以便其他组件或应用程序可以使用它们的功能。

具体来说,以下是它的作用和用法:

  • 服务接口定义:首先,您需要定义一个服务接口或抽象类,这是您想要不同实现的抽象描述。在您的例子中,RegisterCenter.class 似乎是一个服务接口。

  • 服务提供者实现:不同的模块或库可以提供服务接口的不同实现,这些实现类可以独立于应用程序开发并且可以在运行时加载。

  • 服务提供者注册:每个服务提供者实现类需要在 META-INF/services 目录下创建一个文件,该文件的名称是服务接口的全限定名,内容是服务提供者实现类的全限定名。这告诉 Java 运行时系统哪些类实现了该服务接口。

  • 加载服务提供者:使用 ServiceLoader.load(RegisterCenter.class),您可以加载所有已经注册的服务提供者实现类。这返回一个 ServiceLoader 对象,您可以迭代这个对象以获取所有已加载的实现类的实例。

这个机制允许应用程序在不修改源代码的情况下动态地切换和使用不同的服务提供者实现,从而提高了应用程序的可扩展性和灵活性。它通常用于框架和库,以允许开发者插入他们自己的实现,例如数据库驱动程序、日志记录器、插件等。

实现将网关注册到注册中心

想要将网关注册到注册中心,我们首先需要引入Nacos的客户端依赖。

       <!--引入Nacos的客户端依赖--><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>2.0.4</version></dependency><!--导入我们直接实现的注册中心接口--><dependency><groupId>blossom.project</groupId><artifactId>BlossomGateway-Register-Center-Api</artifactId><version>1.0</version></dependency></dependencies>

之后,我们就可以使用Nacos客户端中提供的服务注册方法进行服务注册了。
方式如下:


@Slf4j
public class NacosRegisterCenter implements RegisterCenter {/*** 注册中心的地址*/private String registerAddress;/*** 环境选择*/private String env;/*** 主要用于维护服务实例信息*/private NamingService namingService;/*** 主要用于维护服务定义信息*/private NamingMaintainService namingMaintainService;/*** 监听器列表* 这里由于监听器可能变更 会出现线程安全问题*/private List<RegisterCenterListener> registerCenterListenerList = new CopyOnWriteArrayList<>();@Overridepublic void init(String registerAddress, String env) {this.registerAddress = registerAddress;this.env = env;try {this.namingMaintainService = NamingMaintainFactory.createMaintainService(registerAddress);this.namingService = NamingFactory.createNamingService(registerAddress);} catch (NacosException e) {throw new RuntimeException(e);}}@Overridepublic void register(ServiceDefinition serviceDefinition, ServiceInstance serviceInstance) {try {//构造nacos实例信息Instance nacosInstance = new Instance();nacosInstance.setInstanceId(serviceInstance.getServiceInstanceId());nacosInstance.setPort(serviceInstance.getPort());nacosInstance.setIp(serviceInstance.getIp());//实例信息可以放入到metadata中nacosInstance.setMetadata(Map.of(GatewayConst.META_DATA_KEY, JSON.toJSONString(serviceInstance)));//注册namingService.registerInstance(serviceDefinition.getServiceId(), env, nacosInstance);//更新服务定义namingMaintainService.updateService(serviceDefinition.getServiceId(), env, 0,Map.of(GatewayConst.META_DATA_KEY, JSON.toJSONString(serviceDefinition)));log.info("register {} {}", serviceDefinition, serviceInstance);} catch (NacosException e) {throw new RuntimeException(e);}}
}

这里需要对Nacos的源码有了解,你才能明白如何将一个服务实例注册到Nacos的注册中心,Nacos这边要求你提供服务的ip、端口、服务名称等信息。
完成这一步之后,我们大概就已经成功的将服务注册到Nacos了。

实现服务的订阅

这里我们开始实现服务订阅,要想实现服务订阅,首先需要拉取Nacos上面的所有的服务的信息,并且服务信息会不断的更新变化,因此我们还需要使用定时任务的方式不断的更新我们的服务订阅信息。
要先实现对Nacos服务信息的订阅,需要用到Nacos的事件监听器,NamingEvent。
在Nacos注册中心中,NamingEvent 是一个事件对象,用于表示与服务命名空间(Naming)相关的事件。NamingEvent 的作用是用于监听和处理命名空间中的服务实例(Service Instance)的变化,以便应用程序可以根据这些变化来动态地更新服务实例列表,以保持与注册中心的同步。

具体来说,NamingEvent 主要用于以下目的:

  • 监听服务实例的变化:Nacos注册中心可以包含大量的服务实例,而这些实例可能会因服务上线、下线、实例元数据变化等原因而发生变化。NamingEvent 允许应用程序注册监听器,以便在服务实例发生变化时得到通知。

  • 动态更新服务实例列表:通过监听 NamingEvent,应用程序可以实时获得有关服务实例的状态变化,从而可以及时更新自己维护的服务实例列表,以确保使用最新的服务实例信息。

  • 实现负载均衡:应用程序可以根据 NamingEvent 提供的信息来实现负载均衡策略,例如选择合适的服务实例以提供服务请求。负载均衡策略可以根据服务实例的可用性、健康状态和其他元数据来进行调整。

  • 动态路由:一些应用程序可能需要实现动态路由,根据服务实例的变化来动态更新路由规则,以确保请求被正确路由到可用的服务实例。

大致的代码实现如下:

 @Overridepublic void subscribeAllServices(RegisterCenterListener registerCenterListener) {//服务订阅首先需要将我们的监听器加入到我们的服务列表中registerCenterListenerList.add(registerCenterListener);//进行服务订阅doSubscribeAllServices();//可能有新服务加入,所以需要有一个定时任务来检查ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1, new NameThreadFactory("doSubscribeAllServices"));//循环执行服务发现与订阅操作scheduledThreadPool.scheduleWithFixedDelay(() -> doSubscribeAllServices(), 10, 10, TimeUnit.SECONDS);}private void doSubscribeAllServices() {try {//得到当前服务已经订阅的服务//这里其实已经在init的时候初始化过namingservice了,所以这里可以直接拿到当前服务已经订阅的服务//如果不了解的可以debugSet<String> subscribeService =namingService.getSubscribeServices().stream().map(ServiceInfo::getName).collect(Collectors.toSet());int pageNo = 1;int pageSize = 100;//分页从nacos拿到所有的服务列表List<String> serviseList = namingService.getServicesOfServer(pageNo, pageSize, env).getData();//拿到所有的服务名称后进行遍历while (CollectionUtils.isNotEmpty(serviseList)) {log.info("service list size {}", serviseList.size());for (String service : serviseList) {//判断是否已经订阅了当前服务if (subscribeService.contains(service)) {continue;}//nacos事件监听器 订阅当前服务//这里我们需要自己实现一个nacos的事件订阅类 来具体执行订阅执行时的操作EventListener eventListener = new NacosRegisterListener();eventListener.onEvent(new NamingEvent(service, null));namingService.subscribe(service, env, eventListener);log.info("subscribe {} {}", service, env);}//遍历下一页的服务列表serviseList = namingService.getServicesOfServer(++pageNo, pageSize, env).getData();}} catch (NacosException e) {throw new RuntimeException(e);}}/*** 实现对nacos事件的监听器* NamingEvent 是一个事件对象,用于表示与服务命名空间(Naming)相关的事件。* NamingEvent 的作用是用于监听和处理命名空间中的服务实例(Service Instance)的变化,* 以便应用程序可以根据这些变化来动态地更新服务实例列表,以保持与注册中心的同步。*/public class NacosRegisterListener implements EventListener {@Overridepublic void onEvent(Event event) {if (event instanceof NamingEvent) {log.info("the triggered event info is:{}",JSON.toJSON(event));NamingEvent namingEvent = (NamingEvent) event;String serviceName = namingEvent.getServiceName();try {//获取服务定义信息Service service = namingMaintainService.queryService(serviceName, env);ServiceDefinition serviceDefinition =JSON.parseObject(service.getMetadata().get(GatewayConst.META_DATA_KEY),ServiceDefinition.class);//获取服务实例信息List<Instance> allInstances = namingService.getAllInstances(service.getName(), env);Set<ServiceInstance> set = new HashSet<>();for (Instance instance : allInstances) {ServiceInstance serviceInstance =JSON.parseObject(instance.getMetadata().get(GatewayConst.META_DATA_KEY),ServiceInstance.class);set.add(serviceInstance);}registerCenterListenerList.stream().forEach(l -> l.onChange(serviceDefinition, set));} catch (NacosException e) {throw new RuntimeException(e);}}}}

此时,我们就完成了在Nacos注册中心发生信息变更的时候,能在一次拉取到最新的配置信息。也就是我们完成了对注册中心的订阅。

相关文章:

【从0到1设计一个网关】整合Nacos-服务注册与服务订阅的实现

文章目录 Nacos定义服务注册与订阅方法服务信息加载与配置实现将网关注册到注册中心实现服务的订阅 Nacos Nacos提供了许多强大的功能&#xff1a; 比如服务发现、健康检测。 Nacos支持基于DNS和基于RPC的服务发现。 同时Nacos提供对服务的实时的健康检查&#xff0c;阻止向不…...

【uniapp】短信验证码输入框

需求是短信验证码需要格子输入框 如图 网上找了一个案例改吧改吧 直接上代码 结构 <template><view class"verify-code"><!-- 输入框 --><input id"input" :value"code" class"input" :focus"isFocus"…...

负载均衡的综合部署练习(hproxy+keepalived和lvs-DR+keepalived+nginx+Tomcat)

一、haproxykeepalived haproxy 2台 20.0.0.21 20.0.0.22 nginx 2台 20.0.0.23 20.0.0.24 客户机 1台 20.0.0.30 这里没有haproxy不是集群的概念&#xff0c;他只是代理服务器。 访问他直接可以直接访问后端服务器 关闭防火墙 安装haproxy和环境&#xff1a; yum in…...

设计模式——策略模式(Strategy Pattern)+ Spring相关源码

文章目录 一、策略模式定义二、例子1. 菜鸟教程例子&#xff08;略有改动&#xff09;1.1 、定义。1.2、定义加法策略类1.3、定义乘法策略类1.4、创建 Context 类1.5、使用 2、JDK awt包——BufferStrategy3、Spring源码 —— InstantiatorStrategy4、Spring源码 —— Instanti…...

ORB-SLAM3算法2之开源数据集运行ORB-SLAM3生成轨迹并用evo工具评估轨迹

文章目录 0 引言1 数据和真值1.1 TUM1.2 EuRoc1.3 KITTI2 ORB-SLAM3的EuRoc示例3 ORB-SLAM3的TUM-VI示例4 ORB-SLAM3的ROS各版本示例4.1 单目4.2 单目和IMU4.3 双目4.4 双目和IMU4.5 RGB-D0 引言 ORB-SLAM3算法1 已成功编译安装ORB-SLAM3到本地,本篇目的是用TUM、EuRoc和KITT…...

Qt 序列化函数和反序列化函数

文章目录 界面学生类序列化函数反序列化函数刷新所选择的下拉表值添加 界面 学生类 // 创建学生信息类 class studentInfo { public:QString id; // 学号QString name; // 学生姓名QString age; // 学生年龄// 重写QDataStream& operator<<操作符&…...

Linux之线程池

线程池 线程池概念线程池的应用场景线程池实现原理单例模式下线程池实现STL、智能指针和线程安全其他常见的各种锁 线程池概念 线程池&#xff1a;一种线程使用模式。 线程过多会带来调度开销&#xff0c;进而影响缓存局部性和整体性能。而线程池维护着多个线程&#xff0c;等待…...

MAC安装stable diffusion

./webui.sh --precision full --no-half-vae --disable-nan-check --api Command: "/Users/xxxx/aigc/stable-diffusion-webui/venv/bin/python3" -m pip install torch2.0.1 torchvision0.15.2 Error code: 2 执行命令&#xff1a; pip install torch2.0.1 torchvi…...

FPGA_状态机工作原理

FPGA_状态机介绍和工作原理 状态机工作原理Mealy 状态机模型Moore 状态机模型状态机描述方式代码格式 总结 状态机工作原理 状态机全称是有限状态机&#xff08;Finite State Machine、FSM&#xff09;&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的数学…...

【python练习】python斐波那契数列超时问题

计算斐波那契数列第n项的数字 Description计算斐波那契数列第n项的数字&#xff0c;其中f(1)f(2)1,f(n)f(n-1)f(n-2)&#xff0c;如1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5,......Input 正整数n(n<100)Output 一个整数f(n)Sample Input 1 8 Sample Output 1…...

SpringCloud 微服务全栈体系(五)

第七章 Feign 远程调用 先来看我们以前利用 RestTemplate 发起远程调用的代码&#xff1a; 存在下面的问题&#xff1a; 代码可读性差&#xff0c;编程体验不统一 参数复杂 URL 难以维护 Feign 是一个声明式的 http 客户端&#xff0c;官方地址&#xff1a;https://github.…...

msvcp140.dll丢失的正确解决方法

在使用电脑中我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp140.dll丢失”。这个错误通常会导致某些应用程序无法正常运行。为了解决这个问题&#xff0c;我们需要采取一些措施来修复丢失的msvcp140.dll文件。本文将介绍6个不同的解决方法&#xff0c;帮助读者解决…...

go pprof 如何使用 --chatGPT

gpt: pprof 是 Go 语言的性能分析工具&#xff0c;它可以用来检测 CPU 使用情况、内存使用情况、以及阻塞情况。你可以使用 pprof 来帮助诊断程序的性能问题&#xff0c;包括内存泄漏。 以下是如何使用 pprof 来分析内存泄漏的基本步骤&#xff1a; 1. **导入 pprof 包**&am…...

大数据可视化BI分析工具Apache Superset实现公网远程访问

大数据可视化BI分析工具Apache Superset实现公网远程访问 文章目录 大数据可视化BI分析工具Apache Superset实现公网远程访问前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网…...

软考系统架构师知识点集锦二:软件工程

一、考情分析 二、考点精讲 2.1 软件过程模型 &#xff08;1&#xff09;原型模型 典型的原型开发方法模型。适用于需求不明确的场景,可以帮助用户明确需求。可以分为[抛弃型原型]与[演化型原型] 原型模型两个阶段: 1、原型开发阶段;2、目标软件开发阶段。 &#x…...

Go并发:使用sync.Pool来性能优化

简介 在Go提供如何实现对象的缓存池功能&#xff1f;常用一种实现方式是&#xff1a;sync.Pool, 其旨在缓存已分配但未使用的项目以供以后重用&#xff0c;从而减轻垃圾收集器&#xff08;GC&#xff09;的压力。 快速使用 sync.Pool的结构也比较简单&#xff0c;常用的方法…...

git stash的使用方法

git stash的使用方法 应用场景 当我们在开发一个新功能的时候&#xff0c;或者开发到一半&#xff0c;然后就收到了线上master 出现了bug&#xff0c;当分支开发已经进行了或者进行到一半了&#xff0c;这时怎么办呢&#xff1f; 这时解决方案有两种&#xff1a;一种是先先将当…...

【影刀演示_发送邮件的格式化HTML留存】

发送邮件的格式化HTML留存 纯文本&#xff1a; 亲爱的小张: 端午节将至&#xff0c;公司为了感谢大家一年以来的辛勤工作和付出&#xff0c;特别为大家准备了京客隆超市福利卡&#xff0c;希望为大家带来些许便利和节日的喜悦。 以下是您的福利卡卡号和密码&#xff0c;请您…...

深度学习(4)---生成式对抗网络(GAN)

文章目录 一、原理讲述1.1 概念讲解1.2 生成模型和判别模型 二、训练过程2.1 训练原理2.2 损失函数 三、应用 一、原理讲述 1.1 概念讲解 1. 生成式对抗网络&#xff08;Generative Adversarial Network&#xff0c;GAN&#xff09;是一种深度学习模型&#xff0c;是近年来复杂…...

ThinkPad电脑HDMI接口失灵如何解决?

ThinkPad电脑HDMI接口失灵如何解决&#xff1f; 如果平时正常使用的外接显示器&#xff0c;某天突然无法使用了&#xff0c;重新插拔依然无信号的话&#xff0c;可以打开系统的设备管理器&#xff08;快捷键winx&#xff09;&#xff0c;首先看一下监视器的识别情况&#xff0c…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...