源码篇--Nacos服务--中章(8):Nacos服务端感知客户端实例变更-3

news2024/12/26 10:38:16

文章目录

  • 前言
  • 一、客户端实例变更:
  • 二、实例变更感知:
    • 2.1 实例注册信息通知:
      • 2.1.1 接收DistroDataRequest 请求:
      • 2.1.2 onReceive 处理请求:
      • 2.1.3 processData 处理请求:
      • 2.1.4 handlerClientSyncData 处理数据:
      • 2.1.5 upgradeClient 数据对比和更新:
    • 2.2 服务端节点启动,全量拉取数据:
      • 2.2.1 DistroProtocol对象创建触发全量拉取:
      • 2.2.2 DistroLoadDataTask 全量任务的执行:
      • 2.2.3 load 拉取其它节点实例信息:
      • 2.2.4 loadAllDataSnapshotFromRemote 拉取&处理:
        • 2.2.4.1 获取数据和处理:
        • 2.2.4.2 获取数据:
    • 2.3 集群节点心跳监测:
  • 总结


前言

Nacos 集群中的节点通过distro 协议,grpc 通信互相同步节点中的实例信息;本文对服务端实例同步的3种场景进行介绍;服务端版本 3.0.13。


一、客户端实例变更:

我们知道客户端在启动的时候会与服务端建立grpc 连接,并且在启动完成向其注册实例信息,此时客户端的实例只被保存在服务端的某一个节点上,需要将改实例信息发送到集群中的其它节点;

我们知道Nacos 是集群的,支持进行水平扩展,所以在向集群内添加节点时,新加入的节点也需要获取到其它节点保存的实例信息,并保存到自己的节点上;

Nacos 集群中的节点,可能分布在不同的环境,节点之间需要网络进行连接,此时就不可能避免的出现,集群内某个节点短暂失联的情况,当网络恢复正常后,落后的节点就需要同步其它节点的信息,并覆盖本地的实例信息,从而达到实例信息数据的一致性;

本文对以上提到的3中场景展开进行介绍。

二、实例变更感知:

2.1 实例注册信息通知:

当客户端注册到集群中的某个节点,该节点需要在保存实例注册信息后,也需要负责将注册的实例信息同步到集群内的其它节点;

在这里插入图片描述
在:源码篇–Nacos服务–中章(8):Nacos服务端感知客户端注册-2 ,介绍了集群内节点通信通道的建立;现在就可以使用改通道向集群内其它运行的节点同步客户端实例信息;

2.1.1 接收DistroDataRequest 请求:

DistroDataRequestHandler distro 协议处理类负责对集群内发送的 DistroDataRequest (实例变更请求)请求进行处理;

@Override
public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
    try {
        // 获取操作类型
        switch (request.getDataOperation()) {
            case VERIFY:
                return handleVerify(request.getDistroData(), meta);
            case SNAPSHOT:
                return handleSnapshot();
            case ADD:
            case CHANGE:
            case DELETE:
                // 实例变化类型
                return handleSyncData(request.getDistroData());
            case QUERY:
                return handleQueryData(request.getDistroData());
            default:
                return new DistroDataResponse();
        }
    } catch (Exception e) {
        Loggers.DISTRO.error("[DISTRO-FAILED] distro handle with exception", e);
        DistroDataResponse result = new DistroDataResponse();
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("handle distro request with exception");
        return result;
    }
}
private DistroDataResponse handleSyncData(DistroData distroData) {
 DistroDataResponse result = new DistroDataResponse();
    // 请求处理
    if (!distroProtocol.onReceive(distroData)) {
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("[DISTRO-FAILED] distro data handle failed");
    }
    return result;
}

2.1.2 onReceive 处理请求:

从请求的DistroData 参数中解析出来发送端注册的客户端实例信息,然后与本节点进行对比,完成对本节点注册实例的更新;

