「Java」本地文件上传下载预览

简介: Java的 IO 包,内容很多,不过万变不离其宗。从JDK来看,就是对于操作系统文件的封装;从应用层Java来看,就是处理输入输出、格式的转化,并且由于场景比较多,而划分了很多的类,以供开发者使用。其中适用于大文件上传的就是RandomAccessFile,其他还有最普通的File等等。

Java本地文件管理,主要是梳理 Java  RaddomAccessFile  模块


话不多说,拿到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支持移动到文件指定位置处开始读或写。


importjava.io.File;
importjava.io.IOException;
importjava.io.RandomAccessFile;

都是 java.io 包里面的内容


入口 controller 代码


@PostMapping(value="/breakpoint-upload", 
consumes="multipart/*", 
headers="content-type=multipart/form-data",
produces="application/json;charset=UTF-8")
publicRestResponse<Object>breakpointResumeUpload(UploadFileParamparam, HttpServletRequestrequest) {
returnRestResponses.
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

JNIEXPORTvoidJNICALLJava_java_io_RandomAccessFile_setLength(JNIEnv*env, jobjectthis,
jlongnewLength)
{
FDfd;
jlongcur;
fd=getFD(env, this, raf_fd);
if (fd==-1) {
JNU_ThrowIOException(env, "Stream Closed");
return;
    }
if ((cur=IO_Lseek(fd, 0L, SEEK_CUR)) ==-1) gotofail;
if (IO_SetLength(fd, newLength) ==-1) gotofail;
if (cur>newLength) {
if (IO_Lseek(fd, 0L, SEEK_END) ==-1) gotofail;
    } else {
if (IO_Lseek(fd, cur, SEEK_SET) ==-1) gotofail;
    }
return;
fail:
JNU_ThrowIOExceptionWithLastError(env, "setLength failed");
}
jinthandleSetLength(FDfd, jlonglength)
{
intresult;
RESTARTABLE(ftruncate64(fd, length), result);
returnresult;
}
最终调用在操作系统层面,调用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开始上传文件,继续。


文件下载

Stringfilename= (!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)
publicResponseEntity<byte[]>viewFilesImage(@PathVariableStringid)
需要转化为byte[]
publicstaticbyte[] inputStreamToByte(InputStreaminputStream) throwsIOException {
ByteArrayOutputStreambyteArrayOutputStream=newByteArrayOutputStream();
byte[] buff=newbyte[1024];
intrc=0;
while ((rc=inputStream.read(buff, 0, 1024)) >0) {
byteArrayOutputStream.write(buff, 0, rc);
        }
returnbyteArrayOutputStream.toByteArray();
    }


总结


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


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


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

相关文章
|
15小时前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
12 4
|
5天前
|
Web App开发 Java
使用java操作浏览器的工具selenium-java和webdriver下载地址
【10月更文挑战第12天】Selenium-java依赖包用于自动化Web测试,版本为3.141.59。ChromeDriver和EdgeDriver分别用于控制Chrome和Edge浏览器,需确保版本与浏览器匹配。示例代码展示了如何使用Selenium-java模拟登录CSDN,包括设置驱动路径、添加Cookies和获取页面源码。
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
52 2
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
|
26天前
|
存储 前端开发 Java
Java后端如何进行文件上传和下载 —— 本地版(文末配绝对能用的源码,超详细,超好用,一看就懂,博主在线解答) 文件如何预览和下载?(超简单教程)
本文详细介绍了在Java后端进行文件上传和下载的实现方法,包括文件上传保存到本地的完整流程、文件下载的代码实现,以及如何处理文件预览、下载大小限制和运行失败的问题,并提供了完整的代码示例。
178 1
|
3月前
|
Java
Java通过HttpClient从外部url下载文件到本地
该Java程序旨在通过URL将外部网络文件(如图片)下载至本地,并解决防盗链问题。首先,它通过`HttpGet`请求获取远程文件,并通过设置`Referer`头防止防盗链。然后,根据响应内容类型确定文件后缀并保存至指定路径。测试表明,程序能够成功下载文件。
407 8
Java通过HttpClient从外部url下载文件到本地
|
2月前
|
存储 缓存 监控
Java——图片文件位于 bin 目录下,下载新图片会导致应用程序重启
【9月更文挑战第22天】在Java应用中,若图片位于bin目录下且下载新图片导致应用重启,可能是因为部署方式不当或资源监控机制过于敏感。解决方法包括:更改图片存储位置至独立目录;配置应用服务器减少资源监控敏感度;使用独立资源服务器托管静态资源;优化代码减少资源重复加载。具体方案需根据应用实际情况和技术栈调整。
|
2月前
|
JSON 前端开发 JavaScript
java中post请求调用下载文件接口浏览器未弹窗而是返回一堆json,为啥
客户端调接口需要返回另存为弹窗,下载文件,但是遇到的问题是接口调用成功且不报错,浏览器F12查看居然返回一堆json,而没有另存为弹窗; > 正确的效果应该是:接口调用成功且浏览器F12不返回任何json,而是弹窗另存为窗口,直接保存文件即可。
112 2
|
23天前
|
Java
java 文件上传和下载
java 文件上传和下载
18 0
|
2月前
|
Java API 开发者
【Java字节码操控新篇章】JDK 22类文件API预览:解锁Java底层的无限可能!
【9月更文挑战第6天】JDK 22的类文件API为Java开发者们打开了一扇通往Java底层世界的大门。通过这个API,我们可以更加深入地理解Java程序的工作原理,实现更加灵活和强大的功能。虽然目前它还处于预览版阶段,但我们已经可以预见其在未来Java开发中的重要地位。让我们共同期待Java字节码操控新篇章的到来!
|
3月前
|
存储 Java 开发工具
【Azure Developer】VS Code运行Java 版Azure Storage SDK操作Blob (新建Container, 上传Blob文件,下载及清理)
【Azure Developer】VS Code运行Java 版Azure Storage SDK操作Blob (新建Container, 上传Blob文件,下载及清理)