使用SSE解决获取状态不一致问题

news2025/7/27 17:22:43

使用SSE解决获取状态不一致问题

  • 1. 问题描述
  • 2. SSE介绍
    • 2.1 SSE 的工作原理
    • 2.2 SSE 的事件格式规范
    • 2.3 SSE与其他技术对比
    • 2.4 SSE 的优缺点
  • 3. 实战代码

1. 问题描述

目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中需要轮询文件上传的状态的接口,还有要调用整个任务状态的接口,因为我们的Mysql是单例的,有多个大文件上传的时候会出现任务状态不一致的问题,经过调研有WebSocket与SSE两种方式,但是我只需要把信息推给前端,所以使用个更轻量的SSE。

2. SSE介绍

SSE(Server-Sent Events) 是 HTML5 标准中的一项技术,它允许服务器通过单向连接持续地将数据推送给客户端(通常是浏览器)。

  • 与传统的 HTTP 请求响应不同,SSE 是一种服务器主动推送数据的机制。
  • 与 WebSocket 不同,它是单向通信:服务器 -> 客户端。

SSE 使用标准的 HTTP 协议 和 文本/event-stream 格式,通常用于实时消息推送,如股票行情、社交动态通知、在线状态更新等。

2.1 SSE 的工作原理

  1. 客户端通过普通的 HTTP 请求发起连接:
GET /events HTTP/1.1
Accept: text/event-stream
  1. 服务器响应:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
  1. 事件流
event: message
id: 123
data: 这是要推送的数据
  1. 客户端自动处理每一条数据,并在连接断开时自动重连。

2.2 SSE 的事件格式规范

每条消息由一组字段组成,字段以冒号: 分隔,每条消息之间由两个换行符 \n\n 分隔:
在这里插入图片描述

示例:

id: 1001
event: update
data: {"progress": 50}

2.3 SSE与其他技术对比

在这里插入图片描述

2.4 SSE 的优缺点

✅ 优点:

  • 使用标准 HTTP 协议,兼容性好;
  • 实现简单,开发成本低;
  • 支持自动重连机制;
  • 支持事件类型、多行数据等;
  • 适用于需要实时更新、但无需客户端发送大量消息的场景。

❌ 缺点:

  • 只支持单向通信;
  • 不支持二进制数据;
  • 不支持所有浏览器(如 IE);
  • 有并发连接数限制(部分浏览器每个域名默认最大连接数限制);
  • 需配置好心跳机制、防止代理服务器中断连接。

3. 实战代码

@GetMapping("/file-upload-status")
@Operation(summary = "文件上传状态SSE")
public SseEmitter fileUploadStatus(@RequestParam("taskCode") String taskCode,
                                  @RequestHeader(value = "Last-Event-ID", required = false) String lastEventId) {
log.info("文件上传状态SSE, 请求参数: {}, Last-Event-ID: {}", taskCode, lastEventId);

try {
   return dataConfigService.fileUploadStatus(taskCode, lastEventId);
} catch (Exception e) {
 log.error("文件上传状态SSE,失败,错误信息:{}", e.getMessage());
  throw e;
   }
}





/**
     * 用于存储每个任务对应的 SseEmitter 实例。
     * key 为任务唯一标识 taskCode,value 为与该任务关联的 SseEmitter。
     * 使用 ConcurrentHashMap 是为了支持多线程环境下的并发读写,确保线程安全。
     * <p>
     * 场景:
     * - 客户端发起 SSE 连接时,将对应的 SseEmitter 存入此 Map。
     * - 后台定时任务或上传进度更新时可以通过 taskCode 拿到对应的 emitter 推送数据。
     * - 当连接断开(如超时、关闭、出错)时,从 Map 中移除对应的 emitter,避免内存泄漏。
     */
    private final Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();

    /**
     * 定时任务线程池,用于定期推送每个任务的上传状态到前端客户端(通过 SseEmitter)。
     * 使用 Runtime.getRuntime().availableProcessors() 获取当前机器的可用处理器核心数,
     * 并以此设置线程池大小,合理利用系统资源。
     * <p>
     * 特点:
     * - ScheduledExecutorService 支持定时或周期性任务调度,适合做周期性状态推送。
     * - 使用线程池而不是单线程可以支持多个任务并发状态推送。
     * - 可根据系统并发量和任务复杂度调整线程池大小。
     */
    private final ScheduledExecutorService scheduledExecutorService =
            Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors());

    /**
     * 缓存最近一次推送的状态(用于断线续传)
     */
    private final Map<String, DataFileUploadStatusVO> lastEventDataMap = new ConcurrentHashMap<>();
