通过WebSocket实现异步导出

这篇具有很好参考价值的文章主要介绍了通过WebSocket实现异步导出。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本篇文章记录大批量数据导出时间过长,导致接口请求超时问题。

解决思路与流程

  1. 前端点击导出按钮时开启websocket连接
  2. 逻辑处理异步执行
  3. 文件处理好后,得到文件的绝对路径
  4. 后台socket通知前端绝对路径的地址
  5. 前端下载文件到浏览器

文章目录

本地环境

 一、WebSocket配置

1.pom文件配置

2.信号量相关处理

3.websocket 配置

4.websocket 消息处理

5.websocket 用户集

二、建立WebSocket连接

1.导出按钮

2.导出方法

3.导出接口地址

4.流程说明

三、异步导出接口

1.导出接口示例

四、服务器部署nginx配置

总结


本地环境

Vue版本:2.6.12

Java版本:1.8

SpringBoot版本:2.2.13

项目使用若依前后分离版

 一、WebSocket配置

1.pom文件配置

<!-- SpringBoot Websocket -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <version>2.2.13.RELEASE</version>
</dependency>

2.信号量相关处理

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Semaphore;

/**
 * 信号量相关处理
 *
 * @author
 */
public class SemaphoreUtils {

    /**
     * SemaphoreUtils 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);

    /**
     * 获取信号量
     *
     * @param semaphore
     * @return
     */
    public static boolean tryAcquire(Semaphore semaphore) {
        boolean flag = false;

        try {
            flag = semaphore.tryAcquire();
        } catch (Exception e) {
            LOGGER.error("获取信号量异常", e);
        }

        return flag;
    }

    /**
     * 释放信号量
     *
     * @param semaphore
     */
    public static void release(Semaphore semaphore) {

        try {
            semaphore.release();
        } catch (Exception e) {
            LOGGER.error("释放信号量异常", e);
        }
    }
}

3.websocket 配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * websocket 配置
 *
 * @author
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

4.websocket 消息处理

import com.hnxr.scada.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.Semaphore;

/**
 * websocket 消息处理
 *
 * @author
 */
@Component
@ServerEndpoint("/websocket/message")
public class WebSocketServer {
    /**
     * WebSocketServer 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 默认最多允许同时在线人数100
     */
    public static int socketMaxOnlineCount = 100;

    public static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) throws Exception {
        boolean semaphoreFlag = false;
        // 尝试获取信号量
        semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);
        if (!semaphoreFlag) {
            // 未获取到信号量
            LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);
            WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);
            session.close();
        } else {
            //截取前端传入的id
            String sessionId = StringUtils.substringAfterLast(session.getQueryString(), "socketId=");
            System.out.println("---------------------解析socketId" + sessionId);
            // 添加用户
            WebSocketUsers.put(sessionId, session);
            System.out.println("-----------------当前用户" + WebSocketUsers.getUsers());
            LOGGER.info("\n 建立连接 - {}", session);
            LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());
            WebSocketUsers.sendMessageToUserByText(session, "连接成功");
        }
    }

    /**
     * 连接关闭时处理
     */
    @OnClose
    public void onClose(Session session) {
        LOGGER.info("\n 关闭连接 - {}", session);
        String sessionId = StringUtils.substringAfterLast(session.getQueryString(), "socketId=");
        // 移除用户
        WebSocketUsers.remove(sessionId);
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 抛出异常时处理
     */
    @OnError
    public void onError(Session session, Throwable exception) throws Exception {
        if (session.isOpen()) {
            // 关闭连接
            session.close();
        }
        String sessionId = StringUtils.substringAfterLast(session.getQueryString(), "socketId=");
        LOGGER.info("\n 连接异常 - {}", sessionId);
        LOGGER.info("\n 异常信息 - {}", exception);
        // 移出用户
        WebSocketUsers.remove(sessionId);
        // 获取到信号量则需释放
        SemaphoreUtils.release(socketSemaphore);
    }

    /**
     * 服务器接收到客户端消息时调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        WebSocketUsers.sendMessageToUserByText(session, message);
    }
}

白名单放开socket配置接口

.antMatchers("/websocket/**").permitAll()

5.websocket 用户集

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.Session;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * websocket 客户端用户集
 *
 * @author
 */
public class WebSocketUsers {
    /**
     * WebSocketUsers 日志控制器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);

    /**
     * 用户集
     */
    private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();

    /**
     * 存储用户
     *
     * @param key     唯一键
     * @param session 用户信息
     */
    public static void put(String key, Session session) {
        USERS.put(key, session);
    }

    /**
     * 移除用户
     *
     * @param session 用户信息
     * @return 移除结果
     */
    public static boolean remove(Session session) {
        String key = null;
        boolean flag = USERS.containsValue(session);
        if (flag) {
            Set<Map.Entry<String, Session>> entries = USERS.entrySet();
            for (Map.Entry<String, Session> entry : entries) {
                Session value = entry.getValue();
                if (value.equals(session)) {
                    key = entry.getKey();
                    break;
                }
            }
        } else {
            return true;
        }
        return remove(key);
    }

    /**
     * 移出用户
     *
     * @param key 键
     */
    public static boolean remove(String key) {
        LOGGER.info("\n 正在移出用户 - {}", key);
        Session remove = USERS.remove(key);
        if (remove != null) {
            boolean containsValue = USERS.containsValue(remove);
            LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");
            return containsValue;
        } else {
            return true;
        }
    }

    /**
     * 获取在线用户列表
     *
     * @return 返回用户集合
     */
    public static Map<String, Session> getUsers() {
        return USERS;
    }

    /**
     * 群发消息文本消息
     *
     * @param message 消息内容
     */
    public static void sendMessageToUsersByText(String message) {
        Collection<Session> values = USERS.values();
        for (Session value : values) {
            sendMessageToUserByText(value, message);
        }
    }

    /**
     * 发送文本消息
     *
     * @param session 缓存
     * @param message 消息内容
     */
    public static void sendMessageToUserByText(Session session, String message) {
        if (session != null) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                LOGGER.error("\n[发送消息异常]", e);
            }
        } else {
            LOGGER.info("\n[你已离线]");
        }
    }
}

