티스토리 뷰
이번에는 게시글에 이미지를 넣어보도록 해보자
이미지 및 저장할 파일들은 서버내부에서 관리하면 용량이 끝도없이 커지고 관리하기도 힘들기에 서버와는 별도로 외부에 저장소를 따로 두고 관리한다. AWS 배포시 S3가 이와 같은 역할을 한다.
게시판 앱에서 사용자는 파일을 업로드하고 이미지API를 호출하여 업로드된 이미지를 볼 수 있게끔 해보자
원하는 사항은 이와 같다.
- 사용자가 이미지를 동시에 여러개 서버로 전송할 수 있음
- 외부 저장소에 저장할 때는 이미지 이름의 중복을 방지하기 위해 UUID+확장명으로 파일 이름을 변경
- 게시글DB에는 변경된 파일이름을 리스트로 저장
- 이미지 호출 API를 제작
- 게시글을 변경하면 기존 저장된 파일들을 삭제
application.yml에 추가
file:
dir: E:/savefile/ #경로
저장로직
@Component
public class FileStore {
@Value("${file.dir}")
private String fileDir; //설정해둔 경로
public String storeFile(MultipartFile multipartFile) throws IOException {
if (!multipartFile.isEmpty()) { //파일이 존재한다면
//원본파일 이름을 가져와
String originalFilename = multipartFile.getOriginalFilename();
String uuid = UUID.randomUUID().toString();
String ext = extracted(originalFilename);
//UUID와 확장명을 합친다
String storeFileName = uuid + "." + ext;
//설정해둔 저장소에 저장
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return storeFileName;
}
throw new MultipartException("multipartFile is Null");
}
public String getFullPath(String filename) {
return fileDir + filename;
}
private String extracted(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
}
Controller
@RestController
@RequiredArgsConstructor
public class ItemController {
private final FileStore fileStore;
private final MainTextRepository mainTextRepository;
@Value("${file.dir}")
private String fileDir;
//-- 게시글 첨부파일 받기 --
@PostMapping("/items/new/{mainTextId}")
public void saveImage(@RequestParam("file") List<MultipartFile> form, @PathVariable("mainTextId") Long id) throws IOException {
List<String> files = new ArrayList<>();
for (MultipartFile multipartFile : form) {
files.add(fileStore.storeFile(multipartFile));//저장된 이름을 리스트에 추가
//System.out.println("multipartFile = " + multipartFile.getOriginalFilename());
}
//게시글 id로 해당 게시글을 찾는다
Optional<MainText> mainText = mainTextRepository.findById(id);
//리스트를 추가
mainText.orElseThrow().setImage(files.toString());
//저장
mainTextRepository.save(mainText.get());
}
//-- 이미지 호출 URI --
@GetMapping("/items/get/{imageName}")
public ResponseEntity<Resource> getImage(@PathVariable("imageName") String imgName) {
Resource resource = new FileSystemResource(fileDir + imgName);
// 로컬 서버에 저장된 이미지 파일이 없을 경우
if (!resource.exists()) {
return new ResponseEntity<Resource>(HttpStatus.NOT_FOUND); // 리턴 결과 반환 404
}
// 로컬 서버에 저장된 이미지가 있는 경우 로직 처리
HttpHeaders header = new HttpHeaders();
Path filePath = null;
try {
filePath = Paths.get(fileDir + imgName);
// 인풋으로 들어온 파일명 .png / .jpg 에 맞게 헤더 타입 설정
header.add("Content-Type", Files.probeContentType(filePath));
} catch (Exception e) {
e.printStackTrace();
}
// 이미지 리턴 실시 [브라우저에서 get 주소 확인 가능]
return new ResponseEntity<Resource>(resource, header, HttpStatus.OK);
}
}
PostMan으로 file을 두개 넘겨보면
데이터 베이스에 잘 들어간 것을 확인할 수 있고
설정해둔 경로에 DB에 있는 데이터와 같은이름으로 파일이 저장된 것을 확인 할 수 있다.
이미지를 불러와보면 잘 응답된다.
응답 헤더타입도 파일 확장명에 맞게 설정되었다.
이미지뿐만 아니라 gif, mp4등 다른 파일들도 가능하다.
- 수정 및 삭제
FileStore에 추가
@Component
public class FileStore {
@Value("${file.dir}")
private String fileDir; //설정해둔 경로
public String storeFile(MultipartFile multipartFile) throws IOException {
if (!multipartFile.isEmpty()) { //파일이 존재한다면
//원본파일 이름을 가져와
String originalFilename = multipartFile.getOriginalFilename();
String uuid = UUID.randomUUID().toString();
String ext = extracted(originalFilename);
//UUID와 확장명을 합친다
String storeFileName = uuid + "." + ext;
//설정해둔 저장소에 저장
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return storeFileName;//변경된 이름을 반환
}
throw new MultipartException("multipartFile is Null");
}
public String getFullPath(String filename) {
return fileDir + filename;
}
private String extracted(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
// ---- 삭제 로직 --- ///
public void deleteFile(String image) {
if(image != null){
// DB 에 있는 IMAGE Column [] 괄호 제거
String substring = image.substring(1, image.length() - 1);
//각 파일 이름을 분리
String[] filenames = substring.split(", ");
for (String filename : filenames) {
System.out.println("filename = " + filename);
//분리한 이름을 가지고 새로운 파일객체 생성
File file = new File(fileDir + filename);
//저장된 파일 지우기
boolean delete = file.delete();
}
}
}
}
Controller에도 추가
@RestController
@RequiredArgsConstructor
public class ItemController {
private final FileStore fileStore;
private final MainTextRepository mainTextRepository;
@Value("${file.dir}")
private String fileDir;
@PostMapping("/items/new/{mainTextId}")
public void saveImage(@RequestParam("file") List<MultipartFile> form, @PathVariable("mainTextId") Long id) throws IOException {
List<String> files = new ArrayList<>();
for (MultipartFile multipartFile : form) {
files.add(fileStore.storeFile(multipartFile));//저장된 이름을 리스트에 추가
//System.out.println("multipartFile = " + multipartFile.getOriginalFilename());
}
Optional<MainText> mainText = mainTextRepository.findById(id);
// ------------------------- 이 부분 추가 ---------------------
//찾은 게시글에서 이미지를 가져온다
String image = mainText.orElseThrow().getImage();
//파일삭제
fileStore.deleteFile(image);
//----------------------------------------------------------------
//DB에 저장된 이미지이름들을 변경
mainText.orElseThrow().setImage(files.toString());
mainTextRepository.save(mainText.get());
}
이렇게 하면 사용자가 새로운 파일들을 서버로 요청하면 기존의 파일들은 자동으로 지워지고 새로 요청된 파일이 저장된다.
이제 Controller를 수정하여 GET요청을 하면 다음과 같은 응답이 오도록 했다.
앱에서 images의 내용을 split하여 이미지 조회 API로 요청을 보내면 이미지가 나타나게 된다.
Flutter로 실행해 보았다.
Reference
'Spring > 게시판API서버' 카테고리의 다른 글
[Spring] 게시판 API (9) PushAlarm FCM 활용 (0) | 2023.03.27 |
---|---|
[Spring] 게시판 API (8) 댓글과 대댓글 (0) | 2023.03.23 |
[Spring] 게시판 API (6-2) JWT 적용 (0) | 2023.03.13 |
[Spring] 게시판 API (6-1) Spring Security 적용하기 (0) | 2023.03.13 |
[Spring] 게시판 API (5) 비밀번호 암호화 (1) | 2023.03.08 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- Bcypt
- Kubernetes
- 로컬캐시
- authorization
- Authentication
- spring orphan
- springboot
- ERD설계
- Spring Stomp
- Stomp Kafka
- 푸시알림동작원리
- Vmmem종료
- Vmmem
- 게시판 채팅
- 웹소켓 채팅
- MessageBroker
- Stomp RabbitMQ
- FCM
- Spring WebSocket
- loadbalancing
- Spring RabbitMQ
- nativeQuery
- Security
- bcrypt
- Spring
- ChattingApp
- Flutter
- Spring 채팅
- Spring 대댓글
- Cache
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함