SpringBoot整合多数据源,并支持动态新增与切换

SpringBoot整合多数据源,并支持动态新增与切换

一、概述

在项目的开发过程中,遇到了需要从数据库中动态查询新的数据源信息并切换到该数据源做相应的查询操作,这样就产生了动态切换数据源的场景。为了能够灵活地指定具体的数据库,本文基于注解和AOP的方法实现多数据源自动切换。在使用过程中,只需要添加注解就可以使用,简单方便。(代码获取方式:见文章底部(开箱即用))

二、构建核心代码

2.1、AbstractRoutingDataSource构建


package com.wonders.dynamic;import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;/*** @Description: TODO:抽象类AbstractRoutingDataSource,实现动态数据源切换* @Author: yyalin* @CreateDate: 2023/7/16 14:40* @Version: V1.0*/
public abstract class AbstractRoutingDataSource extends AbstractDataSourceimplements InitializingBean {//目标数据源map集合,存储将要切换的多数据源bean信息@Nullableprivate Map<Object, Object> targetDataSources;//未指定数据源时的默认数据源对象@Nullableprivate Object defaultTargetDataSource;private boolean lenientFallback = true;//数据源查找接口,通过该接口的getDataSource(String dataSourceName)获取数据源信息private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();//解析targetDataSources之后的DataSource的map集合@Nullableprivate Map<Object, DataSource> resolvedDataSources;@Nullableprivate DataSource resolvedDefaultDataSource;//将targetDataSources的内容转化一下放到resolvedDataSources中,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSourcepublic void afterPropertiesSet() {//如果目标数据源为空,会抛出异常,在系统配置时应至少传入一个数据源if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");} else {//初始化resolvedDataSources的大小this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());//遍历目标数据源信息map集合,对其中的key,value进行解析this.targetDataSources.forEach((key, value) -> {//resolveSpecifiedLookupKey方法没有做任何处理,只是将key继续返回Object lookupKey = this.resolveSpecifiedLookupKey(key);//将目标数据源map集合中的value值(德鲁伊数据源信息)转为DataSource类型DataSource dataSource = this.resolveSpecifiedDataSource(value);//将解析之后的key,value放入resolvedDataSources集合中this.resolvedDataSources.put(lookupKey, dataSource);});if (this.defaultTargetDataSource != null) {//将默认目标数据源信息解析并赋值给resolvedDefaultDataSourcethis.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);}}}protected Object resolveSpecifiedLookupKey(Object lookupKey) {return lookupKey;}protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {if (dataSource instanceof DataSource) {return (DataSource)dataSource;} else if (dataSource instanceof String) {return this.dataSourceLookup.getDataSource((String)dataSource);} else {throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);}}//因为AbstractRoutingDataSource继承AbstractDataSource,而AbstractDataSource实现了DataSource接口,所有存在获取数据源连接的方法public Connection getConnection() throws SQLException {return this.determineTargetDataSource().getConnection();}public Connection getConnection(String username, String password) throws SQLException {return this.determineTargetDataSource().getConnection(username, password);}protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");//调用实现类中重写的determineCurrentLookupKey方法拿到当前线程要使用的数据源的名称Object lookupKey = this.determineCurrentLookupKey();//去解析之后的数据源信息集合中查询该数据源是否存在,如果没有拿到则使用默认数据源resolvedDefaultDataSourceDataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}}@Nullableprotected abstract Object determineCurrentLookupKey();
}

**2.2、**DynamicDataSource类

