SpringBoot websocket + java swing 实现大文件上传与下载

2023-10-29

使用场景

无法直接登录服务器上传文件,使用web端上传超大文件出现超时

实现原理

上传

server端与client端建立websocket连接,client将待传文件进行分块,然后将文件的相关信息(文件名、md5值、分块大小、总块数、当前块数)及文件数据一并上传到服务端,服务端在本地建立文件通过追加的方式将上传的数据写入文件中,当当前块与总块数相等且文件MD5相同时认为文件上传成功

下载

与上传相反,将client当成服务端,client与server建立连接后,向服务端发送可接收请求,服务端收到后将文件进行分块处理并记录文件相关信息连同数据一并发送给client端,当服务端发现文件读取完毕后将块大小设置为-1,client端读取后进行文件md5校验,校验通过则认为下载成功。

代码展示

jdk版本: 14
项目地址: https://gitee.com/LovingL/big-file-upload-project

使用依赖
服务端
dependencies {
    implementation('org.springframework.boot:spring-boot-starter-undertow')
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation('org.springframework.boot:spring-boot-starter-websocket') {
        exclude module: 'spring-boot-starter-tomcat'
    }
    implementation 'cn.hutool:hutool-json:5.6.5'
    implementation 'cn.hutool:hutool-crypto:5.6.5'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.taobao.arthas:arthas-spring-boot-starter:3.4.8'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
客户端
dependencies {
    implementation("org.java-websocket:Java-WebSocket:1.5.1")
    implementation("cn.hutool:hutool-json:5.4.3")
    implementation("cn.hutool:hutool-crypto:5.4.3")
    implementation("org.slf4j:slf4j-jdk14:1.7.25")
    compileOnly("org.projectlombok:lombok:1.18.12")
    annotationProcessor("org.projectlombok:lombok:1.18.12")
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
消息定义
文件发送消息:
public class SendFileData {

    private String fileName;  //文件名
    private int currentBlock; //当前块号
    private int totalBlock;   //总块号
    private int blockSize;    //块大小
    private String md5sum;    //md5值
    private byte[] data;      //文件数据

}
文件接收消息:
public class ReceiveFileData {

    private int status;      //接收状态码
    private String errMsg;   //错误消息返回
    private String uri;      //文件地址
}
文件上传
服务端
    public void onMessage(Session session, String reqData) throws IOException {
        SendFileData data = JSONUtil.toBean(reqData, SendFileData.class);
        String filePath = StrUtil.format("{}/{}", fileConfig.getTempDir(), data.getFileName());
        if (file == null) {
            file = FileUtil.file(filePath);
            if (!FileUtil.exist(file)) {
                FileUtil.touch(file);
            }
            md5sum = MD5.create().digestHex16(file);
        }
        ReceiveFileData receiveFileData = new ReceiveFileData();
        //先进行md5校验,如果md5已相同说明已经上传过了就直接返回服务端地址即可
        if (md5sum.equals(data.getMd5sum())) {
            receiveFileData.setStatus(HttpStatus.OK.value());
            receiveFileData.setUri(filePath);
            session.getBasicRemote().sendText(JSONUtil.toJsonStr(receiveFileData));
            return;
        }
        int currentBlock = data.getCurrentBlock();
        if (totalBlocks == null) {
            totalBlocks = data.getTotalBlock();
        }
        int blockSize = data.getBlockSize();
        //通过追加方式将数据写入文件
        FileUtil.writeBytes(data.getData(), file, 0, blockSize, true);
        //当前块号与总块号相等时判断MD5是否相同
        if (currentBlock == totalBlocks) {
            md5sum = MD5.create().digestHex16(file);
            if (md5sum.equals(data.getMd5sum())) {
                receiveFileData.setStatus(HttpStatus.OK.value());
                receiveFileData.setUri(filePath);
            } else {
                receiveFileData.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                receiveFileData.setErrMsg("MD5计算异常,文件上传异常");
            }
        } else {
            receiveFileData.setStatus(HttpStatus.ACCEPTED.value());
        }
        log.info("cur:{}, tot:{}", currentBlock, totalBlocks);
        session.getBasicRemote().sendText(JSONUtil.toJsonStr(receiveFileData));
    }
客户端
try (InputStream inputStream = FileUtil.getInputStream(file)) {
            URI uri = new URI(this.sendUrl);
            MyWebSocketClient webSocketClient = new MyWebSocketClient(uri, this.logInfo);
            webSocketClient.connectBlocking();
            SendFileData sendFileData = new SendFileData();
            long fileLength = file.length();
            int currentBlock = 1;
            //计算文件总块数
            int totalBlocks = (int) Math.ceil((double) fileLength / Double.valueOf(4096));
            sendFileData.setFileName(file.getName());
            sendFileData.setTotalBlock(totalBlocks);
            sendFileData.setMd5sum(MD5.create().digestHex16(file));
            this.logInfo.append("总块数:" + totalBlocks + "\n");
            byte[] b = new byte[4096];
            int i = 0;
            String message = null;
            StringBuffer stringBuffer = new StringBuffer();
            while ((i = inputStream.read(b)) != -1) {
                byte[] data = new byte[i];
                sendFileData.setBlockSize(i);
                System.arraycopy(b, 0, data, 0, i);  //最后读取部分不为固定大小,按照实际大小拷贝不然md5将不同
                sendFileData.setData(data);
                sendFileData.setCurrentBlock(currentBlock);
                message = webSocketClient.sendAndGet(JSONUtil.toJsonStr(sendFileData));
                if (message != null) {
                    ReceiveFileData receiveFileData = JSONUtil.toBean(message, ReceiveFileData.class);
                    //当服务器返回值为200说明已经接收完成
                    if (receiveFileData.getStatus() == 200) {
                        this.logInfo.append("文件所在服务器路径为:" + receiveFileData.getUri() + "\n");
                        webSocketClient.close();
                        break;
                    }
                    if (receiveFileData.getStatus() == 500) {
                        this.logInfo.append("文件上传异常:" + receiveFileData.getErrMsg() + "\n");
                        webSocketClient.close();
                        break;
                    }
                }
                currentBlock++;
            }
        } catch (IOException ioException) {
            this.logInfo.append("异常:文件打开异常\n");
        } catch (URISyntaxException uriSyntaxException) {
            this.logInfo.append("异常:不可识别的URI地址\n");
        } catch (InterruptedException interruptedException) {
            this.logInfo.append("异常:连接被中断\n");
        } catch (JSONException jsonException) {
            this.logInfo.append("异常:服务器返回值无法解析:" + jsonException.getMessage() + "\n");
        }
文件下载
服务端
public void onMessage(Session session, String receiveStringData) throws IOException {
        ReceiveFileData receiveFileData = JSONUtil.toBean(receiveStringData, ReceiveFileData.class);
        if (receiveFileData.getStatus() == HttpStatus.ACCEPTED.value()) {
            if (file == null) {
                file = new File(receiveFileData.getUri());
            }
            if (fileLength == null) {
                fileLength = file.length();
            }
            SendFileData sendFileData = new SendFileData();
            if (totalBlocks == null) {
                totalBlocks = (int) Math.ceil((double) fileLength / Double.valueOf(blockSize));
            }
            sendFileData.setFileName(file.getName());
            sendFileData.setTotalBlock(totalBlocks);
            if (md5Sum == null) {
                md5Sum = MD5.create().digestHex16(file);
            }
            sendFileData.setMd5sum(md5Sum);
            //使用RandomAccessFile来将文件指针定位到当前块号所处位置
            try(RandomAccessFile randomAccessFile = new RandomAccessFile(file.getAbsolutePath(), "r");) {
                byte[] b = new byte[blockSize];
                randomAccessFile.seek((currentBlock - 1) * blockSize);
                int realBlockSize = randomAccessFile.read(b, 0, blockSize);
                log.info("totalBlocks:{} currentBlock:{}, realBlockSize:{}", totalBlocks, currentBlock, realBlockSize);
                if (currentBlock > totalBlocks || realBlockSize == -1) {
                    log.info("文件已读取完毕");
                } else {
                    byte[] data = new byte[realBlockSize];
                    sendFileData.setBlockSize(realBlockSize);
                    System.arraycopy(b, 0, data, 0, realBlockSize); //一定要按照实际大小进行拷贝不然MD5值将不同
                    sendFileData.setData(data);
                    sendFileData.setCurrentBlock(currentBlock);
                    currentBlock++;
                }
                sendFileData.setBlockSize(realBlockSize);
                session.getBasicRemote().sendText(JSONUtil.toJsonStr(sendFileData));
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        } else {
            session.getBasicRemote().sendText("发送完成");
            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "发送完成"));
        }
    }
客户端
try {
            URI uri = new URI(this.downloadUrl);
            String name = FileUtil.getName(fileUriText);
            MyWebSocketClient webSocketClient = new MyWebSocketClient(uri, logInfo2);
            webSocketClient.connectBlocking();
            ReceiveFileData receiveFileData = new ReceiveFileData();
            //开始设置202状态告知服务器客户端已准备接收
            receiveFileData.setStatus(202);
            receiveFileData.setUri(fileUriText);
            File downloadFile = FileUtil.file(saveDir, name);
            if (!FileUtil.exist(downloadFile)) {
                FileUtil.touch(downloadFile);
            }
            while (true) {
                String md5Sum = MD5.create().digestHex16(downloadFile);
                String response = webSocketClient.sendAndGet(JSONUtil.toJsonStr(receiveFileData));
                if (response != null) {
                    SendFileData sendFileData = JSONUtil.toBean(response, SendFileData.class);
                    if (md5Sum.equals(sendFileData.getMd5sum())) {
                        this.logInfo2.append("接收完成\n");
                        webSocketClient.close(1000, "接收完成");
                        break;
                    }
                    if (sendFileData.getBlockSize() == -1) {
                        this.logInfo2.append("异常:与远程MD5计算不一致\n");
                        webSocketClient.close(1006, "MD5异常");
                        break;
                    } else {
                    	//通过追加方式将文件数据写入文件当中
                        FileUtil.writeBytes(sendFileData.getData(), downloadFile, 0, sendFileData.getBlockSize(), true);
                    }
                }
            }
        } catch (URISyntaxException uriSyntaxException) {
            this.logInfo2.append("异常:远程服务器地址异常");
        } catch (InterruptedException interruptedException) {
            this.logInfo2.append("异常:程序异常中断");
        }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot websocket + java swing 实现大文件上传与下载 的相关文章

随机推荐

  • 【毕设记录】Stata稳健性检验~ Robustness

    一些网上看的 自己用的tips汇总 1 DID 平行趋势检验 自己用的 1 在论文呈现上 可以绘制平行趋势图 也可以做一个Table表示平行趋势检验的结果 2 学习文献的做法 创建政策发生前两年的变量 比如政策是Rights 解释变量是A
  • python情感词典_sentimentpy模块进行中文文本情感分类

    sentimentpy是我根据R语言的一个文本情感分析包sentiment进行开发的 开发的初衷有 R的sentiment已经被弃坑 没人维护 Python比R更擅长文本处理 sentiment包不支持中文 而sentimentpy包有如下
  • nginx源码编译安装出现“make[1]: Leaving directory `/usr/local/nginx’“解决办法

    因为我所有的操作步骤都是按照上面来的 我的解决办法也非常的简单 如果你走到make这一步的时候只出现了一行 make 1 Leaving directory usr local nginx 1 12 1 提示 不用管它 继续走make in
  • 五人合伙最佳股份分配_五人合伙股份分配协议书范本

    股份合作企业与合伙企业是当前改制 改组中倍受关注的企业组织形式 那么合伙股份转让协议书又是怎么一回事呢 以下是在小编为大家整理的合伙股份转让协议书范文 感谢您的阅读 合伙股份转让协议书范文1转让方 甲方 身份证号码 住址 受让方 甲方 身份
  • Unity制作摇杆

    1 导入UGUI与Tools资源包 Unity中就会出现两个插件 2 导入人物模型资源包全部导入完之后 3 新建Plane 把人物模型拖到场景中 4 给人物模型制作动画状态机 4 1找到人物模型动画 并将全部的动画类型设置为Genic类型
  • 串行测试 并行测试_什么是并行测试,为什么要采用它?

    串行测试 并行测试 随着技术的进步 随着组织从手动测试转向Selenium测试自动化 测试解决方案变得比以往更具可扩展性 但是 大多数组织仍在努力的领域之一是可并行运行多个测试的可伸缩性 许多公司仍在使用顺序测试方法来提供质量保证 这会浪费
  • 大学生团体天梯赛(第六届)

    题目地址 天梯赛 include
  • 深度学习8

    Generative Adversarial Network 正如我们前面所说的 GAN里面有两个重要的东西 其中一个就是Generator Generator可以是一个NN 它的输入是一个vector 它的输出是一个更高维的vector
  • JSP-javabean技术

  • ELK 4.5——加入机器学习

    如果你也是 Elaticsearch 的粉丝 或者机器学习的爱好者 你肯定不会错过这个东西 5 月份 Elaticsearch 推出了新版本 5 4 准确地说是 Elastic Stack 全家桶都更新为 5 4 了 在 X pack 中的
  • Angular input延迟防抖debounceTime

    import Component OnInit from angular core import AbstractControl FormBuilder FormGroup Validators from angular forms imp
  • 【代码复现】NER之GlobalPointer解析

    前言 在NER任务中 主要分为三类实体 嵌套实体 非嵌套实体 不连续实体 今天分享方法以end to end的方式解决前两个问题 GlbalPointer 它利用全局归一化的思路来进行命名实体识别 NER 可以无差别地识别嵌套实体和非嵌套实
  • Mysql事务---MVCC详解

    Mysql数据库事务隔离级别 SQL 标准定义了四个隔离级别 READ UNCOMMITTED 读取未提交 事务的修改 即使没有提交 对其他事务也都是可见的 事务能够读取未提交的数据 这种情况称为脏读 READ COMMITTED 读取已提
  • Jetpack学习-1-Lifecycle+Activity源码分析

    解耦是软件开发亘古不变的追求 而Lifecycle正是这一名言的体现 Android开发过程中 有些功能不可避免与页面的生命周期关联 LifeCycle作为Jetpack中具有生命周期感知姓的组件 通过感知activity fragment
  • CVE-2022-22963:Spring Cloud Function SpEL 远程代码执行漏洞

    读者需知 本文仅供学习使用 由于传播和利用此文所造成的损失均由使用者本人负责 文章作者不为此承担责任 简介 SpringCloud Function作为SpringCloud家族成员最早在2017年提出 旨在为快速发展的Serverless
  • 以太坊开发文档09 - javascriptApi

    Web3 JavaScript appAPI 为了让您的应用程序能够在以太坊上工作 您可以使用web3 js库web3提供的对象 在底层 它通过RPC调用与本地节点通信 web3 js与任何暴露RPC层的以太坊节点一起工作 web3包含et
  • Vue3 isProxy

    isProxy 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理 实例
  • [计算机毕业设计]深度学习的图标型验证码识别系统

    前言 大四是整个大学期间最忙碌的时光 一边要忙着准备考研 考公 考教资或者实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力 近几年各个学校要求的毕设项目越来越难 有不少课题是研究生级别难度的 对本科同学来说是充满挑战 为帮助大
  • OpenGL ES 3.0 开发(一)

    什么是 OpenGLES OpenGLES 全称 OpenGL for Embedded Systems 是三维图形应用程序接口 OpenGL 的子集 本质上是一个跨编程语言 跨平台的编程接口规范 主要应用于嵌入式设备 如手机 平板等 由科
  • SpringBoot websocket + java swing 实现大文件上传与下载

    使用场景 无法直接登录服务器上传文件 使用web端上传超大文件出现超时 实现原理 上传 server端与client端建立websocket连接 client将待传文件进行分块 然后将文件的相关信息 文件名 md5值 分块大小 总块数 当前