技术标签: java javascript 文件上传和下载
之前的文章有写过
vue+springboot使用文件流实现文件下载
实现如何通过
D:\file\文件名.文件格式
的形式进行下载文件
但是它对于很多业务场景相对适用性不是很广泛。
以及
elementUI加springboot实现上传excel文件给后端并读取excel
也只能是通过elementui的元素类型进行上传。
因此,本次文章将通过两种方式
存放本地文件
和存放字节流
两种方式教大家如何进行文件的上传和下载,适用绝大部分场景.
我们在开发完项目,并且将项目部署到服务器的时候,如果我们有实现上传的功能,实际上还是保存文件在部署项目的这个服务器上,下载也是从服务器下载,因此,我们就可以直接使用存放本地文件的方式实现上传和下载
我们都知道,在前端如果需要接收文件的传递给后端的话,可以使用
formData
进行保存数据,那么对应前端代码如下:
为演示方便,后续上传都使用原生的input进行获取文件
界面
<input type="file" id="upload" />
<button onclick="uploadFile">上传</button>
js逻辑
uploadFile() {
// 获取上传图片
let dom = document.getElementById("upload");
// 定义接收文件的formData,并用"file"去接收数据
let formDataInfo = new FormData();
// 接收第一个文件,适用单文件
formDataInfo.append("file",dom.files[0]);
// 需要引入axios
this.$axios({
method: 'post', // 为post请求
url: "/fileUplod"// 请求后端的路径,改成自己的就行,
data: formDataInfo,// 传递参数
responseType: 'json',// 响应类型
}).then(res = > {
// 响应数据
})
以上是适用于单文件,如果需要上传多文件,参考如下代码
uploadFile() {
// 获取上传图片
let dom = document.getElementById("upload");
// 定义接收文件的formData,并用"file"去接收数据
let formDataInfo = new FormData();
// 接收文件数量
formDataInfo.append("length",dom.files.length)
// 遍历文件加入formData,后端处理的时候获取length的长度进行遍历文件
for (let i = 0;i<dom.files.length;i++) {
formDataInfo.append("file"+i,dom.files[i]);
}
// 需要引入axios
this.$axios({
method: 'post', // 为post请求
url: "/fileUplod"// 请求后端的路径,改成自己的就行,
data: formDataInfo,// 传递参数
responseType: 'json', // 响应类型
}).then(res = > {
// 响应数据
})
}
@POST
@Path("fileUplod")
public String fileUplod(FormDataMultiPart form) {
try {
// 需要保存文件路径
String path = "D:/file";
// 文件流
InputStream inputStream= form.getField("file").getValueAs(InputStream.class);
// 文件名
String fileName = form.getField("file").getContentDisposition().getFileName();
// 调用下面的保存本地文件方法
uploadFileToLocal(inputStream,path,fileName);
return "上传文件成功!";
}catch(Exception e) {
return "上传文件到本地磁盘失败" + e.getMessage();
}
}
/**
* @param inputStream 文件流
* @param path 上传路径
* @param fileName 文件名
* @return 如果返回含义字符串【报错】上传失败,否则返回文件路径
* @author ks027276
* @description 上传文件到本地方法
*/
public String uploadFileToLocal(InputStream inputStream, String path, String fileName) throws Exception {
try {
File folder = new File(path);
// 创建没有的文件夹
if (!folder.isDirectory()) {
folder.mkdirs();
}
//创建空文件
FileOutputStream outputStream = new FileOutputStream(path + "/" + fileName);
// 将文件流数据填充到空文件
int index = 0;
byte[] bytes = new byte[1024];
while ((index = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, index);
outputStream.flush();
}
inputStream.close();
outputStream.close();
return path + "/" + fileName;
} catch (Exception e) {
return "【报错】" + e.getMessage();
}
}
假如需要获取文件名,使用代码:
String fileName = form.getField("file").getContentDisposition().getFileName();
值得一提的是,如果上传文件名为中文,那么这个方式上传会乱码,需要处理为如下:
String fileName = new String(form.getField("file").getContentDisposition().getFileName()
.getBytes("iso-8859-1"), "UTF-8");
上述皆为单文件上传的方式,假如使用我上方前端多文件上传,逻辑其实差不多,就是多了一个遍历而已,具体逻辑如下:
@POST
@Path("fileUplod")
public String fileUplod(FormDataMultiPart form) {
try {
// 需要保存文件路径
String path = "D:/file";
for (int i = 0;i <Integer.valueof(form.getField("length").getvalue());i++) {
// 文件流
InputStream inputStream= form.getField("file"+i).getValueAs(InputStream.class);
// 文件名
String fileName = form.getField("file"+i).getContentDisposition().getFileName();
// 调用下面的保存本地文件方法
uploadFileToLocal(inputStream,path,fileName);
}
return "上传文件成功!";
}catch(Exception e) {
return "上传文件到本地磁盘失败" + e.getMessage();
}
}
/**
* @param inputStream 文件流
* @param path 上传路径
* @param fileName 文件名
* @return 如果返回含义字符串【报错】上传失败,否则返回文件路径
* @author ks027276
* @description 上传文件到本地方法
*/
public String uploadFileToLocal(InputStream inputStream, String path, String fileName) throws Exception {
try {
File folder = new File(path);
// 创建没有的文件夹
if (!folder.isDirectory()) {
folder.mkdirs();
}
//创建空文件
FileOutputStream outputStream = new FileOutputStream(path + "/" + fileName);
// 将文件流数据填充到空文件
int index = 0;
byte[] bytes = new byte[1024];
while ((index = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, index);
outputStream.flush();
}
inputStream.close();
outputStream.close();
return path + "/" + fileName;
} catch (Exception e) {
return "【报错】" + e.getMessage();
}
}
以下截图为我用上方功能实现保存到本地:
下载的话,既然上面已经接收到了路径,那么直接用路径去查找就好了
路径形式如下:
D:/file/1.jpg
<button onclick="getFile">获取文件</button>
// path值形式为:D:/file/1.jpg
// name值形式为:1.jpg
getFile(path,name) {
// 需要引入axios
this.axios({
method: 'get', // 为get请求
url: "/downFile"// 请求后端的路径,改成自己的就行,
data: {
path: path,
name: name
},// 传递参数
responseType: 'json',// 响应类型
}).then(res => {
var a = document.createElement('a');
a.href = res.data // 这里需要根据自己实际项目作变更,可能你的数据在res,或res.data或res.data.data
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
})
}
/**
* 文件下载 根据路径
*/
// response类型引用为:
import javax.ws.rs.core.Response;
@GET
@Path("/downFile")
public Response downLoadPanorama(@QueryParam("path") String path,
@QueryParam("name") String name) {
try {
FileInputStream inputStream = new FileInputStream(path);
byte[] bytes = toByteArray(inputStream);
return Response.ok(new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
output.write(bytes);
} catch (Exception ex) {
}
}
}).header("Content-disposition",
"attachment;filename=" + name)
.header("Cache-Control", "no-cache")
.build();
} catch (Exception e) {
e.printStackTrace();
return Response.status(Response.Status.NOT_FOUND).build();
}
}
使用上面的代码会将文件下载下来
形式如:
一般来说,会更推荐使用字节流的方式进行实现文件的上传和下载,因为是文件流,所以我们可以更方便便捷的用它实现各种操作。
存字节流实际上是保存在数据库,因此对数据库的内存会占用更多。
首先,数据库表的字段需要新建blob
类型的字段接收数据
在java代码对象需要用byte数组类型接收文件转字节流的字段属性接收
@Data
public class FileObject {
private byte[] file;
}
界面
<input type="file" id="upload" />
<button onclick="uploadFile">上传</button>
js逻辑
uploadFile() {
// 获取上传图片
let dom = document.getElementById("upload");
// 定义接收文件的formData,并用"file"去接收数据
let formDataInfo = new FormData();
// 接收第一个文件,适用单文件
formDataInfo.append("file",dom.files[0]);
// 需要引入axios
this.$axios({
method: 'post', // 为post请求
url: "/fileUplod"// 请求后端的路径,改成自己的就行,
data: formDataInfo,// 传递参数
responseType: 'json',// 响应类型
}).then(res = > {
// 响应数据
})
以上是适用于单文件,如果需要上传多文件,参考如下代码
uploadFile() {
// 获取上传图片
let dom = document.getElementById("upload");
// 定义接收文件的formData,并用"file"去接收数据
let formDataInfo = new FormData();
// 接收文件数量
formDataInfo.append("length",dom.files.length)
// 遍历文件加入formData,后端处理的时候获取length的长度进行遍历文件
for (let i = 0;i<dom.files.length;i++) {
formDataInfo.append("file"+i,dom.files[i]);
}
// 需要引入axios
this.$axios({
method: 'post', // 为post请求
url: "/fileUplod"// 请求后端的路径,改成自己的就行,
data: formDataInfo,// 传递参数
responseType: 'json', // 响应类型
}).then(res = > {
// 响应数据
})
}
其实逻辑是一样的,和存放本地文件上传的数据一样
最主要的逻辑差距在后端
@POST
@Path("fileUplod")
public String fileUplod(FormDataMultiPart form) {
try {
// 文件流
InputStream inputStream= form.getField("file").getValueAs(InputStream.class);
// 调用下面的保存本地文件方法
byte[] fileBytes = getBytes(inputStream);
// 调用新增数据库sql存到数据库
// 这边就属于自己的功能逻辑了,我以下就不写了
return "上传文件成功!";
}catch(Exception e) {
return "上传文件到数据库失败" + e.getMessage();
}
}
/**
* 存字节流到数据库
* @param inputStream 字节流
* 有值说明获取成功,无值说明失败
*/
public byte[] getBytes(InputStream inputStream) throws Exception {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer;
int len;
byte[] buf = new byte[2048];
while ((len = bufferedInputStream.read(buf)) != -1) {
byteArrayOutputStream.write(buf, 0, len);
}
byteArrayOutputStream.flush();
buffer = byteArrayOutputStream.toByteArray();
inputStream.close();
return buffer;
} catch (Exception e) {
return null;
}
}
上述皆为单文件上传的方式,假如使用我上方前端多文件上传,逻辑其实差不多,就是多了一个遍历而已,具体逻辑如下:
@POST
@Path("fileUplod")
public String fileUplod(FormDataMultiPart form) {
try {
for (int i = 0;i <Integer.valueof(form.getField("length").getvalue());i++) {
// 文件流
InputStream inputStream= form.getField("file"+i).getValueAs(InputStream.class);
// 调用下面的保存本地文件方法
byte[] "file"+i = getBytes(inputStream);
// 调用新增数据库sql存到数据库
// 这边就属于自己的功能逻辑了,我以下就不写了
}
return "上传文件成功!";
}catch(Exception e) {
return "上传文件到数据库失败" + e.getMessage();
}
}
/**
* 存字节流到数据库
* @param inputStream 字节流
* 有值说明获取成功,无值说明失败
*/
public byte[] getBytes(InputStream inputStream) throws Exception {
try {
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer;
int len;
byte[] buf = new byte[2048];
while ((len = bufferedInputStream.read(buf)) != -1) {
byteArrayOutputStream.write(buf, 0, len);
}
byteArrayOutputStream.flush();
buffer = byteArrayOutputStream.toByteArray();
inputStream.close();
return buffer;
} catch (Exception e) {
return null;
}
}
如果需要获取文件名,参照上面存放本地文件方式有说明
存到数据库结果为:
是一个BLOB数据
通过sql查询语句我们查找到存放的字节流文件数据,展示到前端,结果为:
是一个Array数组的形式
ps:需要注意的是,由于不同后端项目架构的问题,可能返回给前端的不是字节流
而是如下方的base64:
因此我们对于处理base64的需要下载,思路应该是将base64转为文件
后端逻辑就不写了,单纯就是一个sql查数据而已
如果是图片文件,可以使用一个img元素直接展示图片
<img :src="base64"/>
<button onclick="showImage">上传</button>
上面我们绑定了一个自定义属性:base64,js逻辑直接把它拿过来赋值即可
showImage(byte,type) {
let binary = "";
const bytes = new Uint8Array(byte);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary +=String.fromCharCode(bytes[i]);
}
// base64前缀
let base64Before = "";
if (type == 'jpg' || type == 'jpeg') {
base64Before = 'data:image/jpeg;base64,';
} else if (type == 'png') {
base64Before = 'data:image/png;base64,';
}
this.base64 =base64Before +window.btoa(binary);
}
假如你需要拿到字节流下载文件的话,使用如下逻辑:
// byte,字节流,数据形式如: [11,-1,44,20,......]
// _type,数据形式如: jpg txt
// name,文件名,数据形式如 1.jpg 2.txt
downFile (byte,_type,name) {
var eleLink = document.createElement('a');
// 根据文件类型设定blob文件类型
let fileType = this.extToMimes(_type);
// 设定下载文件名
eleLink.download = name;
// 设置a标签不显示
eleLink.style.display = 'none';
// 将文件流转Uint8Array
const bytes = new Uint8Array(byte);
// 将转换后的数据和获取的文件类型,创建blob
var blob = new Blob([bytes],{
type: fileType});
eleLink.href = URL.createObjectURL(blob);
// 自动触发点击
document.body.appendChild(eleLink);
eleLink.click();
// 然后移除
document.body.removeChild(eleLink);
}
// 根据传入的类型设定blob文件类型
extToMimes(ext) {
let type = undefined;
switch (ext) {
case 'jpg':
type = 'image/jpeg'
case 'png':
type = 'image/png'
case 'jpeg':
type = 'image/jpeg'
break;
case 'txt':
type = 'text/plain'
break;
case 'xls':
type = 'application/vnd.ms-excel'
break;
case 'doc':
type = 'application/msword'
break;
case 'xlsx':
type = 'application/vnd.ms-excel'
break;
default:
}
return type;
}
通过以上,可以下载文件,并且正确显示出内容
测试有效
除了上面那种获取字节流文件的方式,还有另外一种,上面基本上需要前端和后端同时参与,并且代码量也不少。
而这里我的字节流获取文件的方式大部分工作教给后端处理,前端单纯只是获取而已。
后端代码如下:
这里我的思路是,将文件存在数据库,要用的时候直接通过主键拿到存储的byte数组,后端处理逻辑
// 通过id获取byte数组传给前端
@GetMapping("getFileById")
@ResponseBody
public ResponseEntity<byte[]> getFileById(@RequestParam("id")Integer id) {
HttpHeaders httpHeaders=new HttpHeaders();
try {
// 获取数据库byte数组,这里我假定已经从数据库拿到了字节流
byte[] data= xxx数据库获取到的数据;
// 文件名 格式如1.png
String fileName
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
httpHeaders.setContentDisposition(ContentDisposition.builder("file").filename(fileName, StandardCharsets.UTF_8).build());
return new ResponseEntity<>(data, httpHeaders, HttpStatus.OK);
}catch (Exception e) {
return new ResponseEntity<>(null, httpHeaders, HttpStatus.NOT_FOUND);
}
}
按照如上我的后端代码,前端需要给入一个id调后端接口,
如果是图片,想展示出来显示图片可以这么做:
界面写一个img元素,并且直接给src赋值调用后端的接口
<img :src="localhost:端口/getFileById?id=11"/>
通过js可以动态赋值展示图片
如果想直接下载文件,则使用如下代码:
<button @click="down">点击下载</button>
down() {
window.location.href = "localhost:端口/getFileById?id=11"";
}
两种方式皆可,只不过上面那种方式可以应对的情况更多,按照自己的需求酌情选择方式方法即可
以上,为通过字节流和本地文件的方式进行上传和下载的方法。
文章浏览阅读2w次,点赞51次,收藏33次。一、需求给定一个整数,返回大于等于该整数的最小2次幂(2的乘方)。例: 输入 输出 -1 1 1 1 3 4 9 16 15 16二、分析当遇到这个需求的时候,我们可能会很容易想到一个"笨"办法:..._整数 最小的2的几次方
文章浏览阅读865次。选项,以防止命令将 IP 地址解析为主机名。如果只想在命令的输出中显示 unix套接字 连接,可以使用。不带任何选项,用来显示已建立连接的所有套接字的列表。如果只想在命令的输出中显示 tcp 连接,可以使用。如果只想在命令的输出中显示 udp 连接,可以使用。如果不想将ip地址解析为主机名称,可以使用。如果要取消命令输出中的标题行,可以使用。如果只想显示被侦听的套接字,可以使用。如果只想显示ipv4侦听的,可以使用。如果只想显示ipv6侦听的,可以使用。_ss@,,x,, 0
文章浏览阅读568次。CommandNotFoundError: 'activate'_commandnotfounderror: 'activate
文章浏览阅读426次,点赞10次,收藏19次。完成以上步骤后,您已在 Windows 10 上成功安装并验证了 Apache Kafka。在生产环境中,通常会将 Kafka 与外部 ZooKeeper 集群配合使用,并考虑配置安全、监控、持久化存储等高级特性。在生产者窗口中输入一些文本消息,然后按 Enter 发送。ZooKeeper 会在新窗口中运行。在另一个命令提示符窗口中,同样切换到 Kafka 的。Kafka 服务器将在新窗口中运行。在新的命令提示符窗口中,切换到 Kafka 的。,应显示已安装的 Java 版本信息。_win10安装部署kafka
文章浏览阅读1.4w次。缓冲区对象(Buffer Object)是在OpenGL中用于存储和管理数据的一种机制。缓冲区对象可以存储各种类型的数据,例如顶点、纹理坐标、颜色等。在渲染过程中,缓冲区对象中存储的数据可以被复制到渲染管线的不同阶段中,例如顶点着色器、几何着色器和片段着色器等,以完成渲染操作。相比传统的CPU访问内存,缓冲区对象的数据存储和管理更加高效,能够提高OpenGL应用的性能表现。_js 缓冲数据 new float32array
文章浏览阅读912次。(1)图(Graph):图是数学和计算机科学中的一个抽象概念,它由一组节点(顶点)和连接这些节点的边组成。图可以是有向的(有方向的,边有箭头表示方向)或无向的(没有方向的,边没有箭头表示方向)。图用于表示各种关系,如社交网络、电路、地图、组织结构等。(2)网络(Network):网络是一个更广泛的概念,可以包括各种不同类型的连接元素,不仅仅是图中的节点和边。网络可以包括节点、边、连接线、路由器、服务器、通信协议等多种组成部分。网络的概念在各个领域都有应用,包括计算机网络、社交网络、电力网络、交通网络等。_图论与网络优化数学建模
文章浏览阅读1.5k次。我们经常会碰见 正在加载中,加载出错, “暂无商品”等一系列的相似的布局,因为我们有很多请求网络数据的页面,我们不可能每一个页面都写几个“正在加载中”等布局吧,这时候将这些状态的布局封装在一起就很有必要了。我们可以将这些封装为一个自定布局,然后每次操作该自定义类的方法就行了。 首先一般来说,从服务器拉去数据之前都是“正在加载”页面, 加载成功之后“正在加载”页面消失,展示数据;如果加载失败,就展示_adnroid加载数据转圈封装全屏转圈封装
文章浏览阅读1.6k次,点赞23次,收藏29次。PS: 如果执行sudo grep 'temporary password' /var/log/mysqld.log 后没有报错,也没有任何结果显示,说明默认密码为空,可以直接进行下一步(后面设置密码时直接填写新密码就行)。3.(可选)当操作系统为Alibaba Cloud Linux 3时,执行如下命令,安装MySQL所需的库文件。下面示例中,将创建新的MySQL账号,用于远程访问MySQL。2.依次运行以下命令,创建远程登录MySQL的账号,并允许远程主机使用该账号访问MySQL。_alibaba cloud linux 3
文章浏览阅读7.8k次。EXCEL中数据如何做离散性分析纠错。离散不是均值抄AVEDEV……=AVEDEV(A1:A100)算出来的是A1:A100的平均数。离散是指各项目间指标袭的离散均值(各数值的波动情况),数值较低表明项目间各指标波动幅百度小,数值高表明波动幅度较大。可以用excel中的离散公式为STDEV.P(即各指标平均离散)算出最终度离散度。excel表格函数求一组离散型数据,例如,几组C25的...用exc..._excel数据分析离散
文章浏览阅读406次,点赞7次,收藏8次。i < 5){ //第3行。int count;System.out.println ("危险!System.out.println(”真”);System.out.println(”假”);System.out.print(“姓名:”);System.out.println("无匹配");System.out.println ("安全");
文章浏览阅读3.6k次。背景测试到性能、压力时,经常需要查看磁盘、网络、内存、cpu的性能值这里简单介绍下各个指标的含义一般磁盘比较关注的就是磁盘的iops,读写速度以及%util(看磁盘是否忙碌)CPU一般比较关注,idle 空闲,有时候也查看wait (如果wait特别大往往是io这边已经达到了瓶颈)iostatiostat uses the files below to create ..._/proc/diskstat
文章浏览阅读2.4k次。问题:在Android上使用 glReadPixel 读取当前渲染数据,在若干机型(华为P9以及魅族某魅蓝手机)上读取数据失败,glGetError()没有抓到错误,但是获取到的数据有误,如果将获取到的数据保存成为图片,得到的图片为黑色。解决方法:glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区,则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓..._glreadpixels 全黑