/**
 * Receive synced distro data, find processor to process.
 *
 * @param distroData Received data
 * @return true if handle receive data successfully, otherwise false
 */
public boolean onReceive(DistroData distroData) {
    Loggers.DISTRO.info("[DISTRO] Receive distro data type: {}, key: {}", distroData.getType(),
            distroData.getDistroKey());
    // 获取资源类型: Nacos:Naming:v2:ClientData
    String resourceType = distroData.getDistroKey().getResourceType();
    // 获取资源处理器
    DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
    if (null == dataProcessor) {
        Loggers.DISTRO.warn("[DISTRO] Can't find data process for received data {}", resourceType);
        return false;
    }
    // 数据处理
    return dataProcessor.processData(distroData);
}

2.1.3 processData 处理请求:

对CHANGE 事件做出处理,先反序列化得到原始数据,然后进行处理;

@Override
public boolean processData(DistroData distroData) {
    switch (distroData.getType()) {
        case ADD:
        case CHANGE:
            // 反序列化,传入的调用sync 接口的节点下注册的实例信息
            ClientSyncData clientSyncData = ApplicationUtils.getBean(Serializer.class)
                    .deserialize(distroData.getContent(), ClientSyncData.class);
            // 对实例信息进行处理
            handlerClientSyncData(clientSyncData);
            return true;
        case DELETE:
            String deleteClientId = distroData.getDistroKey().getResourceKey();
            Loggers.DISTRO.info("[Client-Delete] Received distro client sync data {}", deleteClientId);
            clientManager.clientDisconnected(deleteClientId);
            return true;
        default:
            return false;
    }
}

2.1.4 handlerClientSyncData 处理数据:

创建本节点client(对应发送端),集群内对每个节点(除了自己之外)都会建立对应的client 客户端(根据客户端的id 进行区分),后续可以使用其客户端,进行请求的发送;

private void handlerClientSyncData(ClientSyncData clientSyncData) {
    Loggers.DISTRO
            .info("[Client-Add] Received distro client sync data {}, revision={}", clientSyncData.getClientId(),
                    clientSyncData.getAttributes().getClientAttribute(ClientConstants.REVISION, 0L));
    // 创建与发送请求节点服务的client 对象
    clientManager.syncClientConnected(clientSyncData.getClientId(), clientSyncData.getAttributes());
    Client client = clientManager.getClient(clientSyncData.getClientId());
    // 数据更新
    upgradeClient(client, clientSyncData);
}

2.1.5 upgradeClient 数据对比和更新:

先保存发送过来的客户端信息,然后在与本地保存的客户端信息进行对比,剔除掉本地过时的客户端实例信息;

private void upgradeClient(Client client, ClientSyncData clientSyncData) {
    Set<Service> syncedService = new HashSet<>();
    // process batch instance sync logic
    processBatchInstanceDistroData(syncedService, client, clientSyncData);
    List<String> namespaces = clientSyncData.getNamespaces();
    List<String> groupNames = clientSyncData.getGroupNames();
    List<String> serviceNames = clientSyncData.getServiceNames();
    List<InstancePublishInfo> instances = clientSyncData.getInstancePublishInfos();
    
    for (int i = 0; i < namespaces.size(); i++) {
        // 遍历命名空间
        // 组装服务实例
        Service service = Service.newService(namespaces.get(i), groupNames.get(i), serviceNames.get(i));
        // 获取本节点之前已经存在的服务注册实例
        Service singleton = ServiceManager.getInstance().getSingleton(service);
        syncedService.add(singleton);
        // 获取服务实例信息
        InstancePublishInfo instancePublishInfo = instances.get(i);
        if (!instancePublishInfo.equals(client.getInstancePublishInfo(singleton))) {
            // 与本地缓存的 服务实例进行对比
            // 不相同,则在服务实例添加到 改client  下 ConcurrentHashMap<Service, InstancePublishInfo> publishers 中
            client.addServiceInstance(singleton, instancePublishInfo);
            // 发布服务实例注册事件
            NotifyCenter.publishEvent(
                    new ClientOperationEvent.ClientRegisterServiceEvent(singleton, client.getClientId()));
            NotifyCenter.publishEvent(
                    new MetadataEvent.InstanceMetadataEvent(singleton, instancePublishInfo.getMetadataId(), false));
        }
    }
    for (Service each : client.getAllPublishedService()) {
        // 遍历本节点的所有服务注册实例key
        if (!syncedService.contains(each)) {
            // 本节点已经失效的服务注册实例,进行实例异常
            client.removeServiceInstance(each);
            NotifyCenter.publishEvent(
                    new ClientOperationEvent.ClientDeregisterServiceEvent(each, client.getClientId()));
        }
    }
    client.setRevision(clientSyncData.getAttributes().<Integer>getClientAttribute(ClientConstants.REVISION, 0));
}

