2023. 10. 19. 03:49ㆍ기술 창고/Spring
이미지 파일이나 일반적인 파일, 음악과 같은 파일을 업로드하게 되는 것은 많은 웹 사이트에서 지원해주는 기능이라고 볼 수 있습니다.
카페에 게시글을 올리거나 스크린샷을 올리거나 커뮤니티 사이트에서 파일을 업로드해서 내용을 공유하는 식으로 일상에서 흔히 볼 수 있는 기능입니다.
이 파일 업로드 기능에 대해서 로컬과 배포 서버에서 구현하는 과정에 대해서 정리해보고자 합니다.
view 단 jsp, html 이 존재하고 클라이언트가 해당 페이지에서 문의 파일과 함께 문의 작성 요청을 보내 문의 작성을 수행한다는 전제하에 정리해보겠습니다.
1. API 구성
Controller
@PostMapping("/contact/inquiry")
public RedirectView contactInquiry(
MultipartHttpServletRequest imageRequest,
@ModelAttribute("contactRequest") ContactRequestDto contactRequestDto) throws IOException {
// 문의용 이미지 파일을 리스트에 저장
List<MultipartFile> contactImage = imageRequest.getFiles("contactImage");
// service 단으로 진입하여 실질적으로 문의 사항을 등록시킬 비즈니스로직 수행 (이후 이메일로 직접적으로 문의 메일을 보낼 로직 추가 필요)
contactService.contactInquiry(contactImage, contactRequestDto);
return new RedirectView("/gds/contact");
}
우선 파일과 json 데이터를 넘겨받는 api를 만들어주도록 하겠습니다.
해당 api는 문의 작성 api로서 기본적으로 문의하고자 하는 내용을 json 형식 데이터로 요청받고, 추가로 첨부하는 파일을 요청받습니다.
- imageRequest
MultipartHttpServletRequest 데이터로서 jsp 혹은 html 에서 넘겨받은 파일들을 받아옵니다.
이 imageRequest 안에는 파일 데이터를 넘겨준 html 태그의 name 속성명을 키 값으로 실제 파일이 넘겨져오게 됩니다.
즉,
<input type="file" id="file-upload" name="contactImage" style="display: none;">
이런 형식의 파일을 넘겨주는 input 태그의 name 속성 값인 contactImage 라는 이름으로 imageRequest에 파일이 담겨져서 넘어오게 됩니다.
이후, getFile() 혹은 getFiles() 함수에 input 태그 name 속성 값을 넣어 해당 파일을 호출해옵니다.
getFile() 은 주로 단일 파일을 가져올 때 사용하고, getFile() 는 다중 파일을 가져올 때 주로 사용합니다.
- contactRequest
html
<input type="text" id="contactor" name="contactor" placeholder="이름을 입력하세요" required>
<input type="text" id="address" name="address" placeholder="주소를 입력하세요" required>
<input type="tel" id="phoneNumber" name="phoneNumber" placeholder="전화번호를 입력하세요" required>
<input type="email" id="email" name="email" placeholder="이메일을 입력하세요" required>
<textarea id="additionalInfo" name="additionalInfo" placeholder="추가 정보를 입력하세요"></textarea>
contactRequest
@Setter
@Getter
public class ContactRequestDto {
private String contactor; // 문의자
private String address; // 문의자 주소
private String phoneNumber; // 문의자 전화번호
private String email; // 문의자 이메일
private String additionalInfo; // 문의자 추가 정보
}
contactRequest는 문의 작성 내용을 json 형식으로 받는 데이터입니다.
html 에서 input 태그를 통해 넘겨주고자 하는 데이터들을 마찬가지로 name 속성을 통해 ContactRequestDto 객체의 속성 명과 완벽하게 일치하게 하여 Dto 객체(json) 형식으로 넘겨줄 수 있습니다.
이 imageRequest 와 contactRequest 모두 form 태그의 action과 method 형식을 지정하여 넘겨받게 됩니다.
getFiles() 함수를 통해 MultipartFile 형식으로 가져온 파일과 contactRequestDto 형식으로 가져온 json 형식의 데이터들을 service 단으로 넘겨줍니다.
2. Service 구성
Service
// 문의 작성 기능 service
public void contactInquiry(List<MultipartFile> contactFile, ContactRequestDto contactRequestDto) throws IOException {
log.info("문의 작성 기능 service 진입 - 문의자 : {}", contactRequestDto.getContactor());
Contact contact = Contact.builder()
.contactor(contactRequestDto.getContactor()) // 문의자
.address(contactRequestDto.getAddress()) // 문의자 주소
.phoneNumber(contactRequestDto.getPhoneNumber()) // 문의자 전화번호
.email(contactRequestDto.getEmail()) // 문의자 이메일
.additionalInfo(contactRequestDto.getAdditionalInfo()) // 문의자 추가 정보
.build();
// 문의 작성 내역 저장
contactRepository.save(contact);
log.info("[Success] 문의 사항 생성 완료");
// 문의 사항 시 포함된 파일 등록
if(mediaUploadInterface.contactUploadFile(contactFile, contact, "contact")){
log.info("[Success] 문의 사항 관련 파일들이 정상적으로 등록 되었습니다.");
}else{
log.info("[Error] 문의 사항 관련 파일들의 업로드가 실패하였습니다.");
}
// 이메일 보내기
mailService.sendSimpleEmail(contactRequestDto);
}
service로 넘어온 데이터들을 가지고 Contact 엔티티에 정보를 저장하고, 실제로 파일을 업로드 시켜주는 MediaUploadInterface의 contactUploadFile 함수로 보내 파일을 업로드 하도록 처리 합니다.
업로드가 정상적으로 수행되면 success 로그가 찍힐 것이며, 수행되지 않으면 error 로그가 찍힐 것입니다.
그 다음 이메일 전송 기능이 수행되게 됩니다.
3. Upload Interface 구성
이 Upload Interface 부분이 핵심 내용인 업로드를 수행하는 부분입니다.
로컬과 배포 서버 환경에서 구성하는 로직이 다소 다른 점이 있기 때문에 잘 확인하고 적용해야 합니다.
< 로컬 환경 기준 >
Upload Interface
// 업로드 시킬 문의 사항 관련 파일
@Override
public boolean contactUploadFile(List<MultipartFile> contactFile, Contact contact, String classification) throws IOException {
// 파일 저장 경로 (이후 배포 시 배포 서버를 연동하면서 경로를 따로 지정해주어야함.)
String uploadFilePath = "C:" + File.separator + "BackEnd_Project" + File.separator + "GDS" + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "static" + File.separator + "file" + File.separator;
String callFilePath = File.separator + "img" + File.separator;
// 정상적으로 이미지 파일들이 저장 되었는지 확인하지 위한 리스트
List<MediaResponseDto> mediaCheckList = new ArrayList<>();
// 문의 사항을 생성할 때 들어온 업로드 이미지들을 조회
for (MultipartFile eachFile : contactFile) {
// 업로드할 파일의 진짜 이름 추출
String originalFilename = eachFile.getOriginalFilename();
// 난수화된 파일 이름과 확장자를 합친 파일명 추출
String serverUploadFileName = createServerFileName(originalFilename);
// 업로드한 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
File file = new File(getFullPath(serverUploadFileName, uploadFilePath));
// multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송 - 현재 프로젝트 내부에 전송하고 있으므로 배포하게 되었을 때 수정해줘야 할 필요가 있음.)
eachFile.transferTo(file);
// 등록시키려는 파일들의 정보를 엔티티에 담아 저장
Media fileMedia = Media.builder()
.mediaUuidTitle(serverUploadFileName) // 파일의 난수화된 이름
.mediaName(originalFilename) // 파일의 원래 이름
.classification(classification) // 용도 구분 (project, product, contact)
.callUploadUrl(getFullPath(serverUploadFileName, callFilePath)) // 파일을 불러올 path 경로
.mediaUploadUrl(getFullPath(serverUploadFileName, uploadFilePath)) // 실제로 저장된 파일의 경로
.contentId(contact.getContactId()) // 속한 프로젝트의 id
.build();
// 미디어 정보 저장
mediaRepository.save(fileMedia);
// 확인용 리스트 객체에 정보들 저장
mediaCheckList.add(
MediaResponseDto.builder()
.mediaId(fileMedia.getMediaId())
.mediaUuidTitle(fileMedia.getMediaUuidTitle())
.mediaName(fileMedia.getMediaName())
.classification(fileMedia.getClassification())
.mediaUploadUrl(fileMedia.getMediaUploadUrl())
.callUploadUrl(fileMedia.getCallUploadUrl())
.contentId(fileMedia.getContentId())
.build()
);
}
return mediaCheckList.size() == contactFile.size() &&
!mediaCheckList.isEmpty() &&
mediaCheckList.get(0).getMediaName().equals(contactFile.get(0).getOriginalFilename()) &&
mediaCheckList.get(mediaCheckList.size() - 1).getMediaName().equals(contactFile.get(contactFile.size() - 1).getOriginalFilename());
}
우선 로컬 환경에서의 파일 업로드에 대해서 정리해보도록 하겠습니다.
(1) uploadFilePath 변수
String uploadFilePath = "C:" + File.separator + "BackEnd_Project" + File.separator + "GDS" + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "static" + File.separator + "file" + File.separator;
uploadFilePath 변수에는 로컬 환경에서 파일을 업로드할 경로는 지정해줍니다.
저는 최상위 C드라이브 경로부터 GDS 프로젝트 내부의 static 경로의 file 까지의 경로를 잡아 주었습니다.
반드시 최상위 경로부터 마지막 경로까지의 절대 경로로 지정해주어야 합니다.
(2) callFilePath 변수
String callFilePath = File.separator + "img" + File.separator;
callFilePath는 프로젝트 내부 경로에 저장된 파일들을 호출하기 위한 경로를 지정해두는 변수라고 볼 수 있습니다.
jsp나 html에서 태그의 src 속성을 사용해서 파일들을 호출할 때 이 callFilePath 변수를 활용하여 호출하게 되는 것입니다.
실제로 uploadFilePath에 지정한 절대 경로가 아닌 비교적 짧은 경로를 지정해주었다는 점에서 다른 점이 존재하는데,
프로젝트 내부에 저장된 파일들에 접근 할 때는 보통 파일들이 resources 의 static 경로 안에 저장되어있고, 프로젝트에서 css나 js, 파일 등에 접근할 때의 프로젝트 최상위 경로가 resources/static 경로로 잡혀져있기 때문에 callFilePath를 qkfh ㅑimg 경로로 잡아준 것입니다.
즉, callFilePath의 처음 File.separator 부분이 resources/static 경로까지를 나타내는 것이라고 보면 됩니다.
uploadFilePath는 transferTo를 통해 실제로 파일들을 저장하기 직접적으로 지정해둔 절대 경로,
callFilePath는 프로젝트 내부에 업로드 된 파일들에 접근하여 호출하기 위한 경로라고 보면 됩니다.
(3) mediaCheckList 리스트 객체 변수
List<MediaResponseDto> mediaCheckList = new ArrayList<>();
mediaCheckList 리스트 객체 변수는 업로드 후 최종적으로 정상 업로드가 되었는지 파일들을 확인하기 위한 리스트 객체입니다.
(4) for문을 통한 파일 업로드 및 정보 저장
// 문의 사항을 생성할 때 들어온 업로드 이미지들을 조회
for (MultipartFile eachFile : contactFile) {
// 업로드할 파일의 진짜 이름 추출
String originalFilename = eachFile.getOriginalFilename();
// 난수화된 파일 이름과 확장자를 합친 파일명 추출
String serverUploadFileName = createServerFileName(originalFilename);
// 업로드한 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
File file = new File(getFullPath(serverUploadFileName, uploadFilePath));
// multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송 - 현재 프로젝트 내부에 전송하고 있으므로 배포하게 되었을 때 수정해줘야 할 필요가 있음.)
eachFile.transferTo(file);
// 등록시키려는 파일들의 정보를 엔티티에 담아 저장
Media fileMedia = Media.builder()
.mediaUuidTitle(serverUploadFileName) // 파일의 난수화된 이름
.mediaName(originalFilename) // 파일의 원래 이름
.classification(classification) // 용도 구분 (project, product, contact)
.callUploadUrl(getFullPath(serverUploadFileName, callFilePath)) // 파일을 불러올 path 경로
.mediaUploadUrl(getFullPath(serverUploadFileName, uploadFilePath)) // 실제로 저장된 파일의 경로
.contentId(contact.getContactId()) // 속한 프로젝트의 id
.build();
// 미디어 정보 저장
mediaRepository.save(fileMedia);
// 확인용 리스트 객체에 정보들 저장
mediaCheckList.add(
MediaResponseDto.builder()
.mediaId(fileMedia.getMediaId())
.mediaUuidTitle(fileMedia.getMediaUuidTitle())
.mediaName(fileMedia.getMediaName())
.classification(fileMedia.getClassification())
.mediaUploadUrl(fileMedia.getMediaUploadUrl())
.callUploadUrl(fileMedia.getCallUploadUrl())
.contentId(fileMedia.getContentId())
.build()
);
}
우선 클라이언트로부터 넘겨받은 파일들을 기준으로 for문을 통해 하나씩 데이터를 추출합니다.
ㄴ originalFilename = 각 파일들의 원래 파일명을 추출
ㄴ serverUploadFileName = 각 파일들의 원래 파일 명들을 가지고 createServerFileName 함수를 통해 UUID로 난수화된 파일명으로 변환 추출
ㄴ file = uploadFilePath와 serverUploadFileName을 getFullPath 함수로 넘겨 완전한 파일이 업로드될 경로를 추출하고 File 객체를 사용하여 해당 경로에 존재하는 File을 만들어준다.
ㄴ eachFile.transferTo(file) = for문을 돌리면서 현재 조회 및 추출하고 있는 각 파일에 방금 업로드 경로를 넣어 만들어낸 file 객체를 transferTo 함수를 통해 전달하여 해당 경로에 실질적으로 업로드하도록 처리
ㄴ fileMedia = 추출한 정보와 전달받은 정보들을 토대로 Media 엔티티에 정보 저장
ㄴ mediaCheckList.add = 이때까지 업로드하고 추출 및 저장했던 파일의 정보들을 Dto 객체에 담아 최종적으로 반환받아 확인할 리스트 객체에 저장.
mediaCheckList는 전달받은 파일들과 수가 동일해야 합니다.
(5) 최종 업로드 확인 후 return 값 반환
return mediaCheckList.size() == contactFile.size() &&
!mediaCheckList.isEmpty() &&
mediaCheckList.get(0).getMediaName().equals(contactFile.get(0).getOriginalFilename()) &&
mediaCheckList.get(mediaCheckList.size() - 1).getMediaName().equals(contactFile.get(contactFile.size() - 1).getOriginalFilename());
이제 파일 업로드 및 엔티티에 정보 저장까지 완료가 되었으면 반환 리스트 객체인 mediaCheckList를 가지고 정상적으로 업로드 되었는지 확인합니다.
각 조건을 살펴보면,
- mediaCheckList 리스트 안에 데이터가 전달받은 파일의 개수와 동일한지,
- mediaCheckList 가 null이 아닌지,
- mediaCheckList 의 첫 번째로 저장된 파일 데이터의 파일명과 전달받을 당시에 들어온 첫 번째 파일 데이터의 파일명과 일치하는지,
- mediaCheckList 의 마지막으로 저장된 파일 데이터의 파일명과 전달받을 당시에 마지막으로 들어온 파일의 이름과 일치하는지
확인합니다.
조건이 다 맞다면 정상적으로 업로드가 수행된 것이므로 true 를 반환, 하나라도 틀리다면 false를 반환홥니다.
< 배포 서버 기준 >
Upload Interface
// 업로드 시킬 문의 사항 관련 파일
@Override
public boolean contactUploadFile(List<MultipartFile> contactFile, Contact contact, String classification) throws IOException {
// 배포 서버 주소
String deployServerPath = "http://111.222.333.444:8080/"
// 파일 저장 경로 (이후 배포 시 배포 서버를 연동하면서 경로를 따로 지정해주어야함.)
String uploadFilePath = File.separator + "home" + File.separator + "gds_korea" + File.separator + "gds_korea" + File.separator + "webapp" + File.separator + "contact" + File.separator;
// 정상적으로 이미지 파일들이 저장 되었는지 확인하지 위한 리스트
List<MediaResponseDto> mediaCheckList = new ArrayList<>();
// 문의 사항을 생성할 때 들어온 업로드 이미지들을 조회
for (MultipartFile eachFile : contactFile) {
// 업로드할 파일의 진짜 이름 추출
String originalFilename = eachFile.getOriginalFilename();
// 난수화된 파일 이름과 확장자를 합친 파일명 추출
String serverUploadFileName = createServerFileName(originalFilename);
// 업로드한 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
File file = new File(getFullPath(serverUploadFileName, uploadFilePath));
// multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송 - 현재 프로젝트 내부에 전송하고 있으므로 배포하게 되었을 때 수정해줘야 할 필요가 있음.)
eachFile.transferTo(file);
// 등록시키려는 파일들의 정보를 엔티티에 담아 저장
Media fileMedia = Media.builder()
.mediaUuidTitle(serverUploadFileName) // 파일의 난수화된 이름
.mediaName(originalFilename) // 파일의 원래 이름
.classification(classification) // 용도 구분 (project, product, contact)
.mediaUploadUrl(getFullPath(serverUploadFileName, uploadFilePath)) // 배포 서버에 업로드된 경로
.callUploadUrl(deployServerPath + "contact" + File.separator + serverUploadFileName) // 배포환경에서의 호출 경로
.contentId(contact.getContactId()) // 속한 프로젝트의 id
.build();
// 미디어 정보 저장
mediaRepository.save(fileMedia);
// 확인용 리스트 객체에 정보들 저장
mediaCheckList.add(
MediaResponseDto.builder()
.mediaId(fileMedia.getMediaId())
.mediaUuidTitle(fileMedia.getMediaUuidTitle())
.mediaName(fileMedia.getMediaName())
.classification(fileMedia.getClassification())
.mediaUploadUrl(fileMedia.getMediaUploadUrl())
.callUploadUrl(fileMedia.getCallUploadUrl())
.contentId(fileMedia.getContentId())
.build()
);
}
return mediaCheckList.size() == contactFile.size() &&
!mediaCheckList.isEmpty() &&
mediaCheckList.get(0).getMediaName().equals(contactFile.get(0).getOriginalFilename()) &&
mediaCheckList.get(mediaCheckList.size() - 1).getMediaName().equals(contactFile.get(contactFile.size() - 1).getOriginalFilename());
}
이번에는 자체적으로 배포하는 서버 환경에서의 업로드에 대해서 정리해보도록 하겠습니다.
배포 서버 환경에서 파일 업로드를 적용하는 것은 로컬 환경이랑은 업로드 경로를 지정해주는 부분에서 차이가 있습니다.
(1) deployServerPath 변수
String deployServerPath = "http://111.222.333.444:8080";
일단 배포하는 자체적인 서버의 주소를 String 타입 변수로 잡아줍니다.
이 배포 서버 주소를 변수로 잡아주는 이유는 파일이 업로드 되고 추후에 이 파일들에 접근해서 가지고 올 때 배포된 서버에 업로드된 경로를 지정해주어야하기 때문에 배포 서버의 주소가 직접적으로 필요합니다.
예를 들어, 이미지 파일을 자체 서버에 업로드 하고 이 업로드 된 이미지 파일을 jsp 혹은 html에서 불러오고자 할 때 img 태그의 src 속성에 불러올 이미지의 경로를 적어주는 데 이곳에 업로드된 배포 서버의 경로를 넣어주어야 합니다.
이때, 이 deployServerPath 변수에 저장된 서버의 주소와 업로드 된 경로가 같이 들어가게 되고 원하는 대로 이미지가 불러오게 되는 것입니다.
지금은 임의의 ip 주소를 대놓고 적어놓긴 했지만 properties 파일에서 서버 주소를 관리해주고 불러오는 방식이 더욱 안전합니다.
(2) uploadFilePath 변수
String uploadFilePath = File.separator + "home" + File.separator + "gds_korea" + File.separator + "gds_korea" + File.separator + "webapp" + File.separator + "contact" + File.separator;
이 uploadFilePath 변수에는 잘체 서버의 경로를 String 타입으로 지정해줍니다.
로컬 환경에서는 직접적으로 C 드라이브부터 시작해서 프로젝트의 내부 경로까지 잡아줬으므로 "C:\경로1\경로2\경로3" 이런 형식으로 지정해주었지만 배포 서버 환경에서는 다릅니다.
당연히 C드라이브가 아니므로 최상위 C 경로는 제외하고 파일이 업로드 될 배포 서버의 경로를 지정해줍니다.
이 경로는 FileZila 를 사용한다면 쉽게 확인하고 지정해줄 수 있습니다.
제가 활용한 위의 예시 코드를 예로 들면,
String uploadFilePath = File.separator + "home" + File.separator + "gds_korea" + File.separator + "gds_korea" + File.separator + "webapp" + File.separator + "contact" + File.separator;
File.separator로 경로 구분을 해주었습니다.
제일 처음에 들어간 File.separator는 최상위 경로를 뜻합니다. (로컬 환경의 C:\ 최상위 경로와 마찬가지)
그 뒤 home 경로 -> gds_korea 경로 -> gds_korea 경로 -> webapp 경로 -> contact 경로까지 잡아준 것입니다.
이 경로가 이후에 transferTo 함수를 사용했을 때 해당 파일들이 업로드될 경로입니다.
(3) mediaCheckList 리스트 객체 변수
List<MediaResponseDto> mediaCheckList = new ArrayList<>();
mediaCheckList 리스트 객체 변수는 업로드 후 최종적으로 정상 업로드가 되었는지 파일들을 확인하기 위한 리스트 객체입니다.
(4) for문을 통한 파일 업로드 및 정보 저장
for (MultipartFile eachFile : contactFile) {
// 업로드할 파일의 진짜 이름 추출
String originalFilename = eachFile.getOriginalFilename();
// 난수화된 파일 이름과 확장자를 합친 파일명 추출
String serverUploadFileName = createServerFileName(originalFilename);
// 업로드한 파일과 경로를 합친 업로드 경로를 File 객체에 넣어 생성
File file = new File(getFullPath(serverUploadFileName, uploadFilePath));
// multipartfile에서 지원하는 transferTo 함수 사용. (해당 파일을 지정한 경로로 전송 - 현재 프로젝트 내부에 전송하고 있으므로 배포하게 되었을 때 수정해줘야 할 필요가 있음.)
eachFile.transferTo(file);
// 등록시키려는 파일들의 정보를 엔티티에 담아 저장
Media fileMedia = Media.builder()
.mediaUuidTitle(serverUploadFileName) // 파일의 난수화된 이름
.mediaName(originalFilename) // 파일의 원래 이름
.classification(classification) // 용도 구분 (project, product, contact)
.mediaUploadUrl(getFullPath(serverUploadFileName, callImagePath)) // 파일의 콜 path
.callUploadUrl(deployServerPath + "contact" + File.separator + serverUploadFileName) // 배포환경에서의 호출 경로
.contentId(contact.getContactId()) // 속한 프로젝트의 id
.build();
// 미디어 정보 저장
mediaRepository.save(fileMedia);
// 확인용 리스트 객체에 정보들 저장
mediaCheckList.add(
MediaResponseDto.builder()
.mediaId(fileMedia.getMediaId())
.mediaUuidTitle(fileMedia.getMediaUuidTitle())
.mediaName(fileMedia.getMediaName())
.classification(fileMedia.getClassification())
.mediaUploadUrl(fileMedia.getMediaUploadUrl())
.callUploadUrl(fileMedia.getCallUploadUrl())
.contentId(fileMedia.getContentId())
.build()
);
}
우선 클라이언트로부터 넘겨받은 파일들을 기준으로 for문을 통해 하나씩 데이터를 추출합니다.
ㄴ originalFilename = 각 파일들의 원래 파일명을 추출
ㄴ serverUploadFileName = 각 파일들의 원래 파일 명들을 가지고 createServerFileName 함수를 통해 UUID로 난수화된 파일명으로 변환 추출
ㄴ file = uploadFilePath와 serverUploadFileName을 getFullPath 함수로 넘겨 완전한 파일이 업로드된 경로를 추출하고 File 객체를 사용하여 해당 경로에 존재하는 File을 만들어준다.
ㄴ eachFile.transferTo(file) = for문을 돌리면서 현재 조회 및 추출하고 있는 각 파일에 방금 업로드 경로를 넣어 만들어낸 file 객체를 transferTo 함수를 통해 전달하여 해당 경로에 실질적으로 업로드하도록 처리
ㄴ fileMedia = 추출한 정보와 전달받은 정보들을 토대로 Media 엔티티에 정보 저장
!!!! 주의해야할 점 !!!!
여기서 엔티티에 저장하는 속성들 중 callUploadUrl 부분에는 다른 작업 처리가 필요합니다.
로컬 환경에서는 프로젝트 내부에 업로드가 되어있어 resources/static 경로가 루트 경로로 잡힌 상태에서 간단하게 /file/ 이런식으로 static 내부에 업로드 된 경로를 추가하여 넣어주기만 하면 되었습니다.
하지만 배포된 서버에서 업로드된 파일들을 가져올 때는 ip 주소와 함께 해당 파일이 업로드된 경로와 파일명을 다같이 요청해야 호출됩니다.
즉, http://111.222.333.444.8080/contact/zdflkjbnalb.txt 이런식의 callUploadUrl을 지정해주어야 하고 호출해야 한다는 뜻입니다.
하지만 우리가 생각하기에 배포 서버에 업로드 된 파일의 절대 경로를 그냥 넣어주면 되지 않을까 생각할 수 있지만 그렇지 않습니다.
단순 업로드 절대 경로를 넣어주면 인식을 하지 못합니다.
따라서 접근할 수 있는 후속 처리를 해주어야 합니다.
+ 추가 Config 클래스 생성
FileConfig
package com.project.gds.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class FileConfig implements WebMvcConfigurer {
// http://111.222.333.444.8080/contact/adfjonwpoefjb.txt
// 리소스 접근 시 addResourceHandler를 통해 요청 경로 값을 설정하고
// add ResourceLocations를 통해 접근할 파일 이들어가져있는 실질적인 경로를 입력받아
// 요청 url + addResourceHandlers에서 설정한 경로 값을 요청하면 locations 에 설정한 파일들에 실질적으로 접근할 수 있또록 하는 Config 함수
// 예) http://111.222.333.444:8080/contact/adfjonwpoefjb.txt 요청하면 locations에 설정한 접근 경로 중 하나에 일치한 파일에 접근한다.
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/project/**", "/contact/**", "/product/**", "/product/img/**", "/product/file/**")
.addResourceLocations("file:///home/gds_korea/gds_korea/webapp/project/",
"file:///home/gds_korea/gds_korea/webapp/contact/",
"file:///home/gds_korea/gds_korea/webapp/product/img/",
"file:///home/gds_korea/gds_korea/webapp/product/file/");
}
}
배포 서버에 업로드 된 파일들에 접근할 수 있도록 경로를 설정해줄 수 있는 FileConfig 설정 플래스입니다.
addResourceHandlers 를 오버라이딩하여 registry에 핸들링할 파일 경로를 지정해줍니다.
- addResourceHandler()
addResourceHandler 함수는 넣은 텍스트에 따라 요청 경로로 핸들링하여 해당 텍스트 경로에 존재하는 파일들을 접근하여 요청할 수 있도록 합니다.
- addResourceLocations()
addResourceLocations() 함수는 배포 서버에 업로드 되어 접근 할 파일들의 배포 서버 경로를 지정해줍니다.
즉, 위와 같이 이 두 가지를 설정하게 되면 http://111.222.333.444.8080/contact/file1.txt 를 요청하게 되면 addResourceLocations() 에 지정해주었던 경로 중에 file1.txt 라는 파일에 접근하여 호출할 수 있게 되는 것입니다.
ㄴ mediaCheckList.add = 이때까지 업로드하고 추출 및 저장했던 파일의 정보들을 Dto 객체에 담아 최종적으로 반환받아 확인할 리스트 객체에 저장.
mediaCheckList는 전달받은 파일들과 수가 동일해야 합니다.
(5) 최종 업로드 확인 후 return 값 반환
return mediaCheckList.size() == contactFile.size() &&
!mediaCheckList.isEmpty() &&
mediaCheckList.get(0).getMediaName().equals(contactFile.get(0).getOriginalFilename()) &&
mediaCheckList.get(mediaCheckList.size() - 1).getMediaName().equals(contactFile.get(contactFile.size() - 1).getOriginalFilename());
이제 파일 업로드 및 엔티티에 정보 저장까지 완료가 되었으면 반환 리스트 객체인 mediaCheckList를 가지고 정상적으로 업로드 되었는지 확인합니다.
각 조건을 살펴보면,
- mediaCheckList 리스트 안에 데이터가 전달받은 파일의 개수와 동일한지,
- mediaCheckList 가 null이 아닌지,
- mediaCheckList 의 첫 번째로 저장된 파일 데이터의 파일명과 전달받을 당시에 들어온 첫 번째 파일 데이터의 파일명과 일치하는지,
- mediaCheckList 의 마지막으로 저장된 파일 데이터의 파일명과 전달받을 당시에 마지막으로 들어온 파일의 이름과 일치하는지
확인합니다.
로컬 환경이든 배포 서버 환경이든 조건이 다 맞다면 정상적으로 업로드가 수행된 것이므로 true 를 반환, 하나라도 틀리다면 false를 반환홥니다.
service에 true가 반환되면 success 로그가 출력될 것이고, false가 반환되면 error 에러가 반환될 것입니다.
그 후 정상적으로 메일 기능 전송 기능이 수행되면 메일까지 보내게 될 것입니다.
다시 중요한 부분을 정리하자면,
(1) 로컬 환경
ㄴ 업로드 경로 : C드라이브 최상위 경로를 시작으로 업로드될 마지막 경로까지의 절대 경로 (반드시 C드라이브나 프로젝트 내부 경로일 필요 X)
ㄴ 파일 호출 경로 : 프로젝트 내부에 로컬로 업로드 시 루트 경로가 resources/static 으로 잡혀있으므로 단순히 static 경로에 업로드된 파일의 경로를 지정 (예 : /file/TestFile.txt - resources/static/file/TestFile.txt를 뜻함.)
ㄴ transferTo 함수로 업로드
ㄴ 호출 경로 추가 작업 필요 X
(2) 배포 서버 환경
ㄴ 업로드 경로 : 배포 서버에 업로드 할 경로 ( / 기호인 최상위 경로부터 시작하여 업로드 될 마지막 경로까지 지정)
ㄴ 파일 호출 경로 : 서버 ip주소를 포함한 FileConfig를 통해 핸들링한 접근 허용 요청 경로 (예 : http://111.222.333.444.555:8080/contact/TestFile.txt)
ㄴ transferTo 함수로 업로드
ㄴ 호출 경로 추가 작업 필요 O (배포 서버에 업로드된 파일에 접근이 가능하도록 FileConfig 생성)
이렇게 로컬 환경이든 배포 서버 환경에서 파일을 업로드하는 방법에 대해서 알아보았습니다.
'기술 창고 > Spring' 카테고리의 다른 글
[Spring Boot] 타임리프 문법 - th:href (0) | 2023.11.22 |
---|---|
[Spring Boot] Thymeleaf (타임리프) (0) | 2023.11.13 |
[Spring Boot] Email 기능 구현 (0) | 2023.10.16 |
[Spring Boot] Springdoc-openapi Swagger 적용 (+ JWT 사용 시 적용법) (0) | 2023.10.04 |
[Spring Boot] 구글 Oauth2 인증 및 구글 소셜 로그인 기능 구현 (0) | 2023.09.20 |