Mrli
别装作很努力,
因为结局不会陪你演戏。
Contacts:
QQ博客园

前后端-实现上传和下载

2021/12/05 后端 前端
Word count: 1,793 | Reading time: 8min

前端

上传

前端上传主要是把文件发送过去,一般采用表单的形式,或者也可以针对上传接口发送请求

  • 表单
1
2
3
4
5
6

<form action="http://127.0.0.1:5000/" enctype="multipart/form-data">
<input type="file" name="toupdate"/> # file的input不允许设置value
<input type="submit" value="上传咯" /> # 按钮显示的文字
</form>
# action为表单要提交到的路径, 必须填写,如果省略则默认当前页面
  • 针对接口,发送网络请求

下载

a标签

1
2
3
4
<div>
<h2>法一: 通过`a`标签来实现下载 </h2>
<a href="http://127.0.0.1:5000/download">ss</a>
</div>

此外,HTML 5 里面为 <a> 标签添加了一个 download 的属性,我们可以轻易的利用它来实现下载重命名的功能。

1
2
3
4
<div>
<h2>法一: 通过`a`标签来实现下载 </h2>
<a href="http://127.0.0.1:5000/download" download="something.txt">ss</a>
</div>

JavaScript

添加a元素并点击

用 JavaScript 来下载文件也是利用这一特性来实现的,我们的 JavaScript 代码不外乎就是:

  • 用 JavaScript 创建一个隐藏的 <a> 标签
  • 设置它的 href 属性
  • 设置它的 download 属性
  • 用 JavaScript 来触发这个它的 click 事件

翻译成 JavaScript 代码就是:

1
2
3
4
5
6
7
8
9
10
11
methods: {
downit(){
var a = document.createElement('a');
// 已经知道下载链接
var url = "http://127.0.0.1:5000/download"
var filename = 'what-you-want.txt';
a.href = url;
a.download = filename;
a.click();
}
}

fetch + blob

Blob 全称是 Binary large object,它表示一个类文件对象,可以用它来表示一个文件。根据 MDN 上面的说法,File API 也是基于 blob 来实现的。
我们构建 blob 的方式就是通过服务器返回的文件来创建 blob 拉!而最简单的方式就是用 fetch API 了,我们可以整合上面的例子:

1
2
3
4
5
6
7
8
9
fetch('http://somehost/somefile.zip').then(res => res.blob().then(blob => {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
var filename = 'myfile.zip';
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}))

已知下载接口URL, 并传入相应的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function downLoad(option){
// 下载接口+请求参数
fetch('http://somehost/download', options).then(res => {
if (res.code === 0) {
var a = document.createElement('a');
// 得到请求文件完整的请求路径
var url = res.data.url;
var filename = 'myfile.zip';
a.href = url;
a.download = filename; // w3c: value: filename——规定被下载的超链接目标。从这边也可以看出其实就是指定文件名
a.click();
} else {
alert('You have no permission to download the file!');
}
});
}

流程为:通过后端服务器来计算出用户的下载链接,然后再利用之前提到的动态创建 <a> 标签的方式来实现下载

跟: JS实现文件下载的三种方式—a标签下载、form表单下载、blob转换url下载中的一样,也都是先计算出资源所在真实的URL位置,然后再请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function downLoadFile(res){
const ele = document.createElement('a');
ele.setAttribute('href',res.url); //设置下载文件的url地址
ele.setAttribute('download' , 'download');//用于设置下载文件的文件名
ele.click();
}
window.onload = function(){
$.ajax({
url:"get-file",
type:"get",
dataType:'JSON'
}).then(function(res){
if(res.bizNO > 0 ){
downLoadFile(res);
}else{
alert(res.bizMsg);
}
}).always(function(){
alert("连接异常");
})
}

为什么要用 JavaScript 下载文件

好拉,说了半天,其实我们一直说的都是:「不要用 JavaScript 下载文件拉,限制多多,又不好用,直接用 html 就好拉,简单方便又快捷」这个论调。事实上也确实如此,但有些时候我们确实需要通过 JavaScript 来做一些处理, 如权限校验。

下载文件前的权限校验