/*** @Description: TODO:动态数据源* @Author: yyalin* @CreateDate: 2023/7/16 14:46* @Version: V1.0*/
/**** 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map,* 并将新的数据源信息添加到map中,并替换targetdatasources中的map* 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称")*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {//备份所有数据源信息,private Map<Object, Object> defineTargetDataSources;/*** 决定当前线程使用哪个数据源*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceHolder.getDynamicDataSourceKey();}}

2.3、DynamicDataSourceHolder


/*** @Description: TODO:数据源切换处理* DynamicDataSourceHolder类主要是设置当前线程的数据源名称,* 移除数据源名称,以及获取当前数据源的名称,便于动态切换* @Author: yyalin* @CreateDate: 2023/7/16 14:51* @Version: V1.0*/
@Slf4j
public class DynamicDataSourceHolder {/*** 保存动态数据源名称*/private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();/*** 设置/切换数据源,决定当前线程使用哪个数据源*/public static void setDynamicDataSourceKey(String key){log.info("数据源切换为:{}",key);DYNAMIC_DATASOURCE_KEY.set(key);}/*** 获取动态数据源名称,默认使用mater数据源*/public static String getDynamicDataSourceKey(){String key = DYNAMIC_DATASOURCE_KEY.get();return key == null ? DbsConstant.mysql_db_01 : key;}/*** 移除当前数据源*/public static void removeDynamicDataSourceKey(){log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());DYNAMIC_DATASOURCE_KEY.remove();}}

2.4、数据源工具类

