【Nacos】Nacos配置中心客户端启动源码分析

news2024/11/28 8:49:18

在这里插入图片描述

SpringCloud项目启动过程中会解析bootstrop.properties、bootstrap.yaml配置文件,启动父容器,在子容器启动过程中会加入PropertySourceBootstrapConfiguration来读取配置中心的配置。

PropertySourceBootstrapConfiguration#initialize

PropertySourceBootstrapConfiguration是SpringCloud的配置类,实现了ApplicationContextInitializer,会在容器创建前调用其initialize()方法。
org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration#initialize

public void initialize(ConfigurableApplicationContext applicationContext) {
	List<PropertySource<?>> composite = new ArrayList<>();
	AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
	boolean empty = true;
	ConfigurableEnvironment environment = applicationContext.getEnvironment();
	for (PropertySourceLocator locator : this.propertySourceLocators) {
		// 调用PropertySourceLocator.locateCollection()来读取配置
		Collection<PropertySource<?>> source = locator.locateCollection(environment);
		if (source == null || source.size() == 0) {
			continue;
		}
		List<PropertySource<?>> sourceList = new ArrayList<>();
		for (PropertySource<?> p : source) {
			if (p instanceof EnumerablePropertySource) {
				EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p;
				sourceList.add(new BootstrapPropertySource<>(enumerable));
			}
			else {
				sourceList.add(new SimpleBootstrapPropertySource(p));
			}
		}
		logger.info("Located property source: " + sourceList);
		composite.addAll(sourceList);
		empty = false;
	}
	if (!empty) {
		MutablePropertySources propertySources = environment.getPropertySources();
		String logConfig = environment.resolvePlaceholders("${logging.config:}");
		LogFile logFile = LogFile.get(environment);
		for (PropertySource<?> p : environment.getPropertySources()) {
			if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
				propertySources.remove(p.getName());
			}
		}
		insertPropertySources(propertySources, composite);
		reinitializeLoggingSystem(environment, logConfig, logFile);
		setLogLevels(applicationContext, environment);
		handleIncludedProfiles(environment);
	}
}

在PropertySourceBootstrapConfiguration这个单例对象初始化的时候会将Spring容器中所有的PropertySourceLocator实现注入进来。然后在initialize()方法中循环所有的PropertySourceLocator进行配置的获取,从这儿可以看出SpringCloud应用是支持我们引入多个配置中心实现的,获取到配置后调用insertPropertySources方法将所有的PropertySource(封装的一个个配置文件)添加到Spring的环境变量environment中。

org.springframework.cloud.bootstrap.config.PropertySourceLocator#locateCollection(org.springframework.core.env.Environment)

default Collection<PropertySource<?>> locateCollection(Environment environment) {
	return locateCollection(this, environment);
}

static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
													  Environment environment) {
	// 最终会调用PropertySourceLocator.locate()
	PropertySource<?> propertySource = locator.locate(environment);
	if (propertySource == null) {
		return Collections.emptyList();
	}
	if (CompositePropertySource.class.isInstance(propertySource)) {
		Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
			.getPropertySources();
		List<PropertySource<?>> filteredSources = new ArrayList<>();
		for (PropertySource<?> p : sources) {
			if (p != null) {
				filteredSources.add(p);
			}
		}
		return filteredSources;
	}
	else {
		return Arrays.asList(propertySource);
	}
}

上面会将CompositePropertySource拆分为多个PropertySource。

NacosPropertySourceLocator#locate

Nacos也实现了SpringCloud配置中心规范,其实现类为NacosPropertySourceLocator。

com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#locate

public PropertySource<?> locate(Environment env) {
	nacosConfigProperties.setEnvironment(env);
	ConfigService configService = nacosConfigManager.getConfigService();

	if (null == configService) {
		log.warn("no instance of config service found, can't load config from nacos");
		return null;
	}
	long timeout = nacosConfigProperties.getTimeout();
	nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService,
																timeout);
	String name = nacosConfigProperties.getName();

	String dataIdPrefix = nacosConfigProperties.getPrefix();
	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = name;
	}

	if (StringUtils.isEmpty(dataIdPrefix)) {
		dataIdPrefix = env.getProperty("spring.application.name");
	}

	CompositePropertySource composite = new CompositePropertySource(
		NACOS_PROPERTY_SOURCE_NAME);

	// 读取共享配置
	loadSharedConfiguration(composite);
	// 读取扩展配置
	loadExtConfiguration(composite);
	// 读取应用配置
	loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
	return composite;
}

