首页 > 编程语言 >Java项目文件上传和下载

Java项目文件上传和下载

时间:2023-09-10 15:25:27浏览次数:70  
标签:文件 Java RandomAccessFile fd 分片 上传 下载

话不多说,拿到 Java 项目,跑起来。这是前后端分离的项目,前端比较简单,直接打开 html 文件。

仓库地址:https://gitee.com/hicey/file-manager

提供:分片上传、断点续传、秒传功能 另外的下载、删除功能

开发环境:JDK8,SpringBoot2.x,MySQL5.5,web-uploader

秒传

上传完成后再次选择这个文件就会启动秒传功能,在百度网盘等应用里可以看到类似的功能

基本判断逻辑是记录文件的 md5,通过文件 md5 来判断文件是否已经存在,如果已存在则直接使用。

代码逻辑可以有多种,比如

1、使用 redis 或者 mysql 记录 文件记录,根据唯一的值 md5,如果文件在,则说明文件已经上传,直接增加一条文件记录,文件地址指向已存在的那个文件地址。前端可以选择对应的库,比如说 spark-md5.js,快速计算文件的 md5。

2、根据文件名和地址,找到磁盘中是否有一样的文件,如果有 conf 配置文件,也需要一起判断。

那什么是 md5 呢?一个文件只有对应唯一的 md5 吗?

