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
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
- 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
'Server > Spring boot' 카테고리의 다른 글
[Spring Boot] BootMyweb (5) - 자바측 API, Gson, 카카오로그인 (0) | 2023.02.23 |
---|---|
[Spring Boot] BootMyweb (4) - 세션, redirect, interceptor (0) | 2023.02.22 |
[Spring Boot] RestAPI, 부메랑, @RestController, @RequestBody, CrossOrigin (0) | 2023.02.16 |
[Spring Boot] BootMyweb (1) - 관리자 홈페이지 / 기본 연결, 검색&Paging (0) | 2023.02.15 |
[Spring Boot] Valiadation(유효성 검사), MyBatis (0) | 2023.02.14 |