[SpringBoot + React] 파일 업로드 (다중)

P__.mp4·2022년 10월 15일
1

Spring

목록 보기
5/6

SpringBoot에서 파일 업로드를 위한 종속성을 추가해줘야한다.

<dependency>
	<groupId>commons-io</groupId>
  <artifactId>commons-io</artifactId>
  <version>2.11.0</version>
</dependency>
<dependency>
	<groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.4</version>
</dependency>

우선

Front-end에서 Back-end로 요청을 보낼 때, HTTP Request를 통해 요청이 전송된다. 이때 각종 데이터는 HTTP Request의 Body 에 데이터를 저장하여 보내게된다. Body 에 들어가는 데이터의 형태는 Header 에 Content-type을 통하여 어떠한 데이터를 전송하는 지 명시하게 된다.

파일 전송을 하려면, Content-type을 multipart/form-data 로 지정해줘야한다.

<form action="/uploadfiles" method="post" enctype="multipart/form-data">
    파일명 : <input type="file" name="myfile">
    <button type="submit">제출하기</button>
</form>

일반적으로는 저렇게 명시하여 각종 파일들을 `Body` 에 담아 HTTP Request를 통해 서버로 전송하게 된다.

React에서 파일 전송 (Front-end)

React에서 서버와 통신하기 위해서 Axios 를 사용해야하는데, 해당 라이브러리는 따로 작성된 글을 참고한다.
링크

파일 데이터를 비동기를 보내기 위해서는 new FormData() 라는 객체를 생성하여 사용한다.

const formData = new FormData();

생성된 객체에 파일 데이터와 JSON 데이터를 담기 위해 FormData 에서 append() 라는 메소드를 제공한다. JSON 데이터는 content-type을 application/json 로 명시하여 전송하지만, 파일 데이터가 있기 때문에 JSON.stringify() 메소드를 통해 JSON 객체를 텍스트로 변환 후 new Blob() 을 통해 담아준다.

const formData = new FormData();
const contentsData = {
	userNo: user.userInfo.no,
	title,
	content,
	categoryCode,
	dues,
	personNumber
}
const fileData = inputRef.current.file.files;
        
for(let i = 0; i < fileData.length; i++) {
	formData.append("file", fileData[i]);
}

formData.append("contentsData", new Blob([JSON.stringify(contentsData)], { type: "application/json" }));

그리고 Axios로 POST 방식으로 데이터를 전송하면 된다.


SpringBoot에서 (Back-end)

파라미터는 HttpServletRequest@RequestPart 를 통해 데이터를 받아줄려고한다. 파일데이터를 받기 위해서는 MultipartHttpServletRequest 라는 클래스가 필요한데, HttpServletRequest 의 하위 클래스다.

