= 파일 업/다운로드 == 개요 클라이언트의 파일 업/다운로드 API 호출을 처리한다. === 파일 업로드 단일 파일 업로드를 처리한다. 하나의 파일만 업로드하는 경우가 아니라면 보통은 멀티 파일 업로드를 이용한다. === 멀티 파일 업로드 ==== 컨트롤러에서 파일 업로드 서비스를 호출한다. .FileManagerController [source,java] ---- @PostMapping("/resource/attachments/multifile-upload") public List uploadMultiFile( @Parameter(description = "MultipartFile[]", required = true) @RequestParam(required = true) MultipartFile[] files, @Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType) { return fileManagerService.save(downloadType, Arrays.asList(files)); } ---- ==== ==== 서비스에서 지정된 경로에 파일 리소스를 저장하고, 업로드된 파일 정보를 DB 에 저장한다. .FileManagerServiceImpl [source,java] ---- /** * 기본 업로드 패스 설정 값 */ @Value("${common.upload-path}") private String fileUploadPath; /** * 업로드 루트 하위 폴더 자릿수 설정 값 */ @Value("${common.upload.directory-name-len}") private int directoryNameLen; /** * 사용자지정 업로드 패스 설정 여부 */ @Value("${custom.upload-path.enabled}") private boolean customUploadPathEnabled; /** * 사용자지정 업로드 패스 설정 값 */ @Value("${custom.upload-path}") private String customUploadPaths; @Override @Transactional public List save(String downloadType, List files) { return this.save(downloadType, null, files); } @Override @Transactional public List save(String downloadType, String refId, List files) { List attachFileList = new ArrayList<>(); files.stream().forEach(file -> { AttachFile attachFile = this.save(downloadType, refId, file); attachFileList.add(attachFile); }); return attachFileList; } @Override @Transactional public AttachFile save(String downloadType, String refId, MultipartFile file) { String fileId = this.store(file, downloadType); // <1> AttachFile attachFile = new AttachFile(); attachFile.setDownloadType(downloadType); attachFile.setFilePathName(this.rootUploadPath.toString()); attachFile.setFileExtensionName(fileId); attachFile.setFileName(file.getOriginalFilename()); attachFile.setFileMimeTypeName(file.getContentType()); attachFile.setFileSize(file.getSize()); attachFile.setOwnerObjectPkId(refId); return fileManagerDao.insertFileInfo(attachFile); // <2> } ---- <1> 파일 리소스 저장 <2> 업로드된 파일 정보 DB저장 ==== ==== 파일 리소스 저장 경로는 기본 업로드 패스 값(common.upload-path)과 하위 디렉토리 길이 설정 값(common.upload.directory-name-len)에 따라 결정된다. + 기본 업로드 패스외에 사용자정의 업로드 패스 설정도 가능하다. .config.properties [source,properties] ---- ## File Attach Configuration common.upload-path=/NAS/SDL/upload // <1> common.upload.directory-name-len=2 // <2> # custom upload path 설정 custom.upload-path.enabled=false // <3> custom.upload-path=\ // <4> notice=/NAS/SDL/upload/notice,\ faq=/NAS/SDL/upload/faq ---- <1> 기본 업로드 패스 설정 <2> 업로드 패스 하위 디렉토리 길이 + 파일명(UUID)에서 이 길이 만큼 잘라서 기본 업로드 패스 하위 디렉토리가 생성된다. <3> 사용자정의 업로드 패스 사용 여부 <4> 파일 컴포넌트별 사용자정의 업로드 패스 설정 (custom.upload-path.enabled=true 일 경우 적용됨) ==== === 파일 다운로드 컨트롤러에서 파일 다운로드 서비스를 호출한다. .FileManagerController [source,java] ---- @GetMapping("/resource/attachments/file-download/{fileId}") public ResponseEntity downloadFile( @Parameter(description = "File ID", required = true) @PathVariable String fileId, @Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType, HttpServletRequest request) { long startTime = System.nanoTime(); if (StringUtils.isBlank(fileId)) { throw new FileManagerException("No File ID"); } AttachFile attachFile = fileManagerService.getAttachFile(fileId, downloadType); // <1> if (attachFile == null) { throw new FileManagerException("Cannot find file info: " + fileId); } Resource resource = fileManagerService.getResource(attachFile.getFileExtensionName(), attachFile.getFilePathName()); // <2> String contentType = attachFile.getFileMimeTypeName(); if (StringUtils.isBlank(contentType)) { contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE; } String fileName = attachFile.getFileName(); // <3> String encodeFileName = null; // 다운로드 파일명 UTF-8 인코딩 encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); // 다운로드 이력 로깅 fileManagerService.saveFileDownloadLog(fileName, startTime, request); // <4> return ResponseEntity.ok() .contentType(MediaType.parseMediaType(contentType)) .header("Content-Transfer-Encoding", "binary") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodeFileName + "\"") .body(resource); } ---- <1> 파일 정보 조회 <2> 파일 리소스 가져오기 <3> 파일 업로드 시점의 파일명 <4> 파일 다운로드 이력 로깅 === 멀티 파일 다운로드 여러 파일을 zip 파일 형태로 다운로드할 수 있도록 제공한다. .FileManagerController [source,java] ---- @Value("${common.download.zipfilename}") private String zipFileName; @GetMapping("/resource/attachments/multifile-download") public ResponseEntity downloadMultiFile( @Parameter(description = "File IDs", required = true) @RequestParam(required = true) String[] fileIds, @Parameter(description = "다운로드 구분 (컴포넌트 한개 이상일 경우 구분)", required = true) @RequestParam(required = true) String downloadType, @Parameter(description = "zip 파일명", required = false) @RequestParam(required = false) String zipFileName, HttpServletRequest request) { long startTime = System.nanoTime(); if (fileIds == null || fileIds.length == 0) { throw new FileManagerException("No File ID(s)"); } if (StringUtils.isBlank(zipFileName)) { zipFileName = this.zipFileName; } // 다운로드 파일명 UTF-8 인코딩 String fileName = null; fileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8).replace("+", "%20"); // 다운로드 이력 로깅 fileManagerService.saveFileDownloadLog(zipFileName, startTime, request); ByteArrayResource baResource = fileManagerService.getByteArrayResource(fileIds, downloadType); return ResponseEntity.ok() .contentLength(baResource.contentLength()) .contentType(MediaType.parseMediaType("application/zip")) .header("Content-Transfer-Encoding", "binary") .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + ".zip\";") .body(baResource); } ----