원지의 개발
article thumbnail
728x90

 

파일 업로드

  • 파일 첨부 기능은 매우 중요
  • 사용자 편의 ▶ 드래그 앤 드롭(JS), 첨부파일의 유효성 검사(server), 파일 전송의 진행률(JS)

스프링

  • 일반적으로 commons-fileupload 이용 (서블릿 3.0 이전, 이후 모두 사용 가능)
  • 서블링 3.0 이상에서부터는 자체적인 파일 업로드 지원
  • 파일럽로드 처리 라이브러리 필요

스프링부트

  • WAS(웹 어플리케이션 서버)의 버전이 낮거나 WAS가 아닌 환경이라면 추가 라이브러리 필요
  • 내장 tomcat 이용 - 별도의 라이브러리 추가 없이 업로드 사용 가능

[ 스프링부트 ]

일반 업로드

1. application.properties 설정

application.properties

################### 파일업로드 관련 설정 ###################
### 파일 업로드 사용
spring.servlet.multipart.enabled=true

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

### 파일 최대 가능 업로드 - 크기 제한 필수
spring.servlet.mulfipart.max-request-size=50MB
  • 파일 업로드는 간단한 설정으로 가능
  • 파일의 크기 제한은 필수

2. 업로드 경로에 대한 별도의 설정값 추가

application.properties

### 업로드 경로
project.uploadpath(변수명)=C:\\Users\\user\\Desktop\\JWcourse\\boot\\upload
  • 업로드 할 폴더(upload) 생성 후 업로드 경로 추가
  • 여기서 project.uploadpath는 변수명으로 임의로 설정 가능
  • 업로드 파일의 임시 저장 경로 지정 가능
    spring.servlet.multipart.location=C:\\Users\\user\\Desktop\\JWcourse\\boot\\upload-test

3. Controller에서 참조 가능

UploadController.java

package com.example.basic.controller;

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.RequestMapping;

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

	@GetMapping("/ex01")
	public void ex01() { }
	
	@Value("${project.uploadpath}")
	private String uploadpath;
}
project.uploadpath=C:\\upload
  • @Value 사용하여 application.properties의 업로드 경로 속성을 가져와 uploadpath 변수에 담기
    @value 안에 파라미터로 EL형식의 키 값을 넣어주면, 불러올 수 있음

  • value를 이용해서 업로드 해 줌

2022.12.02 - [Server/JSP] - [JSP] MVC2, JSTL, 형변환태그

4. 업로드시 날짜별 폴더 생성 (upload 폴더 하위)

UploadController.java

	//날짜별로 폴더 생성
	public String makeDir() {
		
		//지금 날짜 가져오기
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
		String now = sdf.format(date);
		
		//폴더 생성구문 - upload 폴더 하위로
		String path = uploadpath + "\\" + now; //경로
		File file = new File(path);
		
		if(file.exists() == false) { //존재하면 true
			file.mkdir(); //폴더 생성
		}
		
		return path; //최종 저장 경로에 사용하려고 return은 만들 폴더 경로(path)
	}
  • 날짜별로 폴더를 생성하는 makeDir 함수 생성
  • 지금 날짜 가져오기
    Date 객체 생성 후 SimpleDateFormat으로 형식 지정, now라는 변수에 date 담기
  • 폴더 생성 구문
    path 변수에 최종적으로 완성할 경로 만들어줌
    java.io의 File 클래스에 url을 담아서 url 경로에 대한 파일의 File 객체 생성
  • 폴더 생성 조건문
    조건문으로 file이 존재하지 않으면 mkdir() 메서드를 이용해 폴더 생성
File 클래스
기존의 파일이나 폴더에 대한 제어를 하는 데 사용하는 File 클래스를 제공

File(URI uri) - file uri 경로에 대한 파일의 File 객체를 생성 (생성자)
boolean mkdir() - 해당 경로에 폴더를 만듦 (메서드)
https://xzio.tistory.com/305
 

[JAVA] File 클래스 정리 (파일정보, 파일목록, 하드디스크 정보 출력)

File 클래스 정리 java.io 패키지는 기존의 파일이나 폴더에 대한 제어를 하는 데 사용하는 File 클래스를 제공한다. 이 클래스를 이용해서 파일과 폴더에 대한 다양한 기능을 제공한다. 파일을 나타

