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')

发布了25 篇原创文章 · 获赞 24 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/arnolan/article/details/93617198