/ SPRING

스프링 Client 파일 업로드(1)

스프링 목록

스프링 부트 목록



Client가 파일을 업로드할때 서버측에서 구현해야할 것들



1. Commons-fileupload 설치


pom.xml파일에 해당내용 추가( <dependencies> 태그안에 )
<!-- 파일업로드 -->
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

Commons FileUpload(링크)


Commons FileUpload 패키지를 사용하면 강력한 고성능 파일 업로드 기능을 서블릿과 웹 애플리케이션에 쉽게 추가할 수 있습니다.

FileUpload는 HTTP 요청을 RFC 1867, “HTML에서 양식 기반 파일 업로드”에 따라 구문 분석합니다. 즉, POST 메서드를 사용하여 HTTP 요청을 제출하면 “멀티파트/폼 데이터”의 콘텐츠 유형으로 해당 요청을 구문 분석하여 호출자가 쉽게 결과를 사용할 수 있습니다.





2. servlet-context.xml <bean> 추가


src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml
<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="defaultEncoding" value="UTF-8"/>
	<property name="maxUploadSize" value="104857568"/>
	<property name="maxUploadSizePerFile" value="2097152"/>
</bean>

mutipartResolver 빈 추가한 후 인코딩형식, 최대 파일사이즈

  • maxUploadSize : 한 요청당 업로드가 허용되는 최대 용량을 바이트 단위로 설정합니다. -1 은 제한이 없다는 뜻으로 이 프로퍼티를 지정하지 않을때 기본값입니다.

  • maxUploadSizePerFile : 한 파일당 업로드가 허용되는 최대 용량을 바이트 단위로 설정합니다. -1 은 제한이 없다는 뜻으로 이 프로퍼티를 지정하지 않을때 기본값입니다.

  • defaultEncoding : 요청을 파싱할때 사용한 기본 인코딩을 지정합니다. 이 값을 개발 파트의 헤더와 폼 필드에 적용됩니다. 기본값은 서블릿 스펙에 따라 ISO-859-1 입니다. 만약 요청이 문자 인코딩을 지정하면 요청 인코딩이 이 설정을 재정의 합니다. 또한 ServletRequest.setCharacterEncoding 메서드를 호출하는 필터에서의 문자 인코딩을 일반적으로 재정의할 수 있습니다.

multipartResolver 자료 출처: https://offbyone.tistory.com/345 [쉬고 싶은 개발자]





3. 구현해야할 기능


3-1. Front 로부터 받은 letterFiles, imageFile, repBoard 데이터 처리
  • reBoard값 DB에 저장
  • List <letterFiles> 서버에 저장
  • imageFile 서버에 저장
  • 요청 url은 POST 방식으로 : “board/write”





3-2. 사용자가 올린 사진을 매번 풀 로드하는건 비효율 적임으로
  • 섬네일 생성
  • 생성된 섬네일 게시글 클릭시 보여주기
  • 올린 파일도 추가로 게시글 클릭시 보여주고, 받을수 있게 설정하기





4. BackEnd Controller 구현



  • log는 log4J 설정후 사용
     Logger log = Logger.getLogger(this.getClass());
    
  • 아래 변수들 Controller 멤버 변수로 추가하기
     //@Controller 멤버변수로 선언
     private	String saveDirectory = "f:\\files";//자기가 원하는 경로
     private int s_width=100;
     private int s_height=100;
    
  • 항상 글이나 ,자료같은건 Post방식으로 받기 (@PostMapping )

  • 받는 파라미터 타입은 MultipartFile 사용(@RequestPard 어노테이션 붙이기!)

  • 파일들 저장되는 위치는 String saveDirectory 참고(직접 수정가능)



