【Nacos】Nacos配置中心客户端启动源码分析
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 defaultloadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,fileExtension, true);// load with suffix, which have a higher priority than the defaultloadNacosDataIfPresent(compositePropertySource,dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);// Loaded with profile, which have a higher priority than the suffixfor (String profile : environment.getActiveProfiles()) {String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,fileExtension, true);}}
加载应用配置时,同时会加载以下三种配置,配置的优先级从低到高,分别是:
- 不带扩展名后缀,application
- 带扩展名后缀,application.yml、application.propertie
- 带环境,带扩展名后缀,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中的值了。
相关文章:

【Nacos】Nacos配置中心客户端启动源码分析
SpringCloud项目启动过程中会解析bootstrop.properties、bootstrap.yaml配置文件,启动父容器,在子容器启动过程中会加入PropertySourceBootstrapConfiguration来读取配置中心的配置。 PropertySourceBootstrapConfiguration#initialize PropertySource…...

中国特色地流程管理系统,天翎让流程审批更简单
编者按:本文分析了国内企业在采购流程管理系统常遇到的一些难点,并从适应中国式流程管理模式的特点出发,介绍了符合中国特色的流程审批管理系统——天翎流程管理系统。关键词:可视化开发,拖拽建模,审批控制…...
Python算法:DFS排列与组合算法(手写模板)
自写排列算法: 例:前三个数的全排列(从小到大) def dfs(s,t):if st: #递归结束,输出一个全排列print(b[0:n])else:for i in range(t):if vis[i]False:vis[i]Trueb[s]a[i] #存排列dfs(s1,t)vis[i]Falsea[1,2,3,4,…...

拿来就用的Java海报生成器ImageCombiner(一)
背景如果您是UI美工大师或者PS大牛,那本文一定不适合你;如果当您需要自己做一张海报时,可以立马有小伙伴帮您实现,那本文大概率也不适合你。但是,如果你跟我一样,遇上到以下场景,最近公司上了不…...

【C++】类和对象(二)
目录 一、默认成员函数 二、构造函数 1、构造函数概念 2、构造函数编写 3、默认构造函数 4、内置类型成员的补丁 三、析构函数 1、析构函数概念 2、析构函数编写 3、默认析构函数 四、拷贝构造函数 1、拷贝构造函数概念及编写 2、默认拷贝构造函数 3、拷贝构造…...

UDP协议
文章目录一、前沿知识应用层传输层二、UDP协议一、前沿知识 应用层 应用层:描述了应用程序如何理解和使用网络中的通信数据。 我们程序员在应用层的主要工作是自定义协议,因为下面四层都在系统内核/驱动程序/硬件中已经实现好了,不能去修改…...

IT人的晋升之路——关于人际交往能力的培养
对于咱们的程序员来说,工作往往不是最难的,更难的是人际交往和关系的维护处理。很多时候我们都宁愿加班,也不愿意是社交,认识新的朋友,拓展自己的圈子。对外的感觉就好像我们丧失了人际交往能力,是个呆子&a…...

Docker进阶 - 8. docker network 网络模式之 container
目录 1. container 模式概述 2. 使用Alpine操作系统来验证 container 模式 1. container 模式概述 container网络模式新建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个…...

2年功能测试月薪9.5K,100多天自学自动化,跳槽涨薪4k后我的路还很长...
前言 其实最开始我并不是互联网从业者,是经历了一场六个月的培训才入的行,这个经历仿佛就是一个遮羞布,不能让任何人知道,就算有面试的时候被问到你是不是被培训的,我还是不能承认这段历史。我是为了生存,…...

“数字孪生”:为什么要仿真嵌入式系统?
01.仿真是什么? 仿真的概念非常广泛,但归根结底都是使用可控的手段来模仿真实的情况,通常应用于现实世界中实施难度大甚至是无法实践的事物。 众所周知,嵌入式系统通常是形式多样的、面向特定应用的软硬件综合体,无…...
Java基础知识总结(上)
Java基础知识总结 1. Java语言的特点 简单易学,相较于python等语言具有较好的严谨性以及报错机制; 面向对象(封装,继承,多态),Java中所有内容都是基于类进行扩展的,由类创建的实体…...
MySQL 2:MySQL约束
一、定义 约束(constraint),即表中数据的限制条件。在表设计中加入约束的目的是保证表中记录的完整性和有效性。 比如user表,有些列(手机号)的值不能为空,有些列(身份证号ÿ…...

C4--Vivado添加列表中不存在的FLash器件2023-02-10
以华邦SPI FLASH W25Q128JVEIQ为例进行说明。(其他Flash添加步骤一致) 1.本地vivado安装目录D:\Softwares\xlinx_tools\Vivado\2020.2\data\xicom下,找到xicom_cfgmem_part_table.csv文件,这个表与vivado hardware manager中的器…...

php代码审计
准备工作 了解CMS的基本信息 该CMS使用的是什么设计模式?该CMS每个目录大概负责的功能(视图、缓存、控制器等)。该CMS处理请求的基本流程是如何走的?以及在系统中使用的全局过滤函数是如何对数据进行处理的? 代码审计方法 敏感函数回溯 …...

接口测试入门,如何划分接口文档
1.首先最主要的就是要分析接口测试文档,每一个公司的测试文档都是不一样的。具体的就要根据自己公司的接口而定,里面缺少的内容自己需要与开发进行确认。 我认为一针对于测试而言的主要的接口测试文档应该包含的内容分为以下几个方面。 a.具体的一个业…...
数据库学习第二天
第7章 系统预定义函数 函数:代表一个独立的可复用的功能。 和Java中的方法有所不同,不同点在于:MySQL中的函数必须有返回值,参数可以有可以没有。 MySQL中函数分为: (1)系统预定义函数&…...

NODE => CORS跨域资源共享学习
1.CORS跨域资源共享 cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地解决跨域问题 运行npm install cors 安装中间件使用const cors require(‘cors’) 导入中间件在路由之前调用 app.use(cors()&#…...

golang rabbitMQ 生产者复用channel以及生产者组分发策略
引用的是rabbitMQ官方示例的库:github.com/rabbitmq/amqp091-go在网络编程中我们知道tcp连接的创建、交互、销毁等相关操作的"代价"都是很高的,所以就要去实现如何复用这些连接,并要做到高效并可靠。预期效果:项目初始化…...

掌握了这项技能的性能测试师,90%都升职加薪了
初入职场的新人该怎么做才能让自己快速成长?在公司一直做着手工测试,如何才能提升自己,避免陷入“只涨年龄不涨经验”的尴尬?做为一名软件测试工程师,我们不得不去面对这些问题,有的人找到了答案࿰…...

linux中crontab定时任务导致磁盘满和云监控未报警的的坑
一个后台开发者,兼职运维工作中,配置linux中crontab定时任务,导致磁盘满和云监控未报警的问题的坑。 1.磁盘满 使用命令 df -h2.问题排查 2.1排查日志 命令 cat /var/log/messages日志文件的默认路径是:/var/log 下面是几个…...

国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...

毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...