2.2 服务端节点启动,全量拉取数据:

在这里插入图片描述

集群内某个节点在启动时,全量拉取其它节点的实例信息,进行整理并保存到该节点的实例注册信息中;改部分的工作是在 DistroProtocol 对象构建时通过startLoadTask() 方法进行的;

2.2.1 DistroProtocol对象创建触发全量拉取:

public DistroProtocol(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder,
        DistroTaskEngineHolder distroTaskEngineHolder) {
    this.memberManager = memberManager;
    this.distroComponentHolder = distroComponentHolder;
    this.distroTaskEngineHolder = distroTaskEngineHolder;
    // 开始任务
    startDistroTask();
}

private void startDistroTask() {
    if (EnvUtil.getStandaloneMode()) {
        isInitialized = true;
        return;
    }
    // 校验的定时任务 每隔5s 发送一次
    startVerifyTask();
    // 启动加载任务
    startLoadTask();
}
private void startLoadTask() {
    DistroCallback loadCallback = new DistroCallback() {
        @Override
        public void onSuccess() {
            isInitialized = true;
        }
        
        @Override
        public void onFailed(Throwable throwable) {
            isInitialized = false;
        }
    };
    // 值执行一次
    GlobalExecutor.submitLoadDataTask(
            new DistroLoadDataTask(memberManager, distroComponentHolder, DistroConfig.getInstance(), loadCallback));
}

2.2.2 DistroLoadDataTask 全量任务的执行:

public DistroLoadDataTask(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder,
            DistroConfig distroConfig, DistroCallback loadCallback) {
   this.memberManager = memberManager;
    this.distroComponentHolder = distroComponentHolder;
    this.distroConfig = distroConfig;
    this.loadCallback = loadCallback;
    loadCompletedMap = new HashMap<>(1);
}

@Override
public void run() {
    try {
        // 加载
        load();
        if (!checkCompleted()) {
            GlobalExecutor.submitLoadDataTask(this, distroConfig.getLoadDataRetryDelayMillis());
        } else {
            loadCallback.onSuccess();
            Loggers.DISTRO.info("[DISTRO-INIT] load snapshot data success");
        }
    } catch (Exception e) {
        loadCallback.onFailed(e);
        Loggers.DISTRO.error("[DISTRO-INIT] load snapshot data failed. ", e);
    }
}

2.2.3 load 拉取其它节点实例信息:

遍历集群内除了自己的节点,只要有一个节点返回了注册的实例信息就可以进行本节点实例信息的更新;

private void load() throws Exception {
     // 集群内只有自己,不需要加载数据
     while (memberManager.allMembersWithoutSelf().isEmpty()) {
         Loggers.DISTRO.info("[DISTRO-INIT] waiting server list init...");
         TimeUnit.SECONDS.sleep(1);
     }
     // 数据存储对象是否为空
     while (distroComponentHolder.getDataStorageTypes().isEmpty()) {
         Loggers.DISTRO.info("[DISTRO-INIT] waiting distro data storage register...");
         TimeUnit.SECONDS.sleep(1);
     }
     for (String each : distroComponentHolder.getDataStorageTypes()) {
         if (!loadCompletedMap.containsKey(each) || !loadCompletedMap.get(each)) {
             // 从远处加载快照数据
             loadCompletedMap.put(each, loadAllDataSnapshotFromRemote(each));
         }
     }
 }