@Override
    public SseEmi
    tter fileUploadStatus(String taskCode, String lastEventId) {
        // 检查任务代码是否为空
        if (StringUtils.isBlank(taskCode)) {
            throw new ServiceException(400, "请求参数不能为空");
        }

        // 创建 SseEmitter 实例,设置超时时间为 1 小时(单位:毫秒)
        SseEmitter emitter = new SseEmitter(60 * 60 * 1000L);

        // 将当前任务的 emitter 注册到全局 emitterMap 中,用于后续状态推送
        emitterMap.put(taskCode, emitter);

        // 定义清理逻辑(连接关闭时自动移除 emitter,避免内存泄漏)
        Runnable cleanup = () -> {
            emitterMap.remove(taskCode);
            lastEventDataMap.remove(taskCode);
        };

        // 注册回调:客户端主动关闭连接时调用
        emitter.onCompletion(cleanup);

        // 注册回调:连接超时后自动关闭并清理
        emitter.onTimeout(cleanup);

        // 注册回调:出现异常时进行日志记录并清理资源
        emitter.onError(e -> {
            log.error("文件上传状态SSE 错误,taskCode:{}, 错误信息:{}", taskCode, e.getMessage(), e);
            cleanup.run();
        });

        // 尝试断点续传(只在客户端提供 Last-Event-ID 的情况下)
        if (StringUtils.isNotBlank(lastEventId)) {
            DataFileUploadStatusVO lastData = lastEventDataMap.get(taskCode);
            if (lastData != null) {
                try {
                    emitter.send(SseEmitter.event()
                            .id(lastEventId)
                            .name("upload-status")
                            .reconnectTime(5000L)
                            .data(lastData, MediaType.APPLICATION_JSON));
                    log.info("文件上传状态SSE,断点续传成功,taskCode:{}, lastEventId:{}, 数据:{}", taskCode, lastEventId,
                            JSON.toJSONString(lastData));
                } catch (IOException e) {
                    log.error("文件上传状态SSE,断点续传失败,taskCode:{}, 错误:{}", taskCode, e.getMessage(), e);
                }
            }
        }

        // 创建一个原子容器用于持有定时任务引用,以便后续取消任务
        AtomicReference<ScheduledFuture<?>> futureRef = new AtomicReference<>();

        // 启动定时任务
        ScheduledFuture<?> future = scheduledExecutorService.scheduleAtFixedRate(() -> {
            try {
                // 从全局映射中获取当前连接的 emitter,若为空说明连接已关闭,停止执行
                SseEmitter currentEmitter = emitterMap.get(taskCode);
                if (currentEmitter == null) {
                    return;
                }

                // 查询文件上传状态
                List<DataConfigRespVO> dataConfigList = getDataConfigList(taskCode);

                // 查询任务信息
                TaskRespVO taskRespVO = taskService.getTask(taskCode);

                // 将数据库实体转换为状态对象列表
                List<DataFileUploadStatusVO.FileStatus> fileStatusList = dataConfigList.stream()
                        .map(item -> {
                            DataFileUploadStatusVO.FileStatus fileStatus = new DataFileUploadStatusVO.FileStatus();
                            fileStatus.setId(item.getId());
                            fileStatus.setCode(item.getCode());
                            fileStatus.setImportMethod(item.getImportMethod());
                            fileStatus.setFilename(item.getFilename());
                            fileStatus.setFileUrl(item.getFileUrl());
                            fileStatus.setSize(item.getSize());
                            fileStatus.setUploadStatusCode(item.getUploadStatusCode());
                            return fileStatus;
                        })
                        .collect(Collectors.toList());

                // 构建完整状态返回对象
                DataFileUploadStatusVO statusVO = new DataFileUploadStatusVO();
                statusVO.setTaskCode(taskCode);
                statusVO.setCurrentTime(TimeUtil.getCurrentTime());
                statusVO.setFileStatusList(fileStatusList);
                statusVO.setName(taskRespVO.getName());
                statusVO.setDataType(taskRespVO.getDataType());
                statusVO.setDataTypeStr(taskRespVO.getDataTypeStr());
                statusVO.setImportType(taskRespVO.getImportType());
                statusVO.setCurrentLinkCode(taskRespVO.getCurrentLinkCode());
                statusVO.setCurrentLink(taskRespVO.getCurrentLink());
                statusVO.setCurrentLinkStatusCode(taskRespVO.getCurrentLinkStatusCode());
                statusVO.setCurrentLinkStatus(taskRespVO.getCurrentLinkStatus());

                String eventId = String.valueOf(System.currentTimeMillis());

                // 推送状态
                try {
                    // 设置状态
                    statusVO.setFinished(fileUploadFished(statusVO));

                    // 保存最后一次推送的数据,用于断线续传
                    lastEventDataMap.put(taskCode, statusVO);

                    currentEmitter.send(SseEmitter.event()
                            // 事件 ID,供断线续传
                            .id(eventId)
                            // 事件名
                            .name("upload-status")
                            // 告诉客户端:断线后5秒再重连
                            .reconnectTime(5000L)
                            // 推送数据为 JSON
                            .data(statusVO, MediaType.APPLICATION_JSON));

                    log.info("文件上传状态SSE,当前时间:{},taskCode:{},推送数据:{}", TimeUtil.getCurrentTime(), taskCode,
                            JSON.toJSONString(statusVO));
                } catch (IOException ioException) {
                    // 客户端断开连接或传输异常,主动清理资源并中止定时任务
                    log.error("文件上传状态SSE,错误:{},taskCode:{}", ioException.getMessage(), taskCode);
                    currentEmitter.completeWithError(ioException);
                    cleanup.run();
                    futureRef.get().cancel(true);
                    return;
                }

                // 判断任务是否完成
                if (fileUploadFished(statusVO)) {
                    // 设置任务完成
                    statusVO.setFinished(true);

                    currentEmitter.send(SseEmitter.event()
                            .name("upload-status")
                            .data(statusVO, MediaType.APPLICATION_JSON));
                    // 主动关闭连接
                    currentEmitter.complete();
                    // 清理资源并取消定时任务
                    cleanup.run();
                    futureRef.get().cancel(true);
                    log.info("文件上传状态SSE,任务已完成,taskCode:{},当前时间:{},emitterMap:{},大小:{}", taskCode,
                            TimeUtil.getCurrentTime(), JSON.toJSONString(emitterMap), emitterMap.size());
                }

            } catch (Exception e) {
                log.error("文件上传状态SSE, SSE推送失败,taskCode:{},错误信息:{}", taskCode, e.getMessage(), e);
                SseEmitter failedEmitter = emitterMap.get(taskCode);
                if (failedEmitter != null) {
                    failedEmitter.completeWithError(e);
                }
                cleanup.run();
                futureRef.get().cancel(true);
            }
            // 每2秒执行一次
        }, 0, 2, TimeUnit.SECONDS);

        // 记录定时任务引用
        futureRef.set(future);

        // 返回 emitter 给前端,保持连接
        return emitter;
    }