Nacos启动会加载以下三种配置文件,也就是我们在bootstrap.yml文件里配置的扩展配置extension-configs、共享配置 shared-configs以及应用自己的配置,加载到配置文件后会封装成NacosPropertySource返回,最后只会返回一个CompositePropertySource。

NacosPropertySourceLocator#loadApplicationConfiguration

实际开发过程中我们主要使用的就是应用配置,所以这里重点关注应用配置的加载过程。

com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadApplicationConfiguration

private void loadApplicationConfiguration(
	CompositePropertySource compositePropertySource, String dataIdPrefix,
	NacosConfigProperties properties, Environment environment) {
	String fileExtension = properties.getFileExtension();
	String nacosGroup = properties.getGroup();
	// load directly once by default
	loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
						   fileExtension, true);
	// load with suffix, which have a higher priority than the default
	loadNacosDataIfPresent(compositePropertySource,
						   dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
	// Loaded with profile, which have a higher priority than the suffix
	for (String profile : environment.getActiveProfiles()) {
		String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
		loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
							   fileExtension, true);
	}

}

加载应用配置时,同时会加载以下三种配置,配置的优先级从低到高,分别是:

  1. 不带扩展名后缀,application
  2. 带扩展名后缀,application.yml、application.propertie
  3. 带环境,带扩展名后缀,application-prod.yml

com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadNacosDataIfPresent

private void loadNacosDataIfPresent(final CompositePropertySource composite,
									final String dataId, final String group, String fileExtension,
									boolean isRefreshable) {
	if (null == dataId || dataId.trim().length() < 1) {
		return;
	}
	if (null == group || group.trim().length() < 1) {
		return;
	}
	NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
																	  fileExtension, isRefreshable);
	this.addFirstPropertySource(composite, propertySource, false);
}

com.alibaba.cloud.nacos.client.NacosPropertySourceLocator#loadNacosPropertySource

private NacosPropertySource loadNacosPropertySource(final String dataId,
													final String group, String fileExtension, boolean isRefreshable) {
	if (NacosContextRefresher.getRefreshCount() != 0) {
		if (!isRefreshable) {
			// 从缓冲中获取
			return NacosPropertySourceRepository.getNacosPropertySource(dataId,
																		group);
		}
	}
	return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
											isRefreshable);
}

com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#build

NacosPropertySource build(String dataId, String group, String fileExtension,
						  boolean isRefreshable) {
	// 加载数据
	List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
															fileExtension);
	NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
																	  group, dataId, new Date(), isRefreshable);
	// 加入缓存
	NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
	return nacosPropertySource;
}

com.alibaba.cloud.nacos.client.NacosPropertySourceBuilder#loadNacosData

private List<PropertySource<?>> loadNacosData(String dataId, String group,
											  String fileExtension) {
	String data = null;
	try {
		// 委托configService获取配置,我们也可以手动使用此类获取配置
		data = configService.getConfig(dataId, group, timeout);
		if (StringUtils.isEmpty(data)) {
			log.warn(
				"Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]",
				dataId, group);
			return Collections.emptyList();
		}
		if (log.isDebugEnabled()) {
			log.debug(String.format(
				"Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId,
				group, data));
		}
		return NacosDataParserHandler.getInstance().parseNacosData(dataId, data,
																   fileExtension);
	}
	catch (NacosException e) {
		log.error("get data from Nacos error,dataId:{} ", dataId, e);
	}
	catch (Exception e) {
		log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
	}
	return Collections.emptyList();
}

loadNacosData()方法中会将实际配置加载请求委托给ConfigService去做,然后解析返回的字符串,解析器实现了PropertySourceLoader接口,支持yml、properties、xml、json这几种格式。

NacosConfigService#getConfig

getConfig()方法会调用到getConfigInner()方法,通过namespace, dataId, group唯一定位一个配置文件。

com.alibaba.nacos.client.config.NacosConfigService#getConfig

