mjeongriver
article thumbnail
Published 2023. 2. 21. 18:18
day90-spring boot TIL/Spring boot

1. 파일 업로드

- 업로드: 1) 일반 업로드: form 형식, 2) 비동기 업로드 AJAX(드래그 하면 바로 올라가는 거)

- 저장: 1) 데이터 베이스 2) 하드 디스크 3) 클라우드

 

 

1) 일반 업로드

 

2번째 사진처럼 크기 제한이 있어야 합니다. 

 

- 4번째 사진 참고 사항

- 파일 업로드에서는 enctype(인코딩 타입)을 multipart/form-data 반드시 써줄 것!

- 파일 type은 file

- 아래는 컨트롤러

- trasferTo: 파일 업로드를 진행해줍니다.

 

- 일반 업로드 실습

1] 단일

 

경로 상에 한글이 포함되어 있으면 C: 아래로 경로 잡을 것, \ 하나는 명령문으로 인식 되어서 2개로 넣어줄 것!

 

mkdir, rmdir로 폴더 생성하고 지울 수 있듯이 컨트롤러에서도 사용이 가능함

 

파일을 업로드 하면 폴더에 생성됨

 

2] 단일 태그로 다중 파일 업로드

 

 

3] 복수 태그로 여러 파일 업로드: list에서 빈 값 제거 넣을 것!

 

list.stream().filter( (x) -> x.isEmpty() == false ).collect(Collectors.toList());

 

(x)가 비어 있지 않으면 새로운 리스트를 반환 시켜줌

 

3~4번째 사진에서의 오류: 3개를 다 업로드 했을 때는 문제가 없지만 2개 넣으면 오류 발생

 

list로 빈 값 제거해주면 하나만 넣어도 올라감

 

2) 비동기 업로드

 

 

- JSON 형식으로 보낼 때는 content 타입이 application/json이었음

- XML로 보낼 때는 application/xml

- 일반 폼 형식: form 데이터를 전송 할 때는 enctype=enctype="multipart/form-data"

- 파일 전송 형태의 폼 형식: multipart/formdata

- 서버에서 값을 받는 것은 동일한데 responsebody 달아줘야 성공 실패 여부를 알 수 있음

 

 

* application.properties, UploadController, ex01, ex01_ok

더보기

- application.properties

########## 파일 업로드 관련 내용 설정 #############
# 파일 업로드 가능 여부 설정
spring.servlet.multipart.enabled=true

# 한번에 최대 업로드 가능한 용량
spring.servlet.multipart.max-request-size=50MB

# 파일 하나당 최대 크기
spring.servlet.multipart.max-file-size=10MB

# 실제 저장 경로(업로드 경로)
project.uploadpath=C:\\Users\\user\\Desktop\\minjeong\\program\\course\\springboot\\upload

 

UploadController

package com.simple.basic.controller;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import net.coobird.thumbnailator.Thumbnailator;
import oracle.jdbc.proxy.annotation.Post;

@Controller
@RequestMapping("/upload")
public class UploadController {

