티스토리 뷰
728x90
파일 업로드 처리
- 웹 개발에서 사용자가 가잔 파일을 서버에 업로드하고 이를 처리하는 기능은 필수적으로 필요한 기능
- 파일 업로드는 기능 자체로도 신경 써야 하는 일이 많지만, JPA를 이용해서 처리할 때도 생각보다 많은
주의가 필요 - 이번 장은 @ManyToOne이 아닌 @OneToMany를 이용해 특정 게시물에 속한 파일들의 정보를 처리한다
7.1 첨부파일과 @OneToMany
파일 업로드를 위한 설정
- 서블릿 3이상 되면서 서블릿 API 자체에 파일 업로드를 처리할 수 있는 API를 제공하므로 추가적인 라이브러리가 필요하지 않다
- application.properties 파일에 설정 추가
# 파일 업로드 기능을 위한 설정
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=C:\\upload
spring.servlet.multipart.max-request-size=100MB
spring.servlet.multipart.max-file-size=100MB
com.board.upload.path=C:\\upload
업로드 처리를 위한 DTO
- 파일 업로드는 MultipartFile 이라는 API를 이용해서 처리
- 컨트롤러에서 파라미터를 MultipartFile로 지정해주면 간단한 파일 업로드 처리는 가능하지만 SwaggerUI와 같은 프레임워크로 테스트하기 불편하기 때문에 dto 패키지에 별도의 DTO로 선언해서 사용하는 것이 좋다.
- dto 패키지에 upload 하위 패키지를 생성하고 UploadFileDTO를 선언
@Data
public class UploadFileDto {
private List<MultipartFile> files;
}
컨트롤러와 Swagger UI 테스트
- 실제 파일 업로드 설정은 controller 패키지에 UpDown-Controller를 작성해서 처리
- UpDownController는 파일 업로드와 파일을 보여주는 기능을 메소드로 처리
- @RestController로 설정하고 파일의 업로드를 처리하기 위해 upload() 작성
- controller -> UpDownController.class
@Log4j2
@RestController
public class UpDownController {
@ApiOperation(value = "Upload POST", notes = "POST 방식으로 파일 등록")
@PostMapping(value ="/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String upload(UploadFileDto uploadFileDto){
log.info(uploadFileDto);
return null;
}
}
- 실제 파일을 처리할 때는 파일의 저장 경로가 필요하므로 application.properties의 설정 정보는 @Value를 이용해 처리
@Value("${com.board.upload.path}") // import 시에 springframwork로 시작하는 value
private String uploadPath;
// 파일이 올라오면
if(uploadFileDto.getFiles() != null){
uploadFileDto.getFiles().forEach(multipartFile -> { //반복
log.info(multipartFile.getOriginalFilename());
}); // end each
} //end if
- @Value는 application.properties 파일의 설정 정보를 읽어서 변수의 값으로 사용 가능
- uploadPath는 파일을 업로드 하는 경로로 사용
첨부파일 저장
- 같은 이름의 파일이면 문제가 생기는데 이 문제를 해결하고자 가장 많이 사용하는 방법은 java.util.UUID를 이용해 새로운 값을 만들어 내는 방법 (UUID는 중복될 가능성이 거의 없는 코드 값을 생성)
- UUID(16자리) + _ + 원래 파일명
//// 첨부 파일 저장 - 중복 가능성이 거의 없는 코드 값을 생성하는 UUID
String uuid = UUID.randomUUID().toString();
Path savePath = Paths.get(uploadPath, uuid + "_" + originalName);
try{
multipartFile.transferTo(savePath); // 실제 파일 저장
} catch (IOException e){
e.printStackTrace();
}
- MultipartFile의 transfer()를 이용 하면 간단히 파일 업로드 완료
- transfer()은 예외처리 필수
- properties에 업로드 경로를 c:\upload 했을 때 오류 발생
- c:/ 로 하니 정상 작동
Thumbnail 파일 처리
- 첨부파일이 이미지일 때는 용량을 줄여 작은 이미지 (이하 썸네일)을 생성하고 이를 나중에 사용하도록 구성
- Thumbnailator 라이브러리를 이용
- 썸네일 라이브러리 추가
implementation group: 'net.coobird', name: 'thumbnailator', version: '0.4.17'
- 썸네일 이미지는 업로드하는 파일이 이미지일 때만 처리하도록 구성
- 파일 이름 맨 앞에 's_'로 시작하도록 구성
- upload() 메서드 수정해 섬네일 생성하도록 구성
// 이미지 라면
try{
if(Files.probeContentType(savePath).startsWith("image")){
File thumbFile = new File(uploadPath, "s_" + uuid + "_" + originalName);
Thumbnailator.createThumbnail(savePath.toFile(), thumbFile, 200, 200);
}
}
// 의존성 설정 시 썸네일 최신 버전 아니면 오류가 발생한다. 이유는 모르겠다..
//썸네일
// https://mvnrepository.com/artifact/net.coobird/thumbnailator
implementation group: 'net.coobird', name: 'thumbnailator', version: '0.4.20'
업로드 결과의 반환처리
- 여러 개의 파일이 업로드 되면 업로드 결과도 여러 개 발생 하게 되고 여러 정보를 반환해야 하므로
별도의 DTO를 구성 - dto 의 upload 패키지에 UploadResultDto 클래스 추가
- UploadResultDto는 업로드된 파일의 UUID 값과 파일 이름 (fileName), 이미지 여부를 객체로 구성하고 getLink()를
통해 첨부파일의 경로 처리에 사용
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UploadResultDto {
private String uuid;
private String fileName;
private boolean img;
public String getLink(){
if(img){
return "s_" + uuid + "_" + fileName; // 이미지인 경우 섬네일
} else{
return uuid + "_" + fileName;
}
}
}
- UploadResultDto의 getLink()는 나중에 JSON으로 처리될 때는 link라는 속성으로 자동 처리
- UpDownController의 upload() List<UploadResultDto>를 반환하도록 수정
@ApiOperation(value = "Upload POST", notes = "POST 방식으로 파일 등록")
@PostMapping(value ="/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<UploadResultDto> upload(UploadFileDto uploadFileDto){
log.info(uploadFileDto);
// 파일이 올라오면
if(uploadFileDto.getFiles() != null){
// 업로드 결과의 반환처리
// List<UploadResultDto>를 반환하도록 수정
final List<UploadResultDto> uploadResultDtoList = new ArrayList<>();
uploadFileDto.getFiles().forEach(multipartFile -> { //반복
String originalName = multipartFile.getOriginalFilename();
log.info(originalName);
//// 첨부 파일 저장 - 중복 가능성이 거의 없는 코드 값을 생성하는 UUID
String uuid = UUID.randomUUID().toString();
Path savePath = Paths.get(uploadPath, uuid + "_" + originalName);
// 업로드 결과의 반환 처리때 추가
boolean image = false;
try{
multipartFile.transferTo(savePath); // 실제 파일 저장
// 이미지 파일의 종류라면
if(Files.probeContentType(savePath).startsWith("image")){
// 업로드 결과의 반환 처리
image = true;
File thumbFile = new File(uploadPath, "s_" + uuid + "_" + originalName);
Thumbnailator.createThumbnail(savePath.toFile(), thumbFile, 200, 200);
}
} catch (IOException e){
e.printStackTrace();
}
// 업로드 결과의 반환 처리
uploadResultDtoList.add(UploadResultDto.builder().uuid(uuid)
.fileName(originalName)
.img(image).build()
);
}); // end each
return uploadResultDtoList;
} //end if
return null;
}
}
- 리스트를 쓰기 위해 반환타입을 제네릭으로 바꿔준다. public String -> public List<UploadResultDto>로 바꿔 준다.
- 반환이 JSON 데이터가 생성 되고, getLink() 대신 link라는 속성이 생긴 것을 확인 할 수 있다.
[
{
"uuid": "31c30880-a776-4d89-a9bb-c9ba8c7e9f6a",
"fileName": "dd.png",
"img": true,
"link": "s_31c30880-a776-4d89-a9bb-c9ba8c7e9f6a_dd.png"
},
{
"uuid": "72701ddf-dc9a-4451-a035-2cd10124d799",
"fileName": "mes.pdf",
"img": false,
"link": "72701ddf-dc9a-4451-a035-2cd10124d799_mes.pdf"
}
]
첨부파일 조회
- 첨부파일 조회는 가능하면 GET 방식으로 바로 가능하도록 설정
- 첨부파일은 나중에 보안 문제가 발생하므로 코드를 통해서 접근 여부를 허용하도록 컨트롤러를 이용하는 것이 좋다
- 첨부파일을 조회할 때는 '/view/파일이름' 으로 동작하도록 UpDownController에 작성
- 조회테스트를 간단히 하기 위해 upload 폴더에 aaa.jpg 파일 하나 추가
- UpDownController에 vieFileGET() 메소드 추가
// 첨부파일 조회
@ApiOperation(value = "view 파일", notes = "GET 방식으로 첨부파일 조회")
@GetMapping("/view/{fileName}")
public ResponseEntity<Resource> viewFileGet(@PathVariable String fileName){
Resource resource = new FileSystemResource(uploadPath + File.separator + fileName);
String resourceName = resource.getFilename();
HttpHeaders headers = new HttpHeaders();
try{
headers.add("Content-Type", Files.probeContentType( resource.getFile().toPath()));
} catch (Exception e){
return ResponseEntity.internalServerError().build();
}
return ResponseEntity.ok().headers(headers).body(resource);
}
첨부파일 삭제
- 조회와 비슷한 DELETE 방식의 호출하는 형태로 첨부파일 삭제 구현
- 첨부파일 삭제 할 때 이미지 라면 섬네일이 존재할 수도 있으므로 같이 삭제하도록 구현
- UpDownController에는 removeFile() 메서드 작성
// 첨부파일 삭제
@ApiOperation(value = "remove 파일", notes = "DELETE 방식으로 파일 삭제 ")
@DeleteMapping("/remove/{fileName}")
public Map<String, Boolean> removeFile(@PathVariable String fileName){
Resource resource = new FileSystemResource(uploadPath + File.separator + fileName);
String resourceName = resource.getFilename();
Map<String, Boolean> resultMap = new HashMap<>();
boolean removed = false;
try{
String contentType = Files.probeContentType(resource.getFile().toPath());
removed = resource.getFile().delete();
// 이미지 파일이면 ( 썸네일이 존재하면 )
if (contentType.startsWith("image")){
File thumbnailFile = new File(uploadPath + File.separator + "s_" + fileName);
thumbnailFile.delete();
}
log.info("삭제된 파일 이름은 : " + resourceName);
} catch (Exception e){
log.error(e.getMessage());
e.printStackTrace();
}
resultMap.put("result", removed);
return resultMap;
}
- 프로젝트를 개발할 때 파일 업로드는 필수로 필요한 기능이고, 여러 곳에서 사용할 수 있어야 한다.
728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 셀렉트박스
- reactApp
- 씹어먹는 C 언어
- JPA
- 받아오기
- react 시작 오류
- React
- optional
- ID
- 다중체크박스 처리
- commit 에러
- @reqeustBody
- App
- 다른데서 react
- 서버전송
- 셀프로젝트
- CheckBox
- @RequestParam
- mircrosoft visual studio
- selectbox
- @Builder
- C
- C언어
- reactStart
- 체크박스
- SCP
- 아이디
- th:selected
- 제약조건
- findFirstBy
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
글 보관함