/*** @Description: TODO:数据源工具类* @Author: yyalin* @CreateDate: 2023/7/16 15:00* @Version: V1.0*/
@Slf4j
@Component
public class DataSourceUtils {@ResourceDynamicDataSource dynamicDataSource;/*** @Description: 根据传递的数据源信息测试数据库连接* @Author zhangyu*/public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) {DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(dataSourceInfo.getUrl());druidDataSource.setUsername(dataSourceInfo.getUserName());druidDataSource.setPassword(dataSourceInfo.getPassword());druidDataSource.setDriverClassName(dataSourceInfo.getDriverClassName());druidDataSource.setBreakAfterAcquireFailure(true);druidDataSource.setConnectionErrorRetryAttempts(0);try {druidDataSource.getConnection(2000);log.info("数据源连接成功");return druidDataSource;} catch (SQLException throwables) {log.error("数据源 {} 连接失败,用户名:{},密码 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassword());return null;}}/*** @Description: 将新增的数据源加入到备份数据源map中* @Author zhangyu*/public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){Map<Object, Object> defineTargetDataSources = dynamicDataSource.getDefineTargetDataSources();defineTargetDataSources.put(dataSourceName, druidDataSource);dynamicDataSource.setTargetDataSources(defineTargetDataSources);dynamicDataSource.afterPropertiesSet();}

2.5、DynamicDataSourceConfig

/*** @Description: TODO:数据源信息配置类,读取数据源配置信息并注册成bean。* @Author: yyalin* @CreateDate: 2023/7/16 14:54* @Version: V1.0*/
@Configuration
@MapperScan("com.wonders.mapper")
@Slf4j
public class DynamicDataSourceConfig {@Bean(name = DbsConstant.mysql_db_01)@ConfigurationProperties("spring.datasource.mysqldb01")public DataSource masterDataSource() {log.info("数据源切换为:{}",DbsConstant.mysql_db_01);DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return dataSource;}@Bean(name = DbsConstant.mysql_db_02)@ConfigurationProperties("spring.datasource.mysqldb02")public DataSource slaveDataSource() {log.info("数据源切换为:{}",DbsConstant.mysql_db_02);DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return dataSource;}@Bean(name = DbsConstant.oracle_db_01)@ConfigurationProperties("spring.datasource.oracledb01")public DataSource oracleDataSource() {log.info("数据源切换为oracle:{}",DbsConstant.oracle_db_01);DruidDataSource dataSource = DruidDataSourceBuilder.create().build();return dataSource;}@Bean@Primarypublic DynamicDataSource dynamicDataSource(){Map<Object, Object> dataSourceMap = new HashMap<>(3);dataSourceMap.put(DbsConstant.mysql_db_01,masterDataSource());dataSourceMap.put(DbsConstant.mysql_db_02,slaveDataSource());dataSourceMap.put(DbsConstant.oracle_db_01,oracleDataSource());//设置动态数据源DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setDefaultTargetDataSource(masterDataSource());dynamicDataSource.setTargetDataSources(dataSourceMap);//将数据源信息备份在defineTargetDataSources中dynamicDataSource.setDefineTargetDataSources(dataSourceMap);return dynamicDataSource;}}

三、测试代码


/*** @Description: TODO* @Author: yyalin* @CreateDate: 2023/7/16 15:02* @Version: V1.0*/
@Slf4j
@Api(tags="动态切换多数据源测试")
@RestController
public class TestController {@ResourceDataSourceUtils dataSourceUtils;@Autowiredprivate StudentMapper studentMapper;@ApiOperation(value="动态切换多数据源测试", notes="test")@GetMapping("/test")public Map<String, Object> dynamicDataSourceTest(String id){Map<String, Object> map = new HashMap<>();//1、默认库中查询数据Student student=studentMapper.selectById(id);map.put("1、默认库中查询到的数据",student);//2、指定库中查询的数据DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);Student student02=studentMapper.selectById(id);map.put("2、指定库中查询的数据",student02);//3、从数据库获取连接信息,然后获取数据//模拟从数据库中获取的连接DataSourceInfo dataSourceInfo = new DataSourceInfo("jdbc:mysql://127.0.0.1:3308/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false","root","root","mysqldb03","com.mysql.cj.jdbc.Driver");map.put("dataSource",dataSourceInfo);log.info("数据源信息:{}",dataSourceInfo);//测试数据源连接DruidDataSource druidDataSource = dataSourceUtils.createDataSourceConnection(dataSourceInfo);if (Objects.nonNull(druidDataSource)){//将新的数据源连接添加到目标数据源map中dataSourceUtils.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDatasourceKey());//设置当前线程数据源名称-----代码形式DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDatasourceKey());//在新的数据源中查询用户信息Student student03=studentMapper.selectById(id);map.put("3、动态数据源查询的数据",student03);//关闭数据源连接druidDataSource.close();}//4、指定oracle库中查询的数据DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.oracle_db_01);Student student04=studentMapper.selectById(id);map.put("4、指定oracle库中查询的数据",student04);return map;}
}

测试结果如下:

从结果中可以明显的看出,通过切换不同的数据源,可以从不同的库中获取不同的数据,包括:常见库Mysql、oracle、sqlserver等数据库相互切换。也可以从数据库的某张表中获取连接信息,实现*动态切换数据库。*

图片

四、使用注解方式切换数据源

从上述TestController 中代码不难看出,若要想切换数据源需要在mapper调用之前调用:

DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);

不够简洁优雅,所以下面推荐使用注解的方式来动态进行数据源的切换。

4.1、创建注解类DataSource

/*** @Description: TODO:自定义多数据源切换注解* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准* @Author: yyalin* @CreateDate: 2023/7/17 14:00* @Version: V1.0*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {//切换数据源名称,默认mysql_db_01public String value() default DbsConstant.mysql_db_01;
}

**4.2、**创建切面DataSourceAspect类

/*** @Description: TODO:创建切面DataSourceAspect类* @Author: yyalin* @CreateDate: 2023/7/17 14:03* @Version: V1.0*/
@Aspect
@Component
public class DataSourceAspect {// 设置DataSource注解的切点表达式@Pointcut("@annotation(com.wonders.dynamic.DataSource)")public void dynamicDataSourcePointCut(){}//环绕通知@Around("dynamicDataSourcePointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable{String key = getDefineAnnotation(joinPoint).value();DynamicDataSourceHolder.setDynamicDataSourceKey(key);try {return joinPoint.proceed();} finally {DynamicDataSourceHolder.removeDynamicDataSourceKey();}}/*** 功能描述:先判断方法的注解,后判断类的注解,以方法的注解为准* @MethodName: getDefineAnnotation* @MethodParam: [joinPoint]* @Return: com.wonders.dynamic.DataSource* @Author: yyalin* @CreateDate: 2023/7/17 14:09*/private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);if (Objects.nonNull(methodSignature)) {return dataSourceAnnotation;} else {Class<?> dsClass = joinPoint.getTarget().getClass();return dsClass.getAnnotation(DataSource.class);}}
}

4.3、进行数据源切换

//@Mapper 与 启动类的@MapperScan({"com.example.demo.mapper"}) 二选一即可
@Repository
public interface StudentMapper extends BaseMapper<Student> {/*** 功能描述:在mysql_db_01中查询数据* @MethodName: findStudentById* @MethodParam: [id]* @Return: com.wonders.entity.Student* @Author: yyalin* @CreateDate: 2023/7/17 14:20*/@DataSource(value = DbsConstant.oracle_db_01)Student findStudentById(String id);
}

或在service层

@Service
public class StudentServiceImpl implements StudentService{@Autowiredprivate StudentMapper studentMapper;//注解加在实现层才能生效@DataSource(value = DbsConstant.mysql_db_01)@Overridepublic Student findStudentById(String id) {return studentMapper.selectById(id);}
}

4.4、测试效果


@ApiOperation(value="使用注解方式动态切换多数据源", notes="test02")@GetMapping("/test02")public Student test02(String id){Student student=studentMapper.findStudentById(id);return student;}

–结果如下:

图片

五、功能点

1、使用注解的方式来动态进行数据源的切换;

2、支持动态新增新的数据源;

3、支持oracle\mysql等常见数据库切换。

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

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

相关文章

dyld: Library not loaded: /usr/lib/swift/libswiftCoreGraphics.dylib

更新Xcode14后低版本iPhone调试报错 dyld: Library not loaded: /usr/lib/swift/libswiftCoreGraphics.dylib Referenced from: /var/containers/Bundle/Application/…/….app/… Reason: image not found 这是缺少libswiftCoreGraphics库 直接导入libswiftCoreGraphics库即…

python小工具之弱密码检测工具

一、引用的python模块 Crypto&#xff1a; Python中一个强大的加密模块&#xff0c;提供了许多常见的加密算法和工具。它建立在pyc.ypodome或pyc.ypto等底层加密库之上&#xff0c;为Python程序员提供了简单易用的API&#xff0c;使其可以轻松地实现各种加密功能。 commands…

2024新年快乐

前言 2023年马上就要过去&#xff0c;希望2024年会越来越好&#xff0c;根据自己学的内容来实现一些我的idea。 单片机 思路 最开始的构思是这样的&#xff0c;“2024”用数码管来输出然后中文的“新年快乐”用点阵来实现&#xff0c;但是点阵是动态的截图不好看&#xff0…

Jenkins部署项目

一.安装jenkins 1.1进入jenkins官网下载jenkins war包&#xff0c;上传到服务器/usr/local目录。 1.2执行启动jenkins命令&#xff0c;&#xff08;注意jenkins版本需要的jdk版本&#xff09; /usr/local/java11/bin/java -Djava.awt.headlesstrue -jar /usr/local/jenkins.wa…

【Linux】进程控制深度了解

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握Linux下的进程控制 > 毒鸡汤&#xff…

经典卷积神经网络-VGGNet

经典卷积神经网络-VGGNet 一、背景介绍 VGG是Oxford的Visual Geometry Group的组提出的。该网络是在ILSVRC 2014上的相关工作&#xff0c;主要工作是证明了增加网络的深度能够在一定程度上影响网络最终的性能。VGG有两种结构&#xff0c;分别是VGG16和VGG19&#xff0c;两者并…

Cuk、Zeta和Sepic开关电源拓扑结构

Cuk、Zeta和Sepic变换器,三种拓扑结构大致类似。不同点在于电感和二极管&#xff0c;MOS管的位置关系的变化。 Cuk电源是一种非隔离的直流电源转换器&#xff0c;其基本结构包括输入滤波电容、开关管、输入电感、输出电感和输出电容等元件。Cuk电路可以看作是Boost和Buck电路的…

适用于各种危险区域的火焰识别摄像机,实时监测、火灾预防、安全监控,为安全保驾护航

火灾是一种极具破坏力的灾难&#xff0c;对人们的生命和财产造成了严重的威胁。为了更好地预防和防范火灾&#xff0c;火焰识别摄像机作为一种先进的监控设备&#xff0c;正逐渐受到人们的重视和应用。本文将介绍火焰识别摄像机在安全监控和火灾预防方面的全面应用方案。 一、火…

自动驾驶论文

文章目录 一、Convolutional Social Pooling for Vehicle Trajectory Prediction二、QCNet&#xff1a;Query-Centric Trajectory Prediction三、VectorNet: Encoding HD Maps and Agent Dynamics from Vectorized Representation 一、Convolutional Social Pooling for Vehicl…

【Spark精讲】一文讲透Spark宽窄依赖的区别

宽依赖窄依赖的区别 窄依赖&#xff1a;RDD 之间分区是一一对应的宽依赖&#xff1a;发生shuffle&#xff0c;多对多的关系 宽依赖是子RDD的一个分区依赖了父RDD的多个分区父RDD的一个分区的数据&#xff0c;分别流入到子RDD的不同分区特例&#xff1a;cartesian算子对应的Car…

vue +elementui 项目登录通过不同账号切换侧边栏菜单的颜色

前景提要&#xff1a;要求不同权限账号登录侧边栏颜色不一样。分为 theme&#xff1a;1代表默认样式&#xff0c;theme:2代表深色主题样式。 1.首先定义一个主题文件 theme.js&#xff0c;定义两个主题样式 // 主要是切换菜单栏和菜单头部主题的设计&#xff0c;整体主题样式切…

JavaScript系列——正则表达式

文章目录 需求场景正则表达式的定义创建正则表达式通过 / 表示式/ 创建通过构造函数创建 编写一个正则表达式的模式使用简单模式使用特殊字符常用特殊字符列表特殊字符组和范围 正则表达式使用代码演示 常用示例验证手机号码合法性 小结 需求场景 在前端开发领域&#xff0c;在…

MVCC 并发控制原理-源码解析(非常详细)

基础概念 并发事务带来的问题 1&#xff09;脏读&#xff1a;一个事务读取到另一个事务更新但还未提交的数据&#xff0c;如果另一个事务出现回滚或者进一步更新&#xff0c;则会出现问题。 2&#xff09;不可重复读&#xff1a;在一个事务中两次次读取同一个数据时&#xff0c…

数字化制造安全防线:迅软DSE助力通用设备企业终端安全卫士

客户简要介绍 某公司是一家主要生产新型激光打印机、喷墨打印机、其它打印机、精密多功能机、传真机等办公自动化用品的企业。公司与顾客建立长期的信赖忠诚关系”的方针&#xff0c;逐步完善公司的各项运营&#xff0c;不断扩充市场前景。产品除国内销售外&#xff0c;还销往…

uni-app模版(扩展插件)

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

python统计分析——直方图(plt.hist)

使用matplotlib.pyplot.hist()函数绘制直方图 from matplotlib.pyplot as pltdata_setnp.array([2,3,3,4,4,4,4,5,5,6]) plt.hist(fish_data) 下面介绍plt.hist()函数中常用的几个重要参数&#xff08;参数等号后为默认设置&#xff09;&#xff1a; &#xff08;1&#xff0…

WebStorm 创建一个Vue项目(1)

一、下载并安装WebStorm 步骤一 步骤二 选择激活方式 激活码&#xff1a; I2A0QUY8VU-eyJsaWNlbnNlSWQiOiJJMkEwUVVZOFZVIiwibGljZW5zZWVOYW1lIjoiVU5JVkVSU0lEQURFIEVTVEFEVUFMIERFIENBTVBJTkFTIiwiYXNzaWduZWVOYW1lIjoiVGFvYmFv77yaSkVU5YWo5a625qG25rAIOa0uW3peS9nOWup…

[Angular] 笔记 16:模板驱动表单 - 选择框与选项

油管视频&#xff1a; Select & Option (Template Driven Forms) Select & Option 在 pokemon.ts 中新增 interface: export interface Pokemon {id: number;name: string;type: string;isCool: boolean;isStylish: boolean;acceptTerms: boolean; }// new interface…

从0搭建github.io网页

点击跳转到&#x1f517;我的博客文章目录 从0搭建github.io网页 文章目录 从0搭建github.io网页1.成果展示1.1 网址和源码1.2 页面展示 2.new对象2.1 创建仓库 3.github.io仓库的初始化3.1 千里之行&#xff0c;始于足下3.2 _config.yml3.3 一点杂活 4.PerCheung.github.io.p…

工业4G 物联网网关——机房动环监控系统应用方案介绍

机房动环监控系统是什么&#xff1f;机房动环监控系统的全称为机房动力环境监控系统&#xff0c;是一套安装在机房内的监控系统&#xff0c;可以对分散在机房各处的独立动力设备、环境和安防进行实时监测&#xff0c;统计和分析处理相关数据&#xff0c;第一时间侦测到故障发生…