90行Python代码开发个人云盘应用
- 作者: 不穿裤子为了放屁方便
- 来源: 51数据库
- 2021-08-28
本文示例代码已上传至我的github仓库https://github.com/cnfeffery/datasciencestudynotes
1 简介
在今天的教程中,我们将介绍如何在dash中高效地开发web应用中非常重要的「文件上传」及「下载」功能。
2 在dash中实现文件上传与下载
2.1 在dash中配合dash-uploader实现文件上传
其实在自带的dash_core_components中就封装了基于html5原生api的dcc.upload()组件,可以实现简单的文件上传功能,但说实话,非常的「不好用」,其主要缺点有:
- 「文件大小有限制,150m到200m左右即出现瓶颈」
- 「策略是先将用户上传的文件存放在浏览器内存,再通过base64形式传递到服务端再次解码,非常低效」
- 「整个上传过程无法配合准确的进度条」
正是因为dash自带的上传部件如此不堪,所以一些优秀的第三方拓展涌现出来,其中最好用的要数dash-uploader,它解决了上面提到的dcc.upload()的所有短板。通过pip install dash-uploader进行安装之后,就可以直接在dash应用中使用了。
我们先从极简的一个例子出发,看一看在dash中使用dash-uploader的正确姿势:
app1.py
import dash import dash_uploader as du import dash_bootstrap_components as dbc import dash_html_components as html app = dash.dash(__name__) # 配置上传文件夹 du.configure_upload(app, folder='temp') app.layout = html.div( dbc.container( du.upload() ) ) if __name__ == '__main__': app.run_server(debug=true)

可以看到,仅仅十几行代码,我们就配合dash-uploader实现了简单的文件上传功能,其中涉及到dash-uploader两个必不可少的部分:
2.1.1 利用du.configure_upload()进行配置
要在dash中正常使用dash-uploader,我们首先需要利用du.configure_upload()进行相关配置,其主要参数有:
「app」,即对应已经实例化的dash对象;
「folder」,用于设置上传的文件所保存的根目录,可以是相对路径,也可以是绝对路径;
「use_upload_id」,bool型,默认为true,这时被用户上传的文件不会直接置于「folder」参数指定目录,而是会存放于du.upload()部件的upload_id对应的子文件夹之下;设置为false时则会直接存放在根目录,当然没有特殊需求还是不要设置为false。
通过du.configure_upload()我们就完成了基本的配置。
2.1.2 利用du.upload()创建上传部件
接下来我们就可以使用到du.upload()来创建在浏览器中渲染供用户使用的上传部件了,它跟常规的dash部件一样具有「id」参数,也有一些其他的丰富的参数供开发者充分自由地自定义功能和样式:
「text」,字符型,用于设置上传部件内显示的文字;
「text_completed」,字符型,用于设置上传完成后显示的文字内容前缀;
「cancel_button」,bool型,用于设置是否在上传过程中显示“取消”按钮;
「pause_button」,bool型,用于设置是否在上传过程中显示“暂停”按钮;
「filetypes」,用于限制用户上传文件的格式范围,譬如['zip', 'rar', '7zp']就限制用户只能上传这三种格式的文件。默认为none即无限制;
「max_file_size」,int型,单位mb,用于限制单次上传的大小上限,默认为1024即1gb;
「default_style」,类似常规dash部件的style参数,用于传入css键值对,对部件的样式进行自定义;
「upload_id」,用于设置部件的唯一id信息作为du.configure_upload()中所设置的缓存根目录的下级子目录,用于存放上传的文件,默认为none,会在dash应用启动时自动生成一个随机值;
「max_files」,int型,用于设置一次上传最多可包含的文件数量,默认为1,也推荐设置为1,因为目前对于多文件上传仍有「进度条异常」、「上传结束显示异常」等bug,所以不推荐设置大于1。
知晓了这些参数的作用之后,我们就可以创建出更符合自己需求的上传部件:
app2.py
import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html
app = dash.dash(__name__)
# 配置上传文件夹
du.configure_upload(app, folder='temp')
app.layout = html.div(
dbc.container(
du.upload(
id='uploader',
text='点击或拖动文件到此进行上传!',
text_completed='已完成上传文件:',
cancel_button=true,
pause_button=true,
filetypes=['md', 'mp4'],
default_style={
'background-color': '#fafafa',
'font-weight': 'bold'
},
upload_id='我的上传'
)
)
)
if __name__ == '__main__':
app.run_server(debug=true)

但像前面的例子那样直接在定义app.layout时就传入实际的du.upload()部件,会产生一个问题——应用启动后,任何访问应用的用户都对应一样的upload_id,这显然不是我们期望的,因为不同用户的上传文件会混在一起。
因此可以参考下面例子的方式,在每位用户访问时再渲染随机id的上传部件,从而确保唯一性:
app3.py
import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html
import uuid
app = dash.dash(__name__)
# 配置上传文件夹
du.configure_upload(app, folder='temp')
def render_random_id_uploader():
return du.upload(
id='uploader',
text='点击或拖动文件到此进行上传!',
text_completed='已完成上传文件:',
cancel_button=true,
pause_button=true,
filetypes=['md', 'mp4'],
default_style={
'background-color': '#fafafa',
'font-weight': 'bold'
},
upload_id=uuid.uuid1()
)
def render_layout():
return html.div(
dbc.container(
render_random_id_uploader()
)
)
app.layout = render_layout
if __name__ == '__main__':
app.run_server(debug=true)
可以看到,每次访问时由于upload_id不同,因此不同的会话拥有了不同的子目录。

