Nacos源码—8.Nacos升级gRPC分析六

news2025/5/13 6:02:10

大纲

7.服务端对服务实例进行健康检查

8.服务下线如何注销注册表和客户端等信息

9.事件驱动架构源码分析

一.处理ClientChangedEvent事件

也就是同步数据到集群节点:

public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {
    ...
    @Override
    public void onEvent(Event event) {
        ...
        if (event instanceof ClientEvent.ClientVerifyFailedEvent) {
            syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);
        } else {
            syncToAllServer((ClientEvent) event);
        }
    }

    private void syncToAllServer(ClientEvent event) {
        Client client = event.getClient();
        //Only ephemeral data sync by Distro, persist client should sync by raft.
        //临时实例使用Distro协议,持久化实例使用Raft协议
        //ClientManager.isResponsibleClient()方法,判断只有该client的责任节点才能进行集群数据同步
        if (null == client || !client.isEphemeral() || !clientManager.isResponsibleClient(client)) {
            return;
        }
        if (event instanceof ClientEvent.ClientDisconnectEvent) {
            //如果event是客户端注销实例时需要进行集群节点同步的事件
            DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
            distroProtocol.sync(distroKey, DataOperation.DELETE);
        } else if (event instanceof ClientEvent.ClientChangedEvent) {
            //如果event是客户端注册实例时需要进行集群节点同步的事件
            DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);
            distroProtocol.sync(distroKey, DataOperation.CHANGE);
        }
    }
    ...
}

@Component
public class DistroProtocol {
    private final ServerMemberManager memberManager;
    private final DistroTaskEngineHolder distroTaskEngineHolder;
    ...

    //Start to sync by configured delay.
    public void sync(DistroKey distroKey, DataOperation action) {
        sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis());
    }

    //Start to sync data to all remote server.
    public void sync(DistroKey distroKey, DataOperation action, long delay) {
        //遍历集群中除自身节点外的其他节点
        for (Member each : memberManager.allMembersWithoutSelf()) {
            syncToTarget(distroKey, action, each.getAddress(), delay);
        }
    }

    //Start to sync to target server.
    public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) {
        //先把要同步的集群节点targetServer包装成DistroKey对象
        DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(), targetServer);
        //然后根据DistroKey对象创建DistroDelayTask任务
        DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);
        //接着调用NacosDelayTaskExecuteEngine.addTask()方法
        //往延迟任务执行引擎DistroDelayTaskExecuteEngine中添加延迟任务DistroDelayTask
        distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);
        if (Loggers.DISTRO.isDebugEnabled()) {
            Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer);
        }
    }
    ...
}

二.处理ClientDeregisterServiceEvent事件

也就是移除注册表 + 订阅表的服务实例:

@Component
public class ClientServiceIndexesManager extends SmartSubscriber {
    //注册表(服务提供者),一个Service服务对象,对应多个服务实例的clientId
    private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();

    //订阅者列表(服务消费者),一个Service服务对象,对应多个订阅者的clientId
    private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();

    ...
    @Override
    public void onEvent(Event event) {
        if (event instanceof ClientEvent.ClientDisconnectEvent) {
            handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);
        } else if (event instanceof ClientOperationEvent) {
            handleClientOperation((ClientOperationEvent) event);
        }
    }

    private void handleClientOperation(ClientOperationEvent event) {
        Service service = event.getService();
        String clientId = event.getClientId();
        if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
            //处理客户端注册事件ClientRegisterServiceEvent
            addPublisherIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
            //处理客户端注销事件ClientDeregisterServiceEvent
            removePublisherIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
            //处理客户端订阅服务事件ClientSubscribeServiceEvent
            addSubscriberIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
            //处理客户端取消订阅事件ClientUnsubscribeServiceEvent
            removeSubscriberIndexes(service, clientId);
        }
    }

    private void removePublisherIndexes(Service service, String clientId) {
        if (!publisherIndexes.containsKey(service)) {
            return;
        }
        //移除注册表中的服务实例
        publisherIndexes.get(service).remove(clientId);
        //发布服务改变事件ServiceChangedEvent
        NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
    }
    ...
}