	@GetMapping("/ex01")
	public void ex01() {}
	
	
	@Value("${project.uploadpath}")
	private String uploadpath;
	
	
	//날짜 별로 폴더 생성
	public String makeDir() {
		
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
		String now = sdf.format(date);
		
		String path = uploadpath + "\\" + now; //경로
		File file = new File(path);
		
		if(file.exists() == false ) { //존재 하면 true
			file.mkdir(); //폴더 생성
		}
				
		return path; 
	}
	
	
	//단일 파일 업로드
	@PostMapping("/upload_ok")
	public String uploadOk(@RequestParam("file") MultipartFile file) {
		
//		String origin = file.getOriginalFilename(); //파일명 
//		long size = file.getSize(); //사이즈
//		String type = file.getContentType(); //파일 데이터에 컨텐츠 타입
		
		//파일 명
		String origin = file.getOriginalFilename(); 
		
		//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
		String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
		
		//폴더 생성
		String filepath = makeDir();
				
		//중복 파일의 처리
		String uuid = UUID.randomUUID().toString();
		
		//최종 저장 경로
		String savename = filepath + "\\" + uuid + "_" + filename;
		
		System.out.println(filename);
		System.out.println(filepath); 
		System.out.println(uuid);
		System.out.println(savename);
		
		try { 
			File save = new File(savename); //세이브 경로
			file.transferTo(save);
			
			//썸네일 경로
			String thumbsname = filepath + "\\" + uuid + "_thumbs_" + filename;
			
			//썸네일 생성(복사할 파일 위치, 썸네일 생성 위, 가로, 세로)
			Thumbnailator.createThumbnail(new File(savename), 
										  new File(thumbsname),
										  150, 
										  150);
		
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return "upload/ex01_ok";
	}
	
	//multiple 옵션으로 다중파일 업로드
	@PostMapping("/upload_ok2")
	public String uploadOk2(MultipartHttpServletRequest files) {
		
		//name 태그가 file인 것을 찾음
		List<MultipartFile> list = files.getFiles("file");
		
		//반복 처리
		for(MultipartFile file : list) {
			//파일 명
			String origin = file.getOriginalFilename(); 
			
			//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
			String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
			
			//폴더 생성
			String filepath = makeDir();
					
			//중복 파일의 처리
			String uuid = UUID.randomUUID().toString();
			
			//최종 저장 경로
			String savename = filepath + "\\" + uuid + "_" + filename;
			
			System.out.println(filename);
			System.out.println(filepath); 
			System.out.println(uuid);
			System.out.println(savename);
			
			try { 
				File save = new File(savename); //세이브 경로
				file.transferTo(save);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		
		return "upload/ex01_ok";
	}
	
	//복수 태그로 여러 파일 업로드
	@PostMapping("/upload_ok3")
	public String uploadOk3(@RequestParam("file") List<MultipartFile> list) {
		
		//리스트에서 빈 값은 제거
		list = list.stream().filter( (x) -> x.isEmpty() == false ).collect(Collectors.toList());
		
		
		//반복 처리
		for(MultipartFile file : list) {
			//파일 명
			String origin = file.getOriginalFilename(); 
			
			//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
			String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
			
			//폴더 생성
			String filepath = makeDir();
					
			//중복 파일의 처리
			String uuid = UUID.randomUUID().toString();
			
			//최종 저장 경로
			String savename = filepath + "\\" + uuid + "_" + filename;
			
			System.out.println(filename);
			System.out.println(filepath); 
			System.out.println(uuid);
			System.out.println(savename);
			
			try { 
				File save = new File(savename); //세이브 경로
				file.transferTo(save);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		
		return "upload/ex01_ok";
	}
	
	//비동기 업로드 - 넘어오는 데이터는 form 형식이기 때문에 vo or requestParam으로 받으면 됩니다.
	@PostMapping("/upload_ok4")
	@ResponseBody //return 값이 요청이 온 곳으로 반환
	public String uploadOk4(@RequestParam("file") MultipartFile file, 
							@RequestParam("writer") String writer) {
		
		System.out.println(file);
		System.out.println(writer);
		
		//파일 명
		String origin = file.getOriginalFilename(); 
		
		//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
		String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
		
		//폴더 생성
		String filepath = makeDir();
				
		//중복 파일의 처리
		String uuid = UUID.randomUUID().toString();
		
		//최종 저장 경로
		String savename = filepath + "\\" + uuid + "_" + filename;
		
		System.out.println(filename);
		System.out.println(filepath); 
		System.out.println(uuid);
		System.out.println(savename);
		
		try { 
			File save = new File(savename); //세이브 경로
			file.transferTo(save);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return "success";
	}
	
}

 

- ex01

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

	<h3>업로드 예제</h3>

	<!-- 단일파일 업로드 -->
	<form action="upload_ok" method="post" enctype="multipart/form-data" >
		<input type="file" name="file"><br/>
		<input type="submit" value="업로드"><br/>
	</form>
	
	<hr/>
	
	<h3>multiple옵션으로 다중파일 업로드</h3>
	<form action="upload_ok2" method="post" enctype="multipart/form-data" >
		<input type="file" name="file" multiple="multiple"><br/>
		<input type="submit" value="업로드"><br/>
	</form>
	
	<hr/>
	
	<h3>복수태그로 여러파일 업로드</h3>
	
		<form action="upload_ok3" method="post" enctype="multipart/form-data" >
		<input type="file" name="file"><br/>
		<input type="file" name="file"><br/>
		<input type="file" name="file"><br/>
		<input type="submit" value="업로드"><br/>
	</form>
	
	<hr/>
	
	<h3>비동기형식의 업로드</h3>
	<div>
		<input type="file" name="file" id="a"><br/>
		<input type="text" name="writer" id="writer"><br/>
		
		<input type="button" value="업로드" id="btn"><br/>
	</div>
	
	<script src="https://code.jquery.com/jquery-3.6.3.js"></script>
	<script>
		
		$("#btn").click(function() {
		
			//파일데이터 추출
			var file = $("#a");
			console.log(file[0]); //순수한태그
			console.dir(file[0].files[0]); //파일데이터
			
			//사용자가 입력 text값
			var writer = $("#writer").val();
						
			//폼태그로 추가
			var formData = new FormData(); //폼객체
			formData.append("file", file[0].files[0]); //name, 값
			formData.append("writer", writer); //name, 값
			
			$.ajax({
				url : "upload_ok4", 
				type : "post",
				data : formData, //보내는데이터 form
				contentType: false, //보내는데이터타입 false -> "multipart/form-data" 로 선언됩니다
				processData : false, //폼데이터가 name=값&name=값 형식으로 자동변경되는 것을 막아줍니다.
				success: function(result) { //콜백
					
					if(result == "success") {
						alert("업로드가 완료되었습니다");
					}
				},
				error: function(err) {
					alert("업로드 에러발생");
				}
			})	
		})
		
	</script>
	
</body>
</html>

 

- ex01_ok

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	
	<h3>결과 페이지</h3>
	<a href="ex01">다시 올리기</a>

</body>
</html>

 

2. 일반 업로드를 이용하여 상품 이미지 구현

 

 

- 메이븐 레파지토리

 

한 다음에 refresh gradle

 

upload_ok에서 생성할 부분

 

파일 2개 생성 됨(하나는 미들 파일로 사용할 수 있음)-하나는 원본, 하나는 프로필을 나타내는 작은 파일에 사용할 수 있음

 

* jpg, png, jpeg 형식이 아니면 등록되지 않게끔 구현

- bootMyweb 프로젝트 열어서 build.gradle에 추가

//sql 실행 로그 기능
	implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'

	//이미지 썸네일
	implementation 'net.coobird:thumbnailator:0.4.8'

 

- application.property에 추가

########## 파일 업로드 관련 내용 설정 #############
# 파일 업로드 가능 여부 설정
spring.servlet.multipart.enabled=true

# 한번에 최대 업로드 가능한 용량
spring.servlet.multipart.max-request-size=50MB

# 파일 하나당 최대 크기
spring.servlet.multipart.max-file-size=10MB

# 실제 저장 경로(업로드 경로)
project.uploadpath=C:\\Users\\user\\Desktop\\minjeong\\program\\course\\springboot\\upload

 

- productController

 

 

//파일 업로드 작업 → 
		//리스트에서 빈 값은 제거
		list = list.stream()
				.filter( (x) -> x.isEmpty() == false )
				.collect(Collectors.toList());
		
		//확장자가 image라면 경고문
		for(MultipartFile file : list) {
			if( file.getContentType().contains("image") == false ) {
				ra.addFlashAttribute("msg", "png, jpg, jpeg 형식만 등록 가능합니다.");
				return "redirect:/product/productReg";
			}
		}

 

- productReg 맨 마지막에 alert 추가, enctype 추가

 

<script th:inline="javascript">
var msg = JSON.parse('[[${msg}]]');
if (msg != null) {
	alert(msg);
}
</script>

 

저 파일들이 아니면 튕겨냄(txt 파일 첨부 했음)

 

* 파일 업로드 작업을 → service영역으로 위임

 

 

1) 서비스에 위임

 

서비스에 위임(service, serviceImpl, controller)

 

2) 테이블 생성

 

 

3) serviceimpl에 추가

//업로드 패스
	@Value("${project.uploadpath}")
	private String uploadpath;
	
	//날짜 별로 폴더 생성
	public String makeDir() {
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
		String now = sdf.format(date);
		
		String path = uploadpath + "\\" + now; //경로
		File file = new File(path);
		
		if(file.exists() == false ) { //존재하면 true
			file.mkdir(); //폴더생성
		}
				
		return path; 
	}
	
	
	//글 등록
	@Override
	public int regist(ProductVO vo, List<MultipartFile> list) {
		
		//1. 글 등록 처리 -> 
		int result = productMapper.regist(vo);
		
		//2. 파일 인서트 ->
		//반복 처리
			for(MultipartFile file : list) {
			//파일 명
			String origin = file.getOriginalFilename(); 
					
			//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
			String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
					
			//폴더 생성
			String filepath = makeDir();
							
			//중복 파일의 처리
			String uuid = UUID.randomUUID().toString();
					
			//최종 저장 경로
			String savename = filepath + "\\" + uuid + "_" + filename;
					
			try { 
				File save = new File(savename); //세이브 경로
				file.transferTo(save);
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			//인서트
			
			
		} //end for
		
		return ;
	}

 

4) mapper.java에 추가

 

 

5) productUploadVO에 추가

 

package com.coding404.myweb.command;

import java.time.LocalDateTime;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductUploadVO {

	/*
 	CREATE TABLE PRODUCT_UPLOAD (
 	UPLOAD_NO INT PRIMARY KEY auto_increment,
 	FILENAME varchar(100) not null, ##실제파일명
 	FILEPATH varchar(100) not null, ##폴더명
 	UUID varchar(50) not null, ##UUID명
 	REGDATE TIMESTAMP default now(),
 	PROD_ID INT, ##FK
 	PROD_WRITER VARCHAR(20) ##FK
	);
	 */
	
	private int upload_no;
	private String filename;
	private String filepath;
	private String uuid;
	private LocalDateTime regdate;
	
	private int prod_id;
	private String prod_writer;
	
}

 

5) mapper.xml에 추가

<insert id="registFile" parameterType="ProductUploadVO">
  		insert into PRODUCT_UPLOAD(filename,
  								   filepath,
  								   uuid,
  								   prod_id,
  								   prod_writer)
  		values(#{filename},
  			   #{filepath},
  			   #{uuid},
  			   #{prod_id}
  			   #{prod_writer})
  	</insert>

 

6) productServiceImpl 위에 인서트 부분 수정

 

 

ProductUploadVO prodVO = ProductUploadVO.builder()
						   .filename(filename)
						   .filepath(filepath)
						   .uuid(uuid)
						   .prod_id(??) //?
						   .prod_writer(vo.getProd_writer()) //
						   .bulid();
			