有些时候,我们需要对下载做一些限制,最常见的就是权限校验了,如检查该用户是否有下载的权限,是否有高速下载的权限等等。这时候,我们可以利用 JavaScript 做一些预处理。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fetch('http://somehost/check-permission', options).then(res => {
if (res.code === 0) {
var a = document.createElement('a');
var url = res.data.url;
var filename = 'myfile.zip';

a.href = url;
a.download = filename;
a.click();

// const ele = document.createElement('a');
// ele.setAttribute('href',res.url); //设置下载文件的url地址
// ele.setAttribute('download' , 'download');//用于设置下载文件的文件名
// ele.click();
} else {
alert('You have no permission to download the file!');
}
});

参考:

Python Flask实现上传和下载接口

上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import os
from flask import Flask, request, url_for, send_from_directory
from werkzeug.utils import secure_filename
from flask import send_from_directory, make_response
# 设置上传文件允许种类
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

app = Flask(__name__)
# 设置上传路径
app.config['UPLOAD_FOLDER'] = os.path.join(os.getcwd(), "updates")
# 设置上传文件最大大小
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

html = '''
<!DOCTYPE html>
<title>Upload File</title>
<h1>Photo Upload</h1>
<form method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=upload>
</form>
<div>
<a href='http://127.0.0.1:5000/download'><p>下载</p></a>
</div>
'''


def allowed_file(filename):
"""
是否是允许上传的文件
"""
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS


@app.route('/uploads/<filename>')
def uploaded_file(filename):
"""
获得上传后的链接
"""
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)

@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file'] # 可以通过requests.files属性拿到multipart/form-data上传的数据
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# 保存文件
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
# 获得保存后的图片链接
file_url = url_for('uploaded_file', filename=filename)
print(file_url)
return html + '<br><img src=' + file_url + '>'
return html

下载

1
2
3
4
5
@app.route("/download")
def index():
# 可以看到上传代码中也用到了send_from_directory;这边的区别主要是添加了as_attachment参数, 使得可以下载
# return make_response(send_from_directory("./", "test.txt", as_attachment=True))
return send_from_directory("./", "./", filename="test.txt", as_attachment=True)

Java实现上传下载

上传

application.properties

1
2
3
4
5
6
# 用来指定服务器端文件大小的限制
spring.servlet.multipart.max-file-size=300MB
# 用来指定客户端文件大小的限制
spring.servlet.multipart.max-request-size=300MB

fileLocation=static/files

FileUploadController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 读取配置文件中的路径 static/files
@Value("${fileLocation}")
private String fileLocation;

@PostMapping("upload")
@ResponseBody
public void upload(MultipartFile file) throws IOException {
// 获得 classpath 的绝对路径
String realPath = ResourceUtils.getURL("classpath:").getPath() + fileLocation;
File newFile = new File(realPath);
// 如果文件夹不存在、则新建
if (!newFile.exists()) newFile.mkdirs();
// 上传
String fileName = date.getTime() +"@" + file.getOriginalFilename();
file.transferTo(new File(newFile, fileName));
}

下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

@Value("${fileLocation}")
private String fileLocation;

@GetMapping("download")
public void download(String fileName, HttpServletResponse response) throws IOException {
// 获得待下载文件所在文件夹的绝对路径
String realPath = ResourceUtils.getURL("classpath:").getPath() + fileLocation;
// 获得文件输入流
FileInputStream inputStream = new FileInputStream(new File(realPath, fileName));
// 设置响应头、以附件形式打开文件
response.setHeader("content-disposition", "attachment; fileName=" + fileName);
ServletOutputStream outputStream = response.getOutputStream();
int len = 0;
byte[] data = new byte[1024];
while ((len = inputStream.read(data)) != -1) {
outputStream.write(data, 0, len);
}
outputStream.close();
inputStream.close();
}

Author: Mrli

Link: https://nymrli.top/2021/12/05/前后端-实现上传和下载/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
Js-ShortCode
NextPost >
VBA学习——正则提取单元格指定内容并加粗
CATALOG
  1. 1. 前端
    1. 1.1. 上传
    2. 1.2. 下载
      1. 1.2.1. a标签
      2. 1.2.2. JavaScript
        1. 1.2.2.1. 添加a元素并点击
        2. 1.2.2.2. fetch + blob
    3. 1.3. 为什么要用 JavaScript 下载文件
      1. 1.3.1. 下载文件前的权限校验
  2. 2. Python Flask实现上传和下载接口
    1. 2.1. 上传
    2. 2.2. 下载
  3. 3. Java实现上传下载
    1. 3.1. 上传
    2. 3.2. 下载