前言
由于最近在做文件管理模塊的功能闺鲸,所以難免會(huì)遇到文件上傳下載這塊的功能菠劝。不過(guò)文件上傳那塊是調(diào)用的OSS api,所以接觸的不多淤刃。
文件的下載:
1. 接口返回真實(shí)的文件
這種情況比較簡(jiǎn)單晒他, flask里帶有此類(lèi)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個(gè)參數(shù), 第1個(gè)參數(shù)是本地目錄的path, 第2個(gè)參數(shù)是文件名(帶擴(kuò)展名)
directory = os.getcwd() # 假設(shè)在當(dāng)前目錄
return send_from_directory(directory, filename, as_attachment=True)
后邊那個(gè)as_attachment參數(shù)需要賦值為T(mén)rue,不過(guò)此種辦法有個(gè)問(wèn)題逸贾,就是當(dāng)filename里邊出現(xiàn)中文的時(shí)候陨仅,會(huì)報(bào)如下錯(cuò)誤:
解決辦法:
使用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個(gè)參數(shù), 第1個(gè)參數(shù)是本地目錄的path, 第2個(gè)參數(shù)是文件名(帶擴(kuò)展名)
directory = os.getcwd() # 假設(shè)在當(dāng)前目錄
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函數(shù)建立一個(gè)response對(duì)象,然后將filename編碼轉(zhuǎn)為latin-1铝侵,可以看到server.py里邊會(huì)嚴(yán)格按照l(shuí)atin-1編碼來(lái)解析filename灼伤,所以我這里的做法是先將utf8編碼的中文文件名默認(rèn)轉(zhuǎn)為latin-1編碼。
2. 接口返回文件數(shù)據(jù)流
這種情況比較適合我現(xiàn)在的需求咪鲜,因?yàn)槲疫@邊是用requests庫(kù)狐赡,先請(qǐng)求一個(gè)oss鏈接,獲取到文件的數(shù)據(jù)疟丙,然后我發(fā)現(xiàn)目前flask沒(méi)有這樣的api實(shí)現(xiàn)颖侄,這里還是使用make_response方法實(shí)現(xiàn)。
代碼如下:
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很強(qiáng)大享郊,下載一個(gè)文件览祖,需要在response的headers里邊添加一些信息,比如文件的類(lèi)型炊琉,文件的名字展蒂,是否以附件形式添加,這3個(gè)是比較關(guān)鍵的信息苔咪。
mime_type是文件的類(lèi)型锰悼,我觀察send_file的源代碼發(fā)現(xiàn)里邊用到了mimetypes.guess_type()這個(gè)方法,也就是猜測(cè)文件的類(lèi)型悼泌,然后這里我就直接搬過(guò)來(lái)用了哈哈松捉,r.content其實(shí)就是文件的數(shù)據(jù)流,之前我是通過(guò)
with open(filename, 'wb') as file:
file.write(r.content)
這樣實(shí)現(xiàn)下載文件到本地的馆里,所以其實(shí)r.content是一個(gè)文件數(shù)據(jù)流隘世,也不清楚我的名詞用的是否恰當(dāng)哈哈。
之所以不用第一種方式鸠踪,是因?yàn)槲冶镜厣晌募酥蟊撸枰獎(jiǎng)h除他,但是刪除的時(shí)候總是會(huì)提示該文件已經(jīng)被另一個(gè)程序使用营密,所以猜測(cè)是send_file這個(gè)api還在使用該文件械媒,為了達(dá)到更好的效果,找到了第二種解決辦法。
其實(shí)還有一種解決辦法:
3. 發(fā)送靜態(tài)文件
其實(shí)原來(lái)和第一種差不多纷捞,調(diào)用的api不一樣痢虹,api是
from flask import app
import os
@app.route("/download/<filepath>", methods=['GET'])
def download_file(filepath):
# 此處的filepath是文件的路徑,但是文件必須存儲(chǔ)在static文件夾下主儡, 比如images\test.jpg
return app.send_static_file(filepath)