xzio.tistory.com


5-1. 단일 파일 업로드

templates.upload - ex01.html

<!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>
	
</body>
</html>
파일 업로드에서는 enctype(인코딩 타입)을 multipart/form-data로 반드시 설정
파일을 전송하는 타입이야라고 알려주기 때문에 빠지면 전송x
  • post타입으로 form 태그를 날려주는데 enctype 반드시 설정해야함
  • input 태그의 속성으로 file을 선택하면 파일 보낼 수 있는 형식이 나옴

UploadController.java

	//단일 파일 업로드
	@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);
		
		//폴더 생성 + filepath는 만든 폴더 경로
		String filepath = makeDir();
		//중복파일의 처리 - 랜덤한 String 값
		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 (Exception e) {
			e.printStackTrace();
		}
		return "upload/ex01_ok";
	}

  • controller에서 getMapping한 후 requestParam으로 파일 타입으로 받음
    MultipartFile file을 "file" 변수에 담음
  • file.get 메서드로 필요한 부분 꺼낼 수 있음
file.getOriginalFilename(); //파일명
file.getSize(); //사이즈
file.getContentType(); //파일데이터의 컨텐츠 타입

예시

		//파일명
		String origin = file.getOriginalFilename();
		//브라우저별로 경로가 포함되서 올라오는 경우가 있어서 간단한 처리
		String filename = origin.substring(origin.lastIndexOf("\\") + 1);
		
		//폴더 생성
		String filepath = makeDir();
		//중복파일의 처리 - 랜덤한 String 값
		String uuid = UUID.randomUUID().toString();
		//최종 저장 경로
		String savename  = filepath + "\\" + uuid + "_" + filename;

		System.out.println(origin); //r10.jpg
		System.out.println(filename); //r10.jpg
		System.out.println(filepath); //C:\upload\230222
		System.out.println(uuid); //8d406167-481c-456f-a642-c318c5ea1e8c
		System.out.println(savename); //C:\upload\230222\8d406167-481c-456f-a642-c318c5ea1e8c_r10.jpg
  • 파일명에 경로가 포함되어 나오는 경우가 있기 때문에  subString으로 자르기 마지막 \\ 기준으로 +1 (인덱스니까)
  • 폴더 생성하기 위해 만들어둔 makeDir 호출
  • 중복을 피하기 위한 랜덤 UUID 생성
  • 최종 저장 경로는 폴더 만든 경로 + uuid_파일명
	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 (Exception e) {
			e.printStackTrace();
		}
		return "upload/ex01_ok";
  • try-catch 안에서 업로드 경로를 가진 파일 객체(save) 생성
  • file.transferTo(생성한 객체)로 FileWriter 작업
  • 썸네일 처리 ▼(아래에) - 크기가 다른 이미지 두개 생성
    이미지의 파일 크기를 줄여서 웹 성능의 향상 및 서버의 부하를 줄일 수 있음
    사용하기 전에 dependency 추
  • 처리 후 ex01_ok 파일로 넘어가기

templates.upload - ex01_ok.html

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

	<h3>결과 페이지</h3>
	<a href="ex01">다시올리기</a>

</body>
</html>

5-2. 다중 파일 업로드 - multiple

templates.upload - ex01.html

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

	<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>
	
</body>
</html>
파일 업로드에서는 enctype(인코딩 타입)을 multipart/form-data로 반드시 설정
파일을 전송하는 타입이야라고 알려주기 때문에 빠지면 전송x

옵션으로 다중 파일을 업로드 하려면 multiple
  • post타입으로 form 태그를 날려주는데 enctype 반드시 설정해야함
  • input 태그의 속성으로 file을 선택하면 파일 보낼 수 있는 형식이 나옴

  • name="file", files.getFiles("file");을 찾음

UploadController.java

	//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 값
			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 (Exception e) {
				e.printStackTrace();
			}
			
		}
		return "/upload/ex01_ok";
	}

5-3. 다중 파일 업로드 - 복수 태그

  • stream 사용해서 필터

2022.10.28 - [프로그래밍 언어/Java] - [Java] Nested Class, Lambda, Stream API, Optional