2.2.4 loadAllDataSnapshotFromRemote 拉取&处理:

向集群中的某一节点发送 DistroDataRequest 事件是 SNAPSHOT,获取到返回的注册实例信息;反序列化得到原始数据,进行改节点的实例信息保存;

2.2.4.1 获取数据和处理:
private boolean loadAllDataSnapshotFromRemote(String resourceType) {
  // 传输代理类
    DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType);
    // 数据处理器
    DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);
    if (null == transportAgent || null == dataProcessor) {
        Loggers.DISTRO.warn("[DISTRO-INIT] Can't find component for type {}, transportAgent: {}, dataProcessor: {}",
                resourceType, transportAgent, dataProcessor);
        return false;
    }
    for (Member each : memberManager.allMembersWithoutSelf()) {
        // 遍历集群内其它节点
        long startTime = System.currentTimeMillis();
        try {
            Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {}", resourceType, each.getAddress());
            // 从集群内其它节点获取注册的信息
            DistroData distroData = transportAgent.getDatumSnapshot(each.getAddress());
            Loggers.DISTRO.info("[DISTRO-INIT] it took {} ms to load snapshot {} from {} and snapshot size is {}.",
                    System.currentTimeMillis() - startTime, resourceType, each.getAddress(),
                    getDistroDataLength(distroData));
            // 处理
            boolean result = dataProcessor.processSnapshot(distroData);
            Loggers.DISTRO
                    .info("[DISTRO-INIT] load snapshot {} from {} result: {}", resourceType, each.getAddress(),
                            result);
            if (result) {
                // 设置数据处理完毕标识
                distroComponentHolder.findDataStorage(resourceType).finishInitial();
                return true;
            }
        } catch (Exception e) {
            Loggers.DISTRO.error("[DISTRO-INIT] load snapshot {} from {} failed.", resourceType, each.getAddress(), e);
        }
    }
    return false;
}
2.2.4.2 获取数据:

(1) DistroDataRequest 请求发送获取数据

 @Override
  public DistroData getDatumSnapshot(String targetServer) {
      // 获取集群内改节点信息
      Member member = memberManager.find(targetServer);
      if (checkTargetServerStatusUnhealthy(member)) {
          throw new DistroException(
                  String.format("[DISTRO] Cancel get snapshot caused by target server %s unhealthy", targetServer));
      }
      // 构建 DistroDataRequest 对象
      DistroDataRequest request = new DistroDataRequest();
      request.setDataOperation(DataOperation.SNAPSHOT);
      try {
          // 通过 grpc 发送普通的request 请求
          Response response = clusterRpcClientProxy
                  .sendRequest(member, request, DistroConfig.getInstance().getLoadDataTimeoutMillis());
          if (checkResponse(response)) {
              return ((DistroDataResponse) response).getDistroData();
          } else {
              throw new DistroException(
                      String.format("[DISTRO-FAILED] Get snapshot request to %s failed, code: %d, message: %s",
                              targetServer, response.getErrorCode(), response.getMessage()));
          }
      } catch (NacosException e) {
          throw new DistroException("[DISTRO-FAILED] Get distro snapshot failed! ", e);
      }
  }

(2)集群其它节点接收DistroDataRequest 并处理SNAPSHOT 事件:

DistroDataRequestHandler #handle 负责请求的处理;

