일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Merge sort
- 파이썬
- 파이썬 알고리즘
- 코딩
- 해시
- 강화학습
- BOJ
- HTTP
- 지도학습
- 파이썬 오류
- 머신러닝
- 코딩테스트
- 스택과 힙
- 오버라이딩
- 코테
- 딥러닝
- 깊이우선탐색
- 멱등
- 캐싱
- 알고리즘
- 이진탐색
- bineary search
- 자바
- 비지도학습
- post
- rest api
- 프로그래머스
- 백준
- 딕셔너리
- 너비우선탐색
- Today
- Total
chae._.chae
(6) 프로필 페이지 - 이미지 업로드 및 예외처리 본문
프로필 페이지에 들어갔을때 사진을 등록해 업로드하고, 업로드한 사진이 하단에 뜨게끔 하는 기능을 구현해보자.
- Image를 서버와 db에 업로드
- 예외처리
위와 같은 순서로 진행
먼저, Image엔티티를 생성해준다.
< Image >
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity
public class Image {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String caption; // 이미지 설명.
private String postImageUrl; // 경로. 사진을 전송받아서 그 사진을 서버의 특정폴더에 저장 -> DB에는 저장된 경로를 insert해준다.
@JoinColumn(name = "userId") // "userId"의 컬럼명으로 저장되도록 지정해준다
@ManyToOne
private User user; // 1명의 유저가 이미지 여러개 올릴 수 있음
// 이미지 좋아요
// 댓글
private LocalDateTime createDate;
@PrePersist
public void createDate(){
this.createDate = LocalDateTime.now();
}
}
사용자가 컴퓨터의 사진을 선택해 업로드 버튼을 누르면 이 사진을 받아와서 저장하는 방식이다.
이미지를 받아와서 저장할 때에는,
사진을 전송받아서 그 사진을 서버의 특정 폴더에 저장하고,
db에는 사진이 저장된 경로가 저장된다.
(사진 자체의 주소가 저장되는 것이 X)
< ImageController >
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto, @AuthenticationPrincipal PrincipalDetails principalDetails){
// 서비스 호출
imageService.사진업로드(imageUploadDto, principalDetails);
return "redirect:/user/" + principalDetails.getUser().getId(); // "/user/유저인덱스번호"로 돌아가게끔
}
사진업로드가 정상적으로 수행되면, http://localhost:8080/user/{user_id}로 해당 유저의 프로필 페이지로 리다이렉트 되게 설정해준다.
< ImageUploadDto >
@Data
public class ImageUploadDto {
private MultipartFile file;
private String caption;
public Image toEntity(String postImageUrl, User user){
return Image.builder()
.caption(caption)
.postImageUrl(postImageUrl)
.user(user)
.build();
}
}
파일을 다룰때에는 MultipartFile 타입을 사용한다.
< ImageService >
@RequiredArgsConstructor
@Service
public class ImageService {
private final ImageRepository imageRepository;
@Value("${file.path}") // application.yml에서 file-path에 있는 경로를 가져온다
private String uploadFolder; // yml에 적은 값도 가져올 수 있다!
// private String uploadFolder = "C:/workspace/springbootwork/upload/"; // 경로뒤에 / 반드시 있어야함
@Transactional
public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails){
UUID uuid = UUID.randomUUID(); // uuid(Universally Unique IDentifier) : 네트워크상에서 고유성이 보장되는 id를 만들기 위해 사용한다. 중복이 되지 않게 나온다.
String imageFileName = uuid + "_" + imageUploadDto.getFile().getOriginalFilename(); // 실제 파일 이름이 들어간다 ex) bag.jpg
// 서버의 upload라는 폴더에 bag.jpg가 저장되고, db에는 "/upload/bag.jpg"라는 경로가 저장된다.
// 따라서 동일한 파일명이 저장되면 덮어씌워지기에 우리가 사진을 받아서 구분해줘야 한다. -> 구분에 uuid사용
System.out.println("이미지 파일 이름: " + imageFileName);
Path imageFilePath = Paths.get(uploadFolder + imageFileName); // 실제 저장되는 경로를 지정(경로 + 파일명)
// 통신이 일어나거나 I/O가(하드디스크에 기록하거나 읽을때) 일어날때 -> 예외가 발생
// 어떤 파일을 읽어오고 싶은데 하드디스크에 해당 파일이 없는 경우 -> 컴파일시에 잡아낼 수가 없어 런타임시에만(실제 실행될때) 잡아낼 수 있음 -> 예외처리 해줘야한다
try{
Files.write(imageFilePath, imageUploadDto.getFile().getBytes()); // 경로, 실제 이미지 파일(바이트화해서)
} catch (Exception e){
e.printStackTrace();
}
// image테이블에 저장 (imageUploadDto로 image객체로 변환하는 과정이 필요)
Image image = imageUploadDto.toEntity(imageFileName, principalDetails.getUser()); // 13b00cd8-e623-47c5-be31-0a9c5acb6aef_cat.jpg가 db에 저장된다
Image imageEntity = imageRepository.save(image);
System.out.println(imageEntity);
}
}
- @Value어노테이션을 이용하면 application.yml에 있는 아래의 값을 읽어올 수 있다.
file:
path: C:/workspace/springbootwork/upload/
- 위의 값을 가져오고 싶으면, @Value("${file.path}")로 어노테이션을 달아준다.
@Value("${file.path}") // application.yml에서 file-path에 있는 경로를 가져온다
private String uploadFolder; // yml에 적은 값도 가져올 수 있다!
String imageFileName = uuid + "_" + imageUploadDto.getFile().getOriginalFilename();
사진 파일 이름은 imageUploadDto.getFile().getOriginalFilename()으로 실제 파일이름이 들어간다.
실제 파일 이름만으로 저장하면 동일한 파일명이 저장되면 기존의 사진이 사라지고 덮어씌워지게 된다.
이를 사용자가 매번 다르게 설정해주기에는 사용자가 서버에 모든 사진의 파일이름을 알 수 없으므로, UUID를 사용해준다.
UUID(Universally Unique IDentifier) : 네트워크 상에서 고유서잉 보장되는 id를 만들기 위해 사용한다. 중복이 되지 않게 값이 나오기에 unique하다.
따라서, 파일을 저장할때 아래와 같이 uuid값 + 실제파일이름을 함께 저장해주어 이름이 겹치는 일이 없도록 막아준다.
String imageFileName = uuid + "_" + imageUploadDto.getFile().getOriginalFilename();
설정해준 경로 uploadFolder에 위에서 만들어준 imageFileName으로 저장해준다.
Path imageFilePath = Paths.get(uploadFolder + imageFileName); // 실제 저장되는 경로를 지정(경로 + 파일명)
실제 프로젝트를 실행시키고 서버에 접속해서 파일을 업로들하면 사진이 해당 경로에 저장되는 것을 확인할 수 있다.!!
사진 파일이 내가 application.yml에서 file.path에서 설정해준 경로로 들어간다.
C:/workspace/springbootwork/upload/
파일이름은 "uuid + _ + 기존에 내가 설정해둔 파일 이름"의 형태로 저장된다.
예외처리
이미지가 들어오지 않고, caption(이미지 설명)만 들어온 경우에 대해 예외처리를 해주자.
컨트롤러에서 이미지파일이 들어왔는지 확인하고, 들어오지 않았다면 예외를 던저주는 흐름으로 진행한다.
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto, @AuthenticationPrincipal PrincipalDetails principalDetails){
if (imageUploadDto.getFile().isEmpty()){
throw new CustomValidationException("이미지가 첨부되지 않았습니다.", null);
}
// 서비스 호출
imageService.사진업로드(imageUploadDto, principalDetails);
return "redirect:/user/" + principalDetails.getUser().getId(); // "/user/유저인덱스번호"로 돌아가게끔
}
}
Dto에서 getFile()을 통해 파일이 있는지를 확인하고, 파일이 없다면 CustomValidationException을 던져준다.
< CustionValidationException >
public class CustomValidationException extends RuntimeException{
private static final long serialVersionUID = 1L;
private Map<String, String> errorMap;
public CustomValidationException(String message, Map<String, String> errorMap) {
super(message);
this.errorMap = errorMap;
}
public Map<String, String> getErrorMap(){
return errorMap;
}
}
< ControllerExceptionHandler >
@RestController // 데이터로 리턴한다
@ControllerAdvice // exception이 발생할때 모두 낚아챈다
public class ControllerExceptionHandler {
// js를 리턴
@ExceptionHandler(CustomValidationException.class) // RuntimeException이 발생하면 이 함수가 가로채서 실행된다
public String validationException(CustomValidationException e){
// CMRespDto, Script 비교
// 1. 클라이언트에게 응답할때는 Script가 좋음
// 2. Ajax 통신 - CMRespDto
// 3. Android 통신 - CMRespDto
if (e.getErrorMap() == null){
return Script.back(e.getMessage());
}else {
return Script.back(e.getErrorMap().toString());
}
}
// 데이터를 리턴(ajax로 통신시)
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validationApiException(CustomValidationApiException e){
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_GATEWAY);
}
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<?> apiException(CustomApiException e){
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), null), HttpStatus.BAD_REQUEST);
}
}
ControllerExceptionHandler 는 예외가 터졌을 때, 그 예외를 모두 잡아주는 역할을 한다.
@ExceptionHandler는 Controller계층에서 발생하는 에러를 잡아 메서드로 처리해주는 어노테이션(예외처리를 전역적으로 핸들링하는 역할) 이다. Service, Repository에서 발생한 에러는 잡아주지X
@ExceptionHandler의 value 값으로 어떤 Exception을 처리할 것인지 넘겨줄 수 있는데,value를 설정하지 않으면 모든 Exception을 잡게 되기 때문에 Exception을 구체적으로 적어줘야 한다.
CustionValidationException 예외가 터지면, @ExceptionHandler에 따라 validationException이 실행되어 정상적으로 예외처리가 진행된다.
'스프링 > 인스타그램 클론코딩' 카테고리의 다른 글
(8) 프로필 페이지 - 사용자에 따라 페이지 다르게 보여주기 + 구독자수, 구독 여부 나타내기 (0) | 2022.07.19 |
---|---|
(7) 프로필 페이지 - 이미지 보여주기 (0) | 2022.07.19 |
(3) 로그인하기 - UserDetailsService (0) | 2022.07.18 |
(2) 시큐리티 세팅 및 회원가입 구현 (+예외처리) (0) | 2022.07.13 |
(1) 스프링부트 Controller 기본 동작 방식 (0) | 2022.07.07 |