			productMapper.registFile(prodVO);

 

- 오라클에서는 시퀀스 쓰면 id 부분이 처리가 가능한데, mySQL에서는 안됨

 

 

7) 그래서 select 처리 인서트 위에 해줘야 함(멀티 insert 상황에서 pk 값 얻어오기)

(인서트-insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리)

<!-- 
  	1. insert 전에 product 테이블의 키 값을 selectKey 태그를 이용해서 얻습니다.
  	2. resultType은 조회된 결과 타입, 
  	   KeyProperty은 sql에 전달되는 vo의 저장할 key 값
  	   order는 BEFORE, AFTER = 인서트 이전에 실행 or, 인서트 이후에 실행
  	 -->
  	
  	<insert id="registFile" parameterType="ProductUploadVO">
  		<selectKey resultType="int" keyProperty="prod_id" order="BEFORE">
  			select max(prod_id) as prod_id from PRODUCT where prod_writer = #{prod_writer}
  		</selectKey>
  		
  		insert into PRODUCT_UPLOAD(filename,
  								   filepath,
  								   uuid,
  								   prod_id,
  								   prod_writer)
  		values(#{filename},
  			   #{filepath},
  			   #{uuid},
  			   #{prod_id}
  			   #{prod_writer})
  	</insert>

 

8) serviceImpl 수정

 