@Override
public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException {
    try {
        // 获取操作类型
        switch (request.getDataOperation()) {
            case VERIFY:
                return handleVerify(request.getDistroData(), meta);
            case SNAPSHOT:
                // 返回改节点下的注册实例信息
                return handleSnapshot();
            case ADD:
            case CHANGE:
            case DELETE:
                // 实例变化类型
                return handleSyncData(request.getDistroData());
            case QUERY:
                return handleQueryData(request.getDistroData());
            default:
                return new DistroDataResponse();
        }
    } catch (Exception e) {
        Loggers.DISTRO.error("[DISTRO-FAILED] distro handle with exception", e);
        DistroDataResponse result = new DistroDataResponse();
        result.setErrorCode(ResponseCode.FAIL.getCode());
        result.setMessage("handle distro request with exception");
        return result;
    }
}

快照信息获取1:

private DistroDataResponse handleSnapshot() {
    DistroDataResponse result = new DistroDataResponse();
    // 获取快照信息
    DistroData distroData = distroProtocol.onSnapshot(DistroClientDataProcessor.TYPE);
    result.setDistroData(distroData);
    return result;
}

    

快照信息获取2:

/**
* Query all datum snapshot.
  *
  * @param type datum type
  * @return all datum snapshot
  */
 public DistroData onSnapshot(String type) {
     DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(type);
     if (null == distroDataStorage) {
         Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", type);
         return new DistroData(new DistroKey("snapshot", type), new byte[0]);
     }
     return distroDataStorage.getDatumSnapshot();
 }

快照信息获取3:

@Override
public DistroData getDatumSnapshot() {
    List<ClientSyncData> datum = new LinkedList<>();
    // 遍历注册的客户端信息
    for (String each : clientManager.allClientId()) {
        Client client = clientManager.getClient(each);
        if (null == client || !client.isEphemeral()) {
            continue;
        }
        datum.add(client.generateSyncData());
    }
    ClientSyncDatumSnapshot snapshot = new ClientSyncDatumSnapshot();
    snapshot.setClientSyncDataList(datum);
    byte[] data = ApplicationUtils.getBean(Serializer.class).serialize(snapshot);
    return new DistroData(new DistroKey(DataOperation.SNAPSHOT.name(), TYPE), data);
}
 @Override
 public ClientSyncData generateSyncData() {
     List<String> namespaces = new LinkedList<>();
     List<String> groupNames = new LinkedList<>();
     List<String> serviceNames = new LinkedList<>();
 
     List<String> batchNamespaces = new LinkedList<>();
     List<String> batchGroupNames = new LinkedList<>();
     List<String> batchServiceNames = new LinkedList<>();
     
     List<InstancePublishInfo> instances = new LinkedList<>();
     List<BatchInstancePublishInfo> batchInstancePublishInfos = new LinkedList<>();
     BatchInstanceData  batchInstanceData = new BatchInstanceData();
     // 遍历改服务端下注册的客户端实例
     for (Map.Entry<Service, InstancePublishInfo> entry : publishers.entrySet()) {
         InstancePublishInfo instancePublishInfo = entry.getValue();
         if (instancePublishInfo instanceof BatchInstancePublishInfo) {
             BatchInstancePublishInfo batchInstance = (BatchInstancePublishInfo) instancePublishInfo;
             batchInstancePublishInfos.add(batchInstance);
             buildBatchInstanceData(batchInstanceData, batchNamespaces, batchGroupNames, batchServiceNames, entry);
             batchInstanceData.setBatchInstancePublishInfos(batchInstancePublishInfos);
         } else {
             namespaces.add(entry.getKey().getNamespace());
             groupNames.add(entry.getKey().getGroup());
             serviceNames.add(entry.getKey().getName());
             instances.add(entry.getValue());
         }
     }
     // 返回当前服务端下注册的所有客户端实例
     ClientSyncData data = new ClientSyncData(getClientId(), namespaces, groupNames, serviceNames, instances, batchInstanceData);
     data.getAttributes().addClientAttribute(REVISION, getRevision());
     return data;
}

2.3 集群节点心跳监测:

篇幅原因,此章节放到 源码篇–Nacos服务–中章(8):Nacos服务端感知客户端实例变更(集群数据校验)-4 ,进行介绍。