Controller 의 write 메소드 기본 틀

	@PostMapping("board/write")
	public ResponseEntity<?> write(@RequestPart(name = "letterFiles") List<MultipartFile> paramLetterFiles,
								   @RequestPart(name = "imageFile") MultipartFile paramImageFile, 
								   RepBoard repBoard) {

		log.info("요청전달데이터 title=" + repBoard.getBoardTitle() + ", content=" + repBoard.getBoardContent());
		log.info("letterFiles.size()=" + paramLetterFiles.size());
		log.info("imageFile.getSize()=" + paramImageFile.getSize());
		// 임시유저
		Customer testUser = new Customer();
		testUser.setId("id1");
		testUser.setName("김치");

		// 파일 경로생성
		if (!new File(saveDirectory).exists()) {
			log.info("업로드 실제경로생성");
			new File(saveDirectory).mkdirs();
		}		


		// 1.Add DATABASE
		//Code ~~

		// 2.Save List<letter> File
		//Code ~~

		// 3. Save image FiLe
		//Code ~~

		//아래 예시 코드 보고 주석칸에 코드 채워넣기




		//RETURN
		//프론트에 HttpStatus.OK전달
		return new ResponseEntity(HttpStatus.OK);
	}

아래 주석 Code~ 부분은 밑에 코드들로 채워넣기




1) RepBoard DB에 저장하기

		
		// 1. Add DATABASE
		try {
			repBoard.setBoardC(testUser);
			service.add(repBoard);
		} catch (AddException e1) {
			return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
		}




2) List<MultipartFile> letters 받은거 Server에 저장하기

		// 2.Save List<letter> File
		for (MultipartFile mf : paramLetterFiles) {
			log.info("파일크기: " + mf.getSize() + ", 파일이름:" + mf.getOriginalFilename());
			String mfFileName = repBoard.getBoardNo() + "_letter_" + UUID.randomUUID() + "_" + mf.getOriginalFilename();

			try {
				FileCopyUtils.copy(mf.getBytes(), new File(saveDirectory, mfFileName));
			} catch (IOException e) {
				e.printStackTrace();
				return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
			}
		}




3) MultipartFile imageFile 받은거 Server에 저장하기

		// 3. Save image FiLe
		boolean isSavedImageFileComplete = false;
		boolean isImageFile = paramImageFile.getContentType().contains("image/");

		if (!isImageFile){ 
			return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
		}
		log.info("파일크기: " + paramImageFile.getSize() + ", 파일이름:" + paramImageFile.getOriginalFilename());
		String imageFileName = repBoard.getBoardNo() + "_image_" + UUID.randomUUID() + "_"
				+ paramImageFile.getOriginalFilename(); // 저장할 파일이름을 지정한다 ex) 글번호_image_XXXX_원본이름
														// ex) 0_image_ddf269da-3202-447a-a2c5-7ce395b63887_문어.png

		File createdImagefile = new File(saveDirectory, imageFileName);
		try {
			FileCopyUtils.copy(paramImageFile.getBytes(), createdImagefile);
			isSavedImageFileComplete = true;
			log.info("이미지파일 저장:" + createdImagefile.getAbsolutePath() + ", 이미지파일 크기:" + createdImagefile.length());
		} catch (IOException e1) {
			e1.getStackTrace();
			return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
		}





5.Front 파일 구현


write.html

<link rel="stylesheet" href="./css/write.css?ver=1" />
<script src="./js/write.js?ver=1"></script>
<div class="write">
	<h2>글쓰기</h2>
	<form>
		<div class="data"><label>글제목</label><input type="text" name="boardTitle"></div>
		<div class="data"><label>자소서및이력서첨부</label><input type="file" name="letterFiles" multiple>	</div>
		<div class="data"><label>이미지첨부</label><input type="file" name="imageFile" accept="image/jpeg, image/jpg, image/png" ></div>
		<div class="image">
		     <img class="preview" style="max-width:100px;"><!-- 이미지 미리보기용 태그  -->
		     <img class="downloadview"><!-- 글쓰기후 이미지다운로드용 태그 -->
		 </div>
		<div class="data"><label>글내용</label><textarea name="boardContent"></textarea></div>
		<input type="button" value="글쓰기">
	</form>
</div>

write.css