원래 return 값 path에서 now로 변경

//글 등록
	//한 프로세스 안에서 예외가 발생하면 기존에 진행했던 CRUD 작업을 rollback 시킵니다.
	//조건 - catch를 통해서 예외 처리가 진행 되면 트랜잭션 처리가 되지 않습니다.
	@Override
	@Transactional(rollbackFor = Exception.class)
	//exception 터지면 실행했던 sql rollback 해줌(insert가 여러번 들어갈 때 넣어주기 좋음
	//스프링은 따로 설정해줘야 하지만 스프링 부트는 바로 사용이 가능)
	//예외를 잡을 수가 없음
	public int regist(ProductVO vo, List<MultipartFile> list) {
		
		//1. 글 등록 처리 -> 
		int result = productMapper.regist(vo);
		
		//2. 파일 인서트 ->
		//반복 처리
			for(MultipartFile file : list) {
			//파일 명
			String origin = file.getOriginalFilename(); 
					
			//브라우저 별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
			String filename = origin.substring( origin.lastIndexOf("\\") + 1 ); 
					
			//폴더 생성
			String filepath = makeDir();
							
			//중복 파일의 처리
			String uuid = UUID.randomUUID().toString();
					
			//최종 저장 경로
			String savename = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;
					
			try { 
				File save = new File(savename); //세이브 경로
				file.transferTo(save);
			} catch (IOException e) {
				e.printStackTrace();
				return 0; //실패의 의미로
			}
			
			//인서트-insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리
			ProductUploadVO prodVO = ProductUploadVO.builder()
						   .filename(filename)
						   .filepath(filepath)
						   .uuid(uuid)
						   .prod_writer(vo.getProd_writer()) //
						   .build();
			
			productMapper.registFile(prodVO);
			
		} //end for
		
		return result; //성공시 1, 실패시 0
	}

 

 

 

'TIL > Spring boot' 카테고리의 다른 글

day92-spring boot  (0) 2023.02.23
day91-spring boot  (0) 2023.02.22
day88-spring boot  (0) 2023.02.17
day86-spring boot  (0) 2023.02.16
day85-spring boot  (0) 2023.02.15
profile

mjeongriver

@mjeongriver

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그