728x90
반응형
1. 추가한 디렉토리
- Cotroller
- UpDownController
- DTO
- UploadResultDTO
- UploadFileDTO
- Domain
- BoardImage
2. 추가한 설정
- application.properties 파일
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=C:\\upload
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=30MB
org.zerock.upload.path=C:\\upload
- 업로드관련 설정 추가
- build.gradle dependencies 추가
implementation 'net.coobird:thumbnailator:0.4.16'
- 썸네일 사용을 위해 모듈 추가
3. 추가한 코드
@Data
public class UploadFileDTO {
private List<MultipartFile> files;
}
@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; //이미지파일이면 앞에 s_를 붙인다.
}else{
return uuid+"_"+fileName;
}
}
}
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Board extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
@Column(length = 500, nullable = false)
private String title;
@Column(length = 2000, nullable = false)
private String content;
@Column(length = 50, nullable = false)
private String writer;
@OneToMany(mappedBy = "board", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY, orphanRemoval = true)
@Builder.Default
@BatchSize(size=20)
private Set<BoardImage> imageset = new HashSet<>();
public void change(String title, String content){
this.title = title;
this.content = content;
}
public void addImage(String uuid, String fileName){
BoardImage boardImage = BoardImage.builder()
.uuid(uuid)
.fileName(fileName)
.board(this)
.ord(imageset.size())
.build();
imageset.add(boardImage);
}
public void clearImage(){
imageset.forEach(boardImage -> boardImage.changeBoard(null));
this.imageset.clear();
}
}
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "board")
public class BoardImage implements Comparable<BoardImage> { //BoardImage를 비교하기 위해 인터페이스 구현
@Id
private String uuid;
private String fileName;
private int ord; //파일 업로드 순서
@ManyToOne
private Board board;
@Override
public int compareTo(BoardImage other){
return this.ord - other.ord;
}
public void changeBoard(Board board){
this.board = board;
}
}
@RestController
@Log4j2
public class UpDownController {
// application.properties에 설정된 업로드 경로를 주입받습니다.
@Value("${org.zerock.upload.path}")
private String uploadPath;
// POST 방식의 /upload 요청을 처리하며, multipart/form-data 형식의 데이터를 받습니다.
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) //consume : 데이터 바인딩, 필터링 역할을 한다. 파일 입출력으로 매개변수 타입을 검증한다. 아니면 415에러 발생.
public List<UploadResultDTO> upload(UploadFileDTO uploadFileDTO) {
// 업로드된 파일이 존재하는지 확인합니다.
if (uploadFileDTO.getFiles() != null) {
// 업로드 결과를 저장할 리스트를 생성합니다. (final 키워드는 재할당 방지)
final List<UploadResultDTO> list = new ArrayList<>();
// 각 파일을 처리하는 로직을 수행합니다.
uploadFileDTO.getFiles().forEach(multipartFile -> {
// 파일의 원본 이름을 가져옵니다.
String originalName = multipartFile.getOriginalFilename();
log.info("@@originalName: " + originalName);
// UUID를 생성하여 파일 이름 중복을 방지합니다.
String uuid = UUID.randomUUID().toString();
// 저장 경로를 생성합니다.
Path savePath = Paths.get(uploadPath, uuid + "_" + originalName);
// 이미지 파일 여부를 판단하기 위한 변수입니다.
boolean image = false;
try {
// 파일을 저장합니다.
multipartFile.transferTo(savePath);
// 파일의 MIME 타입을 확인하여 이미지 파일인지 판별합니다.
if (Files.probeContentType(savePath).startsWith("image")) {
image = true;
// 썸네일 파일 경로를 생성합니다.
File thumbFile = new File(uploadPath, "s_" + uuid + "_" + originalName);
// 썸네일을 생성합니다. (가로, 세로 100px)
Thumbnailator.createThumbnail(savePath.toFile(), thumbFile, 100, 100);
}
} catch (IOException e) {
// 예외 발생 시 로그를 출력합니다.
e.printStackTrace();
}
// 업로드 결과 정보를 DTO에 담아 리스트에 추가합니다.
list.add(UploadResultDTO.builder()
.uuid(uuid)
.fileName(originalName)
.img(image).build());
});
// 업로드 결과 리스트를 반환합니다.
return list;
}
// 업로드된 파일이 없으면 null을 반환합니다.
return null;
}
@GetMapping("/view/{fileName}")
public ResponseEntity<Resource> viewFileGET(@PathVariable String fileName) {
// 1. 파일 경로 생성:
// - 업로드 경로 (uploadPath)와 파일 이름 (fileName)을 결합하여 파일의 전체 경로를 생성합니다.
// - File.separator를 사용하여 운영체제에 맞는 파일 경로 구분자를 자동으로 적용합니다.
Resource resource = new FileSystemResource(uploadPath + File.separator + fileName);
// 2. 파일 이름 추출:
// - 리소스 객체에서 파일 이름을 가져옵니다.
String resourceName = resource.getFilename();
// 3. HTTP 응답 헤더 설정:
// - HttpHeaders 객체를 생성하여 응답 헤더를 설정합니다.
HttpHeaders headers = new HttpHeaders();
try {
// 4. Content-Type 헤더 설정:
// - Files.probeContentType() 메서드를 사용하여 파일의 MIME 타입을 확인하고,
// Content-Type 헤더에 해당 MIME 타입을 추가합니다.
// (예: 이미지 파일이면 "image/jpeg", 텍스트 파일이면 "text/plain" 등)
headers.add("Content-Type", Files.probeContentType(resource.getFile().toPath()));
} catch (Exception e) {
// 5. 예외 처리:
// - MIME 타입 확인 과정에서 예외가 발생하면, 500 Internal Server Error 응답을 반환합니다.
return ResponseEntity.internalServerError().build();
}
// 6. 파일 데이터 응답:
// - ResponseEntity를 생성하여 200 OK 상태 코드, 설정된 헤더, 파일 리소스를 포함하여 반환합니다.
// - 이를 통해 클라이언트는 웹 브라우저에서 이미지를 보거나,
// 텍스트 파일 내용을 확인하는 등 파일을 직접 열어볼 수 있습니다.
return ResponseEntity.ok().headers(headers).body(resource);
}
@DeleteMapping("/remove/{fileName}")
public Map<String, Boolean> removeFile(@PathVariable String fileName) {
// 1. 파일 경로 생성:
// - 업로드 경로 (uploadPath)와 파일 이름 (fileName)을 결합하여 파일의 전체 경로를 생성합니다.
// - File.separator를 사용하여 운영체제에 맞는 파일 경로 구분자를 자동으로 적용합니다.
Resource resource = new FileSystemResource(uploadPath + File.separator + fileName);
// 2. 삭제 결과 저장할 Map 생성:
// - 파일 삭제 성공 여부를 저장할 Map을 생성합니다. 키는 "result", 값은 boolean 타입입니다.
Map<String, Boolean> resultMap = new HashMap<>();
// 3. 삭제 성공 여부 초기값 설정:
// - 파일 삭제 성공 여부를 나타내는 변수를 false로 초기화합니다.
boolean removed = false;
try {
// 4. 파일 MIME 타입 확인:
// - Files.probeContentType() 메서드를 사용하여 파일의 MIME 타입을 확인합니다.
String contentType = Files.probeContentType(resource.getFile().toPath());
// 5. 파일 삭제:
// - resource.getFile().delete() 메서드를 사용하여 파일을 삭제하고, 결과를 removed 변수에 저장합니다.
removed = resource.getFile().delete();
// 6. 이미지 파일인 경우 썸네일 삭제:
// - 삭제된 파일이 이미지 파일인 경우 (MIME 타입이 "image"로 시작하는 경우),
// 해당 파일의 썸네일 파일도 함께 삭제합니다.
if (contentType.startsWith("image")) {
File thumbFile = new File(uploadPath + File.separator + "s_" + fileName);
thumbFile.delete();
}
} catch (Exception e) {
// 7. 예외 처리:
// - 파일 삭제 과정에서 예외가 발생하면, 에러 메시지를 로그에 출력합니다.
log.error(e.getMessage());
}
// 8. 삭제 결과 저장:
// - resultMap에 "result" 키와 함께 파일 삭제 성공 여부 (removed)를 저장합니다.
resultMap.put("result", removed);
// 9. 결과 반환:
// - 파일 삭제 성공 여부를 담은 Map 객체를 반환합니다.
return resultMap;
}
}
4. 잡담
- 파일 업로드를 하는 모든 코드를 오늘 배우진 못 했다. 생각보다 설정할 부분도 많고, DB 설계도 새로해야되서 시간이 오래 걸렸다.
'Coding 공부 > IntelliJ' 카테고리의 다른 글
[SpringBoot] Entity와 DTO를 ModelMapper로 쉽게 변환하기 (0) | 2024.06.17 |
---|---|
[SpringBoot_JPA] 엔티티 어노테이션 @OneToMany, @Builder.Default, @BatchSize (0) | 2024.06.13 |
[IntelliJ] 스프링부트 파일 업로트 설정, Multipart (0) | 2024.06.11 |
[IntelliJ_SpringBoot] 스프링 부트에서 세션을 사용한 로그인 정보 저장 (10단계 + 예제 코드) (0) | 2024.05.31 |
[IntelliJ_SpringBoot] consumes, HTML <form>태그 기본 속성 (0) | 2024.05.31 |
댓글