클릭시 펼처짐
div.write {
	width:1000px;
    margin-bottom: 5px;
    /* padding: 29px 0; */
    background: #f4f4f2;
    height: 400px;
}

div.write h2 {
    /* font: bold 36px Avenir, Arial, georgia;
    color: #222222;
    padding-top: 30px;
    position: relative; */
    color: #444;
    padding-top: 20px;
    margin-bottom: 18px;
    line-height: 1.4;
    text-align: center;
}
div.write>form>div.data {
    margin: 10px 20px;
    width: 100%;
    height: 40px;
    vertical-align: baseline;
}

div.write>form>div.data>label {
    padding: 5px 10px;
    background-color: #ddd;
    display: inline-block;
    width: 100px;
    vertical-align: middle;
}

div.write>form textarea {
    font-size: 1em;
    font-family: Arial;
    padding: 10px;
    /* height: 100px; */
    margin-left: 10px;
    width: 70%;
    height: 80px;
    vertical-align: middle;
}



div.write>form>input[type=button] {
    padding: 0px 10px;
    margin-right: 30px;
    width: 100px;
    height: 28px;
    line-height: 28px;
    background: #777777;
    font-size: 12px;
    font-weight: bold;
    color: #ffffff;
    display: block;
    float: right;
    text-align: center;
    text-decoration: none;
    border-radius: 3px;
}


div.write>form>div.data>input[name=boardTitle],
div.write>form>div.data>input[type=file] {
    font-size: 1em;
    font-family: Arial;
    padding-left: 10px;
    margin-left: 10px;
    width: 50%;
    height: 28px;
    line-height: 28px;
    vertical-align: middle;
}


write.js

  • ajax 형식 주의해서 참고
$(function () {
//--이미지첨부파일 변경될때  미리보기 시작--
	$('section>div.write>form>div.data>input[name=imageFile]').change(function(){
		let file = this.files[0];
		$("div.image>img.preview").attr('src',URL.createObjectURL(file));
	});
	//--이미지첨부파일 변경될때  미리보기 끝--


//--글쓰기 버튼 클릭 시작--
	let $btObj = $('div.write form input[type=button]');	
	$btObj.click(function () {
		let $writeFormObj = $("section>div.write form");
		let formData = new FormData($writeFormObj[0]);
		formData.forEach(function (value, key) {
			console.log(key + ":" + value);
		});
		$.ajax({
			url: backContextPath + "/board/write",
			method: "post",
			processData: false, //파일업로드용 설정
			contentType: false, //파일업로드용 설정
			data: formData, //파일업로드용 설정
			
			cache:false, //이미지 다운로드용 설정
	        xhrFields:{  //이미지 다운로드용 설정
	            responseType: 'blob'
	        }, 
			success: function (responseData) {
				let $img = $('div.image>img.downloadview');
				let url = URL.createObjectURL(responseData);
				$img.attr('src', url); 
			},
			error: function (jqXHR, textStatus) {//응답실패
				alert("에러:" + jqXHR.status);
			}
		});
		return false;
	});
	//--글쓰기 버튼 클릭 끝--
});



6.정상적인 BackEnd 콘솔 화면

INFO : com.my.board.controller.RepBoardController - 요청전달데이터 title=테스트 글제목 입니다, content=테스트 글내용 입니다.
INFO : com.my.board.controller.RepBoardController - letterFiles.size()=1
INFO : com.my.board.controller.RepBoardController - imageFile.getSize()=20202
INFO : com.my.board.controller.RepBoardController - 파일크기: 269, 파일이름:책.txt
INFO : com.my.board.controller.RepBoardController - 파일크기: 20202, 파일이름:문어.png
INFO : com.my.board.controller.RepBoardController - 이미지파일 저장:f:\files\33_image_09e70250-03f8-44ac-b380-7b7c3a78f7e4_문어.png, 이미지파일 크기:20202

히카리 log4J 콘솔내용은 가독성을 위해 글에서 지웠음




스프링 목록

스프링 부트 목록