python flask简单文件上传下载实现

功能:用python+flask实现简单的文件上传下载,并简单过滤下文件名的“.”和“/”,同时对文件名进行通过uuid重命名,存到服务器上,并将原文件名、重命名等信息保存到db中,需要时从db查询出来还原即可

PS:简单实现,适合单服务器小附件上传,不适合大文件处理,也不适用于K8S部署,下一篇文章将介绍通过ceph存储文件,适合K8S PVC持久化存储

实现

一、在db中定义文件ORM模型:
#app/db/orm.py
class Attachments(db.Model):
    __tablename__ = 'attachments'
    id = db.Column(db.String(64), primary_key=True)
    file_name = db.Column(db.String(200), nullable=True)
    file_path = db.Column(db.String(64), index=True, nullable=True)
    uploader = db.Column(db.String(30))
    upload_time = db.Column(db.DateTime, default=datetime.now())
    project_num = db.Column(db.String(11))

    def __init__(self, **kwargs):
        super(Attachments, self).__init__(**kwargs)
        if self.id is None:
            self.id = str(uuid.uuid1()).replace('-', '')

    __mapper_args__ = {
        "order_by": project_num.desc()
    }

    def __repr__(self):
        return '<Attachment %s>' % self.file_name
二、API接口视图类
#app/api/v1/projects/project.py
from flask import request
from flask_restplus import Resource
......

@api.route('/<string:project_num>/upload')
class UploadFile(Resource):
    """
    上传立项附件的API
    """
    @login_required
    @api.doc('upload project attachments')
    @api.marshal_with(model.project_attachment)
    @api.response(return_code.Successful, 'upload success')
    @api.response(return_code.InternalServerError, 'upload failed')
    def post(self, project_num):
        """上传立项相关附件材料"""
        file = request.files.get('file')
        resp = business.upload_project_file(file, project_num)
        return resp, return_code.Successful


@api.route('/downloads/<string:file_path>')
@api.param('file_path', '服务器上真实的文件名')
class DownloadFile(Resource):
    """
    下载已上传的文件
    """
    @login_required
    @api.doc('get the upload file by path')
    @api.response(return_code.Successful, 'success')
    @api.response(return_code.NotFound, 'file not found')
    def get(self, file_path):
        """下载已上传的文件"""
        return business.download_file(file_path)

三、上传、下载业务逻辑实现
#app/api/v1/projects/business.py
......

def secure_filename(filename):
    """
    过滤上传文件的文件名中的特殊字符
    :param filename:
    :return:
    """
    file_first_name = filename.rsplit('.', 1)[0].lower()
    return file_first_name.replace('.','').replace('/','') + '.' + filename.rsplit('.', 1)[1].lower()

def change_filename(filename):
    """
    修改上传文件的文件名避免冲突以及恶意代码
    :param filename:
    :return:
    """
    fileinfo = os.path.splitext(filename)
    filename = datetime.now().strftime("%Y%m%d%H%M%S") + str(uuid.uuid4().hex) + fileinfo[-1]
    return filename

def upload_project_file(file, project_num):
    """
    上传立项附件材料
    :return:
    """
    if not file:
        raise exceptions.MyHttpParameterException('No file part in request')

    if file.filename == '':
        raise exceptions.MyHttpParameterException('No selected file')

    if allow_file(file.filename):
        origin_file_name = secure_filename(file.filename)
        filename = change_filename(origin_file_name)
        basepath = os.path.abspath(os.getcwd())  # 当前文件所在工作目录
        uploadpath = os.path.join(basepath, UPLOAD_PATH)
        user = g.current_user

        try:
            if not os.path.exists(uploadpath):
                os.makedirs(uploadpath)

            file.save(os.path.join(uploadpath, filename))
            # 并将文件信息写入数据库
            attachment = Attachments(file_name=origin_file_name, file_path=filename, uploader=user.name, project_num=project_num)
            db.session.add(attachment)
            db.session.commit()
        except Exception as e:
            logger.error('upload failed --> {}'.format(str(e)))
            raise exceptions.MyHttpServerError('upload failed, an error ocure on server -->{}'.format(str(e)))

        # to do with this file
        return attachment
    else:
        raise exceptions.MyHttpForbidden('No allowed file type')


def get_project_attachments(project_num):
    """
    获取项目的所有附件信息
    :param project_num:
    :return:
    """
    attachments = Attachments.query.filter_by(project_num=project_num).all()
    return attachments


def download_file(file_name):
    """
    根据服务器上真实的文件名下载附件
    :param file_name:
    :return:
    """
    basepath = os.path.abspath(os.getcwd())  # 当前文件所在工作目录
    uploadpath = os.path.join(basepath, UPLOAD_PATH)

    if os.path.isfile(os.path.join(uploadpath, file_name)):
        return send_from_directory(uploadpath, file_name, as_attachment=True)
    raise exceptions.MyHttpNotFound('not found file')

本实例采用的是Uploadify上传插件,.NET程序,源程序是从网上找的,但是有Bug,已经修改好,并标有部分注释。绝对好用,支持单文件、多文件上传,支持大文件上传,已经过多方面测试,保证好用。 以下附上Uploadify部分常用的参数介绍,要看全部的就去看其API文件了,一般在下载的包里都有。  uploader : uploadify.swf 文件的相对路径,该swf文件是一个带有文字BROWSE的按钮,点击后弹出打开文件对话框,默认值:uploadify.swf。   script : 后台处理程序的相对路径 。默认值:uploadify.php   checkScript :用来判断上传选择的文件在服务器是否存在的后台处理程序的相对路径   fileDataName :设置一个名字,在服务器处理程序中根据该名字来取上传文件的数据。默认为Filedata   method : 提交方式Post 或Get 默认为Post   scriptAccess :flash脚本文件的访问模式,如果在本地测试设置为always,默认值:sameDomain   folder : 上传文件存放的目录 。   queueID : 文件队列的ID,该ID与存放文件队列的div的ID一致。   queueSizeLimit : 当允许多文件生成时,设置选择文件的个数,默认值:999 。   multi : 设置为true时可以上传多个文件。   auto : 设置为true当选择文件后就直接上传了,为false需要点击上传按钮才上传 。   fileDesc : 这个属性值必须设置fileExt属性后才有效,用来设置选择文件对话框中的提示文本,如设置fileDesc为“请选择rar doc pdf文件”,打开文件选择框效果如下图:   fileExt : 设置可以选择的文件的类型,格式如:'*.doc;*.pdf;*.rar' 。   sizeLimit : 上传文件的大小限制 。   simUploadLimit : 允许同时上传的个数 默认值:1 。   buttonText : 浏览按钮的文本,默认值:BROWSE 。   buttonImg : 浏览按钮的图片的路径 。   hideButton : 设置为true则隐藏浏览按钮的图片 。   rollover : 值为true和false,设置为true时当鼠标移到浏览按钮上时有反转效果。   width : 设置浏览按钮的宽度 ,默认值:110。   height : 设置浏览按钮的高度 ,默认值:30。   wmode : 设置该项为transparent 可以使浏览按钮的flash背景文件透明,并且flash文件会被置为页面的最高层。 默认值:opaque 。   cancelImg :选择文件文件队列中后的每一个文件上的关闭按钮图标 Uploadify还自带了很多参数及有用的方法和回调函数,都在API里,虽然是全英文的,但很容易看懂,这里就不说了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值