二、建立WebSocket连接

1.导出按钮

<el-button type="warning" plain icon="el-icon-download" size="mini" :loading="exportLoading"
           @click="handleExport">导出
</el-button>

2.导出方法

      /** 导出按钮操作 */
      handleExport() {
        this.$confirm('是否确认导出所有?', '警告', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
          .then(() => {
            const ip = window.location.host;
            //TODO 服务器端口号 nginx代理为4430
            const hostIp = ip.split(":")[0];
            console.log('ip:', hostIp)
            const url = "wss://" + hostIp + ":4430/websocket/message";

            //TODO 本地端口号为9996 项目启动端口
            // let url = "wss://localhost:9996/websocket/message";

            console.log('url', url)
            const socketId = this.getRandomId();
            //将socketId当做参数传入后端
            this.queryParams.sessionId = socketId
            const socket = new WebSocket(url + '?socketId=' + socketId);
            socket.onopen = function (event) {
              console.log("已开启websocket连接!" + "sessionId:" + socketId)
            };
            socket.onmessage = function (event) {
              const val = event.data
              console.log("收到消息", val)
              if (val.endsWith("asyExport")) {
                const path = val.slice(0, val.indexOf("asyExport"));
                console.log("文件名:" + path)
                const baseURL = process.env.VUE_APP_BASE_API
                window.location.href = baseURL + "/common/download?fileName=" + encodeURI(path) + "&delete=" + true;
                //延时关闭连接 确保WebSocket处于打开状态
                setTimeout(function () {
                  if (socket.readyState === WebSocket.OPEN) {
                    socket.close();
                  }
                }, 5000);
              }
            };
            // 处理连接关闭
            socket.onclose = function (event) {
              console.log("已经关闭连接!");
            };
            // 处理连接错误
            socket.onerror = function (error) {
              console.error("WebSocket 出现错误:", error);
              if (socket.readyState === WebSocket.OPEN) {
                socket.close();
              }
            };
            exportChineseParsing(this.queryParams)
          })
      },
      // 生成唯一标识符
      getRandomId() {
        return Math.random().toString(36).substring(2) + Date.now().toString(36);
      }

本地项目是http请求,前缀设置为ws

后端application.yml 的ssl配置将server下的enabled改为false

前端代理地址保持http

https请求,前缀设置为wss

ssl配置文件将server下的enabled改为true

前端代理地址改为https

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

3.导出接口地址

//导出中文解析
export function exportChineseParsing(query) {
  return request({
    url: '/basic/chineseParsing/export',
    method: 'get',
    params: query,
    responseType: 'blob'
  })
}

浏览器控制台查看连接时是否正常

前后端socket配置完成后在浏览器控制台输入

new WebSocket("ws://localhost:9996/websocket/message")

测试是否正常 如下图 抛出异常需要检查上方配置是否正确

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

4.流程说明

首先获取当前ip,本地使用localhost即可,服务器部署需要配置nginx代理

ip后跟端口号,端口后是后端websocket接口地址

socket.onopen回调方法验证是否成功建立连接

socket.onmessage是接收后端接口异步导出后的消息

后端文件处理好后,约定发送的消息以asyExport字符为后缀,方便前端识别和截取文件绝对路径

调用下载接口,关闭socket连接

如不是若依前后分离版,替换为自己的下载文件的接口就行

​​​​​​​三、异步导出接口

1.导出接口示例

    @GetMapping("/export")
    public void export(BasicChineseParsing basicChineseParsing) throws Exception {
        //存入文件名称
        AtomicReference<String> path = new AtomicReference<>("");
        //逻辑异步处理
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
            try {
                //模拟逻辑处理时长
                System.out.println("-----------------------睡眠5秒");
                Thread.sleep(5000);
				.......
				
                //创建临时文件 绝对路径
                File tempFile = PoiUtils.createTempNameFile(rows, title, "_中文解析表");
                String result = tempFile.getCanonicalPath().substring(tempFile.getCanonicalPath().indexOf("vehicle"));
                System.out.println("-----------------------处理后文件名:" + result);
                path.set(result);
            } catch (InterruptedException | IOException e) {
                e.printStackTrace();
            }
        });
        //异步处理完成后
        runAsync.thenRun(() -> {
            // 发送消息 拿到前端传入的socketId
            String sessionId = basicChineseParsing.getSessionId();
            System.out.println("-----------------------sessionId:" + sessionId);
            Session session = WebSocketUsers.getUsers().get(sessionId);
            webSocketServer.onMessage(path.toString() + "asyExport", session);
            System.out.println("-----------------------导出成功");
            //前端关闭socket连接
        });
    }

 2.debug调试流程截图

