flask - 上传下载文件


阅读目录

一、上传文件

二、下载文件

引言

本案例前端采用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,缓存数据了,刨析源码中...

你可能感兴趣的:(flask - 上传下载文件)