Python开发篇——基于React-Dropzone开发上传组件
这次我要讲述的是在React-Flask框架上开发上传组件的技巧。我目前主要以React开发前端,在这个过程中认识到了许多有趣的前端UI框架——React-Bootstrap、Ant Design、Material UI、Bulma等。而比较流行的上传组件也不少,而目前用户比较多的是 jQuery-File-Upload和Dropzone,而成长速度快的新晋有Uppy和filepond。比较惋惜的是Fine-Uploader的作者自2018年后就决定不再维护了,原因作为后来者的我就不多过问了,但请各位尊重每一位开源作者的劳动成果。
这里我选择React-Dropzone,原因如下:
- 基于React开发,契合度高
- 网上推荐度高,连Material UI都用他开发上传组件
- 主要以
Drag
和Drop
为主,但是对于传输逻辑可以由开发者自行设计。例如尝试用socket-io来传输file chunks。对于node全栈估计可行,但是我这里使用的是Flask,需要将Blob转ArrayBuffer。但是如何将其在Python中读写,我就没进行下去了。
实例演示
1. axios上传普通文件:
通过yarn将react-dropzone和引入:
yarn add react-dropzone axios
前端js如下(如有缺失,请自行修改):
import React, {
useState,
useCallback,
useEffect,
} from 'react';
import {useDropzone} from 'react-dropzone';
import "./dropzone.styles.css"
import InfiniteScroll from 'react-infinite-scroller';
import {
List,
message,
// Avatar,
Spin,
} from 'antd';
import axios from 'axios';
/**
* 计算文件大小
* @param {*} bytes
* @param {*} decimals
* @returns
*/
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
/**
* Dropzone 上传文件
* @param {*} props
* @returns
*/
function DropzoneUpload(props) {
const [files, setFiles] = useState([])
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const onDrop = useCallback(acceptedFiles => {
setLoading(true);
const formData = new FormData();
smallFiles.forEach(file => {
formData.append("files", file);
});
axios({
method: 'POST',
url: '/api/files/multiplefiles',
data: formData,
headers: {
"Content-Type": "multipart/form-data",
}
})
then(resp => {
addFiles(acceptedFiles);
setLoading(false);
});
}, [files]);
// Dropzone setting
const { getRootProps, getInputProps } = useDropzone({
multiple:true,
onDrop,
});
// 删除附件
const removeFile = file => {
const newFiles = [...files]
newFiles.splice(newFiles.indexOf(file), 1)
setFiles(newFiles)
}
useEffect(() => {
// init uploader files
setFiles([])
},[])
return (
<section className="container">
<div {...getRootProps({className: 'dropzone'})}>
<input {...getInputProps()} />
<p>拖动文件或点击选择文件</p>
</div>
<div className="demo-infinite-container">
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={handleInfiniteOnLoad}
hasMore={!loading && hasMore}
useWindow= {false}
>
<List
dataSource={files}
renderItem={item=> (
<List.Item
actions={[
// <a key="list-loadmore-edit">编辑</a>,
<a key="list-loadmore-delete" onClick={removeFile}>删除</a>
]}
// extra={
// }
key={item.path}>
<List.Item.Meta
avatar={
<>
{
!!item.type && ['image/gif', 'image/jpeg', 'image/png'].includes(item.type) &&
<img
width={100}
alt='logo'
src={item.preview}
/>
}
</>
}
title={item.path}
description={formatBytes(item.size)}
/>
</List.Item>
)}
>
{loading && hasMore && (
<div className="demo-loading-container">
<Spin />
</div>
)}
</List>
</InfiniteScroll>
</div>
</section>
);
}
flask代码:
def multiplefiles():
if 'files' not in request.files:
return jsonify({'message': '没有文件!'}), 200
files = request.files.getlist('files')
for file in files:
if file:
# 通过拼音解决secure_filename中文问题
filename = secure_filename(''.join(lazy_pinyin(file.filename))
Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
file.save(os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename))
return jsonify({'message': '保存成功!!'})
2. 大文件导入:
通过file.slice()方法生成文件的chunks。不要用Promise.all容易产生非顺序型的请求,导致文件损坏。
js代码:
const promiseArray = largeFiles.map(file => new Promise((resolve, reject) => {
const chunkSize = CHUNK_SIZE;
const chunks = Math.ceil(file.size / chunkSize);
let chunk = 0;
let chunkArray = new Array();
while (chunk <= chunks) {
let offset = chunk * chunkSize;
let slice = file.slice(offset, offset+chunkSize)
chunkArray.push([slice, offset])
++chunk;
}
const chunkUploadPromises = (slice, offset) => {
const largeFileData = new FormData();
largeFileData.append('largeFileData', slice)
return new Promise((resolve, reject) => {
axios({
method: 'POST',
url: '/api/files/largefile',
data: largeFileData,
headers: {
"Content-Type": "multipart/form-data"
}
})
.then(resp => {
console.log(resp);
resolve(resp);
})
.catch(err => {
reject(err);
})
})
};
chunkArray.reduce( (previousPromise, [nextChunk, nextOffset]) => {
return previousPromise.then(() => {
return chunkUploadPromises(nextChunk, nextOffset);
});
}, Promise.resolve());
resolve();
}))
flask代码:
filename = secure_filename(''.join(lazy_pinyin(filename)))
Path(UPLOAD_FOLDER + '/' + file_info['dir_path']).mkdir(parents=True, exist_ok=True)
save_path = os.path.join(UPLOAD_FOLDER + '/' + file_info['dir_path'], filename)
try:
with open(save_path, 'ab') as f:
f.seek(offset)
f.write(file.stream.read())
print("time: "+ str(datetime.now())+" offset: " + str(offset))
except OSError:
return jsonify({'Could not write to file'}), 500
结语
文件传输一直都是HTTP的痛点,尤其是大文件传输。最好的方式是自己做个Client,通过FTP和FTPS的协议进行传输。第二种来自于大厂很中心化的方法,通过文件的checksum来确定文件是否已经上传了,来营造秒传的效果。第三种来自去中心化的Bittorrent的方法每一个用户做文件种子,提供文件传输的辅助,目前国内并没有普及使用。
Python开发篇——基于React-Dropzone开发上传组件的更多相关文章
- 基于SWFUpload的angular上传组件
回顾 由于工作内容比较多,特别是架构方面,需要耗费很多的时间调整.重构,因此很久没有写文章了. 话就不多说了,直接进入主题. 实现 首先分析一下SWFUpload初始化的时候,需要传入当前触发上传的元 ...
- Vue.js 3.0搭配.NET Core写一个牛B的文件上传组件
在开发Web应用程序中,文件上传是经常用到的一个功能. 在Jquery时代,做上传功能,一般找jQuery插件就够了,很少有人去探究上传文件插件到底是怎么做的. 简单列一下我们要做的技术点和功能点 使 ...
- vue大文件上传组件选哪个好?
需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制. 第一步: 前端修改 由于项目使用的是 ...
- python 全栈开发,Day75(Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件)
昨日内容回顾 基于对象的跨表查询 正向查询:关联属性在A表中,所以A对象找关联B表数据,正向查询 反向查询:关联属性在A表中,所以B对象找A对象,反向查询 一对多: 按字段:xx book ----- ...
- 基于Node的React图片上传组件实现
写在前面 红旗不倒,誓把JavaScript进行到底!今天介绍我的开源项目 Royal 里的图片上传组件的前后端实现原理(React + Node),花了一些时间,希望对你有所帮助. 前端实现 遵循R ...
- java深入探究10-文件上传组件FileUpload,邮件开发
1.文件上传组件FileUpload 1)java提供了文件上传的工具包 需要引入:commons-fileupload-1.2.1.jar(文件上床组件核心包) commons-oi-1.4(封装了 ...
- 【Java Web开发学习】Spring MVC文件上传
[Java Web开发学习]Spring MVC文件上传 转载:https://www.cnblogs.com/yangchongxing/p/9290489.html 文件上传有两种实现方式,都比较 ...
- 基于tornado python pandas和bootstrap上传组件的mongodb数据添加工具
总体思路:基于bootstrap4的前端页面上传组件,把excel文件上传至服务器,并利用python pandas读取里面的数据形成字典列表 通过pymongo 接口把数据插入或追加到mongodb ...
- Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件
一.Django与Ajax AJAX准备知识:JSON 什么是 JSON ? JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻 ...
随机推荐
- 19、oracle的启动和关闭过程
19.1.oracle数据库实例的启动分三步: 1.启动oracle例程: startup nomount; #读初始化参数文件,启动实例,但不安装数据库.当数据库以这个模式启动时,参数文件被读取, ...
- 使用Vue-Cli搭建Ant Design Vue前端开发环境
如果文章有帮助到你,还请点个赞或留下评论 搭建脚手架 环境准备 nodeJS vue-cli 如果没有安装点击此处查看安装方法 进入 vue ui 1.打开终端,输入命令 vue ui 2.选择项目存 ...
- Python 删除满足条件的某些行
数据: data 字段:col 要删除的内容是 col == False 的行 # 方案一 data1 = data[~data['col'] == False] # ~ 取反 # 方案二 保留 da ...
- 关于easyswoole实现websocket聊天室的步骤解析
在去年,我们公司内部实现了一个聊天室系统,实现了一个即时在线聊天室功能,可以进行群组,私聊,发图片,文字,语音等功能,那么,这个聊天室是怎么实现的呢?后端又是怎么实现的呢? 后端框架 在后端框架上,我 ...
- 第12次抽考(GUI)
1. package week4; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextFiel ...
- HCNA Routing&Switching之RIP防环机制
前文我们了解了RIP的基础概念.RIP的特点.RIP报文格式.RIP度量以及RIP配置认证等相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/150085 ...
- Quzrtz.net 示例
//框架.Net Core 2.0//先用Nuget 安装最新quartz.net using System; using Quartz; using Quartz.Impl; using Syste ...
- MySQL | Xtrabackup 的简介
Xtrabackup 简介 Xtrabackup是由Percona开发的一个开源软件,可实现对InnoDB的数据备份,支持在线热备份(备份时不影响数据读写). Xtrabackup有2款主要工具,xt ...
- CF1458D Flip and Reverse[题解]
Flip and Reverse 题目大意 给定一个 \(01\) 字符串,有机会进行若干次操作,对于每一次操作: 选择该字符串的子串,要求是该子串内包含数量相同的 \(0\) , \(1\) 字符. ...
- c语言:2.3.3
#include <stdio.h> //赋值时类型原则:赋值号右边表达式值 变量 常量的类型最好与左边变量的类型相一致 //二者不相同时,C编译系统会自动实现数据类型转换 //转换原则: ...