总结

本文对Nacos 集群内实例注册的感知,对实例的注册;Nacos 集群节点启动,实例信息的同步进行介绍。

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

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

相关文章

C++学习第十四课:运算符类型与运算符重载

C学习第十四课&#xff1a;运算符类型与运算符重载 在C中&#xff0c;运算符重载是一种使得自定义类型&#xff08;如类对象&#xff09;能够使用C内建运算符的能力。运算符重载允许程序员定义运算符对用户定义类型的特殊行为&#xff0c;这增加了程序的可读性和自然表达能力。…

【介绍下Android开发环境的搭建】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

iOS ------ Method Swizzling (动态方法交换)

一&#xff0c;Method Swizzling 简介 Method&#xff08;方法&#xff09;对应的是objc_method结构体&#xff1b;而objc_method结构体中包含了SEL method_name(方法名&#xff09;&#xff0c;IMP method_imp&#xff08;方法实现&#xff09; // objc_method 结构体 typed…

【GESP】2023年09月图形化一级 -- 小鸡躲球

小鸡躲球 1. 准备工作 (1)删除默认小猫角色。 (2)添加角色Chick和Ball。 (3)删除默认白色背景,添加背景Blue Sky。 2. 功能实现 (1)点击绿旗,小鸡角色站在地面上,初始位置为(X=-140,Y=-120),初始方向为90,旋转方式为任意旋转。在小鸡角色中设置舞台的初始…

办公楼智慧公厕自发电门锁感应器无需电池供电,环保节能!

在当今科技高速发展的时代&#xff0c;办公楼智慧公厕的自发电门锁感应器成以其独特的优势&#xff0c;为办公楼的卫生设施管理带来了全新的变革。这款感应器无需电池供电&#xff0c;真正做到了环保节能&#xff0c;同时在免电池、免维护、信号稳定、门锁升级、把手设计、应用…

编译工具各版本与操作系统版本号兼容性冷知识 : JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况

编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号的兼容情况 文章目录 编译工具各版本与操作系统版本号兼容性冷知识 &#x1f9e0;: JetBrains IntelliJ IDEA 各个主要版本及其对应的操作系统版本号…

深入OceanBase分布式数据库:MySQL 模式下的 SQL 基本操作

码到三十五 &#xff1a; 个人主页 OceanBase与MySQL模式下兼容性序 在当今的大数据时代&#xff0c;数据库技术的选择对于企业的信息化发展至关重要。OceanBase作为一种高性能、高可用的分布式关系数据库&#xff0c;在与MySQL模式的兼容性方面展现出了显著的优势&#xff0c…

qt5-入门-2D绘图-基础

参考&#xff1a; QPainter_w3cschool https://www.w3cschool.cn/learnroadqt/k7zd1j4l.html C GUI Programming with Qt 4, Second Edition 本地环境&#xff1a; win10专业版&#xff0c;64位&#xff0c;Qt 5.12 代码已经测试通过。其他例子日后更新。 目录 基础知识penb…

微信小程序个人中心、我的界面(示例四)

微信小程序个人中心、我的界面&#xff0c;九宫格简单布局&#xff08;示例四&#xff09; 微信小程序个人中心、我的界面&#xff0c;超简洁的九宫格界面布局&#xff0c;代码粘贴即用。更多微信小程序界面示例&#xff0c;请进入我的主页哦&#xff01; 1、js代码 Page({…

gateway全局token过滤器