public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
	return getConfigInner(namespace, dataId, group, timeoutMs);
}

首先获取本地缓存文件的配置内容,如果有直接返回,如果从本地没找到相应配置文件,就从远程服务器拉取,Nacos2.0以上版本使用Grpc协议进行远程通信,1.0及以下使用Http协议进行远程通信,我们这里使用的是1.4.2版本。
com.alibaba.nacos.client.config.NacosConfigService#getConfigInner

private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
	group = blank2defaultGroup(group);
	ParamUtils.checkKeyParam(dataId, group);
	ConfigResponse cr = new ConfigResponse();

	cr.setDataId(dataId);
	cr.setTenant(tenant);
	cr.setGroup(group);

	// 优先使用本地配置
	String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
	if (content != null) {
		LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
					dataId, group, tenant, ContentUtils.truncateContent(content));
		cr.setContent(content);
		String encryptedDataKey = LocalEncryptedDataKeyProcessor
			.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
		cr.setEncryptedDataKey(encryptedDataKey);
		configFilterChainManager.doFilter(null, cr);
		content = cr.getContent();
		return content;
	}

	try {
		// 从远程服务器拉取
		ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs);
		cr.setContent(response.getContent());
		cr.setEncryptedDataKey(response.getEncryptedDataKey());

		configFilterChainManager.doFilter(null, cr);
		content = cr.getContent();

		return content;
	} catch (NacosException ioe) {
		if (NacosException.NO_RIGHT == ioe.getErrCode()) {
			throw ioe;
		}
		LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
					agent.getName(), dataId, group, tenant, ioe.toString());
	}

	LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
				dataId, group, tenant, ContentUtils.truncateContent(content));
	content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
	cr.setContent(content);
	String encryptedDataKey = LocalEncryptedDataKeyProcessor
		.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
	cr.setEncryptedDataKey(encryptedDataKey);
	// 这里会对配置进行拦截,可以用于加解密
	configFilterChainManager.doFilter(null, cr);
	content = cr.getContent();
	return content;
}

com.alibaba.nacos.client.config.impl.ClientWorker#getServerConfig

public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout)
	throws NacosException {
	ConfigResponse configResponse = new ConfigResponse();
	if (StringUtils.isBlank(group)) {
		group = Constants.DEFAULT_GROUP;
	}

	HttpRestResult<String> result = null;
	try {
		Map<String, String> params = new HashMap<String, String>(3);
		if (StringUtils.isBlank(tenant)) {
			params.put("dataId", dataId);
			params.put("group", group);
		} else {
			params.put("dataId", dataId);
			params.put("group", group);
			params.put("tenant", tenant);
		}
		// 调用远程Http接口/v1/cs/configs拉取配置
		result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
	} catch (Exception ex) {
		String message = String
			.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s",
					agent.getName(), dataId, group, tenant);
		LOGGER.error(message, ex);
		throw new NacosException(NacosException.SERVER_ERROR, ex);
	}

	switch (result.getCode()) {
		case HttpURLConnection.HTTP_OK:
			LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
			configResponse.setContent(result.getData());
			String configType;
			if (result.getHeader().getValue(CONFIG_TYPE) != null) {
				configType = result.getHeader().getValue(CONFIG_TYPE);
			} else {
				configType = ConfigType.TEXT.getType();
			}
			configResponse.setConfigType(configType);
			String encryptedDataKey = result.getHeader().getValue(ENCRYPTED_DATA_KEY);
			LocalEncryptedDataKeyProcessor
				.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, encryptedDataKey);
			configResponse.setEncryptedDataKey(encryptedDataKey);
			return configResponse;
		case HttpURLConnection.HTTP_NOT_FOUND:
			LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
			LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, null);
			return configResponse;
		case HttpURLConnection.HTTP_CONFLICT: {
			LOGGER.error(
				"[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, "
				+ "tenant={}", agent.getName(), dataId, group, tenant);
			throw new NacosException(NacosException.CONFLICT,
									 "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
		}
		case HttpURLConnection.HTTP_FORBIDDEN: {
			LOGGER.error("[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}", agent.getName(),
						 dataId, group, tenant);
			throw new NacosException(result.getCode(), result.getMessage());
		}
		default: {
			LOGGER.error("[{}] [sub-server-error]  dataId={}, group={}, tenant={}, code={}", agent.getName(),
						 dataId, group, tenant, result.getCode());
			throw new NacosException(result.getCode(),
									 "http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="
									 + tenant);
		}
	}
}