@PostMapping("/insert")
public Map<String, Object> insertMeeting(
  HttpServletRequest httpServletRequest,
  @RequestPart(value = "contentsData") MeetingVO meetingVO {
	...
  ...
}

우선 파일 업로드에 관련된 클래스와 상수를 선언하는 클래스를 작성하였다.
  1. 파일 업로드 관련 클래스
@Component
public class FileUploadUtil {
	private static final Logger logger
		= LoggerFactory.getLogger(FileUploadUtil.class);

	public List<Map<String, Object>> mulitiFileUpload(HttpServletRequest request,
												int uploadFlag) throws IllegalStateException, IOException {
		MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest)request;

		List<Map<String, Object>> list = new ArrayList<>();

		List<MultipartFile> fileMap = multiRequest.getFiles("file");
		for(MultipartFile multipartFile : fileMap) {
			if(!multipartFile.isEmpty()) {
				long fileSize = multipartFile.getSize();	// 파일 크기
				String oName = multipartFile.getOriginalFilename();	// 원래 파일명

				//변경된 파일이름 구하기
				String fileName = getUniqueFileName(oName);

				//업로드할 폴더 구하기
				String uploadPath
						= getUploadPath(request, uploadFlag);

				//파일 업로드 처리
				File file = new File(uploadPath, fileName);
				multipartFile.transferTo(file);

				//업로드된 파일 정보 저장, (파일크기, 파일명2개를 VO나 Map으로 묶으면 된다)
				//[1] Map에 저장
				Map<String, Object> resultMap = new HashMap<>(); // <>뒤에는 생략가능
				resultMap.put("fileName", fileName);
				resultMap.put("fileSize", fileSize);
				resultMap.put("originalFileName", oName);

				//[2] 여러 개의 Map을 List에 저장
				list.add(resultMap);
			}
		}

		return list;
	}

  
	
	public String getUniqueFileName(String fileName) {
		//파일명이 중복될 경우 파일이름 변경하기
		//파일명에 현재시간(년원일 시분초 밀리초)을 붙여서 변경된 파일이름 구하기
		//ex) a.txt => a_20220602113820123.txt
		
		//순수 파일명만 구하기 => a
		int idx = fileName.lastIndexOf(".");
		String fileNm = fileName.substring(0, idx);		// a
		
		//확장자 구하기 => .txt
		String ext = fileName.substring(idx);		// .txt
		
		//변경된 파일이름
		Date d = new Date();
		SimpleDateFormat sdf = new  SimpleDateFormat("yyyyMMddHHmmssSSS");
		// => '년원일시분초밀리초' 포멧
		String today = sdf.format(d);
		String result = fileNm + "_" + today + ext;
		logger.info("변경된 파일명 : {}", result);
		
		return result;
	}
	
  
	public String getUploadPath(HttpServletRequest request, int pathFlag) {
		//업로드 경로 구하기
		//업로드경로를 설정해도 이클립스에서는 다른 경로를 사용하므로, test경로를 따로사용해야하고
		//직접배포했을시에는 getRealPath()를 사용하면 경로가 잘 잡히기 때문에,
		//테스트경로가 따로 필요함
		String path = "";
		
		if(ConstUtil.FILE_UPLOAD_TYPE.equals("test")) {	// 배포전 이클립스에서 테스트시
			if(pathFlag == ConstUtil.UPLOAD_FILE_FLAG) {	// 자료실
				path = ConstUtil.FILE_UPLOAD_PATH_TEST;
			} else if(pathFlag == ConstUtil.UPLOAD_IMAGE_FLAG) {	// 상품등록
				path = ConstUtil.IMAGE_FILE_UPLOAD_PATH_TEST;
			}
		} else {	// deploy(배포시)
			if(pathFlag == ConstUtil.UPLOAD_FILE_FLAG) {	// 자료실
				path = ConstUtil.FILE_UPLOAD_PATH;	// pds_upload
			} else if(pathFlag == ConstUtil.UPLOAD_IMAGE_FLAG) {	// 상품등록
				path = ConstUtil.IMAGE_FILE_UPLOAD_PATH;	// pd_images
			}
			
			//실제 물리적인 경로 구하기
			path = request.getSession().getServletContext().getRealPath(path);
		}
		
		logger.info("업로드 경로 : {}", path);
		
		return path;
	}
	
}
  1. 상수 선언 인터페이스
package com.pmp4.amoimproject.common;

public interface ConstUtil {
	//파일 업로드 관련 상수
	String FILE_UPLOAD_TYPE = "test";		// 테스트시
	//String FILE_UPLOAD_TYPE = "deploy";	// 배포시
	
	//파일 저장 경로
	String TEST_FILE_PATH = "/~~/~~";	//배포 전, 절대 경로
	String FILE_UPLOAD_PATH = "pd_upload";
	String FILE_UPLOAD_PATH_TEST = TEST_FILE_PATH + FILE_UPLOAD_PATH;

	//이미지 저장 경로
	String IMAGE_FILE_UPLOAD_PATH = "img_upload";
	String IMAGE_FILE_UPLOAD_PATH_TEST = TEST_FILE_PATH + IMAGE_FILE_UPLOAD_PATH;

	//자료실 업로드인지, 상품 등록시 업로드인지 구분값
	int UPLOAD_FILE_FLAG = 1;	// 자료실 업로드
	int UPLOAD_IMAGE_FLAG = 2;	// 이미지 업로드
}

끝!

profile
개발은 자신감

0개의 댓글