HERA-V4 업로드 가이드¶
목차¶
1. 개요¶
HERA-V4의 업로드 시스템은 TUS 재개 가능 프로토콜 위에 4가지 처리 모드를 제공한다. 프론트엔드는 filepond 라이브러리를 통해 일관된 사용자 경험을 제공하고, 백엔드는 TransferMode 별로 분기 처리한다.
1.1 지원 업로드 모드¶
| Mode | 사용 사례 | 저장 위치 |
|---|---|---|
| NORMAL | 일반 파일 업로드 (첨부파일, 이미지 등) | 로컬 디스크 (resources.dir.upload) |
| OBJECT_STORAGE | 영속 저장이 필요한 파일 (회사 자료 등) | S3 호환 스토리지 |
| EXCEL | 엑셀 데이터 파싱 → DB 일괄 적재 | 처리 후 _temp/excel/ |
| EXCEL_ASYNC | 대용량 엑셀 비동기 처리 | (미사용 — 향후 확장) |
1.2 핵심 컴포넌트¶
| 컴포넌트 | 역할 |
|---|---|
FileTransfer (interface) | 업로드 처리 인터페이스 |
WebFileTransfer | NORMAL/OBJECT_STORAGE 처리 |
TusTransferService | TUS 프로토콜 (재개 가능 업로드) |
TransferParams | mode + 파일 형식 화이트리스트 + Object Storage 메타 |
UploadResult | 결과 DTO (saved file path 등) |
cleanup.directory.scheduler | 임시 파일 정리 (cron 기반) |
1.3 적용 가능한 시나리오¶
- 사용자가 파일을 업로드해야 하는 모든 메뉴 (첨부파일, 이미지, 엑셀, 양식 등)
- 대용량 파일 재개 가능 업로드 필요 (네트워크 단절 시 이어 올리기)
- 운영자가 파일 형식/크기 정책을 메뉴별로 다르게 적용해야 하는 경우
1.4 적용 부적절한 시나리오¶
- 외부 API에서 파일을 전송받는 경우 (서버-서버 통신은 별도 프로토콜)
- 1KB 이하 매우 작은 파일의 대량 처리 (오버헤드가 더 큼 — JSON 등으로 처리)
- 권한 분리가 매우 복잡한 경우 (권한 모델을 매뉴얼 외부에서 별도 검토 필요)
2. 빠른 시작¶
신규 메뉴에 일반 파일 업로드를 5분 안에 적용하는 최소 예시이다.
2.1 백엔드 endpoint¶
hera-webapp/src/main/java/.../web/controller/api/<menu>/<Menu>ApiController.java:
@RestController
@RequestMapping(SYS_API)
public class MyMenuApiController extends WebApiRouter {
@ResponseBody
@RequestMapping(value = {"/my-menu/upload", "/my-menu/upload/**"})
public ResponseEntity upload(HttpServletRequest req, HttpServletResponse res) throws Exception {
TransferParams params = TransferParams.builder(NORMAL)
.bucket(getBucketName(req))
.path(getFileMiddlePath(false))
.build();
UploadResult result = fileTransfer.transfer(req, res, params);
return Responses.ok(result);
}
}
2.2 프론트엔드 modal + fragment¶
업로드 모달은 file-upload fragment를 재사용한다.
<menu>.html:
<th:block layout:insert="~{fragments/modal/modal-file-upload :: content('파일 업로드', ${@apiUrl}+'/my-menu/upload')}"/>
→ 모달 내부에서 file-upload.html (hera-webapp/src/main/resources/templates/fragments/file-upload.html) fragment가 filepond를 초기화하고 TUS 청크 업로드를 수행한다.
2.3 결과 확인¶
- 메뉴 화면에서 "업로드" 버튼 클릭 → modal 표시
- 파일 드래그 앤 드롭 또는 클릭으로 선택
- filepond가 TUS 프로토콜로 청크 단위 업로드
- 백엔드
WebFileTransfer가 파일을_temp/upload/{uuid}/에 저장 UploadResultJSON 응답 반환 (저장 경로 포함)- cleanup scheduler가 7일 후 자동 정리
3. 단계별 적용 가이드¶
신규 메뉴에 업로드 기능을 추가하는 표준 절차이다.
Step 1: 경로 설정 점검¶
hera-webapp/src/main/resources/application-<profile>.yml에 표준 디렉토리가 정의되어 있는지 확인한다.
resources.dir:
root: ${user.dir}/_temp # 모든 임시 파일의 루트
upload: ${resources.dir.root}/upload # TUS 업로드 임시 (NORMAL)
excel: ${resources.dir.root}/excel # 엑셀 처리
excel-template: ${resources.dir.excel}/template
excel-result-set: ${resources.dir.excel}/result-set
excel-error: ${resources.dir.excel}/error
user-avatar: ${resources.dir.root}/user-avatar
file-storage: ${resources.dir.root}/file-storage
dataset: ${resources.dir.root}/dataset
원칙: - resources.dir.root 하나로 모든 임시 파일을 관리한다 - 새 모듈/메뉴는 기존 디렉토리 재사용 — 임의로 새 경로 만들지 않는다 - 절대 경로 하드코딩 금지 — 모두 resources.dir.* 변수 사용
Step 2: Cleanup Scheduler 설정¶
장기 누적되는 임시 파일을 정기 삭제한다.
cleanup.directory.scheduler:
enabled: true
options:
- dir: ${resources.dir.upload}
cron: 0 0 1 * * ? # 매일 새벽 1시
cutoff-timestamp: -7
cutoff-timestamp-unit: day # 7일 경과 파일 삭제
- dir: ${resources.dir.excel}/error
cron: 0 0 2 * * ?
cutoff-timestamp: -30
cutoff-timestamp-unit: day
추천 정책:
| 디렉토리 | 보관 기간 |
|---|---|
upload (TUS 임시) | 7일 |
excel/error (오류 엑셀) | 30일 (사용자가 다운로드할 시간 보장) |
dataset (.dat 청크) | 7일 |
user-avatar | 미적용 (영속 저장) |
file-storage | 미적용 (영속 저장) |
Step 3: 백엔드 Controller endpoint¶
업로드 endpoint를 작성한다. WebFileTransfer가 TUS 프로토콜을 처리하므로 Controller는 TransferParams만 구성하면 된다.
@Resource
private FileTransfer fileTransfer;
@Value("${resources.dir.upload}")
private String uploadDir;
@ResponseBody
@RequestMapping(value = {"/my-menu/upload", "/my-menu/upload/**"})
public ResponseEntity upload(HttpServletRequest req, HttpServletResponse res) throws Exception {
TransferParams params = TransferParams.builder(NORMAL)
.bucket(getBucketName(req))
.path(getFileMiddlePath(false))
.enableFileFormats(List.of("xlsx", "pdf", "png", "jpg")) // 화이트리스트
.build();
UploadResult result = fileTransfer.transfer(req, res, params);
return Responses.ok(result);
}
핵심 옵션:
| 옵션 | 의미 | 예시 |
|---|---|---|
TransferMode | 처리 모드 | NORMAL, OBJECT_STORAGE, EXCEL |
bucket | 저장 버킷 (또는 디렉토리) | getBucketName(req) |
path | 하위 경로 prefix | getFileMiddlePath(false) |
enableFileFormats | 허용 파일 확장자 | List.of("xlsx", "csv") |
Step 4: 프론트엔드 Modal + Fragment¶
업로드 UI는 fragment를 조합하여 구성한다. 자체 multipart form 작성 금지 — 항상 fragment를 재사용한다.
4-1. 일반 파일 업로드¶
<!-- 메뉴 페이지 어딘가에 모달 삽입 -->
<th:block layout:insert="~{fragments/modal/modal-file-upload :: content(${@apiUrl}+'/my-menu/upload')}"/>
<!-- 트리거 버튼 -->
<button type="button" data-bs-toggle="modal" data-bs-target="#fileUploadModal">
<i class="ri-upload-line"></i> 업로드
</button>
4-2. Object Storage 첨부파일¶
attachmentIds는 form에 포함될 hidden input 이름. 업로드 완료 시 파일 ID 배열이 자동 채워진다.
4-3. 엑셀 업로드 (룰 엔진 연동)¶
<th:block layout:insert="~{fragments/modal/modal-excel-upload :: content(${@apiUrl}+'/my-menu/excel-upload', 'sys0102')}"/>
→ 두 번째 인자('sys0102')는 menuId. Coordinator가 이 ID로 ExcelAdapter를 dispatch한다.
4-4. 사용 가능한 fragment 목록¶
| Fragment | 용도 |
|---|---|
file-upload | 표준 filepond + TUS (모달 내부) |
file-upload-object-storage | Object Storage 모드 (form에 임베드) |
file-uploader | 단일 파일 업로더 (간소화) |
modal-file-upload | 일반 파일 업로드 모달 |
modal-excel-upload | 엑셀 업로드 모달 (탭 + 검증 그리드) |
modal-layout-upload | 엑셀 레이아웃 업로드 (sys0402 전용) |
file-list, file-list2 | 업로드된 파일 목록 표시 |
file-list-object-storage | Object Storage 파일 목록 |
Step 5: Object Storage 모드 (선택)¶
대용량/영속 저장이 필요하면 NORMAL 대신 OBJECT_STORAGE 모드를 사용한다.
TransferParams params = TransferParams.builder(OBJECT_STORAGE)
.bucket("my-company-attachments")
.path("/board/" + boardId)
.objectId(UUID.randomUUID().toString())
.build();
주의: - Object Storage 자격 증명은 application-<profile>.yml의 별도 섹션에서 관리 - 로컬 개발 시 NORMAL 모드 사용 권장 (인프라 의존성 회피)
Step 6: 엑셀 업로드 적용 (선택)¶
엑셀 데이터를 DB에 일괄 적재하는 메뉴는 별도 처리가 필요하다.
- Adapter 작성:
ExcelUploadMenuAdapter인터페이스 구현 (또는AbstractExcelUploadMenuAdapter<V>상속) - DB 메타데이터 등록: sys0401(템플릿) → sys0402(레이아웃) → sys0403(검증룰) → sys0404(변환룰)
- Frontend:
modal-excel-uploadfragment 사용 (위 §4-3 참조)
상세 절차는 PDCA archive 참조:
- excel-upload-rules-engine — 룰 엔진 + 멀티시트 + postDbAction
- excel-upload-auto-db-unique — 자동 DB 중복 체크 + DbColumnGuard
핵심 코드 참조:
AbstractExcelUploadMenuAdapter.java(hera-commons/src/main/java/kr/co/dandisoft/hera/excel/upload/AbstractExcelUploadMenuAdapter.java) — Adapter base class (V 제네릭)CommonCodeExcelAdapter.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/domain/sys/excel/sys0101/CommonCodeExcelAdapter.java) — 참조 구현
Step 7: 동작 검증¶
- 업로드 모달에서 파일 선택 → 청크 업로드 진행률 확인
- 네트워크 차단 후 재연결 → 자동 이어 올리기 동작 (TUS resume)
_temp/upload/{uuid}/디렉토리에 파일 저장됨 확인- cleanup scheduler 시뮬레이션: cutoff 기간을 짧게 설정 후 cron 실행
4. 핵심 패턴 & 안티패턴¶
4.1 권장 패턴¶
파일 형식 화이트리스트 사용¶
TransferParams.builder(NORMAL)
.enableFileFormats(List.of("xlsx", "pdf", "png", "jpg")) // ✅ 명시적 허용
.build();
비허용 확장자는 자동 거부된다. 메뉴별로 다른 정책 적용 가능.
디렉토리 변수 일관 사용¶
Fragment 재사용¶
메뉴 ID로 Adapter dispatch (엑셀)¶
→ Coordinator가 menuId로 적절한 Adapter를 자동 선택한다.
4.2 안티패턴 (해서는 안 되는 것)¶
❌ 자체 multipart 파싱¶
@PostMapping("/my-menu/upload")
public ResponseEntity upload(@RequestParam("file") MultipartFile file) { ... } // ❌
TUS 청크 업로드와 비호환. WebFileTransfer를 사용한다.
❌ 절대 경로 하드코딩¶
@Value("${resources.dir.upload}")로 외부화한다.
❌ 파일 형식 검증 누락¶
악성 파일 업로드 위험. 메뉴별 화이트리스트 명시.
❌ Cleanup 미설정¶
enabled: true로 두고 메뉴별 cutoff 기간 정의.
❌ 임시 파일을 영속 저장 용도로 사용¶
영속 저장은 file-storage 또는 OBJECT_STORAGE 사용.
❌ 엑셀 Adapter에서 직접 EasyExcel 호출¶
상세는 엑셀 업로드 PDCA 참조.
5. 트러블슈팅¶
5.1 자주 발생하는 오류¶
| 오류 메시지 / 증상 | 원인 | 해결 |
|---|---|---|
| 업로드 진행률 0%에서 멈춤 | TUS endpoint 미응답 | Controller @RequestMapping에 /upload/** 와일드카드 포함 확인 |
Disk space full | cleanup scheduler 미가동 | cleanup.directory.scheduler.enabled: true, cron 표현식 검증 |
엑셀 업로드 후 _temp/upload/{uuid} 잔존 | EXCEL 모드가 정리하지 않은 상태 | 처리 완료 후 Coordinator가 cleanup 호출하는지 확인 |
허용되지 않은 파일 형식 | enableFileFormats 위반 | 메뉴별 화이트리스트 점검 |
| 네트워크 단절 후 처음부터 재업로드 | filepond 설정 누락 | chunkUploads: true, tus 옵션 확인 |
| Object Storage 401 Unauthorized | 자격 증명 누락 | application-<profile>.yml의 Object Storage 섹션 확인 |
/file-upload :: content 에러 | Fragment 호출 시그니처 불일치 | fragment 인자 개수 확인 (위 §4-4 표) |
5.2 디버깅 절차¶
- 브라우저 DevTools Network: TUS HTTP 요청 흐름(
POST /upload,PATCH /upload/{id}) 확인 - 백엔드 로그:
_temp/logs/hera-webapp/에서WebFileTransfer/TusTransferService검색 - 임시 파일:
_temp/upload/{uuid}/에 청크 파일이 생성되는지 확인 - filepond 콘솔: 브라우저 콘솔의 filepond 에러 메시지
5.3 로그 위치¶
| 로그 | 경로 |
|---|---|
| webapp 로그 | _temp/logs/hera-webapp/hera-webapp-yyyy-MM-dd.log |
| TUS 업로드 임시 | _temp/upload/{uuid}/ |
| 엑셀 .dat 청크 | _temp/excel/upload/ (엑셀 모드 한정) |
| 오류 엑셀 출력 | _temp/excel/error/ |
| 엑셀 결과 셋 | _temp/excel/result-set/ |
6. 레퍼런스 링크¶
6.1 백엔드 핵심 코드¶
FileTransfer.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/FileTransfer.java) — Transfer 인터페이스WebFileTransfer.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/WebFileTransfer.java) — NORMAL/OBJECT_STORAGE 처리TusTransferService.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/TusTransferService.java) — TUS 프로토콜TransferMode.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/TransferMode.java) — 4가지 모드TransferParams.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/TransferParams.java) — 빌더 패턴UploadResult.java(hera-webapp/src/main/java/kr/co/dandisoft/hera/web/transfer/file/result/UploadResult.java) — 결과 DTO
6.2 엑셀 업로드 (별도)¶
AbstractExcelUploadMenuAdapter.java(hera-commons/src/main/java/kr/co/dandisoft/hera/excel/upload/AbstractExcelUploadMenuAdapter.java) — Adapter baseExcelUploadCoordinator.java(hera-commons/src/main/java/kr/co/dandisoft/hera/excel/upload/ExcelUploadCoordinator.java) — 멀티시트 라이프사이클DbColumnGuard.java(hera-commons/src/main/java/kr/co/dandisoft/hera/mapper/guard/DbColumnGuard.java) — SQL Injection 방어
6.3 프론트엔드 Fragment¶
file-upload.html(hera-webapp/src/main/resources/templates/fragments/file-upload.html) — filepond + TUSfile-upload-object-storage.html(hera-webapp/src/main/resources/templates/fragments/file-upload-object-storage.html) — Object Storage 변형modal-file-upload.html(hera-webapp/src/main/resources/templates/fragments/modal/modal-file-upload.html) — 일반 모달modal-excel-upload.html(hera-webapp/src/main/resources/templates/fragments/modal/modal-excel-upload.html) — 엑셀 모달
6.4 관련 화면 (운영자용 / 엑셀 업로드 한정)¶
| 화면 ID | 용도 |
|---|---|
| sys0401 | 엑셀 업로드 템플릿 등록 |
| sys0402 | 엑셀 컬럼 레이아웃 (자동 매핑) |
| sys0403 | 검증룰 등록 (notNull/regex/unique) |
| sys0404 | 변환룰 등록 (trim/replace) |
6.5 관련 PDCA archive¶
- excel-upload-rules-engine — 룰 엔진 + 멀티시트 + postDbAction
- excel-upload-auto-db-unique — 자동 DB 중복 체크 + DbColumnGuard
- excel-upload-validation-transform — 검증/변환 초기 설계
- excel-upload-dat-integration —
.dat청크 직렬화
6.6 외부 라이브러리 의존성¶
| 라이브러리 | 버전 | 용도 |
|---|---|---|
| TUS Java Server | 0.x | 재개 가능 업로드 프로토콜 |
| filepond | 4.x | 프론트엔드 업로더 (TUS 플러그인) |
| EasyExcel | 4.x | 엑셀 파싱/생성 (스트리밍) |
| Kryo | 5.x | .dat 청크 직렬화 |
| MyBatis-Flex | 1.11.6 | DB 매핑 |
| PostgreSQL | 14+ | UNNEST/array, information_schema |
6.7 변경 이력¶
| 날짜 | 버전 | 변경 요약 | 작성자 |
|---|---|---|---|
| 2026-04-29 | 1.0 | Guide-only 형식 초안 (엑셀 중심) | 김영훈 |
| 2026-04-29 | 2.0 | 일반 업로드 전반 (TUS/filepond/Object Storage) 포함, 엑셀은 별도 섹션 + PDCA 위임 | 김영훈 |