event:upload-status
data:{"finished":false,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:27","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":1}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":1,"currentLinkStatus":"执行中"}

event:upload-status
data:{"finished":false,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:29","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":1}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":1,"currentLinkStatus":"执行中"}

event:upload-status
data:{"finished":false,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:31","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":1}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":1,"currentLinkStatus":"执行中"}

event:upload-status
data:{"finished":false,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:33","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":1}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":1,"currentLinkStatus":"执行中"}

event:upload-status
data:{"finished":true,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:35","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":2,"currentLinkStatus":"已完成"}

event:upload-status
data:{"finished":true,"taskCode":"366d1b2c760f4afd8b709e64c188a27c","currentTime":"2025-06-06 09:53:35","fileStatusList":[{"id":3922,"code":"64d9c1ab4abc441280abb61bba72a611","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本1.txt","fileUrl":"data-config/file/64d9c1ab4abc441280abb61bba72a611_新闻文本1.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.39 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3923,"code":"33735f65a0b940a996ded37ff4066299","importMethod":{"code":1,"desc":"文件导入"},"filename":"英文文本.txt","fileUrl":"data-config/file/33735f65a0b940a996ded37ff4066299_英文文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"1.77 KB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2},{"id":3924,"code":"e7c06f1da1a34e4fb8ee46eeac6f9f2e","importMethod":{"code":1,"desc":"文件导入"},"filename":"新闻文本.txt","fileUrl":"data-config/file/e7c06f1da1a34e4fb8ee46eeac6f9f2e_新闻文本.txt","tableName":null,"fieldName":null,"createTime":null,"introduction":null,"size":"2.84 MB","includeHeader":null,"csvSeparator":null,"uploadStatusCode":2}],"name":"测试文件上传123","dataType":2,"dataTypeStr":"非结构化数据","importType":"file","currentLinkCode":2,"currentLink":"数据配置","currentLinkStatusCode":2,"currentLinkStatus":"已完成"}


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

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

相关文章

软件工程 期末复习

瀑布模型&#xff1a;计划 螺旋模型&#xff1a;风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合&#xff1a;模块内部功能紧密 模块之间依赖程度小 高内聚&#xff1a;指的是一个模块内部的功能应该紧密相关。换句话说&#xff0c;一个模块应当只实现单一的功能…

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…

阿里云Ubuntu 22.04 64位搭建Flask流程(亲测)

cd /home 进入home盘 安装虚拟环境&#xff1a; 1、安装virtualenv pip install virtualenv 2.创建新的虚拟环境&#xff1a; virtualenv myenv 3、激活虚拟环境&#xff08;激活环境可以在当前环境下安装包&#xff09; source myenv/bin/activate 此时&#xff0c;终端…

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章&#xff0c;抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法&#xff0c;已经有很多的工作和这个任务相关。这两年 diffusion 模型很火&#xff0c;大家又开始用 diffusion 模型做各种 CV 任务了&am…

Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践

前言&#xff1a;本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中&#xff0c;跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南&#xff0c;你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案&#xff0c;并结合内网…

实战设计模式之模板方法模式

概述 模板方法模式定义了一个操作中的算法骨架&#xff0c;并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下&#xff0c;重新定义算法中的某些步骤。简单来说&#xff0c;就是在一个方法中定义了要执行的步骤顺序或算法框架&#xff0c;但允许子类…

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…

Python训练营-Day26-函数专题1:函数定义与参数

题目1&#xff1a;计算圆的面积 任务&#xff1a; 编写一个名为 calculate_circle_area 的函数&#xff0c;该函数接收圆的半径 radius 作为参数&#xff0c;并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求&#xff1a;函数接收一个位置参数 radi…

Android写一个捕获全局异常的工具类

项目开发和实际运行过程中难免会遇到异常发生&#xff0c;系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler&#xff0c;它是Thread的子类&#xff08;就是package java.lang;里线程的Thread&#xff09;。本文将利用它将设备信息、报错信息以及错误的发生时间都…

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili