檢視原始程式碼 外部上傳
此指南從伺服器中的組態繼續,請參閱 上傳指南。
上傳至外部雲端供應商,例如亞馬遜 S3、Google Cloud 等,可以使用 allow_upload/3
中的 :external
選項。
你提供一個 2 元函數,讓伺服器為每個上傳項目產生元資料,該函數會傳給用戶端指定的 JavaScript 函數。
通常當函數被呼叫時,你會產生簽名前置網址,特定於你的雲端儲存供應商,該網址會提供臨時存取權限,讓最終使用者可以直接將資料上傳到你的雲端儲存。
切割 HTTP 上傳
對於任何使用 Content-Range
標頭透過切割 HTTP 要求來支援大型檔案上傳的服務,你可以使用 Mux 的 UpChunk JS 函式庫來完成上傳檔案的所有艱辛工作。要上傳小檔案或快速入門,請改為考慮 直接上傳至 S3。
你只需要將 UpChunk 執行個體連接到 LiveView UploadEntry 回呼函式,LiveView 就會處理其餘的部分。
透過將 UpChunk 的內容儲存到 assets/vendor/upchunk.js
或使用 npm
安裝來安裝 UpChunk
$ npm install --prefix assets --save @mux/upchunk
在 Phoenix.LiveView.mount/3
中組態你的上傳工具
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: :any, max_entries: 3, external: &presign_upload/2)}
end
將 :external
選項提供給 Phoenix.LiveView.allow_upload/3
。它需要一個 2 元函數來產生簽署的 URL,客戶端會將上傳條目的位元組推送到此 URL。此函數必須回傳 {:ok, meta, socket}
或 {:error, meta, socket}
,其中 meta
必須是一個映射。
例如,如果你使用提供 start_session
函數的上下文,你可能會寫下類似這樣的內容
defp presign_upload(entry, socket) do
{:ok, %{"Location" => link}} =
SomeTube.start_session(%{
"uploadType" => "resumable",
"x-upload-content-length" => entry.client_size
})
{:ok, %{uploader: "UpChunk", entrypoint: link}, socket}
end
最後,在用戶端,我們使用 UpChunk 從伺服器上產生的臨時 URL 建立上傳,並為其事件附加監聽器到條目的回呼函式
import * as UpChunk from "@mux/upchunk"
let Uploaders = {}
Uploaders.UpChunk = function(entries, onViewError){
entries.forEach(entry => {
// create the upload session with UpChunk
let { file, meta: { entrypoint } } = entry
let upload = UpChunk.createUpload({ endpoint: entrypoint, file })
// stop uploading in the event of a view error
onViewError(() => upload.pause())
// upload error triggers LiveView error
upload.on("error", (e) => entry.error(e.detail.message))
// notify progress events to LiveView
upload.on("progress", (e) => {
if(e.detail < 100){ entry.progress(e.detail) }
})
// success completes the UploadEntry
upload.on("success", () => entry.progress(100))
})
}
// Don't forget to assign Uploaders to the liveSocket
let liveSocket = new LiveSocket("/live", Socket, {
uploaders: Uploaders,
params: {_csrf_token: csrfToken}
})
直接上傳至 S3
根據 S3 常見問題集,S3 中可以透過單一 PUT 上傳的最大物件為 5 GB。對於較大的檔案上傳,請考慮使用如上所示的分塊方式。
本指南假設已設定現有的 S3 區塊貯存空間,並擁有正確的 CORS 組態,允許將檔案直接上傳至區塊貯存空間。
一個 CORS 組態範例如下:
[
{
"AllowedHeaders": [ "*" ],
"AllowedMethods": [ "PUT", "POST" ],
"AllowedOrigins": [ "*" ],
"ExposeHeaders": []
}
]
您也可以將您的網域放入「allowedOrigins」中。關於如何設定 S3 區塊貯存空間 CORS 的更多資訊,請 瀏覽 AWS。
為了在將檔案上傳至 S3 時套用所有檔案限制,您必須使用檔案資料執行 multipart 表單 POST。在繼續之前,您應該準備好以下 S3 資訊:
- aws_access_key_id
- aws_secret_access_key
- bucket_name
- region
我們將首先實作 LiveView 部分。
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: :any, max_entries: 3, external: &presign_upload/2)}
end
defp presign_upload(entry, socket) do
uploads = socket.assigns.uploads
bucket = "phx-upload-example"
key = "public/#{entry.client_name}"
config = %{
region: "us-east-1",
access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")
}
{:ok, fields} =
SimpleS3Upload.sign_form_upload(config, bucket,
key: key,
content_type: entry.client_type,
max_file_size: uploads[entry.upload_config].max_file_size,
expires_in: :timer.hours(1)
)
meta = %{uploader: "S3", key: key, url: "http://#{bucket}.s3-#{config.region}.amazonaws.com", fields: fields}
{:ok, meta, socket}
end
在此,我們實作了一個 presign_upload/2
函式,並將其傳遞為擷取的匿名函式傳遞至 :external
。它會產生一個預先簽章的 URL,用於上傳並傳回我們的 :ok
結果,其中包含元資料給客戶端,以及我們未變更的 socket。
接下來,我們新增一個遺失的模組 SimpleS3Upload
來產生 S3 的預先簽章 URL。建立一個名為 simple_s3_upload.ex
的檔案。從這個名為 SimpleS3Upload
的無相依性模組取得檔案內容,它是由 Chris McCord 編寫的。
提示:如果您在
:crypto
模組或 S3 阻擋 ACL 中遇到錯誤,請閱讀上述摘要中的評論以取得解決方案。
接下來,我們新增我們的 JavaScript 客户端上傳程式。元資料必須包含 :uploader
金鑰,並指定 JavaScript 客户端上傳程式的名稱。在本例中,如上所示,名稱為 "S3"
。
在 app.js
旁的 assets/js/
下的目錄中,新增一個檔案 uploaders.js
。這個 S3
客户端上傳程式的內容為:
let Uploaders = {}
Uploaders.S3 = function(entries, onViewError){
entries.forEach(entry => {
let formData = new FormData()
let {url, fields} = entry.meta
Object.entries(fields).forEach(([key, val]) => formData.append(key, val))
formData.append("file", entry.file)
let xhr = new XMLHttpRequest()
onViewError(() => xhr.abort())
xhr.onload = () => xhr.status === 204 ? entry.progress(100) : entry.error()
xhr.onerror = () => entry.error()
xhr.upload.addEventListener("progress", (event) => {
if(event.lengthComputable){
let percent = Math.round((event.loaded / event.total) * 100)
if(percent < 100){ entry.progress(percent) }
}
})
xhr.open("POST", url, true)
xhr.send(formData)
})
}
export default Uploaders;
我們定義了一個 Uploaders.S3
函式,該函式會接收到我們的項目。然後,它會針對每個項目執行 AJAX 請求,並使用 entry.progress()
和 entry.error()
函式,將上傳事件報告回 LiveView。上傳程式的名稱必須與我們在 LiveView 中的 :uploader
元資料中傳回的名稱相同。
最後,前往 app.js
並將 uploaders: Uploaders
金鑰新增至 LiveSocket
建構函式,以告知 phoenix 在哪些地方找到在外部元資料中傳回的上傳程式。
// for uploading to S3
import Uploaders from "./uploaders"
let liveSocket = new LiveSocket("/live",
Socket, {
params: {_csrf_token: csrfToken},
uploaders: Uploaders
}
)
現在,伺服器傳回的「S3」將會與客戶端的「S3」相符。若要在嘗試上傳時除錯客户端 JavaScript,您可以檢查您的瀏覽器,並查看「控制台」或網路標籤以查看錯誤記錄。
直接對接 S3 相容
大多數 S3 相容平台(例如 Cloudflare R2)在上傳檔案時都不支援 POST
,因此我們需要使用 PUT
搭配簽署的 URL,而不是簽署的 POST
,然後將檔案直接傳送至服務,為此,我們需要變更 presign_upload/2
函式和執行上傳的 Uploaders.S3
。
新的 presign_upload/2
def presign_upload(entry, socket) do
config = ExAws.Config.new(:s3)
bucket = "bucket"
key = "public/#{entry.client_name}"
{:ok, url} =
ExAws.S3.presigned_url(config, :put, bucket, key,
expires_in: 3600,
query_params: [{"Content-Type", entry.client_type}]
)
{:ok, %{uploader: "S3", key: key, url: url}, socket}
end
新的 Uploaders.S3
Uploaders.S3 = function (entries, onViewError) {
entries.forEach(entry => {
let xhr = new XMLHttpRequest()
onViewError(() => xhr.abort())
xhr.onload = () => xhr.status === 200 ? entry.progress(100) : entry.error()
xhr.onerror = () => entry.error()
xhr.upload.addEventListener("progress", (event) => {
if(event.lengthComputable){
let percent = Math.round((event.loaded / event.total) * 100)
if(percent < 100){ entry.progress(percent) }
}
})
let url = entry.meta.url
xhr.open("PUT", url, true)
xhr.send(entry.file)
})
}