2024년 8월 9일

코딩 - Vue3 : Uppy 을 이용한 파일 업로드

  ◼︎ 환경

  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2.4 (22F66)
  • TOOLS : Visual Studio Code, Java 11, Gradle, Docker
  • Version Control : GitHub
  • Programming Language : Java, Vue3
  • Front-End Framework : Vue 3.4.30, Vuetify 3.6.10, ag-grid-vue3 31.3.2
  • Back-End Framework : Spring Boot 2.7.12, Spring Security 5.7.7 
  • DBMS : MySql 8.0.33
  • Cloud : OCI (free tier account


1. 업로드 라이브러리 & 모듈 조사 

Vue 3 환경에서 파일업로드 구현을 위한 인기있는 라이브러리 또는 모듈들에는  여러 가지가 있다. 추가로 사용성 확인을 위하여 npmjs.com 사이트를 활용하였다.

➀ Dropzone
  • Weekly Downloads: 363,089
  • Version: 6.0.0-beta.2
  • Last Publish: 3년 전
  • 특징: 가장 많이 사용되는 파일 업로드 라이브러리 중 하나이며, Vue.js 에서도 널리 사용되고 있음. 하지만, Vue 3 공식 지원이 부족할 수 있음.
➁ Vue3 Dropzone
  • Weekly Downloads: 10,754
  • Version: 2.2.1
  • Last Publish: 8개월 전
  • 특징: Dropzone.js의 Vue 3 통합 버전으로, Vue 3 프로젝트에서 드래그 앤 드롭 파일 업로드를 쉽게 구현할 수 있음.
➂ Vue Advanced Cropper
  • Weekly Downloads: 82,508
  • Version: 2.8.9
  • Last Publish: 2달 전
  • 특징: 이미지 크롭 기능을 제공하는 파일 업로드 라이브러리로, Vue 3와의 호환성이 높고, 비교적 최근에 업데이트되었음.
➃ Uppy
  • Weekly Downloads: 23,383
  • Version: 4.1.0
  • Last Publish: 10일 전
  • 특징: 강력한 모듈화 파일 업로드 라이브러리로, 여러 소스에서 파일을 업로드할 수 있으며, Vue 3와의 호환성도 좋음.
➄ Vue FilePond
  • Weekly Downloads: 21,154
  • Version: 7.0.4
  • Last Publish: 1년 전
  • 특징: 사용자 친화적인 인터페이스를 제공하며, 다양한 파일 포맷을 지원. 다만, 최근에 업데이트가 없음.
➅ Vue Uploader
  • Weekly Downloads: 115
  • Version: 3.37.1
  • Last Publish: 1년 전
  • 특징: 다운로드 수가 비교적 적으며, 최근 업데이트가 없는 편임. 작은 프로젝트나 간단한 용도로 적합할 수 있음.

간단하게 요약해보면 아래와 같다.
  • 가장 많이 사용되는 라이브러리: Dropzone은 여전히 가장 많은 다운로드 수를 기록하고 있으며, 널리 사용되고 있다. 하지만 Vue 3에 대한 직접적인 지원이 부족할 수 있다. 
  • Vue 3 에 적합한 라이브러리: Vue Advanced Cropper, Uppy, Vue FilePond, Vue3 Dropzone 등은 Vue 3에서 사용할 수 있는 좋은 선택이다.
개인적으로는 Vue3 Dropzone, Vue FilePond 은 사용해 보았고 이번에는 대용량 파일 업로드가 강점이라고 하는 Uppy 을 사용해 보았다. 


2. Uppy 

Uppy는 강력하고 모듈화된 파일 업로드 라이브러리로, 다양한 파일 소스에서 업로드를 지원하며 파일 업로드 경험을 쉽게 관리할 수 있도록 설계되었다. Uppy는 최신 웹 기술을 활용하며, 파일 업로드를 직관적으로 구현할 수 있도록 다양한 플러그인을 제공한다. Vue.js와의 통합도 지원하여, Vue 프로젝트에서 Uppy의 기능을 쉽게 사용할 수 있다.

  • 모듈식 구조: 필요한 기능만 선택적으로 사용할 수 있도록 다양한 플러그인으로 구성되어 있다. 예를 들어, 파일 드래그 앤 드롭, 웹캠, Google Drive, Dropbox 등 다양한 소스를 지원한다. 
  • 다양한 파일 소스: 로컬 파일뿐만 아니라 원격 소스(예: Google Drive, Dropbox, Instagram)에서도 파일을 업로드할 수 있다.

  • Vue.js 통합: @uppy/vue 패키지를 통해 Vue.js 프로젝트에 쉽게 통합할 수 있다.

  • 커스터마이징 가능: 업로드 UI와 동작을 프로젝트의 요구에 맞게 커스터마이징할 수 있다. 또한, 업로드 진행률 표시, 취소, 재시도 등의 기능도 제공. ⇨ UI 커스터마이징이 비교적 용의했다. 


3. Uppy 설치 

Uppy와 Vue 통합 패키지를 설치한다.

npm install @uppy/core @uppy/dashboard @uppy/drag-drop @uppy/file-input @uppy/progress-bar
@uppy/vue @uppy/xhr-upload --save


4. Uppy 을 이용한 파일 업로드  

업로드 구현은 아래와 같은 순서로 코딩하면 된다. 

❶ Uppy 컴포넌트 임포트
<script setup lang="ts">
// import upload component(uppy)
import Uppy from '@uppy/core';
import { Dashboard } from '@uppy/vue';
import XHRUpload from '@uppy/xhr-upload';
import '@uppy/core/dist/style.css';
import '@uppy/dashboard/dist/style.css';


❷ Vue 컴포넌트에서 Dashboard 사용
<div class="dashboard-container">
<Dashboard :uppy="uppy"
inline="true"
:height="200"
:note="'Images only, up to 10MB'"
:metaFields="metaFields" />
</div>

@uppy/vue에서 가져온 Dashboard 컴포넌트를 사용하여 Vue 템플릿에서 Uppy 대시보드를 직접 렌더링. inline 속성을 사용 대시보드를 <div> 요소에 직접 렌더링 되도록 설정.  

metaFields 을 사용하여 업로드할 파일과 관련된 추가적인 메타데이터를 수집하고, 사용자로부터 입력받을 수 있게 한다. 이를 통해 파일에 대한 추가 정보를 서버로 전송하거나, 파일 처리 로직에서 메타데이터를 활용할 수 있다.   → 이부분은 확인 할 수 없었다.

❸ Uppy 인스턴스를 생성하고 파일업로드 플러그인을 설정
const fileUploadUrl = computed(() => {
return `${import.meta.env.VITE_API_URL}/data/secure/mgmt/resources/images/${props.imageId}/upload`
})
const uppy = new Uppy({
autoProceed: false, // 수동으로 업로드 시작
restrictions: {
maxFileSize: 10000000, // 1최대 파일 크기: 10MB
maxNumberOfFiles: 5, // 업로드 가능한 최대 파일 수
minNumberOfFiles: 1, // 업로드 가능한 최소 파일 수
allowedFileTypes: ['image/*']
}
})
.use(XHRUpload, {
endpoint: fileUploadUrl.value, // 업로드할 서버 엔드포인트
fieldName: 'file', // 서버에 전송되는 파일의 필드 이름
formData: true, // FormData를 사용하여 파일을 업로드 (멀티파트 사용)
headers: {
...authHeader(),
},
retryDelays: [0, 1000, 3000, 5000]
});

참로로 authHeader 는 JWT 토큰 관련 값을 정의하는 내용이다. 이런 방식으로 헤더에 값을 추가할 수 있다.

Uppy 는 여러가지 다수의 업로드 방식을 지원하고 있고 여기에서는 가장 일반적인 XHRUpload 을 적용했다.
  1. XHRUpload: 가장 일반적인 파일 업로드 방식으로, 대부분의 서버에서 지원.
  2. Tus: 대규모 파일 업로드 및 네트워크 문제로 인한 업로드 중단 시 재개를 지원하는 프로토콜
  3. Multipart: multipart/form-data 형식으로 서버에 파일을 전송.
  4. S3 Multipart: AWS S3에 대규모 파일을 멀티파트 방식으로 업로드.
  5. Transloadit: 클라우드 기반 업로드 서비스와 통합
❹ 파일 업로드시 추가 정보 전송
파일을 업로드 할때 추가 정보를 전송하려면 uppy 의 upload 이벤트를 사용하여 추가하면된다. 이경우 멀티파트 파마메터 형식으로 추가된 값들이 전달된다.

uppy.on('upload', (data) => {
uppy.setMeta({
objectId: dataRef.value.objectId ,
objectType: dataRef.value.objectType,
link: createSharedLink.value,
description: dataRef.value.description
});
});


❺ onBeforeUnmount 을 사용 컴포넌트가 파괴되기 전에 Uppy 인스턴스를 정리

onBeforeUnmount(() => {
uppy.destroy();
});

❻ 업로드 영역 크기 조정하기 
Dashboard 의 height 을 사용해도 높이가 조정 되지 않기 떄문에 CSS 을 아래와 같은 방식으로 수정해 적용해야 한다.
<style scoped>
::v-deep .uppy-Dashboard {
height: 250px; /* 원하는 높이로 설정 */
max-height: 100%;
display: flex;
flex-direction: column;
}
</style>


그림1. 파일 업로드 화면
그림2. 파일을 추가한 화면

그림3. 파일 업로드가 성공한 경우

➐ 서버 프로그램
XHRUpload 업로드 모듈의 경우 formData 옵션을 사용하면 multipart/form-data 형식으로 서버에 전송되기 때문에 기존 업로드 프로그램을 사용하여 업로드를 구현할 수 있다.

@PostMapping(value = { "/images/{imageId:[\\p{Digit}]+}/upload" }, produces = MediaType.APPLICATION_JSON_VALUE)
public List<Image> upload(
@PathVariable Long imageId,
@RequestParam(value = "objectType", defaultValue = "-1", required = false) Integer objectType,
@RequestParam(value = "objectId", defaultValue = "-1", required = false) Long objectId,
@RequestParam(value = "description", required = false) String description,
@RequestParam(value = "link", defaultValue = "false", required = false) Boolean createLink,
@RequestParam("file") List<MultipartFile> files) throws NotFoundException, IOException, UnAuthorizedException {

User user = SecurityHelper.getUser();
List<Image> list = new ArrayList<>();

for (MultipartFile mpf : files) {
String fileName = StringUtils.cleanPath(mpf.getOriginalFilename());
InputStream is = mpf.getInputStream();
log.debug("upload <file name:{}, size:{}, type:{}>", fileName, mpf.getSize(), mpf.getContentType());
// 업로드 파일을 처리한다.

list.add(newImage);
}
return list;
}

5. 대용량 파일 업로드

Uppy 에서 대용량 파일 업로드는 Tus 프로토콜을 사용하여 구현 할 수 있다.  Tus는 대규모 파일 업로드를 효율적으로 처리할 수 있도록 설계된 오픈 프로토콜로 Uppy는 Tus를 통해 파일을 청크 단위로 업로드하고, 중단된 업로드를 재개하는 등의 기능을 제공한다. 

npm install  @uppy/tus

Uppy 에서는 간단하게 XHRUpload 파일 업로드 모듈을 Tus 로 변경해주면 된다.

import Tus from '@uppy/tus';
const tusFileUploadUrl = computed(()=>{
return `${import.meta.env.VITE_API_URL}/data/secure/mgmt/resources/files/${props.imageId}/tus`;
})
uppy.use(Tus, {
endpoint: tusFileUploadUrl.value, // Tus 서버 엔드포인트
chunkSize: 5 * 1024 * 1024, // 5MB 청크 사이즈
headers: {
...authHeader(),
},
retryDelays: [0, 1000, 3000, 5000]
})
;

클라이언트 부분과 다르게 서버는 Tus 프로토콜에 따른 서버 프로그램을 새로 구현해야한다. 

Tus 프로토콜 

Tus 프로토콜은 클라이언트와 서버 간 4단계로 파일을 업로드합니다.

❶ 파일 생성(Create):
  • 클라이언트는 서버에 새로운 업로드를 생성하도록 요청한다. 
  • 이 요청에는 파일의 크기와 메타데이터가 포함될 수 있다.
  • 서버는 업로드에 고유한 URL을 생성하고 클라이언트에게 반환한다.
  • 파일 및 메타 정보는 이때만 전송된다. 이후에는 데이터만 전송된다.
❷ 파일 업로드(Upload):
  • 클라이언트는 생성된 URL을 사용하여 파일의 특정 바이트를 서버에 업로드한다.
  • 서버는 클라이언트가 업로드한 바이트를 확인하고, 다음 업로드 시 어디서부터 시작할지 오프셋(Upload-Offset)을 반환한다.
❸ 업로드 재개(Resume):
  • 업로드 중단 시 클라이언트는 서버에서 제공한 오프셋을 사용하여 중단된 지점부터 업로드를 재개할 수 있다.
❹ 업로드 완료(Completion):
  • 모든 바이트가 성공적으로 업로드되면 업로드가 완료된다.

프로토콜 메시지

  • OPTIONS: 서버가 Tus 프로토콜을 지원하는지 확인하고, 지원하는 기능을 확인하기 위해 클라이언트가 전송. 서버는 지원하는 버전, 최대 파일 크기 등의 정보를 응답. 
  • POST: 새로운 업로드를 생성하기 위해 사용. 서버는 업로드 URL을 응답합니다. 
  • HEAD:업로드 상태를 확인하기 위해 사용. 서버는 현재 업로드된 바이트의 오프셋을 응답. 
  • PATCH: 파일의 특정 바이트를 업로드하기 위해 사용. 서버는 업로드된 바이트를 저장하고, 새로운 오프셋을 응답. ❷❸ 
  • DELETE:업로드를 취소하거나 삭제하기 위해 사용. 서버는 업로드된 데이터를 삭제합니다.

그림4. 파일 업로드 프로세스


다음은 파일이 업로드되면 유니크한 아이디를 생성하고 메타 정보는 meta.json 형태로 저장하여 청크파일 업로드를 구현하는 예이다. 



댓글 없음:

댓글 쓰기