三.处理ServiceChangeEvent事件

也就是通知订阅了该服务的客户端:

@org.springframework.stereotype.Service
public class NamingSubscriberServiceV2Impl extends SmartSubscriber implements NamingSubscriberService {
    ...
    @Override
    public void onEvent(Event event) {
        if (!upgradeJudgement.isUseGrpcFeatures()) {
            return;
        }
        if (event instanceof ServiceEvent.ServiceChangedEvent) {
            //If service changed, push to all subscribers.
            //如果服务变动,会向Service服务的所有订阅者推送Service服务的实例信息,让订阅者(客户端)更新本地缓存
            ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event;
            Service service = serviceChangedEvent.getService();
            //调用NacosDelayTaskExecuteEngine.addTask()方法,往延迟任务执行引擎添加任务
            delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));
        } else if (event instanceof ServiceEvent.ServiceSubscribedEvent) {
            //If service is subscribed by one client, only push this client.
            //如果Service服务被一个客户端订阅,则只推送Service服务的实例信息给该客户端
            ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;
            Service service = subscribedEvent.getService();
            //调用NacosDelayTaskExecuteEngine.addTask()方法,往延迟任务执行引擎添加任务
            delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay(), subscribedEvent.getClientId()));
        }
    }
    ...
}

(3)总结

9.事件驱动架构源码分析

(1)如何使用Nacos的事件发布

(2)Nacos通知中心的事件发布源码

(3)Nacos通知中心注册订阅者的源码

Nacos 2.x大量使用了事件发布的动作,比如客户端注册服务实例、客户端下线服务实例、服务改变、服务订阅等。

(1)如何使用Nacos的事件发布

一.首先自定义一个事件

下面定义了一个名为TestEvent的事件,继承自Nacos的Event类。

import com.alibaba.nacos.common.notify.Event;

public class TestEvent extends Event {

}

二.然后定义一个订阅者

有了事件之后,还需要一个订阅者,这样发布的事件才能被这个订阅者进行处理。

自定义的订阅者需要继承Nacos的SmartSubscriber抽象类,自定义的订阅者需要实现三个方法。

方法一:构造方法

需要将自定义的订阅者注册到Nacos的通知中心NotifyCenter里,这样NotifyCenter在发布自定义事件时,才能让自定义的订阅者进行响应。

方法二:subscribeTypes()方法

实现该方法时,需要把自定义的事件添加到方法的返回结果中,所以可以通过该方法获取自定义订阅者监听了哪些事件。

方法三:onEvent()方法

Nacos的通知中心NotifyCenter在发布自定义事件时,便会调用该方法,所以该方法中需要实现自定义订阅者对自定义事件的处理。

import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.SmartSubscriber;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;

//自定义的订阅者需要继承Nacos的SmartSubscriber抽象类
@Component
public class TestSubscriber extends SmartSubscriber {
    //构造方法中需要将自定义的订阅者TestSubscriber注册到Nacos的通知中心NotifyCenter
    public TestSubscriber() {
        NotifyCenter.registerSubscriber(this);
    }

    //实现subscribeTypes()方法时,把自定义的事件TestEvent添加进去返回
    @Override
    public List<Class<? extends Event>> subscribeTypes() {
        List<Class<? extends Event>> result = new LinkedList<>();
        result.add(TestEvent.class);
        return result;
    }

    //实现onEvent()方法
    //当Nacos的通知中心NotifyCenter发布一个TestEvent事件时,就会响应该方法处理订阅者的逻辑
    @Override
    public void onEvent(Event event) {
        System.out.println("TestSubscriber onEvent");
    }
}

三.最后通过Nacos的通知中心NotifyCenter发布自定义事件

这样便完成了自定义事件、自定义订阅者通过Nacos实现发布订阅功能。