2.1.3 配合du.upload()进行回调
在du.upload()中额外还有iscompleted与filenames两个属性,前者用于判断当前文件是否上传完成,后者则对应此次上传的文件名称,参考下面这个简单的例子:
app4.py
import dash
import dash_uploader as du
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import input, output, state
app = dash.dash(__name__)
# 配置上传文件夹
du.configure_upload(app, folder='temp')
app.layout = html.div(
dbc.container(
[
du.upload(id='uploader'),
html.h5('上传中或还未上传文件!', id='upload_status')
]
)
)
@app.callback(
output('upload_status', 'children'),
input('uploader', 'iscompleted'),
state('uploader', 'filenames')
)
def show_upload_status(iscompleted, filenames):
if iscompleted:
return '已完成上传:'+filenames[0]
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=true, port=8051)

2.2 配合flask进行文件下载
相较于文件上传,在dash中进行文件的下载就简单得多,因为我们可以配合flask的send_from_directory以及html.a()部件来为指定的服务器端文件创建下载链接,譬如下面的简单示例就打通了文件的上传与下载:
app5.py
from flask import send_from_directory
import dash
import dash_uploader as du
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import input, output
import os
app = dash.dash(__name__)
du.configure_upload(app, 'temp', use_upload_id=false)
app.layout = html.div(
dbc.container(
[
du.upload(id='upload'),
html.div(
id='download-files'
)
]
)
)
@app.server.route('/download/<file>')
def download(file):
return send_from_directory('temp', file)
@app.callback(
output('download-files', 'children'),
input('upload', 'iscompleted')
)
def render_download_url(iscompleted):
if iscompleted:
return html.ul(
[
html.li(html.a(f'/{file}', /download/{file}', target='_blank'))
for file in os.listdir('temp')
]
)
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=true)

3 用dash编写简易个人网盘应用
在学习了今天的案例之后,我们就掌握了如何在dash中开发文件上传及下载功能,下面我们按照惯例,结合今天的主要内容,来编写一个实际的案例;
今天我们要编写的是一个简单的个人网盘应用,我们可以通过浏览器访问它,进行文件的上传、下载以及删除:

import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import input, output, state
import dash_uploader as du
import os
from flask import send_from_directory
import time
app = dash.dash(__name__, suppress_callback_exceptions=true)
du.configure_upload(app, 'netdisk', use_upload_id=false)
app.layout = html.div(
dbc.container(
[
html.h3('简易的个人云盘应用'),
html.hr(),
html.p('文件上传区:'),
du.upload(id='upload',
text='点击或拖动文件到此进行上传!',
text_completed='已完成上传文件:',
max_files=1000),
html.hr(),
dbc.row(
[
dbc.button('删除选中的文件', id='delete-btn', outline=true),
dbc.button('打包下载选中的文件', id='download-btn', outline=true)
]
),
html.hr(),
dbc.spinner(
dbc.checklist(
id='file-list-check'
)
),
html.a(id='download-url', target='_blank')
]
)
)
@app.server.route('/download/<file>')
def download(file):
return send_from_directory('netdisk', file)
@app.callback(
[output('file-list-check', 'options'),
output('download-url', 'children'),
output('download-url', 'href')],
[input('upload', 'iscompleted'),
input('delete-btn', 'n_clicks'),
input('download-btn', 'n_clicks')],
state('file-list-check', 'value')
)
def render_file_list(iscompleted, delete_n_clicks, download_n_clicks, check_value):
# 获取上下文信息
ctx = dash.callback_context
if ctx.triggered[0]['prop_id'] == 'delete-btn.n_clicks':
for file in check_value:
try:
os.remove(os.path.join('netdisk', file))
except filenotfounderror:
pass
if ctx.triggered[0]['prop_id'] == 'download-btn.n_clicks':
import zipfile
with zipfile.zipfile('netdisk/打包下载.zip', 'w') as zipobj:
for file in check_value:
try:
zipobj.write(os.path.join('netdisk', file))
except filenotfounderror:
pass
return [
{'label': file, 'value': file}
for file in os.listdir('netdisk')
if file != '打包下载.zip'
], '打包下载链接', '/download/打包下载.zip'
time.sleep(2)
return [
{'label': file, 'value': file}
for file in os.listdir('netdisk')
if file != '打包下载.zip'
], '', ''
if __name__ == '__main__':
app.run_server(debug=true)
以上就是90行python代码开发个人云盘应用的详细内容,更多关于python 开发个人云盘的资料请关注其它相关文章!
