Fast upload

This commit is contained in:
2025-08-09 02:01:19 +08:00
parent 3444e27a96
commit 83df727f8f
2 changed files with 77 additions and 7 deletions

View File

@@ -34,6 +34,13 @@
:is-date-disabled="disablePreviousDate" :is-date-disabled="disablePreviousDate"
/> />
</div> </div>
<div
v-if="currentFilePool?.policy_config?.enable_fast_upload || route.query.pool"
class="flex items-center gap-2"
>
<p class="pl-1 mb-0.5">Fast Upload</p>
<n-switch v-model:value="fastUpload" />
</div>
</div> </div>
</n-card> </n-card>
</n-collapse-transition> </n-collapse-transition>
@@ -78,12 +85,14 @@ import {
NDatePicker, NDatePicker,
NAlert, NAlert,
NCard, NCard,
NSwitch,
type UploadCustomRequestOptions, type UploadCustomRequestOptions,
type UploadSettledFileInfo, type UploadSettledFileInfo,
type UploadFileInfo, type UploadFileInfo,
useMessage, useMessage,
} from 'naive-ui' } from 'naive-ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { CloudUploadRound } from '@vicons/material' import { CloudUploadRound } from '@vicons/material'
import type { SnFilePool } from '@/types/pool' import type { SnFilePool } from '@/types/pool'
@@ -96,21 +105,26 @@ const props = defineProps<{
bundleId?: string bundleId?: string
}>() }>()
const route = useRoute()
const filePass = ref<string>('') const filePass = ref<string>('')
const fileExpire = ref<number | null>(null) const fileExpire = ref<number | null>(null)
const fastUpload = ref<boolean>(false)
const effectiveFilePool = computed(() => (route.query.pool as string) || props.filePool)
const currentFilePool = computed(() => { const currentFilePool = computed(() => {
if (!props.filePool) return null if (!effectiveFilePool.value) return null
return props.pools?.find((pool) => pool.id === props.filePool) ?? null return props.pools?.find((pool) => pool.id === effectiveFilePool.value) ?? null
}) })
const showRecycleHint = computed(() => { const showRecycleHint = computed(() => {
if (!props.filePool) return true if (!effectiveFilePool.value) return true
return currentFilePool.value?.policy_config?.enable_recycle || false return currentFilePool.value?.policy_config?.enable_recycle || false
}) })
const messageDisplay = useMessage() const messageDisplay = useMessage()
function customRequest({ async function customRequest({
file, file,
headers, headers,
withCredentials, withCredentials,
@@ -118,12 +132,68 @@ function customRequest({
onError, onError,
onProgress, onProgress,
}: UploadCustomRequestOptions) { }: UploadCustomRequestOptions) {
if (fastUpload.value) {
const hash = await crypto.subtle.digest('SHA-256', await file.file!.arrayBuffer())
const hashString = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
const resp = await fetch('/api/files/fast', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: file.name,
size: file.file?.size,
hash: hashString,
mime_type: file.file?.type,
pool_id: effectiveFilePool.value,
}),
})
if (!resp.ok) {
messageDisplay.error(`Failed to get presigned URL: ${await resp.text()}`)
onError()
return
}
const respData = await resp.json()
const url = respData.fast_upload_link
try {
const xhr = new XMLHttpRequest()
xhr.open('PUT', url, true)
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
onProgress({ percent: (event.loaded / event.total) * 100 })
}
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
onFinish()
} else {
messageDisplay.error(`Upload failed: ${xhr.responseText}`)
onError()
}
}
xhr.onerror = () => {
messageDisplay.error('Upload failed due to a network error.')
onError()
}
xhr.send(file.file)
} catch (e) {
console.error(e)
messageDisplay.error(`Upload failed: ${e}`)
onError()
}
return
}
const requestHeaders: Record<string, string> = {} const requestHeaders: Record<string, string> = {}
if (props.filePool) requestHeaders['X-FilePool'] = props.filePool if (effectiveFilePool.value) requestHeaders['X-FilePool'] = effectiveFilePool.value
if (filePass.value) requestHeaders['X-FilePass'] = filePass.value if (filePass.value) requestHeaders['X-FilePass'] = filePass.value
if (fileExpire.value) requestHeaders['X-FileExpire'] = fileExpire.value.toString() if (fileExpire.value) requestHeaders['X-FileExpire'] = fileExpire.value.toString()
if (props.bundleId) requestHeaders['X-FileBundle'] = props.bundleId if (props.bundleId) requestHeaders['X-FileBundle'] = props.bundleId
const upload = new tus.Upload(file.file, { const upload = new tus.Upload(file.file as any, {
endpoint: '/api/tus', endpoint: '/api/tus',
retryDelays: [0, 3000, 5000, 10000, 20000], retryDelays: [0, 3000, 5000, 10000, 20000],
removeFingerprintOnSuccess: false, removeFingerprintOnSuccess: false,

View File

@@ -360,7 +360,7 @@ public class FileController(
return file; return file;
} }
catch (Exception ex) catch (Exception)
{ {
await transaction.RollbackAsync(); await transaction.RollbackAsync();
throw; throw;