@RestController
@RequestMapping("/sub/")
public class SubscriberController {
    @GetMapping("/test")
    public void test() {
        NotifyCenter.publishEvent(new TestEvent());    
    }
}

(2)Nacos通知中心的事件发布源码

通知中心NotifyCenter执行publishEvent()方法发布事件时,比如会调用DefaultPublisher的publish()方法来发布事件。

DefaultPublisher的publish()方法会先把事件放入到一个阻塞队列queue中,而在DefaultPublisher创建时会启动一个线程从阻塞队列取出事件来处理。处理时就会调用到DefaultPublisher的receiveEvent()方法通知事件订阅者,也就是执行DefaultPublisher的notifySubscriber()方法通知事件订阅者。

在DefaultPublisher的notifySubscriber()方法中,首先会创建一个调用订阅者的onEvent()方法的任务,然后如果订阅者有线程池,则将任务提交给订阅者的线程池去执行。如果订阅者没有线程池,则直接执行该任务。

可见事件的发布也使用了阻塞队列 + 异步任务,来实现对订阅者的通知。

public class NotifyCenter {
    private static final NotifyCenter INSTANCE = new NotifyCenter();
    //key是事件Class的canonicalName,value是EventPublisher对象,一个事件对应一个EventPublisher对象
    //在EventPublisher对象中就会包含订阅了该事件的所有订阅者
    //EventPublisher的实现类有DefaultPublisher、NamingEventPublisher
    private final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);
    ...

    //Request publisher publish event Publishers load lazily, calling publisher. Start () only when the event is actually published.
    public static boolean publishEvent(final Event event) {
        try {
            return publishEvent(event.getClass(), event);
        } catch (Throwable ex) {
            LOGGER.error("There was an exception to the message publishing : ", ex);
            return false;
        }
    }

    //Request publisher publish event Publishers load lazily, calling publisher.
    private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {
        if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {
            return INSTANCE.sharePublisher.publish(event);
        }
        //获取发布的事件的Class的canonicalName
        final String topic = ClassUtils.getCanonicalName(eventType);
        //根据发布事件类型获取EventPublisher对象,该对象中会包含所发布事件的所有订阅者信息
        EventPublisher publisher = INSTANCE.publisherMap.get(topic);
        if (publisher != null) {
            //比如调用DefaultPublisher.publish()方法发布事件
            return publisher.publish(event);
        }
        LOGGER.warn("There are no [{}] publishers for this event, please register", topic);
        return false;
    }
    ...
}

//The default event publisher implementation.
//一个事件只会对应一个DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {
    private Class<? extends Event> eventType;

    //阻塞队列存放待发布的事件
    private BlockingQueue<Event> queue;

    //Class为eventType的事件的所有订阅者
    protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();

    @Override
    public void init(Class<? extends Event> type, int bufferSize) {
        ...
        start();
    }

    @Override
    public synchronized void start() {
        if (!initialized) {
            super.start();
            ...
        }
    }

    @Override
    public void run() {
        openEventHandler();
    }

    void openEventHandler() {
        try {
            ...
            for (; ;) {
                ...
                //从阻塞队列取数据
                final Event event = queue.take();
                //处理事件
                receiveEvent(event);
                ...
            }
        } catch (Throwable ex) {
            LOGGER.error("Event listener exception : ", ex);
        }
    }
    ...

    @Override
    public boolean publish(Event event) {
        checkIsStart();
        //把事件放入到了一个阻塞队列queue中,由DefaultPublisher创建时启动的线程来处理
        boolean success = this.queue.offer(event);
        if (!success) {//如果事件放入阻塞队列失败,则直接处理
            LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
            //通知事件的订阅者去进行事件处理
            receiveEvent(event);
            return true;
        }
        return true;
    }

    //通知事件的订阅者去进行事件处理
    void receiveEvent(Event event) {
        ...
        //遍历当前事件的订阅者,对订阅者执行notifySubscriber()方法,实际上就是执行订阅者的onEvent()方法
        for (Subscriber subscriber : subscribers) {
            ...
            //触发执行订阅者的onEvent()方法,实现对订阅者的通知
            notifySubscriber(subscriber, event);
        }
    }

    @Override
    public void notifySubscriber(final Subscriber subscriber, final Event event) {
        //创建一个任务,该任务会调用订阅者的onEvent方法
        final Runnable job = () -> subscriber.onEvent(event);
        final Executor executor = subscriber.executor();
        if (executor != null) {
            //将任务提交给订阅者的线程池去执行
            executor.execute(job);
        } else {
            try {
                //如果订阅者没有线程池,则直接执行该任务
                job.run();
            } catch (Throwable e) {
                LOGGER.error("Event callback exception: ", e);
            }
        }
    }
    ...
}