至此,在项目启动的时候(上下文准备阶段)我们就拉到了远程Nacos中的配置,并且封装成NacosPropertySource放到了Spring 的环境变量里,在Bean的实例化过程中就可以使用Environment中的值了。

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

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

相关文章

实现复选框全选和全不选的切换

今天&#xff0c;复看了一下JS的菜鸟教程&#xff0c;发现评论里面都是精华呀&#xff01;&#xff01; 看到函数这一节&#xff0c;发现就复选框的全选和全不选功能展开了讨论。我感觉挺有意思的&#xff0c;尝试实现了一下。 1. 全选、全不选&#xff0c;两个按钮&#xff…

CentOS8联网部署Ceph-Quincy集群

文章目录1.环境准备1.1 关闭selinux1.2 关闭防火墙1.3 配置免密1.4 设置yum源1.5 安装依赖1.6 设置时间同步1.7 安装docker2.安装Ceph2.1 安装cephadm2.2 部署ceph集群2.3 集群添加节点2.4 部署MON2.5 部署OSD2.6 部署MGR2.7 集群状态3.问题3.1 failed to retrieve runc versio…

腾讯云对象存储+企业网盘 打通数据链“最后一公里

对云厂商和企业用户来说&#xff0c;随着数据规模的快速增长&#xff0c;企业除了对存储功能和性能的要求不断增加&#xff0c;也越来越注重数据分发的效率。在传统数据分发的过程中&#xff0c;数据管理员往往需要先在存储桶下载对应的客户方案/交付资料&#xff0c;再使用微信…

【前言】嵌入式系统简介

随手拍拍&#x1f481;‍♂️&#x1f4f7; 日期: 2022.12.01 地点: 杭州 介绍: 2022.11.30下午两点时&#xff0c;杭州下了一场特别大的雪。隔天的12月路过食堂时&#xff0c;边上的井盖上发现了这个小雪人。此时边上的雪已经融化殆尽&#xff0c;只有这个雪人依旧维持着原状⛄…

【FLASH存储器系列十九】固态硬盘掉电后如何恢复掉电前状态?

掉电分两种&#xff0c;一种是正常掉电&#xff0c;另一种是异常掉电。不管是哪种原因导致的掉电&#xff0c;我们都希望&#xff0c;重新上电后&#xff0c;SSD都需要能从掉电中恢复过来&#xff0c;继续正常工作。正常掉电恢复&#xff0c;这个好理解&#xff0c;主机通知SSD…

Linux(centOS7)虚拟机中配置 vim

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

数据库实践LAB大纲 05 JDBC 连接

概述 Java DataBase Connectivity&#xff0c;Java 数据库连接 执行SQL的Java API 为多种关系型数据提供统一访问 FUNCTION 建立与数据库的连接向数据库发送 SQL 语句处理从数据库返回的结果 四种常见JDBC驱动程序 JDBC-ODBC Bridge drivernative-API, partly Java driver…

LeetCode题目笔记——1.两数之和

文章目录题目描述题目难度——简单方法一&#xff1a;暴力代码/Python方法二&#xff1a;哈希表代码/Python代码/C总结题目描述 这道题可以说是力扣的入坑题了&#xff0c;很经典&#xff0c;好像还是面试的经典题。 给定一个整数数组 nums 和一个整数目标值 target&#xff0c…

Zookeeper技术认知

目录概念理解工作原理文件系统通知系统zookeeper在kakfa中的作用概念理解 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 工作原理 Zookeeper 作为一个分布式的服务框架&#xff0c;主要用来解决分布式集群中应用系统的一致性问题&…

Android IO 框架 Okio 的实现原理,到底哪里 OK?

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 前言 大家好&#xff0c;我是小彭。 今天&#xff0c;我们来讨论一个 Square 开源的 I/O 框架 Okio&#xff0c;我们最开始接触到 Okio 框架还是源于 Square 家的 OkHttp 网络…