templates.upload - ex01.html

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

	<h3>복수 태그로 여러 파일 업로드</h3>
	<form action="upload_ok3" method="post" enctype="multipart/form-data">
		<input type="file" name="file" multiple="multiple"><br/>
		<input type="file" name="file" multiple="multiple"><br/>
		<input type="file" name="file" multiple="multiple"><br/>
		<input type="submit" value="업로드"><br/>
	</form>
	
</body>
</html>

UploadController.java

	//복수태그로 여러 파일 업로드
	@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);
			System.out.println(origin);
			
			//폴더 생성
			String filepath = makeDir();
			//중복파일의 처리 - 랜덤한 String 값
			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 (Exception e) {
				e.printStackTrace();
			}
			
		}
		
		return "/upload/ex01_ok";
	}

비동기 업로드

웹 폼 데이터를 전송할 때 사용되는 인코딩 유형
json - application/json
xml - application/xml
form - application/xml
파일전송 - multipart/form-data

  • 폼 객체로 데이터 보내기 위해 파일데이터, 이름 추출해서 넣음
  • 사용할 때 이름은 file, writer

출처: chat gpt

  • responseBody로 성공하면 리턴값으로 success 문자 보냄

templates.upload - ex01.html

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

<h3>비동기형식의 업로드</h3>
	<div>
		<input type="file" name="file" id="a" ><br/>
		<input type="text" name="writer" id="writer"></br>
		<input type="submit" 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, 폼태그로 받으니까 (json형식 아님) @requestBody 안쓰고 받던대로 받으면 됨
				contentType: false, //보내는 데이터 타입 false ▶ "multiple/form-data"로 선언됩니다, 파일 타입이라고 명시
				processData: false, //폼데이터가 name=값&name=값 형식으로 자동변경되는 것을 막아줌
				success: function(result) { //콜백
					//console.log(result);
				
					if(result == 'success' ) {
						alert("업로드가 완료되었습니다");
					}
				},
				error: function(err) {
					alert("업로드 에러발생");
				}
			})
			
		})
		
	</script>
	
</body>
</html>

UploadController.java

	//비동기 업로드 - 넘어오는 데이터는 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);
				System.out.println(origin);
				
				//폴더 생성
				String filepath = makeDir();
				//중복파일의 처리 - 랜덤한 String 값
				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 (Exception e) {
					e.printStackTrace();
				}
			
		return "success";
	}

썸네일 이미지 사용 (옵션)

  • 사용자가 올리는 이미지를 일정한 크기의 이미지로 변경 (이미지 포맷팅)

build.gradle

	//이미지 썸네일 (필수 아님, 0221 15:10)
	implementation 'net.coobird:thumbnailator:0.4.8'
  • Thumbnailator 라이브러리 다운

UploadController.java

//썸네일 경로 설정 필 - 수정

//썸네일 생성 (복사할 파일위치, 썸네일 생성위치, 가로, 세로)
			Thumbnailator.createThumbnail(new File(savename),
										  new File(thumbsname),
										  150, 150);
  • create Thumbnail(복사할 경로, 썸네일 생성 경로, 가로, 세로)

상품 사진 업로드 - 일반 업로드

일반업로드와 비동기업로드의 차이
ajax(비동기): 던지기만 하면 알아서 업로드(확인 아직 안 눌러도 자동으로)
                      예를들어 실비 청구 파일 올려놓고 뒤로가기 눌러도 파일 있음
post: 파일 올리고 확인을 눌러야 올라감

등록 화면

  • 이미지 박스 누르면 file type=file 열림
  • HTML에서 for 속성은 label 요소와 연결된 입력 요소의 id를 지정
    이를 통해 레이블을 클릭했을 때 해당 입력 요소가 활성화