下载方法大家的和我的会不一样

但是File tempFile只要获取到下载好后的绝对路径就可以

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议
​​​​​​​我这里path存入的是下载的文件名称

因为前端会再次调用下载接口,有文件名称,就可以将文件下载至浏览器了

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

 在webSocketServer.onMessage方法中发送的消息就是文件名称+asyExport一个特殊约定的后缀

前端在收到消息后会截取asyExport前的文件名称

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

 完成流程日志打印如下

通过WebSocket实现异步导出,开发合集,webSocket,websocket,java,vue.js,网络协议

因使用若依前后分离版,大家调整后的下载方法可能和我的不一样

比如socket需要发送的信息是文件的绝对路径,前端调用下载方法传入绝对路径即可

四、服务器部署nginx配置

这个配置看大家是否需要部署至服务器

server {
        listen       4430 ssl;
        server_name  localhost;
        .....
        
        location /websocket/message {
            proxy_pass https://服务器ip:4430;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
}

配置需要放在当前代理的端口配置下

如果是ssl认证的proxy_pass后地址需要用https,没有认证就用http

ip后的端口是你的程序服务使用端口,不是代理映射的端口

这里配置的地址是和前端创建连接时const url = "wss://" + hostIp + ":4430/websocket/message";

ip和端口是一致的


​​​​​​​总结

第一步WebSocket配置大家是可以直接复制使用

第二步我的处理是只有导出时才启用socket连接,下载文件后关闭,也可以设置为全局性的,方便处理其他通知

socket通知前端的下载方式也有很多种,需要考虑的是会有其他用户或其他导出接口,其他socket通知,所以通知下载的socket消息是需要具有唯一性的,上方建立socket连接时就加了保障唯一性的socketId,并当为参数传入后端。

本方法就介绍到这里,需要大家多调试,欢迎留言讨论!文章来源地址https://www.toymoban.com/news/detail-782894.html

到了这里,关于通过WebSocket实现异步导出的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • uniapp通过websocket实现手机APP通知栏消息显示功能(前端部分)

     开门见山地说,在移动应用端,从后端及时获取消息,展示到手机消息通知栏上面来与用户进行交互是一个很高频的应用场景,这篇文章就来介绍一下,在uniapp开发中如何实现这种需求。  要实现这个需求,对于前端来说主要技术需要拆分成两部分:一是从后端及时获取消

    2024年03月18日
    浏览(93)
  • 53、springboot对websocket的支持有两种方式-------1、基于注解开发 WebSocket ,简洁实现多人聊天界面

    –注解就是: @OnOpen、 @OnClose 、 @OnMessage 、@OnError这些 ▲ Spring Boot为WebSocket提供了两种开发方式: 基于spring-boot-starter-websocket.jar开发WebSocket 基于Spring WebFlux开发WebSocket 两种方式对比: springboot API Socket:套接字。 插座。 在通信的两端分别建立虚拟的Socket(插座),网络协议

    2024年02月09日
    浏览(39)
  • 鸿蒙应用开发-录音并使用WebSocket实现实时语音识别

    功能介绍: 录音并实时获取RAW的音频格式数据,利用WebSocket上传数据到服务器,并实时获取语音识别结果,参考文档使用AudioCapturer开发音频录制功能(ArkTS),更详细接口信息请查看接口文档:AudioCapturer8+和@ohos.net.webSocket (WebSocket连接)。 知识点: 熟悉使用AudioCapturer录音并实时

    2024年04月25日
    浏览(42)
  • Java Websocket 02: 原生模式通过 Websocket 传输文件

    Java Websocket 01: 原生模式 Websocket 基础通信 Java Websocket 02: 原生模式通过 Websocket 传输文件 关于 Websocket 传输的消息类型, 允许的参数包括以下三类 以下类型之一, 同时只能出现一个 文本类型 (text messages) 的消息: String, Java primitive, 阻塞的 Stream Reader, 带text decoder(Decoder.Text or Deco

    2024年02月09日
    浏览(53)
  • android开发使用OkHttp自带的WebSocket实现IM功能

    目录 一、背景 二、在项目中添加依赖包 三、框架调用 1、配置OkHttpClient  2、调用Url,构建WebSocket请求 3、建立连接 4、使用WebSocket对象发送消息     android app开发经常会有IM需求,很多新手不晓得如何入手,难点在于通讯不中断。其实android发展到今天,很多技术都很完善,有

    2024年02月08日
    浏览(39)
  • thinkphp websocket 开发实时聊天系统的用户群组与订阅功能实现 (一)

    PHP开发实时聊天系统的用户群组与订阅功能实现 在当今社交互联网时代,实时聊天系统已经成为人们日常交流的重要工具。为了提供更好的用户体验,我们需要实现用户群组与订阅功能,使得用户能够方便地创建和加入群组,并且能够订阅感兴趣的内容。 本篇文章将介绍如

    2024年02月07日
    浏览(44)
  • 通过使用WebSocket使前后端数据交互

    1.对WebSocket的了解 WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数

    2024年02月16日
    浏览(39)
  • 在docker 环境中 websocket 通过nginx代理 不通 怎么解决

    如果你在 Docker 环境中使用 WebSocket 并通过 Nginx 进行代理,可能需要进行一些额外的配置才能使 WebSocket 正常工作。 下面是一些可能会导致 WebSocket 代理失败的问题以及相应的解决方法: 检查 Nginx 配置 在 Nginx 配置中,确保已经正确地设置了 proxy_pass 和 proxy_http_version ,如下所

    2024年02月03日
    浏览(34)
  • 【WebSocket&IndexedDB】node+WebSocket&IndexedDB开发简易聊天室

    序幕介绍: WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 讲人话就是说: WebSocket 使得客户端和服务器之间的数据交换变得更加简单,在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数

    2024年02月10日
    浏览(42)
  • 【微信小程序】通过监听 WebSocket 的状态变化来判断Socket是否已经建立连接

    在微信小程序中,可以通过监听 WebSocket 的状态变化来判断是否已经建立连接。具体的操作步骤如下: 创建 WebSocket 对象并进行连接: 监听 WebSocket 的状态变化: 通过监听上述的状态变化,可以判断 WebSocket 是否已经建立连接。当连接成功时, onSocketOpen 会被触发;连接失败时

    2024年02月07日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包