(3)Nacos通知中心注册订阅者的源码

在执行NotifyCenter的registerSubscriber()方法注册订阅者时,会调用订阅者实现的subscribeTypes()方法获取订阅者要监听的所有事件,然后遍历这些事件并调用NotifyCenter的addSubscriber()方法。

执行NotifyCenter的addSubscriber()方法时会为这些事件添加订阅者。由于每个事件都会对应一个EventPublisher对象,所以会先从NotifyCenter.publisherMap中获取EventPublisher对象,然后调用EventPublisher的addSubscriber()方法向EventPublisher添加订阅者,从而完成向通知中心注册订阅者。

public class NotifyCenter {
    private static final NotifyCenter INSTANCE = new NotifyCenter();

    //key是事件Class的canonicalName,value是EventPublisher对象,一个事件对应一个EventPublisher对象
    //在EventPublisher对象中就会包含订阅了该事件的所有订阅者
    //EventPublisher的实现类有DefaultPublisher、NamingEventPublisher
    private final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);
    ...

    public static void registerSubscriber(final Subscriber consumer) {
        //注册订阅者
        registerSubscriber(consumer, DEFAULT_PUBLISHER_FACTORY);
    }

    public static void registerSubscriber(final Subscriber consumer, final EventPublisherFactory factory) {
        if (consumer instanceof SmartSubscriber) {
            //调用subscribeTypes()方法获取订阅者consumer需要监听的事件,然后对这些事件进行遍历
            for (Class<? extends Event> subscribeType : ((SmartSubscriber) consumer).subscribeTypes()) {
                //For case, producer: defaultSharePublisher -> consumer: smartSubscriber.
                if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {
                    //添加订阅者
                    INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
                } else {
                    //For case, producer: defaultPublisher -> consumer: subscriber.
                    //添加订阅者
                    addSubscriber(consumer, subscribeType, factory);
                }
            }
            return;
        }

        final Class<? extends Event> subscribeType = consumer.subscribeType();
        if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {
            INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
            return;
        }
        addSubscriber(consumer, subscribeType, factory);
    }

    //Add a subscriber to publisher.
    private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType, EventPublisherFactory factory) {
        //获取订阅的事件的Class的canonicalName
        final String topic = ClassUtils.getCanonicalName(subscribeType);
        synchronized (NotifyCenter.class) {
            //MapUtils.computeIfAbsent is a unsafe method.
            //创建EventPublisher对象,一个事件会对应一个EventPublisher对象
            MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);
        }

        //获取事件对应的EventPublisher对象,比如DefaultPublisher对象
        EventPublisher publisher = INSTANCE.publisherMap.get(topic);
        if (publisher instanceof ShardedEventPublisher) {
            ((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);
        } else {
            //往EventPublisher对象添加订阅者信息,比如调用DefaultPublisher.addSubscriber()方法
            publisher.addSubscriber(consumer);
        }
    }
    ...
}

//一个事件只会对应一个DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {
    private Class<? extends Event> eventType;
    //Class为eventType的事件的所有订阅者
    protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();
    ...

    @Override
    public void addSubscriber(Subscriber subscriber) {
        //添加订阅者
        subscribers.add(subscriber);
    }
    ...
}

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

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

相关文章

SpringBoot 自动装配原理 自定义一个 starter

目录 1、pom.xml 文件1.1、parent 模块1.1.1、资源文件1.1.1.1、resources 标签说明1.1.1.2、从 Maven 视角&#xff1a;资源处理全流程​ 1.1.2、插件 1.2、dependencies 模块 2、启动器3、主程序3.1、SpringBootApplication 注解3.2、SpringBootConfiguration 注解3.2.1、Con…

【C++进阶篇】多态

深入探索C多态&#xff1a;静态与动态绑定的奥秘 一. 多态1.1 定义1.2 多态定义及实现1.2.1 多态构成条件1.2.1.1 实现多态两个必要条件1.2.1.2 虚函数1.2.1.3 虚函数的重写/覆盖1.2.1.4 协变1.2.1.5 析构函数重写1.2.1.6 override和final关键字1.2.1.7 重载/重写/隐藏的对⽐ 1…

《AI大模型应知应会100篇》第60篇:Pinecone 与 Milvus,向量数据库在大模型应用中的作用

第60篇&#xff1a;Pinecone与Milvus&#xff0c;向量数据库在大模型应用中的作用 摘要 本文将系统比较Pinecone与Milvus两大主流向量数据库的技术特点、性能表现和应用场景&#xff0c;提供详细的接入代码和最佳实践&#xff0c;帮助开发者为大模型应用选择并优化向量存储解…

Java学习手册:客户端负载均衡

一、客户端负载均衡的概念 客户端负载均衡是指在客户端应用程序中&#xff0c;根据一定的算法和策略&#xff0c;将请求分发到多个服务实例上。与服务端负载均衡不同&#xff0c;客户端负载均衡不需要通过专门的负载均衡设备或服务&#xff0c;而是直接在客户端进行请求的分发…

Docker私有仓库实战:官方registry镜像实战应用

抱歉抱歉&#xff0c;离职后反而更忙了&#xff0c;拖了好久&#xff0c;从4月拖到现在&#xff0c;在学习企业级方案Harbor之前&#xff0c;我们先学习下官方方案registry&#xff0c;话不多说&#xff0c;详情见下文。 注意&#xff1a;下文省略了基本认证 TLS加密&#xff…

Redis+Caffeine构建高性能二级缓存

大家好&#xff0c;我是摘星。今天为大家带来的是RedisCaffeine构建高性能二级缓存&#xff0c;废话不多说直接开始~ 目录 二级缓存架构的技术背景 1. 基础缓存架构 2. 架构演进动因 3. 二级缓存解决方案 为什么选择本地缓存&#xff1f; 1. 极速访问 2. 减少网络IO 3…

【计算机网络】NAT技术、内网穿透与代理服务器全解析:原理、应用及实践

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 上篇文章&#xff1a;以太网、MAC地址、MTU与ARP协议 下篇文章&#xff1a;五种IO模型与阻…

Python训练打卡Day21

常见的降维算法&#xff1a; # 先运行预处理阶段的代码 import pandas as pd import pandas as pd #用于数据处理和分析&#xff0c;可处理表格数据。 import numpy as np #用于数值计算&#xff0c;提供了高效的数组操作。 import matplotlib.pyplot as plt #用于绘…

node .js 启动基于express框架的后端服务报错解决

问题&#xff1a; node .js 用npm start 启动基于express框架的后端服务报错如下&#xff1a; /c/Program Files/nodejs/npm: line 65: 26880 Segmentation fault "$NODE_EXE" "$NPM_CLI_JS" "$" 原因分析&#xff1a; 遇到 /c/Program F…

并发笔记-信号量(四)

文章目录 背景与动机31.1 信号量&#xff1a;定义 (Semaphores: A Definition)31.2 二元信号量 (用作锁) (Binary Semaphores - Locks)31.3 用于排序的信号量 (Semaphores For Ordering)31.4 生产者/消费者问题 (The Producer/Consumer (Bounded Buffer) Problem)31.5 读写锁 (…

