檢視原始碼 檔案上傳
上傳檔案是網頁應用程式常見的任務之一。這些檔案可能是圖片、影片、PDF 或任何其他類型的檔案。若要透過 HTML 介面來上傳檔案,我們需要在多部分表單中使用 file
輸入標籤。
正在尋找 LiveView 上傳指南?
本指南透過
Plug.Upload
來說明多部分 HTTP 檔案上傳。有關 LiveView 檔案上傳的更多資訊,包括客戶端的直接至雲端外部上傳,請參閱 LiveView 上傳指南。
Plug 提供 Plug.Upload
結構,用於儲存來自 file
輸入的資料。當使用者提交表單後選取檔案時,Plug.Upload
結構將自動出現在請求參數中。
在本指南中,您將執行下列動作
設定多部分表單
在表單中加入檔案輸入元素
驗證上傳參數
管理已上傳檔案
在 環境指南
中,我們為產品生成了 HTML 資源。我們可以在此處重新使用所生成的表單,以示範檔案上傳如何在 Phoenix 中運作。有關如何生成您將在此處使用的產品資源的說明,請參閱該指南。
設定多部分表單
您需要執行的第一件事是將表單變更為多部分表單。 HelloWeb.CoreComponents
simple_form/1
元件接受 multipart
屬性,您可以在其中指定此屬性。
以下是 lib/hello_web/controllers/product_html/product_form.html.heex
中包含變更的表單
<.simple_form :let={f} for={@changeset} action={@action} multipart>
. . .
加入檔案輸入
在您擁有多部分表單後,您需要 file
輸入。以下是您執行此動作的方式,同樣在 product_form.html.heex
中
. . .
<.input field={f[:photo]} type="file" label="Photo" />
<:actions>
<.button>Save Product</.button>
</:actions>
</.simple_form>
以下是預設 HelloWeb.CoreComponents
input/1
元件的 HTML 顯示時
<div>
<label for="product_photo" class="block text-sm...">Photo</label>
<input type="file" name="product[photo]" id="product_photo" class="mt-2 block w-full...">
</div>
請注意 file
輸入中的 name
屬性。這會在 product_params
映射中建立 "photo"
金鑰,在控制項動作中可用。
這些是表單端的所有內容。現在,當使用者提交表單時,POST
要求會路由到 HelloWeb.ProductController
create/2
動作。
我應該將照片新增到我的 Ecto 架構嗎?
照片輸入不需要成為您架構的一部分,即可出現在
product_params
中。不過,如果您要將照片的任何屬性持續儲存在資料庫中,您需要將其新增到Hello.Product
架構中。
驗證您的上傳參數
由於您生成了 HTML 資源,您現在可以使用 mix phx.server
啟動伺服器,拜訪 https://127.0.0.1:4000/products/new,並使用照片建立一個新產品。
在您開始之前,將 IO.inspect product_params
新增到 lib/hello_web/controllers/product_controller.ex
中 ProductController.create/2
動作的頂端。這會在您的開發記錄中顯示 product_params
,因此您可以更好地瞭解正在發生的情況。
. . .
def create(conn, %{"product" => product_params}) do
IO.inspect product_params
. . .
當您執行此操作時,這表示您的 product_params
將記錄輸出到記錄中
%{"title" => "Metaprogramming Elixir", "description" => "Write Less Code, Get More Done (and Have Fun!)", "price" => "15.000000", "views" => "0",
"photo" => %Plug.Upload{content_type: "image/png", filename: "meta-cover.png", path: "/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1"}}
您有一個 "photo"
金鑰對應預先填入的 Plug.Upload
結構,表示您上傳的照片。
為了讓這個變得更容易讀取,請專注於結構本身
%Plug.Upload{content_type: "image/png", filename: "meta-cover.png", path: "/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1"}
Plug.Upload
提供了檔案的內容類型、原始檔名以及 Plug 為您建立的暫時檔案路徑。在此情況下,"/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/"
是 Plug 建立的目錄,用於放置上傳的檔案。此目錄會持續存在於所有要求中。"multipart-558399-917557-1"
是 Plug 給您上傳檔案的名稱。如果您有多個 file
輸入,並且使用者為所有輸入都選了照片,您會有多個檔案散佈在暫時目錄中。Plug 會確保所有檔名都是唯一的。
Plug.Upload 檔案是暫時的
Plug 會在要求完成時從其目錄移除上傳檔案。如果您需要使用這個檔案,您必須在那之前完成 (或 讓它消失,但這不在本指南的範圍內)。
管理您的上傳檔案
一旦你的控制器中可以使用 Plug.Upload
結構,你可以對它執行任何你想要的作業。 例如,你可能希望執行以下一項或多項工作
使用
File.exists?/1
檢查檔案是否存在使用
File.cp/2
將檔案複製到檔案系統上的其他位置使用
Plug.Upload.give_away/3
將檔案傳輸給其他 Elixir 程序使用外部函式庫將檔案傳送到 S3
使用
Plug.Conn.send_file/5
將檔案傳回給用戶端
在生產系統中,你可能希望將檔案複製到根目錄,例如 /media
。在這樣做時,保證名稱是唯一的非常重要。例如,如果你允許使用者上傳產品封面圖片,你可以使用產品 ID 來產生唯一的名稱
if upload = product_params["photo"] do
extension = Path.extname(upload.filename)
File.cp(upload.path, "/media/#{product.id}-cover#{extension}")
end
隨後 Plug.Static
外掛可以新增到你的 lib/my_app_web/endpoint.ex
,以在 "/media"
中提供檔案
plug Plug.Static, at: "/uploads", from: "/media"
現在可以使用類似於 "/uploads/1-cover.jpg"
的路徑,從你的瀏覽器存取上傳的檔案。在實際上,在 上傳檔案時有其他你想處理的疑慮,例如驗證附檔檔名、編碼檔案名稱等。許多時候,最好使用一個已經可以處理此類問題的函式庫。
最後,請注意,當 file
輸入沒有資料時,你不會收到 "photo"
金鑰或 Plug.Upload
結構。以下是來自記錄的 product_params
。
%{"title" => "Metaprogramming Elixir", "description" => "Write Less Code, Get More Done (and Have Fun!)", "price" => "15.000000", "views" => "0"}
設定上傳限制
從表單傳送的資料轉換為實際的 Plug.Upload
是由 Plug.Parsers
外掛完成,你可以在 HelloWeb.Endpoint
中找到此外掛
# lib/hello_web/endpoint.ex
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Phoenix.json_library()
除了上述選項,Plug.Parsers
也允許其他選項來控制資料上傳
:length
- 設定要讀取的最大主體長度,預設為8_000_000
bytes:read_length
- 設定一次要讀取的 bytes 數量,預設為1_000_000
bytes:read_timeout
- 設定接收每個區塊的逾時,預設為15_000
毫秒
第一個選項配置所允許的最大資料。其餘的配置我們預期要讀取多少資料,以及其頻率。如果用戶端無法快速推送資料,連接將會終止。Phoenix 附帶合理的預設值,但你可能想要在特殊情況下自訂它,舉例來說,如果你預期非常慢的用戶端傳送大量資料會話。
也值得指出這些限制十分重要,作為一個安全機制。舉例來說,如果你沒有為資料上載設定限制,攻擊者可以對你的應用程式開啟上千個連線,並且每 2 分鐘傳送一個位元組,這將花非常久的時間完成,同時耗盡伺服器連線。以上的限制預計至少會有合理的進度,讓攻擊者的生活變得更困難。