添加gateway依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>创建一个tokenFilter 实现全局过滤器GlobalFilter,并且实现fitler方法 Value("${…

PDF高效编辑器,支持修改PDF文档并转换格式从PDF文件转换成图片文件,轻松管理你的文档世界!

PDF文件已成为我们工作、学习和生活中不可或缺的一部分。然而&#xff0c;传统的PDF阅读器往往只能满足简单的查看需求&#xff0c;对于需要频繁编辑、修改或转换格式的用户来说&#xff0c;就显得力不从心。现在&#xff0c;我们为您带来一款全新的PDF高效编辑器&#xff0c;让…

Vue 组件单元测试深度探索:细致解析与实战范例大全

Vue.js作为一款广受欢迎的前端框架&#xff0c;以其声明式的数据绑定、组件化开发和灵活的生态系统赢得了广大开发者的心。然而&#xff0c;随着项目规模的增长&#xff0c;确保组件的稳定性和可靠性变得愈发关键。单元测试作为软件质量的守护神&#xff0c;为Vue组件的开发过程…

开源AI智能名片商城小程序:深度解读IMC(IP、MarTech、Content)视角

在数字化浪潮中&#xff0c;私域流量的运营已成为企业不可或缺的增长引擎。而开源AI智能名片商城小程序&#xff0c;则是以一种全新的视角——IMC&#xff08;IP、MarTech、Content&#xff09;&#xff0c;为企业打开私域流量运营的新篇章。今天&#xff0c;我们就来一起深入解…

mac安装软件遇到无法验证开发者的解决方法

现象 无法打开“”&#xff0c;因为无法验证开发者。这种情况说明正在安装的软件里面有的包被系统判定为不安全的。 解决方法 1、点击取消按钮 2、系统设置-隐私与安全性&#xff0c;点击打开按钮

python可视化图表

1.测试数据准备 2011年1月销售数据.txt 2011-01-01,4b34218c-9f37-4e66-b33e-327ecd5fb897,1689,湖南省 2011-01-01,5b6a6417-9a16-4243-9704-255719074bff,2353,河北省 2011-01-01,ae240260-68a9-4e59-b4c9-206be4c08a8d,2565,湖北省 2011-01-02,c833e851-880f-4e05-9de5-b5…

第6篇:创建Nios II工程之控制LED<一>

Q&#xff1a;还记得第1篇吗&#xff1f;设计简单的逻辑电路&#xff0c;控制DE2-115开发板上LED的亮与熄灭&#xff0c;一行Verilog HDL的assign赋值语句即可实现。本期开始创建Nios II工程&#xff0c;用C语言代码控制DE2-115开发板上的LED实现流水灯效果。 A&#xff1a;在…

力扣141.环形链表142.环形链表Ⅱ 附证明

题目链接&#xff1a; 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 142. 环形链表 II - 力扣&#xff08;LeetCode&#xff09; 141.环形链表 方法思路&#xff1a;快慢指针 代码: class Solution { public:bool hasCycle(ListNode *head) {if(!head){return fa…

基于uniapp vue3.0 uView 做一个点单页面(包括加入购物车动画和左右联动)

1、实现效果&#xff1a; 下拉有自定义组件&#xff08;商品卡片、进步器、侧边栏等&#xff09;源码 2、左右联动功能 使用scroll-view来做右边的菜单页&#xff0c;title的id动态绑定充当锚点 <scroll-view :scroll-into-view"toView" scroll-with-animation…

vue+element之解决upload组件上传文件失败后仍显示在列表上、自动上传、过滤、findIndex、splice、filter

MENU 前言错误案例(没有用)正确方法结束语 前言 el-upload上传失败后&#xff0c;文件仍显示在列表上。 这个pdf文件上传失败&#xff0c;仍显示在列表&#xff0c;给人错觉是上传成功&#xff0c;所以要把它去掉。 在element中&#xff0c;file-list和v-model:file-list是用于…

Codigger数据篇(中):数据可控性的灵活配置

在数据服务领域中&#xff0c;数据可控性无疑是至关重要的一环。数据可控性不仅关乎数据的安全性和隐私性&#xff0c;更直接影响到数据价值的实现。Codigger&#xff0c;在其数据可控性方面的灵活配置&#xff0c;为用户提供了更加便捷、高效的数据管理体验。 一、自主选择数…