MD5 即 Message-Digest Algorithm 5(信息-摘要算法 5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有 MD5 实现。

MD5 算法具有以下特点:

1、压缩性:任意长度的数据,算出的 MD5 值长度都是固定的,是一个 32 位长度的 16 进制字符串。

2、容易计算:从原数据计算出 MD5 值很容易。

3、抗修改性:对原数据进行任何改动,哪怕只修改 1 个字节,所得到的 MD5 值都有很大区别。

4、强抗碰撞:已知原数据和其 MD5 值,想找到一个具有相同 MD5 值的数据(即伪造数据)是非常困难的。

md5 是一种常见不可逆加密算法,使用简单,计算速度快,在很多场景下都会用到,比如:给用户上传的文件命名,数据库中保存的用户密码,下载文件后检验文件是否正确等。

图床、上传组件

前端不是特别熟悉,可以选用的组件库有:webuploader.js  vue-simple-uploader

webuploader 链接地址:

http://fex.baidu.com/webuploader/getting-started.html

这里选用的是  webuploader.js,需要理解 init 函数和各种 event & callback,init 的时候需要给后端的断点续传接口,其他的都有默认值,同时,fileUpload 用的是 FormData,不需要字段校验,因为前后端库函数的不同,一定需要的字段是当前片段、总片段、md5  name 等等

这里有个疑问,前后端都使用库函数,那字段是怎么做到这么匹配的?因为前端不太熟,所以从后端开始改造,可以考虑自定义的 class FormData,再字段匹配到后端库函数的 UploadFileParam

普通文件上传

用 postman 来测试,就是在 body 中选择 form-data, 选择 file,然后上传文件,在 Java 后端的入参,得到是的 MultipartFile 接口,这个是 springframework 封装好的,在这里的实现类是 StandardMultipartFile,是在 org.springframework.web.multipart.support.StandardMultipartHttpServletRequest 类下的。

普通文件上传比较简单,就是在 http 的 header 头中去 Content-Disposition 字段,在后端可以看成是继承自 InputStream ,文件就是个输入流。

分片上传

所谓的分片,前端可以对文件进行分割,比如 前端利用 h5 的 File api 读文件进行分割(啊,前端不太熟悉了,好多都模糊了)

对于 Java 来说,后端处理就是使用了 RandomAccessFile 来收集分片上传的文件。

上传 test.mp4 文件

1、创建 test.mp4.conf 配置文件,RandomAccessFile,可读可写,用来存放所有的分片(chunks)、当前分片(chunk)、当前分片的 flag,每上传一份则更新 flag,表示已上传。

2、创建 test.mp4.temp 临时文件,可读可写,每次都在这个临时文件 append(追加分片的文件),前端 N 次调用 API 上传,一点一点累积,当最后一个分片完成后,重命名为 test.mp4 文件。

3、「可选」前端调用 add 接口,表示要插入一条文件记录到 mysql 中,其实也可以在 最后一个分片完成后,后端调用 callback 来完成。

最重要的是 RandomAccessFile,相比于 FileInputStream 固定使用 O_RDONLY,FileOutputStream 固定使用 O_WRONLY | O_CREAT,RandomAccessFile 提供了在 Java 中指定打开模式的能力,RandomAccessFile 相当于是 FileInputStream 与 FileOutputStream 的封装结合,即可以读也可以写,并且 RandomAccessFile 支持移动到文件指定位置处开始读或写。

import java.io.File;import java.io.IOException;import java.io.RandomAccessFile;

复制代码

都是 java.io 包里面的内容

入口 controller 代码

@PostMapping(value = "/breakpoint-upload", consumes = "multipart/*", headers = "content-type=multipart/form-data",produces = "application/json;charset=UTF-8")public RestResponse<Object> breakpointResumeUpload(UploadFileParam param, HttpServletRequest request) {    return RestResponses.      newResponseFromResult(fileService.breakpointResumeUpload(param, request));} consumes:指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;  

复制代码

在 RandomAccessFile 比较重要的方法有

setLength

设置文件长度,本案例中是设置 conf 的 chunks 的,用来记录所有分片

在 openjdk 的 方法是:Java_java_io_RandomAccessFile_setLength

JNIEXPORT void JNICALLJava_java_io_RandomAccessFile_setLength(JNIEnv *env, jobject this,                                        jlong newLength){    FD fd;    jlong cur;    fd = getFD(env, this, raf_fd);    if (fd == -1) {        JNU_ThrowIOException(env, "Stream Closed");        return;    }    if ((cur = IO_Lseek(fd, 0L, SEEK_CUR)) == -1) goto fail;    if (IO_SetLength(fd, newLength) == -1) goto fail;    if (cur > newLength) {        if (IO_Lseek(fd, 0L, SEEK_END) == -1) goto fail;    } else {        if (IO_Lseek(fd, cur, SEEK_SET) == -1) goto fail;    }    return; fail:    JNU_ThrowIOExceptionWithLastError(env, "setLength failed");}

jinthandleSetLength(FD fd, jlong length){    int result;    RESTARTABLE(ftruncate64(fd, length), result);    return result;}

最终调用在操作系统层面,调用 ftruncate 来设置文件长度

复制代码

seek

设置文件指针的偏移量,从该文件开始计算,在此位置发生下一个读或写操作。偏移量可以设置在文件末尾之外。设置超出文件结尾的偏移量不会改变文件长度。只有在设置偏移量超过文件末尾后,文件长度才会被写入更改。

在 openjdk 中是 seek0 函数。

seek 方法:在 linux、unix 操作系统下就是调用系统的 lseek 函数。

若 lseek 成功执行,则返回新的偏移量,因此可用以下方法确定一个打开文件的当前偏移量:

write

在 openjdk 是 writeBytes(b, off, len)

这三个 write 方法实现与 FileOutputStream 相同,可以参考 JDK 源码阅读: FileOutputStream(下次再说。)

在 io 说明有输入输出,Java 运行在用户态,需要切换到内核态。这些都是需要走系统调用的。

用户态切换到内核态,都有以下一些方式。

系统调用

这是用户态进程主动要求切换到内核态的一种方式。

系统调用(System Call)是操作系统为在用户态运行的进程与硬件设备(如 CPU、磁盘、打印机等)进行交互提供的一组接口。

当用户进程需要发生系统调用时,CPU 通过软中断切换到内核态开始执行内核系统调用函数。用户态进程通过系统调用申请使 用操作系统提供的服务程序完成工作,比如 print()实际上就是执行了一个输出的系统调用。

异常

当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

外围设备的中断

当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会 暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到 内核态的切换。

比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

断点续传

上传过程如果中断,下次再上传该文件将只会上传剩下的分片

设计逻辑大概就是:

1、判断 conf 文件是否存在,如果存在再读取 conf,确认当前 chunk,并返回给前端。

2、前端直接从当前 chunk 开始上传文件,继续。

文件下载

String filename = (!EmptyUtils.basicIsEmpty(isSource) && isSource) ? fileDetails.getData().getFileName() : fileDetails.getData().getFilePath();inputStream = fileService.getFileInputStream(id);response.setHeader("Content-Disposition", "attachment;filename=" + EncodingUtils.convertToFileName(request, filename));// 获取输出流outputStream = response.getOutputStream();IOUtils.copy(inputStream, outputStream);

复制代码

文件预览(图片、PDF 等)

controller

@GetMapping(value = "/view/{id}", produces = MediaType.IMAGE_PNG_VALUE)public ResponseEntity<byte[]> viewFilesImage(@PathVariable String id)

需要转化为 byte[]

public static byte[] inputStreamToByte(InputStream inputStream) throws IOException {        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        byte[] buff = new byte[1024];        int rc = 0;        while ((rc = inputStream.read(buff, 0, 1024)) > 0) {            byteArrayOutputStream.write(buff, 0, rc);        }        return byteArrayOutputStream.toByteArray();    }

复制代码

总结

Java 的 IO 包,内容很多,不过万变不离其宗。

从 JDK 来看,就是对于操作系统文件的封装;

从应用层 Java 来看,就是处理输入输出、格式的转化,并且由于场景比较多,而划分了很多的类,以供开发者使用。其中适用于大文件上传的就是 RandomAccessFile,其他还有最普通的 File 等等。

 

参考文章:http://blog.ncmem.com/wordpress/2023/09/10/java%e9%a1%b9%e7%9b%ae%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%e5%92%8c%e4%b8%8b%e8%bd%bd/

欢迎入群一起讨论

 

 

标签:文件,Java,RandomAccessFile,fd,分片,上传,下载
From: https://www.cnblogs.com/songsu/p/17691270.html

相关文章

  • Java语言的特点,面向对象和面向过程的区别,八种基本数据类型的大小以及封装类
    1、Java语言有哪些特点1、简单易学、有丰富的类库2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高3、与平台无关性(JVM是Java跨平台使用的根本)4、可靠安全5、支持多线程2、面向对象和面向过程的区别面向过程是分析解决问题的步骤,然后用函数把这些步骤一步一步地实现,然后......
  • Java从入门到精通-类和对象(一)
    0.类和对象1.面向对象概述Java面向对象编程(Object-OrientedProgramming,OOP)是一种强大的编程范式,它基于对象、类、封装、继承和多态等核心概念。这种编程范式使得代码更加模块化、可维护、可重用和可扩展。1.1对象和类在Java中,一切都是对象。对象是程序中的基本单位,它代表现实世......
  • 无涯教程-JavaScript - COUPPCD函数
    描述COUPPCD函数返回一个数字,该数字表示结算日期之前的上一个息票日期。语法COUPPCD(settlement,maturity,frequency,[basis])争论Argument描述Required/OptionalSettlement证券的结算日期。证券结算日期是指在发行日期之后将证券交易给买方的日期。Required......
  • 深入理解Java if判断:提升编程效率的关键步骤
    Java中的if判断语句是一种条件语句,用于根据指定条件执行不同的代码块。if语句通常由一个布尔表达式和一个或多个语句组成。如果布尔表达式的结果为true,则执行if语句后面的语句;否则,跳过if语句后面的语句。下面是一个if语句的示例:intx=10;if(x>5){System.out.println("x......
  • Java中equals和==的区别
    ====比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的地址是否相同,即是否是指相同一个对象。比较的是真正意义上的指针操作。1、比较的是操作符两端的操作数是否是同一个对象。2、两边的操作数必须是同一类型的(可以是父子类之间)才能编译通过。3、比较的是地......
  • Linux虚拟机安装及下载
    centos7操作系统下载及安装步骤(仅供参考)下载:1.打开如下网站:先下载镜像文件ping:https://www.centos.org/download/ 2.进入到如下界面 3.然后到如下界面 4.点击下载5.等待下载完成即可安装:这里要用到VMware软件,没有安装的话网址如下:DownloadVMwareWorkstati......
  • 无涯教程-JavaScript - COUPNCD函数
    描述COUPNCD函数返回一个数字,该数字表示结算日期之后的下一个息票日期。语法COUPNCD(settlement,maturity,frequency,[basis])争论Argument描述Required/OptionalSettlement证券的结算日期。证券结算日期是指在发行日期之后将证券交易给买方的日期。Required......
  • 使用Gitee极速下载Nacos项目制作windows启动
    步骤一使用国内链接下载nacos项目:https://gitee.com/mirrors/Nacos 步骤二进入下载好的nacos主目录下执行maven打包命令,在此之前需要配置好Maven mvn-Prelease-nacos-Dmaven.test.skip=truecleaninstall-U 打包成功的结果图步骤三打包好的启动项存在:E:\Nacos......
  • 基于java的高校社团管理系统设计与开发-计算机毕业设计源码+LW文档
    一、研究的背景意义目前高校大学生的数量越来越多,学生管理越来越复杂。在学习期间,也不能仅仅局限于理论知识,高校领导积极组织各种社团,帮助学生培养兴趣,提高高校文化水平,鼓励和帮助部分老师和优秀学生组建社团。学校社团可以根据某一文化主题或者专业技能进行分类,学生可以选择不同......
  • 无涯教程-JavaScript - COUPDAYS函数
    描述COUPDAYS函数返回包含结算日期的息票期限内的天数。语法COUPDAYS(settlement,maturity,frequency,[basis])争论Argument描述Required/OptionalSettlement证券的结算日期。证券结算日期是指在发行日期之后将证券交易给买方的日期。RequiredMaturity证券......