//파일업로드
$(document).ready(function() {

	var fileTarget = $('.filebox .upload-hidden'); //jquery는 다중 태그이벤트도 한번에 처리
	fileTarget.on('change', function(){ //change이벤트
	if(window.FileReader){ // modern browser 
		var filename = $(this)[0].files[0].name; } 
	else { // old IE 
		var filename = $(this).val().split('/').pop().split('\\').pop(); // 파일명만 추출 
	} // 추출한 파일명 삽입 
	$(this).siblings('.upload-name').val(filename); });

	var imgTarget = $('.preview-image .upload-hidden'); 
	imgTarget.on('change', function() { 
		var parent = $(this).parent(); 
		// parent.children('.upload-display').remove(); 
		
		if(window.FileReader){ //image 파일만 
			if (!$(this)[0].files[0].type.match(/image\//)) return; 

			var reader = new FileReader(); 
			reader.onload = function(e){ 
				var src = e.target.result; 
				// parent.prepend('<div class="upload-display"><div class="upload-thumb-wrap"><img src="'+src+'" class="upload-thumb"></div></div>'); 
				parent.find(".upload-thumb-wrap").children().attr("src", src);
			} 
			reader.readAsDataURL($(this)[0].files[0]); 
		} else { 
			$(this)[0].select(); 
			$(this)[0].blur(); 
			var imgSrc = document.selection.createRange().text; 
			parent.prepend('<div class="upload-display"><div class="upload-thumb-wrap"><img class="upload-thumb"></div></div>'); 

			var img = $(this).siblings('.upload-display').find('img'); 
			img[0].style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(enable='true',sizingMethod='scale',src=\""+imgSrc+"\")"; 
		} 
	});

});
  • 이미지 안에 그림이 들어가고, input 안에 사진 이름 들어감 - jquery의 ready

AjaxController

  • 비동기이므로 리턴에 값들을 싣고 나가려면 @RestController
  • application.properties에서 설정해 놓은 uploadpath는 AjaxController, ProducrServiceImpl에 들어가야 함

파일 등록하기

  • 화면의 form에 enctype 추가
  • 기본 form은 application/x-www-form-urlencoded 이므로 파일 업로드에는 적합하지 않음

  • file을 List<MultipartFile>로 받아서 빈 값은 제거
  • getContentType으로 확장자 확인
  • 나머지 파일 업로드 작업은 service 영역으로 위임
	//업로드 패스
	@Value("${project.uploadpath}")
	private String uploadpath;

	//날짜별로 폴더 생성
	public String makeDir() {

		//지금 날짜 가져오기
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
		String now = sdf.format(date);

		//폴더 생성구문 - upload 폴더 하위로
		String path = uploadpath + "\\" + now; //경로
		File file = new File(path);

		if(file.exists() == false) { //존재하면 true
			file.mkdir(); //폴더 생성
		}
		return now; //년월일 폴더위치
	}
  • 날짜별로 폴더 생성하는 메서드 추가
	@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 값
			String uuid = UUID.randomUUID().toString();
			//최종 저장 경로
			String savename  = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;


			try {
				File save = new File(savename); //세이브 경로
				file.transferTo(save); //업로드 진행

			} catch (Exception 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
	}
  • serviceImpl에서는 1. 글 등록 처리 2. 파일 인서트 처리

selectKey 사용

 

			//insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리
			ProductUploadVO prodVO = ProductUploadVO.builder()
					.filename(filename)
					.filepath(filepath)
					.uuid(uuid)
					.prod_writer(vo.getProd_writer())
					.build();

			productMapper.registFile(prodVO);
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductUploadVO {

	private int upload_no;
	private String filename;
	private String filepath;
	private String uuid;
	private LocalDateTime regdate;
	
	private int prod_id;
	private String prod_writer;
}
	<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>
  • ProductUploadVO를 가지고 파일 등록
  • insert 이전에 prod_id가 필요한데, selectKey 방식으로 처리
1. insert전에 product 테이블의 키값을 selectKey 태그를 이용해서 얻습니다.
2. resultType은 조회된 결과 타입, keyProperty는 sql에 전달되는 vo의 저장할 key값 order는 before, after - 인서트 이전에 실행 or 인서트 이후에 실행
★ selectKey 한 값을 ProductUploadVO에 넣어줄거고, 그것을 prod_id에 집어넣을거다

insert이전에 prod_id가 필요한데, 값을 구할 방법이 없다! 서비스의 regist메서드에서 prod_writer는 화면에서 넘어오는데 prod_id는 넘어오지 않는다. 따라서, 시퀀스를 사용하거나 해야 하는데, mysql은 시퀀스가 없다.
=> mybatis의 <selectKey>태그
- order는 순서. insert 이전인가 이후인가.
- resultType은 조회된 결과 타입.
- keyProperty는 sql에 전달되는 vo에 저장할 key값. vo의 멤버변수에 저장하겠다는 선언.

이 태그는 insert태그 안에 있다. 따라서 select ~ as 이름으로 얻은 값을 insert의 sql구문에서 #{}로 사용할 수 있다

 

 


이미지 파일 불러오기

1. 바이트 배열형으로 리턴

<img src="../display?filepath=폴더경로&uuid=랜덤값&filename=파일명">

<img th:src="@{../display(filepath=${imgs.filepath}, uuid=${imgs.uuid}, filename=${imgs.filename})}">
  • 이미지 태그가 다음과 같을 때, src가 경로를 읽어 호출하는 원리
  • 타임리프에서 처리
	// 1. ?키=값
	// 2. @Pathvariable
	// 화면에는 2진데이터 타입이 반환됩니다.

	// 이미지 불러오는 방법1
	@GetMapping("/display/{filepath}/{uuid}/{filename}")
	public byte[] display(@PathVariable("filepath") String filepath,
						  @PathVariable("uuid") String uuid,
						  @PathVariable("filename") String filename) {

		// 파일이 저장된 경로 String savename = uploadpath +
		String savename = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;
		File file = new File(savename);

		// 저장된 이미지 파일의 이진데이터 형식을 구함, 자바에는 없고 스프링에만 있음
		byte[] result = null; // 파일 경로가 없으면 null값 들어옴

		try {
			result = FileCopyUtils.copyToByteArray(file);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return result;
	}

  • FileCopyUtils.copyToByteArray(): 스프링의 파일데이터를 읽어서 바이트 배열형으로 리턴하는 메서드
    매개변수로 File 타입을 받음
  • try ~ catch문 안에서 실행되야 함

2. 데이터, 헤더, 상태값 리턴 (심화)

이미지 파일을 클라이언트로 전송하기 위한 것
클라이언트가 특정 이미지 파일을 요청할 때 사용
이미지를 직접적으로 보여주고자 할 때 필요
// 이미지 불러오는 방법2
//	produces: 컨트롤러가 생성하는 응답의 미디어 타입을 명시적으로 지정, 요청의 Accept 헤더에 지정된 미디어 타입과 일치해야함
	@GetMapping(value="/display/{filepath}/{uuid}/{filename}", produces="img/jpg")
	public ResponseEntity<byte[]> display(@PathVariable("filepath") String filepath,
						  @PathVariable("uuid") String uuid,
						  @PathVariable("filename") String filename) {
		
		//파일이 저장된 경로
		String savename = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;
		File file = new File(savename);
		
		//저장된 이미지 파일의 이진데이터 형식을 구함
		byte[] result = null; //1. data
		ResponseEntity<byte[]> entity = null;
		
		try {
			result = FileCopyUtils.copyToByteArray(file);
			
			//2. header
			HttpHeaders header = new HttpHeaders();
			header.add("Content-type", Files.probeContentType(file.toPath())); //파일의 컨텐츠타입을 직접 구해서 header에 저장
			
			//3. 응답본문
			entity = new ResponseEntity<>(result, header, HttpStatus.OK); //데이터, 헤더, 상태값
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return entity;
	}

 

 

  • produces ▶ 보내는 데이터의 타입 지정
  • 위에와 같이 파일이 저장된 경로의 이미지를 찾아서 File 객체 생성
  • 데이터를 담을 byte[] 배열을 만들어주고
  • ResponseEntity<byte[]> 클래스 만들어서 제네릭 타입으로 설정 ▶ HTTP 응답을 나타냄
  • try ~ catch문에서
    1. 파일 데이터 읽어서 바이트 배열형으로 리턴
    2. 파일의 경로를 읽어 파일의 MIME타입(변환된 타입)을 헤더에 저장
        Content-type:  Files.probeContentType(file.toPath())
        컨텐츠 타입: 이미지. String타입

    3. 응답 본문에 데이터, 헤더, 상태값 담기

RestFul API를 개발하기 위해 모두 지정(반환 데이터, HttpHeader, 상태코드)해서 반환해주려고 사용
ResponseEntity<> (바디에 담을 내용, 헤더에 담을 내용, 상태 매세지)

이미지 자체를 그림으로 받으려면 content-type을 정해줘야 이미지로 나오는데
그래서 header 부분에 content-type을 달아줘서 이미지로 바로 받을 수 있게 함
화면으로 보내는 데이터의 타입을 고정시켜줬다고 봄

특정 이미지 데이터 불러오기

상품에 대한 이미지 정보를 검색하여 클라이언트에게 전달하는 것
상품 정보와 해당 상품의 이미지들을 관리하고자 할 때 사용
상품 목록을 보여줄 때 각 상품에 대한 이미지도 함께 보여주기 위해 필요
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductUploadVO {

	private int upload_no;
	private String filename;
	private String filepath;
	private String uuid;
	private LocalDateTime regdate;
	
	private int prod_id;
	private String prod_writer;
}
// 특정 이미지 불러오기 - prod_id 값 받아서 이미지 정보를 반환 (함수의 모형을 선언)
	@PostMapping("/getProductImg")
	public ResponseEntity<List<ProductUploadVO>> getProductImg(@RequestBody ProductVO vo) {

		System.out.println("보자" + vo.toString()); // 확인
		// 1. data
		// List<ProductUploadVO> list = productService.getProductImg(vo);
		// ResponseEntity<List<ProductUploadVO>> entity = new ResponseEntity<>(list,
		// HttpStatus.OK); 아래와 같음

		return new ResponseEntity<>(productService.getProductImg(vo), HttpStatus.OK);
	}
	<!-- 이미지 데이터 조회 -->
  	<select id="getProductImg" resultType="ProductUploadVO">
  		select * from PRODUCT_UPLOAD where prod_id = #{prod_id}
  	</select>
  • prod_id 담아서 보내서 ProductUploadVO 객체에 데이터 담아서 ResponseEntity 객체 생성


모달창으로 이미지 띄우기

ajaxController에서 /getProductImg로 img 데이터를 조회 후
../display로 이미지 찾아서 띄우기

 

<!-- 1. modalOn 클래스가 들어가면 모달창 2. modalOn() 으로 켤 수 있음-->
	<!-- 모달창 제어 -->
	<script>
		//이미지 수정버튼을 클릭했을 때 modalOn();
		$(".modalOn").click(function(e) { //jquery 사용
			e.preventDefault(); //a링크의 고유이벤트 중지
			
			//ajax - 이미지데이터 조회 (prod_id 기준으로 조회)
			//1. 클릭한 대상의 prod_id값
			var prod_id = $(e.target).closest("td").next().html();
			//console.log(prod_id) 확인
			
			//2. post방식으로 img데이터 조회
			$.ajax({
				url: "../getProductImg",
				type: "post",
				data: JSON.stringify({prod_id: prod_id}), //제이슨 데이터
				contentType: "application/json", //보내는 데이터에 대한 타입
				success: function(result){
					//console.log(result); //반환된 데이터
					
					var str = "";
					var arr = ['a', 'b', 'c']; //배열 생성
					
					for(var i = 0; i < result.length; i++) {
						str += '<div class="left">';
						str += '<span>추가이미지</span>';
						str += '<label class="upload-display" for="' + arr[i] +'_file">';
						str += '<span class="upload-thumb-wrap">';
						str += '<img class="upload-thumb" src="' + '../display' + '/' + result[i].filepath + '/' + result[i].uuid + '/' + result[i].filename + '">'; /* 상대경로, 한단계 위로 올라가서 display... */
						str += '</span>';
						str += '</label>';
						str += '<input class="upload-name" value="파일선택" disabled="disabled">';
						str += '<input type="file" name="file" id="' + arr[i] + '_file" class="upload-hidden">';
						str += '<input type="hidden" value="">';
						str += '<input type="hidden" value="">';
						str += '<button type="button" class="normal_btn" style="display: block;">삭제</button>';
						
						/* 다운로드 기능 추가 */
						//1번 방법 - a 링크로 바꿔서 보내기
						//str += '<a href="'+ '../download' + '/' + result[i].filepath + '/' + result[i].uuid + '/' + result[i].filename +'" class="normal_btn" style="display: block;">다운로드</a>';
						
						//2번 방법 - js로 onclick 사용
						//str += '<button type="button" class="normal_btn" style="display: block;" onclick="location.href=`'+'../download/' + result[i].filepath + '/' + result[i].uuid + '/' + result[i].filename +'`">다운로드</button>';
						
						str += '<button type="button" class="normal_btn" style="display: block;">다운로드</button>';
						
						str += '</div>';
					}
					
					   $(".filebox").html(str);
		               
		               $(".filebox").on("click", "button", function(e) {
		            	  
		            	  var id = $(e.target).prev().prev().prev().prev().attr("id").replace('_file', '');
		            	  
		            	  var index = arr.indexOf(id);
		            	 
		                  e.preventDefault(); //고유이벤트 중지
		                  location.href="../download/" + result[index].filepath + "/" + result [index].uuid + "/" + result[index].filename;
		               })
					
				}, 
				error: function(err) {
					alert("이미지 조회에 실패했습니다.");
				}
			})
			
		});
	</script>

이미지 다운로드

  • 클릭시 비동기로 요청 보내는 형식
	/* 다운로드 기능 추가 */
	//1번 방법 - a 링크로 바꿔서 보내기
	//str += '<a href="'+ '../download' + '/' + result[i].filepath + '/' + result[i].uuid + '/' + result[i].filename +'" class="normal_btn" style="display: block;">다운로드</a>';
						
	//2번 방법 - js로 onclick 사용
	//str += '<button type="button" class="normal_btn" style="display: block;" onclick="location.href=`'+'../download/' + result[i].filepath + '/' + result[i].uuid + '/' + result[i].filename +'`">다운로드</button>';
						
	str += '<button type="button" class="normal_btn" style="display: block;">다운로드</button>';
  • 다운로드 버튼 만드는 방법 3가지
	//.filebox = 미리보기 폼
	$(".filebox").html(str);
	$(".filebox").on("click", "button", function(e) {  	  
        var id = $(e.target).prev().prev().prev().prev().attr("id").replace('_file', '');
        var index = arr.indexOf(id);
        e.preventDefault(); //고유이벤트 중지
        location.href="../download/" + result[index].filepath + "/" + result [index].uuid + "/" + result[index].filename;
	 })
  • 고유이벤트 중지하고 /download/...로 보내기
// 다운로드 기능
	@GetMapping("/download/{filepath}/{uuid}/{filename}")
	public ResponseEntity<byte[]> download(@PathVariable("filepath") String filepath,
										   @PathVariable("uuid") String uuid,
										   @PathVariable("filename") String filename) {
		// 파일이 저장된 경로
		String savename = uploadpath + "\\" + filepath + "\\" + uuid + "_" + filename;
		File file = new File(savename);

		// 저장된 이미지 파일의 이진데이터 형식을 구함
		byte[] result = null; // 1. data
		ResponseEntity<byte[]> entity = null;

		try {
			result = FileCopyUtils.copyToByteArray(file);

			// 2. header
			HttpHeaders header = new HttpHeaders();

			// 다운로드임을 명시 (파일 이름을 UTF-8로 URL 인코딩, 공백을 %20으로 대체)
			String encodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
			header.add("Content-Disposition", "attachment; filename=" + encodedFilename);
			//header.add("Content-Disposition", "attachment; filename=" + filename); // 떨어트려줄 이름 자체가 파일명

			// 3. 응답본문
			entity = new ResponseEntity<>(result, header, HttpStatus.OK); // 데이터, 헤더, 상태값
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return entity;
	}
  • 해당 경로의 파일의 바이트레이서를 읽어서 응답객체에 (파일 데이터, 헤더 정보, 상태 코드) 저장
  • 파일 이미지 불러오기(심화)에서 한 것처럼 헤더에 포함
  • 한글 이름일 경우 UTF-8로 인코딩 해야함
응답 헤더에 아래를 반드시 담아줘야 함
Content-Disposition, attachment; filename=filename.csv
Content-Disposition, attachment; - 브라우저에게 어떻게 처리할지 알려주는 중요한 정보, attachment인 경우 다운로드 됨
filename=파일명.확장자 - 해당 파일의 확장자가 반드시 포함되어야 함

오늘 하루

더보기

기억에 남는 부분

 

어려운 부분

 

문제 해결 부분

 

728x90
profile

원지의 개발

@원지다

250x250