C4--Vivado添加列表中不存在的FLash器件2023-02-10

以华邦SPI FLASH W25Q128JVEIQ为例进行说明。&#xff08;其他Flash添加步骤一致&#xff09; 1.本地vivado安装目录D:\Softwares\xlinx_tools\Vivado\2020.2\data\xicom下&#xff0c;找到xicom_cfgmem_part_table.csv文件&#xff0c;这个表与vivado hardware manager中的器…

pixhawk2.4.8-APM固件-MP地面站配置过程记录

目录一、硬件准备二、APM固件、MP地面站下载三、地面站配置1 刷固件2 机架选择3 加速度计校准4 指南针校准5 遥控器校准6 飞行模式7 紧急断电&无头模式8 基础参数设置9 电流计校准10 电调校准11 起飞前检查&#xff08;每一项都非常重要&#xff09;12 飞行经验四、遇到的问…

同步线程

↵ 由于这节内容资料比较少&#xff0c;所以以下内容总结自Qt官方文献&#xff0c;在文章最后会给出相应链接。 线程的目的是允许并行运行&#xff0c;但有时线程必须停止等待其他线程。例如&#xff0c;如果两个线程尝试访问同一个变量&#xff0c;这样的话结果是未定义的。强…

【0基础学爬虫】爬虫基础之爬虫的基本介绍

大数据时代&#xff0c;各行各业对数据采集的需求日益增多&#xff0c;网络爬虫的运用也更为广泛&#xff0c;越来越多的人开始学习网络爬虫这项技术&#xff0c;K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章&#xff0c;为实现从易到难全方位覆盖&#xff0c;特设【0基础学…

Jmeter in Linux - 在Linux系统使用Jmeter的坑

Jmeter in Linux - 在Linux系统使用Jmeter的坑Jmeter in Linux系列目录&#xff1a;o.a.j.JMeter: Error in NonGUIDriver起因错误分析&#xff1a;解决方案&#xff1a;解析日志没有展示请求和响应信息起因解决方案&#xff1a;注意Jmeter in Linux系列目录&#xff1a; 【如…

ChatGPT 爆火!谷歌、微软、百度纷纷下场?

近日&#xff0c;智能聊天机器人ChatGPT的爆火引发了国内外网友的热烈讨论&#xff0c;上线两个月后&#xff0c;用户数量达到1亿。2月8日下午&#xff0c;巨大的访问量让系统一度崩溃。 服务重新开放后&#xff0c;我向ChatGPT询问了如何快速扩容&#xff0c;它显然是知道云端…

CSS从入门到精通专栏简介

先让我们来欣赏几个精美的网站&#xff1a; Matt Brett - Freelance Web Designer and WordPress Expert ‎2022 Year in Review • Letterboxd NIO蔚来汽车官方网站 小米官网 Silk – Interactive Generative Art 大屏数据可视化 你是否也有过这样的“烦恼”&#xff1a; * …

(C00034)基于Springboot+html前后端分离技术的宿舍管理系统-有文档

基于Springboothtml技术的宿舍管理系统-有文档项目简介项目获取开发环境项目技术运行截图项目简介 基于Springboothtml的前后端分离技术的宿舍管理系统项目为了方便对学生宿舍进行管理而设计&#xff0c;分为后勤、宿管、学生三种用户&#xff0c;后勤对整体宿舍进行管理、宿管…

第九层(16):STL终章——常用集合算法

文章目录前情回顾常用集合算法set_intersectionset_unionset_difference最后一座石碑倒下&#xff0c;爬塔结束一点废话&#x1f389;welcome&#x1f389; ✒️博主介绍&#xff1a;一名大一的智能制造专业学生&#xff0c;在学习C/C的路上会越走越远&#xff0c;后面不定期更…

JY-7A/3DK/220 19-130V静态【电压继电器】

系列型号 JY-7A/1DK不带辅助电源电压继电器&#xff1b;JY-7B/1DK不带辅助电源电压继电器&#xff1b; JY-7/1DK/120不带辅助电源电压继电器&#xff1b;JY-7/1DK/120不带辅助电源电压继电器&#xff1b; JY-7A/1DKQ不带辅助电源电压继电器&#xff1b;JY-7B/1DKQ不带辅助电源…