package com.interplug.qcast.biz.file; import com.interplug.qcast.biz.file.dto.FileRequest; import com.interplug.qcast.biz.file.dto.FileResponse; import com.interplug.qcast.config.Exception.ErrorCode; import com.interplug.qcast.config.Exception.QcastException; import com.interplug.qcast.config.message.Messages; import com.interplug.qcast.util.ZipFileManager; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLConnection; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.FileCopyUtils; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @Slf4j @Service @RequiredArgsConstructor public class FileService { Messages message; private final FileMapper fileMapper; @Value("${file.ini.root.path}") private String baseDirPath; private ZipFileManager zipFileManager; /** * 파일 업로드 * * @param request * @param fileRequest * @return * @throws Exception */ public List getUploadFileList(HttpServletRequest request, FileRequest fileRequest) throws Exception { List saveFileList = new ArrayList<>(); List multipartFileList = new ArrayList<>(); if (!MultipartHttpServletRequest.class.isAssignableFrom(request.getClass())) { return new ArrayList<>(); } MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Map> mapMultipart = multipartRequest.getMultiFileMap(); if (mapMultipart.isEmpty()) { return null; } String strParamKey; for (String s : mapMultipart.keySet()) { strParamKey = s; multipartFileList.addAll(mapMultipart.get(strParamKey)); } int iFileCnt = multipartFileList.size(); if (iFileCnt > 0) { int iFileSeq = 1; String strSeparator = File.separator; String strFileFolderPath = baseDirPath + strSeparator + fileRequest.getObjectNo() + strSeparator; // planNo가 있는 경우 if (fileRequest.getPlanNo() != null && !fileRequest.getPlanNo().isEmpty()) { strFileFolderPath += fileRequest.getPlanNo() + strSeparator; } log.info("### fileFolderPath : {}", strFileFolderPath); // 파일 폴더 생성 makeFileFolder(strFileFolderPath); for (MultipartFile multipartFile : multipartFileList) { if (!multipartFile.isEmpty()) { // 파일 확장자 발췌 및 확장자 소문자 변환 String strFileExt = multipartFile.getOriginalFilename(); if (strFileExt != null && !strFileExt.isEmpty()) { strFileExt = strFileExt.substring(strFileExt.lastIndexOf(".") + 1).toLowerCase().trim(); } else { strFileExt = ""; } // 파일 확장자 및 사이즈 검증 List listExtension = this.getFileExtension(); if (!validFileExtension(listExtension, strFileExt)) { multipartFile.getInputStream().close(); continue; } String strSrcFileNm = multipartFile.getOriginalFilename(); strSrcFileNm = this.getUniqueFileName(strFileFolderPath, strSrcFileNm); File file = new File(strFileFolderPath, strSrcFileNm); multipartFile.transferTo(file); FileRequest fileSaveReq = new FileRequest(); fileSaveReq.setObjectNo(fileRequest.getObjectNo()); fileSaveReq.setPlanNo(fileRequest.getPlanNo()); fileSaveReq.setCategory(fileRequest.getCategory()); fileSaveReq.setFaileName(strSrcFileNm); fileSaveReq.setUserId(fileRequest.getUserId()); saveFileList.add(fileSaveReq); iFileSeq++; } } } return saveFileList; } /** * 파일 확장자 검증 * * @param listExtension * @param strFileExt * @return * @throws Exception */ private boolean validFileExtension(List listExtension, String strFileExt) throws Exception { boolean bResult = true; try { if (strFileExt == null || strFileExt.isEmpty()) { return false; } for (String s : listExtension) { strFileExt = strFileExt.toLowerCase(); // 업로드 가능한 파일 확장자 검증 if (s.equals(strFileExt)) { return true; } } } catch (Exception e) { log.error("### FileService - validFileExtension : Exception Error"); return false; } return bResult; } // 업로드 가능한 파일 확장자 조회 public List getFileExtension() { return Arrays.asList( "mp3", "wma", "wav", "m4a", "3gp", "hwp", "doc", "docx", "ppt", "pptx", "zip", "xls", "xlsx", "pdf", "txt", "jpg", "gif", "png"); } // 파일 폴더 생성 private void makeFileFolder(String strMakeDir) { try { File dir = new File(strMakeDir.replace("//", "/")); if (!dir.exists()) { if (!dir.mkdirs()) { log.error("### Failed to create directories: {}", strMakeDir); throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR); } } } catch (Exception e) { log.error("### FileService - makeFileFolder : Exception Error"); } } // 파일정보 저장 및 삭제 public Integer setFile(List saveFilelist, List deleteFilelist) throws Exception { Integer iResult = 0; // 파일 삭제 if (deleteFilelist != null && !deleteFilelist.isEmpty()) { for (FileRequest fileDeleteReq : deleteFilelist) { if (!"".equals(fileDeleteReq.getObjectNo())) { fileMapper.deleteFile(fileDeleteReq); // 파일 삭제 } } } // 파일 저장 if (saveFilelist != null && !saveFilelist.isEmpty()) { for (FileRequest fileInsertReq : saveFilelist) { if (!"".equals(fileInsertReq.getObjectNo()) && !"".equals(fileInsertReq.getCategory())) { iResult += fileMapper.insertFile(fileInsertReq); fileMapper.insertFileInfo(fileInsertReq); } } } return iResult; } /** * 파일 1건 다운로드 * * @param request * @param response * @param fileRequest * @throws Exception */ public void downloadFile( HttpServletRequest request, HttpServletResponse response, FileRequest fileRequest) throws Exception { System.out.println("fileRequest>>>" + fileRequest.toString()); // 필수값 체크 if (fileRequest.getObjectNo() == null || fileRequest.getObjectNo().isEmpty()) { throw new QcastException( ErrorCode.INVALID_INPUT_VALUE, message.getMessage("common.message.required.data", "Object No")); } else if (fileRequest.getNo() == 0) { throw new QcastException( ErrorCode.INVALID_INPUT_VALUE, message.getMessage("common.message.required.data", "No")); } // 파일정보 조회 FileResponse fileResponse = fileMapper.selectFile(fileRequest); if (fileResponse == null) { throw new QcastException( ErrorCode.NOT_FOUND, message.getMessage(" common.message.file.download.exists")); } try { // 첨부파일 물리적 경로 String strSeparator = File.separator; String filePath = baseDirPath + strSeparator + fileResponse.getObjectNo(); if (fileResponse.getPlanNo() != null && !fileResponse.getPlanNo().isEmpty()) { filePath += strSeparator + fileResponse.getPlanNo(); } filePath += strSeparator + fileResponse.getFaileName(); File file = new File(filePath); if (!file.exists()) { log.error("### File not found: {}", filePath); throw new QcastException(ErrorCode.INTERNAL_SERVER_ERROR); } String mimeType = URLConnection.guessContentTypeFromName(file.getName()); if (mimeType == null) { mimeType = "application/octet-stream"; } String originalFileName = fileResponse.getFaileName(); String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); response.setHeader("Content-Transfer-Encoding", "binary;"); response.setHeader("Pragma", "no-cache;"); response.setHeader("Expires", "-1;"); response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\""); response.setContentType(mimeType); response.setContentLength((int) file.length()); try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { FileCopyUtils.copy(inputStream, response.getOutputStream()); } catch (IOException e) { throw new QcastException( ErrorCode.INTERNAL_SERVER_ERROR, message.getMessage("common.message.file.download.error")); } } catch (Exception e) { e.printStackTrace(); } } /** * 파일 N건 ZIP 다운로드 * * @param request * @param response * @param fileRequest * @throws Exception */ public void downloadZipFile( HttpServletRequest request, HttpServletResponse response, FileRequest fileRequest) throws Exception { // 필수값 체크 if (fileRequest.getObjectNo() == null || fileRequest.getObjectNo().isEmpty()) { throw new QcastException( ErrorCode.INVALID_INPUT_VALUE, message.getMessage("common.message.required.data", "Object No")); } String strZipFileName = fileRequest.getZipFileName(); if (strZipFileName == null || strZipFileName.isEmpty()) { strZipFileName = "Download"; } // 파일 조회 List fileList = fileMapper.selectFileList(fileRequest); if (fileList == null || fileList.size() == 0) { throw new QcastException( ErrorCode.NOT_FOUND, message.getMessage(" common.message.file.download.exists")); } // 첨부파일 물리적 경로 FileResponse fileResponse = fileList.get(0); String zipFilePath = fileResponse.getObjectNo(); String filePath = baseDirPath + File.separator + fileResponse.getObjectNo() + File.separator; if (fileResponse.getPlanNo() != null && !fileResponse.getPlanNo().isEmpty()) { zipFilePath += File.separator + fileResponse.getPlanNo(); filePath += fileResponse.getPlanNo() + File.separator; } String finalFilePath = filePath; String finalZipFilePath = zipFilePath; List> listFile = fileList.stream() .map( file -> { Map map = new HashMap<>(); map.put("directory", finalZipFilePath); map.put("filename", finalFilePath + file.getFaileName()); return map; }) .collect(Collectors.toList()); // zip 파일로 변환 zipFileManager.createZipFile(response, strZipFileName, listFile); } public Integer deleteFile(FileRequest fileRequest) throws Exception { Integer iResult = 0; // 첨부파일 논리적 삭제 iResult = fileMapper.deleteFile(fileRequest); // 파일 삭제 return iResult; } public List fileUpload(HttpServletRequest request, FileRequest fileRequest) throws Exception { List saveFileList = this.getUploadFileList(request, fileRequest); // Integer resultCnt = this.setFile(saveFileList, null); return saveFileList; } public String getUniqueFileName(String uploadDir, String fileName) { File dir = new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); // 디렉터리가 없으면 생성 } String name = fileName; String extension = ""; // 파일명과 확장자 분리 int dotIndex = fileName.lastIndexOf("."); if (dotIndex >= 0) { name = fileName.substring(0, dotIndex); extension = fileName.substring(dotIndex); } File file = new File(uploadDir, fileName); int counter = 1; // 파일명이 중복될 경우 번호를 붙임 while (file.exists()) { String newFileName = name + "_" + counter + extension; file = new File(uploadDir, newFileName); counter++; } return file.getName(); } }