阅读目录
一、上传文件
二、下载文件
引言
本案例前端采用Vue2.0 后端Flask1.0.2,主要实现上传/下载文件功能
1 上传文件
功能需求:支持用户上传头像的功能
1.1 前端-上传功能
1.1.1 html
1.1.2 JS代码
1.1.2 CSS
1.2 后端-上传
def upload_avatar(self):
"""上传头像"""
"""
后端存放实现这里提供三种选择:
1.存放在数据库
2.存放在硬盘
3.如果是oss地址,直接存放数据库就行
"""
# 第一种,存放在数据服
username = request.values.get("username")
# 此处是直接把文件流经过base64编码 ,data:image/jpg;base64,用来标识为base64编码/ 这里就需要数据库字段avatar必须足够大
avatar_base64 = "data:image/jpg;base64,".encode() + b64encode(request.files['file'].stream.read())
f"update user_table set avatar={avatar_base64} where username={username}"
# 第二种存放在硬盘
file = request.files.get("file")
if not username:
return False
if file and allowed_file(file.filename):
# 用户名作为文件名称存储
filename = secure_filename(username) + "." + file.filename.split('.')[1]
# flask-config 配置 UPLOAD_DIR存放地址
file.save(os.path.join(current_app.config.get("UPLOAD_DIR"), 'avatar', filename))
return True
return False
# 以下接口服务
res = upload_avatar()
if not res:
return response.error("你上传的是个啥呀!")
else:
return repose.success("ok啦...")
1.3 后端-展示
# 这里还是区分数据库存储还是本地存储,当然avtar的信息也可以和用户的其它信息写一个接口
def show_avatar(self, username):
if not username:
return None, "未检测到登录用户名,请先登录"
dirpath = file_obj.join_path(current_app.config['UPLOAD_DIR'], 'avatar')
filename = file_obj.get_file_fill_name(dirpath, username) # 在upload目录下获取用户名称的存储文件
if not filename:
return None, "未检测到登录用户名,请先登录"
filepath = file_obj.join_path(dirpath, filename)
if not file_obj.is_exist(filepath):
return None, "不存在上传头像信息" //这里可以给默认的头像,当然前端给更好了
f = open(filepath, 'rb') # 此处也可以设置读取限制,比如每次读取多少
base64_str = b64encode(f.read())
return "data:image/jpg;base64,".encode() + base64_str, None
# 以下接口服务
file, err = show_avatar(request.get("username"))
if err:
return response.error(err)
else:
return repose.success(data=file)
1.4 前端-展示
前端只需要调用show_avatar接口替换img:scr指向就可以了
data: {
avatarUrl: ''
},
methods:{
let params = {username: 'admin'}
let avatarUrl= config.baseUrl + "/user/showAvatar";
let configs = {
headers:{'Content-Type':'content-type', 'token':config.headers.token}
};
let _this = this;
axios.post(avatarUrl, params, configs).then(function (response) {
if(response.data.code != 200){
this.avatarUrl = response.data.data; //因为后端最终返回的不是base64结果就是oss结果,所以可以直接替换
}else{
alert(response.data.data)
}
})
}
1.5 总结
1.上面很多伪代码,但是基本要点都有体现,可以根据自己的实际项目修改
2.前端avatar最好能封装为组件形式,当用户登录以后获取后端用户信息,包含权限,头像等信息,保存在vuex,sesssioStore中
3.后端用Flask原生的也可以实现返回数据流等,方法send_from_directory(dirpath, filename, as_attachment=True)或者send_file()等前后不分离很好使,但是前后分离项目实验不出效果,郁闷~~
2 下载文件
功能需求:前端点击按钮,后端生成文件后返回前端下载
2.1 前端
2.1.1 Blob实现
exportConfig(){
# 后端接口
exportConfig(this.ids).then(res => { # res为make_response()返回结果
if(res.status === 200){
const blob = new Blob([res.data],{type:"application/zip"}); #初始化Blob都西昂
const fileName = 'execute_file.zip'; # 文件名称
if ('download' in document.createElement('a')) { // 非IE下载
const elink = document.createElement('a')
elink.download = fileName
elink.style.display = 'none'
elink.href = URL.createObjectURL(blob)
document.body.appendChild(elink)
elink.click()
URL.revokeObjectURL(elink.href) // 释放URL 对象
document.body.removeChild(elink)
} else { // IE10+下载
navigator.msSaveBlob(blob, fileName)
}
this.$message({
message: "导出成功",
type: "success"
});
}else{
this.$message.error(res.data.data)
}
})
}
2.1.1 原生a标签实现
exportConfig(){
// 采用a标签的href指定的方式
const elink = document.createElement('a');
elink.download = 'execute_file.zip';
elink.style.display = 'none';
// elink.target = '_blank';
elink.href = config.baseUrl +'/接口路径/?ids='+ JSON.stringify(this.ids[0]);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
document.body.removeChild(elink)
}
2.2 后端
2.2.1 服务 - Services层
def services_export_file():
"""导出文件"""
obj = ConfigXxx()
res, err = obj.export()
if err:
return response.failed(data=err)
return res
2.2.2 分模块处理 - Modules层
class ConfigXxx:
def export(self):
"""导出"""
p_id = request.values.get('ids')
if not str(p_id).isdigit():
return None, f"不支持的导出参数【{p_id}】"
p_info, err = self.structure_config_data(p_id)
if err:
return None, err
file_handle = File()
# 生成文件
dirpath, err = file_handle.generate(p_info)
if err:
return None, err
export_data, err = file_handle.export_zip_file(dirpath)
if err:
return None, err
# 移除文件
file_handle.remove(dirpath)
# 核心->把生成的数据交给交给make_response处理
res = make_response(send_file(export_data, attachment_filename='execute_file.zip', as_attachment=True))
return res, None
2.2.3 文件处理
class File:
def export_zip_files(self, dirpath):
"""查询导出文件"""
import os
import zipfile
from io import BytesIO
try:
memory_file = BytesIO()
dsplit = dirpath.split('\\')
dname = None
if len(dsplit) >= 2:
dname = dsplit[-2]
with zipfile.ZipFile(memory_file, "w", zipfile.ZIP_DEFLATED) as zf:
for path, dirnames, filenames in os.walk(dirpath):
if dname:
hr = path.split(dname, 1)
for filename in filenames:
zf.write(os.path.join(path, filename), os.path.join(*hr[1].split('\\'), filename))
else:
for filename in filenames:
zf.write(os.path.join(path, filename))
# zf.setpassword("kk-123456".encode('utf-8'))
memory_file.seek(0)
return memory_file, None
except Exception as e:
return None, str(e)
2.3 总结
1.前端用Blob实现导出失败,具体原因暂时不详,猜测flask.make_response()生成的文件的流Blob不支持,有知道原因的大神可以回复我一下!
2.原始a标签实现可以对接flask.make_response()下载文件
3.原始a标签实现下载有个隐藏的坑,上述实现方式在后端会有缓存,前端再次访问已经下载过的文件不会触发Services层,目前原因不祥,猜测是flask.make_response()内部注册session,缓存数据了,刨析源码中...