天天看點

Flask下載下傳檔案前言

前言

由于最近在做檔案管理子產品的功能,是以難免會遇到檔案上傳下載下傳這塊的功能。不過檔案上傳那塊是調用的OSS api,是以接觸的不多。

檔案的下載下傳:

1. 接口傳回真實的檔案

這種情況比較簡單, flask裡帶有此類api, 可以用send_from_directory和send_file.

核心代碼如下:

from flask import send_file, send_from_directory
import os


@app.route("/download/<filename>", methods=['GET'])
def download_file(filename):
    # 需要知道2個參數, 第1個參數是本地目錄的path, 第2個參數是檔案名(帶擴充名)
    directory = os.getcwd()  # 假設在目前目錄
    return send_from_directory(directory, filename, as_attachment=True)           

後邊那個as_attachment參數需要指派為True,不過此種辦法有個問題,就是當filename裡邊出現中文的時候,會報如下錯誤:

Flask下載下傳檔案前言

解決辦法:

使用flask自帶的make_response

代碼修改如下

from flask import send_file, send_from_directory
import os
from flask import make_response


@app.route("/download/<filename>", methods=['GET'])
def download_file(filename):
    # 需要知道2個參數, 第1個參數是本地目錄的path, 第2個參數是檔案名(帶擴充名)
    directory = os.getcwd()  # 假設在目前目錄
    response = make_response(send_from_directory(directory, filename, as_attachment=True))
    response.headers["Content-Disposition"] = "attachment; filename={}".format(file_name.encode().decode('latin-1'))
    return response           

使用make_response函數建立一個response對象,然後将filename編碼轉為latin-1,可以看到server.py裡邊會嚴格按照latin-1編碼來解析filename,是以我這裡的做法是先将utf8編碼的中文檔案名預設轉為latin-1編碼。

2. 接口傳回檔案資料流

這種情況比較适合我現在的需求,因為我這邊是用requests庫,先請求一個oss連結,擷取到檔案的資料,然後我發現目前flask沒有這樣的api實作,這裡還是使用make_response方法實作。

代碼如下:

import mimetypes


@app.route('/fileManager/download/<projId>/<id>/<filename>', methods=['GET'])
def download_file(projId, id, filename):
    try:
        url = "your url"
        r = requests.get(url, timeout=500)
        if r.status_code != 200:
            raise Exception("Cannot connect with oss server or file is not existed")
        response = make_response(r.content)
        mime_type = mimetypes.guess_type(filename)[0]
        response.headers['Content-Type'] = mime_type
        response.headers['Content-Disposition'] = 'attachment; filename={}'.format(filename.encode().decode('latin-1'))
        return response
    except Exception as err:
        print('download_file error: {}'.format(str(err)))
        logging.exception(err)
        return Utils.beop_response_error(msg='Download oss files failed!')           

解釋一下:

make_response很強大,下載下傳一個檔案,需要在response的headers裡邊添加一些資訊,比如檔案的類型,檔案的名字,是否以附件形式添加,這3個是比較關鍵的資訊。

mime_type是檔案的類型,我觀察send_file的源代碼發現裡邊用到了mimetypes.guess_type()這個方法,也就是猜測檔案的類型,然後這裡我就直接搬過來用了哈哈,r.content其實就是檔案的資料流,之前我是通過

with open(filename, 'wb') as file:
    file.write(r.content)           

這樣實作下載下傳檔案到本地的,是以其實r.content是一個檔案資料流,也不清楚我的名詞用的是否恰當哈哈。

之是以不用第一種方式,是因為我本地生成檔案了之後,需要删除他,但是删除的時候總是會提示該檔案已經被另一個程式使用,是以猜測是send_file這個api還在使用該檔案,為了達到更好的效果,找到了第二種解決辦法。

其實還有一種解決辦法:

3. 發送靜态檔案

其實原來和第一種差不多,調用的api不一樣,api是

from flask import app
import os


@app.route("/download/<filepath>", methods=['GET'])
def download_file(filepath):
    # 此處的filepath是檔案的路徑,但是檔案必須存儲在static檔案夾下, 比如images\test.jpg
    return app.send_static_file(filepath)