일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 깃
- springboot
- 프로그래밍
- 스프링부트
- 게임개발
- c#
- JSON
- OAuth2.0
- spring
- 백준
- oAuth
- IntelliJ
- 게임
- Python
- jwt
- node.js
- 파이썬
- AWS
- 코딩
- 유니티
- MongoDB
- 스프링
- RiotAPI
- unity
- react
- bcrypt
- frontend
- express
- 백엔드
- netlify
- Today
- Total
Unwound Developer
스프링부트 이미지 저장/불러오기, thymeleaf 재 컴파일 본문
이번엔 사용자의 프로필 이미지 설정 구현을 진행했습니다.
기존에 계획 했던것은, 사용자가 회원가입 시에 이미지 파일을 서버로 전송하면 직접 DB에 저장하는 것이었습니다.
그런데, 이미지 파일을 직접 주고 받는것도 그렇고
너무 많은 자원을 소비하기 때문에 별로 권장하지 않는 과정이라고 합니다.
때문에, 다음과 같은 과정으로 프로필 이미지를 설정했습니다.
- 사용자가 회원가입과 함께 프로필 이미지를 서버로 전달
- 서버는 서버 로컬 저장소에 프로필 이미지를 저장
- DB에 이미지 파일의 정보를 저장 (이미지는 DB에 저장하지 않고, 저장 경로 등의 정보만 저장한다)
- 나중에 이미지를 불러올때, 이미지 파일의 저장 정보를 통해 서버 로컬 저장소에서 이미지를 불러옴
이미지는 서버 로컬 저장소에 저장하고, DB에는 이미지의 정보만을 저장한다는 것이 특징입니다.
코드와 함께 살펴보겠습니다.
우선 타임리프로 작성한 회원가입 view부터 살펴봅니다.
<form action="/api/members" method="post" enctype="multipart/form-data" >
<div class="form-group" align="left">
<label for="name">이름 : </label>
<input type="text" id="name" name="name" placeholder="이름을 입력하세요" width="200px">
...
<input type="file" name="file"/>
<br><br>
</div>
<button type="submit" >제출</button>
</form>
form태그의 속성을 보면 enctype이라는 것이 있는데,
데이터(form data)가 서버로 제출될 때 해당 데이터가 인코딩되는 방법을 명시하는 것이라고 합니다.
회원가입에 필요한 데이터를 입력받는 input들이 나오다가 마지막에 type이 file인 input태그가 존재합니다.
저 태그는 클라이언트에서 서버로 보낼 파일을 선택할 수 있도록 합니다.
다음과 같은 파일 선택 버튼입니다.
클릭시 다음과같이 작동합니다.
이미지를 선택하고 제출 버튼을 누르면 서버로 데이터들이 전송됩니다.
@PostMapping("/api/members")
public String saveMember(MemberRequestDto requestDto, @RequestParam MultipartFile file)throws IOException {
memberService.join(requestDto, file);
return "members/signUpForm";
}
사용자 회원가입에 필요한 데이터들을 dto를 통해 받고, 이미지는 MultipartFile의 형태로 받아옵니다.
public Long join(MemberRequestDto memberRequestDto, MultipartFile file) throws IOException {
Member member = memberRequestDto.toEntity();
validateDuplicateMember(member);
// 현재 날짜 구하기
LocalDateTime now = LocalDateTime.now();
// 포맷 정의
String formatDate = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 포맷 적용
member.setCreateDate(formatDate);
imageStore.storeImage(file,member); // 이미지를 로컬에 저장 후 DB 에도 이미지의 정보 저장
memberRepository.save(member);
return member.getId();
}
멤버 서비스의 join함수 입니다.
멤버를 DB에 저장하는 과정과 함께, imageStore.storeImage가 보입니다.
멤버객체를 생성한 후 이미지를 로컬에 저장하는 과정입니다.
public MemberProfileImage storeImage(MultipartFile file, Member member) throws IOException {
// 프로필 이미지 저장
if(file.isEmpty()) {
return null;
}
String originalFilename = file.getOriginalFilename();
// 작성자가 업로드한 파일명 -> 서버 내부에서 관리하는 파일명
// 파일명을 중복되지 않게끔 UUID로 정하고 ".확장자"는 그대로
String storeFileName = UUID.randomUUID() + "." + extractExt(originalFilename);
// String storeFileName = file.getOriginalFilename();
String fileUrl = "src/main/resources/static/img/"+storeFileName;
String fullPath = "C:\\Users\\82109\\Desktop\\웹\\StarWriting\\src\\main\\resources\\static\\img\\" + storeFileName;
// 파일을 저장하는 부분 -> 파일경로 + storeFilename 에 저장
file.transferTo(new File(fullPath));
System.out.println(originalFilename);
MemberProfileImageDto profileImageDto = new MemberProfileImageDto(
originalFilename,
storeFileName,
fileUrl
);
MemberProfileImage profileImage = profileImageDto.toEntity();
memberProfileImageRepository.save(profileImage);
member.setProfileImage(profileImage); // 회원가입한 멤버의 profileImage에 저장한 이미지를 할당
return profileImage;
}
우선, file이 비었으면 null을 리턴합니다.
사진을 보내지 업로드 하지 않았다면 null을 보내주는 것이에요.
그리고, 이미지 파일 저장 경로와 이미지 파일 저장명 등을 생성해 줍니다.
랜덤 생성한 UUID를 저장명으로 하는 이유는, 혹시나 이미지 파일의 이름이 겹쳐서 오류가 생길 수 있어서 그랬습니다.
file.transferTo 메소드를 사용해 실제 로컬에 이미지 파일을 저장하고
MemberProfileImage라는 데이터베이스 테이블에 저장 정보를 함께 저장합니다.
이제 저장된 회원정보를 조회합니다.
@GetMapping("/api/members/{id}")
public String getMember(@PathVariable("id") Long memberId, Model model) {
MemberResponseDto member = memberService.findMember(memberId).get();
model.addAttribute("member", member);
return "members/memberInfo";
}
조회할 멤버객체를 타임리프 html에 model로 넘깁니다.
다음은 사용자 정보 조회 페이지의 타임리프 html입니다.
<div>
<br/>
<div align="center">
<div class="form-group" align="left">
<div th:text="'회원번호 : ' + ${member.id}"></div>
<br/>
...
<p>프로필 이미지</p>
<img th:src="@{/img/} + ${member.profileImage.storeFileName}" width="100px" height="100px" style="border-color: black; border-style: solid; border-width: thin;"/>
<br/>
</div>
</div>
</div>
이미지 태그를 보면 src에 아까 저장한 이미지 파일의 저장 정보를 할당하는 것을 볼 수 있습니다.
@{/img/} 뒤에 아까 저장한 저장파일명을 붙이는 것이죠.
그리고, 실제로 localhost에 접속 해보면 사진이 안뜹니다..
코드가 잘못된 줄 알고 한참 헤맸는데, 서버를 껐다 켜보면 사진이 뜨더라구요.
이 때, 클라이언트에게 view를 전달해줄 때, 처음 서버를 가동시킨 시점의 view가 계속해서 전달되기 때문에
새롭게 로컬에 저장된 사진을 인식하지 못하는 건가 싶어 찾아봤는데,
서버를 껐다 키지 않고, 타임리프 html만 재 컴파일할 수 있더라구요.
해당 html파일을 띄워놓은 상태에서 Build탭에 들어가 Recompile을 선택하면,
서버를 종료하지 않고 html만 다시 컴파일 합니다.
그랬더니 작동하더라구요. 아마, view를 전달해주는 과정에서 생긴 문제가 맞는 것 같습니다.
이전에 리액트로 작업했을때는, 이런일이 없었는데 말이죠
확실히 리액트가 동적으로 변화하는 환경에서는 제일 좋아보여요.
'Web > Spring' 카테고리의 다른 글
스프링부트 One To Many 관계설정 (0) | 2023.02.18 |
---|---|
SpringBoot에서 Bcrypt와 JWT를 사용 인가와 인증(로그인), Postman 헤더에 토큰 담기 (0) | 2023.02.08 |
OAuth2.0을 통해 구글 계정 정보 받아오기 (0) | 2023.02.01 |
OAuth 2.0 이란 (0) | 2023.01.30 |
스프링 - DTO (0) | 2023.01.24 |