【HTOP 使用指南】:如何理解主从线程?(以 Faster-LIO 为例)

htop 是 Linux 下常用的进程监控工具&#xff0c;它比传统的 top 更友好、更直观&#xff0c;尤其在分析多线程或多进程程序时非常有用。 以下截图就是在运行 Faster-LIO 实时建图时的 htop 状态展示&#xff1a; &#x1f50d; 一、颜色说明 白色&#xff08;或亮色&#xf…

数据同步DataX任务在线演示

数据同步DataX任务在线演示 1. 登录系统 访问系统登录页面&#xff0c;输入账号密码完成身份验证。 2. 环境准备 下载datax安装包&#xff0c;并解压到安装目录 3. 集群创建 点击控制台-多集群管理 计算组件添加DataX 配置DataX引擎,Datax.local.path填写安装目录。 4. …

telnetlib源码深入解析

telnetlib 是 Python 标准库中实现 Telnet 客户端协议的模块&#xff0c;其核心是 Telnet 类。以下从 协议实现、核心代码逻辑 和 关键设计思想 三个维度深入解析其源码。 一、Telnet 协议基础 Telnet 协议基于 明文传输&#xff0c;通过 IAC&#xff08;Interpret As Command…

TAPIP3D:持久3D几何中跟踪任意点

简述 在视频中跟踪一个点&#xff08;比如一个物体的某个特定位置&#xff09;听起来简单&#xff0c;但实际上很复杂&#xff0c;尤其是在3D空间中。传统方法通常在2D图像上跟踪像素&#xff0c;但这忽略了物体的3D几何信息和摄像机的运动&#xff0c;导致跟踪不稳定&#xf…

uniapp 生成海报二维码 (微信小程序)

先下载qrcodenpm install qrcode 调用 community_poster.vue <template><view class"poster-page"><uv-navbar title"物业推广码" placeholder autoBack></uv-navbar><view class"community-info"><text clas…

16.Excel:数据收集

一 使用在线协作工具 简道云。 excel的在线表格协作在国内无法使用&#xff0c;而数据采集最需要在线协作。 二 使用 excel 1.制作表格 在使用excel进行数据采集的时候&#xff0c;会制作表头给填写人&#xff0c;最好还制作一个示例。 1.输入提示 当点击某个单元格的时候&am…

AI系列:智能音箱技术简析

AI系列&#xff1a;智能音箱技术简析 智能音箱工作原理详解&#xff1a;从唤醒到执行的AIPipeline-CSDN博客 挑战真实场景对话——小爱同学背后关键技术深度解析 - 知乎 (zhihu.com) AI音箱的原理&#xff0c;小爱同学、天猫精灵、siri。_小爱同学原理-CSDN博客 智能音箱执行步…

BUUCTF——Ezpop

BUUCTF——Ezpop 进入靶场 给了php代码 <?php //flag is in flag.php //WTF IS THIS? //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95 //And Crack It! class Modifier {protected $v…

三、Hadoop1.X及其组件的深度剖析

作者&#xff1a;IvanCodes 日期&#xff1a;2025年5月7日 专栏&#xff1a;Hadoop教程 一、Hadoop 1.X 概述 &#xff08;一&#xff09;概念 Hadoop 是 Apache 开发的分布式系统基础架构&#xff0c;用 Java 编写&#xff0c;为集群处理大型数据集提供编程模型&#xff0c;…

PDF2zh插件在zotero中安装并使用

1、首先根据PDF2zh说明文档&#xff0c;安装PDF2zh https://github.com/guaguastandup/zotero-pdf2zh/tree/v2.4.0 我没有使用conda&#xff0c;直接使用pip安装pdf2zh &#xff08;Python版本要求3.10 < version <3.12&#xff09; pip install pdf2zh1.9.6 flask pypd…