728x90
320x100
[참고]
김영한님 스프링 강의를 바탕으로 진행되는 토이프로젝트의 과정을 기록하는 글입니다.
둥근 피드백은 언제나 환영입니다.
[오늘의 개발내용]
1. 파일첨부 기능 만들기 (회원 프로필 이미지)
[서론]
파일첨부 기능을 활용하여 회원 프로필 이미지를 넣는 걸 만들어 줄 것임.
프로필은 이미지 한 장만 첨부가능 하도록 할 것임.
(추후에 내가 등록한 이미지를 다운 받을 수있도록 까지도 할껀데 그건 나중에...)
1. Member.java
package toyproject.bookbookclub.domain.Members;
import lombok.Getter;
import lombok.Setter;
import toyproject.bookbookclub.domain.UploadFile;
@Getter @Setter
public class Member {
private String id;
private String NickName;
private String password;
private UploadFile profileImage; //회원 프로필 사진을 받기 위한 객체 추가
```생략```
}
- 회원 클래스에 프로필사진을 담기 위한 객체를 하나 추가해줬다.
2. UploadFile.java
package toyproject.bookbookclub.domain;
import lombok.Data;
@Data
public class UploadFile {
private String uploadFileName;
private String storeFileName;
public UploadFile(String uploadFileName, String storeFileName) {
this.uploadFileName = uploadFileName;
this.storeFileName = storeFileName;
}
}
- 회원이 업로드한 파일명과 서버 내부에서 관리하는 파일명이 둘 다 따로 필요하므로! 업로드 파일 정보를 보관을 위해 생성했음
3.FileStore.java
package toyproject.bookbookclub.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Component
public class FileStore {
@Value("${file.dir}")
private String fileDir;
public String getFullPath(String filename){
return fileDir + filename;
}
public List<UploadFile> storeFiles(List<MultipartFile> multipartFiles) throws IOException {
List<UploadFile> storeFileResult = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
if (!multipartFile.isEmpty()) {
storeFileResult.add(storeFile(multipartFile));
} }
return storeFileResult;
}
public UploadFile storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
String originalFilename = multipartFile.getOriginalFilename();
String storeFileName = createStoreFileName(originalFilename);
multipartFile.transferTo(new File(getFullPath(storeFileName)));
return new UploadFile(originalFilename, storeFileName);
}
private String createStoreFileName(String originalFileName) {
String ext = extractExt(originalFileName);
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
}
private String extractExt(String originalFileName) {
int pos = originalFileName.lastIndexOf(".");
return originalFileName.substring(pos+1);
}
}
- 파일 저장과 관련된 업무를 처리하기 위한 클래스를 하나 생성했다.
- 이 클래스에서는 서버 내부에서 관리하는 유일무이한 파일명을 생성하거나 확장자를 별도로 추출해서 내부에서 관리하는 파일명에도 붙여주는 등 역할도 함.
4. MemberJoinForm.java
package toyproject.bookbookclub.domain.Members;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class MemberJoinForm {
private String id;
private String NickName;
private String password;
private MultipartFile profileImage;
}
- 회원 가입용 전용 폼 객체도 따로 만들었다.
- 여기서는 프로필 이미지를 MultipartFile 형태로 생성한다.
5. BasicMemberController.java
package toyproject.bookbookclub.web.member.basic;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.util.UriUtils;
import toyproject.bookbookclub.domain.FileStore;
import toyproject.bookbookclub.domain.Members.Member;
import toyproject.bookbookclub.domain.Members.MemberJoinForm;
import toyproject.bookbookclub.domain.Members.MemberRepository;
import toyproject.bookbookclub.domain.UploadFile;
import toyproject.bookbookclub.web.validation.MemberValidator;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Controller
@RequiredArgsConstructor
@RequestMapping("/basic/members")
public class BasicMemberController {
private final FileStore fileStore; //fileStore 주입 받음.
```생략```
/**
* 회원 가입
* @param form
* @param bindingResult
* @param redirectAttributes
* @return
* @throws IOException
*/
@PostMapping("/join")
public String join(@Validated @ModelAttribute("member") MemberJoinForm form
, BindingResult bindingResult
, RedirectAttributes redirectAttributes) throws IOException {
if (bindingResult.hasErrors()){
return "basic/joinForm";
}
UploadFile profileImage = fileStore.storeFile(form.getProfileImage());
Member member = new Member();
member.setId(form.getId());
member.setPassword(form.getPassword());
member.setNickName(form.getNickName());
member.setProfileImage(profileImage);
Member savedMember = memberRepository.save(member);
redirectAttributes.addAttribute("memberId", savedMember.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/basic/members/{memberId}";
}
/**
* 이미지 조회
* @param filename
* @return
* @throws MalformedURLException
*/
@ResponseBody
@GetMapping("/images/{filename}")
public UrlResource downloadImage(@PathVariable String filename) throws MalformedURLException {
return new UrlResource("file:" + fileStore.getFullPath(filename));
}
```생략```
}
(+) 뻘 짓한 것
@ModelAttribute("member") MemberJoinForm form
- @ModelAttribut MemberJoinForm form 형태로 처음에 입력하니까 받아오질 못함. 왜냐면 htm에서는 object를 MemberForm 이 아니라 member 로 이름을 지정했기 때문...
6. joinForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="mb-3 text-center" >
<h4 class="mb-3" th:text="#{page.joinMember}">회원 가입</h4>
</div>
<form action="member.html" th:action th:object="${member}" method="post" enctype="multipart/form-data">
===생략===
<div>
<label for="profileImage" th:text="#{label.member.profileImage}">프로필 이미지</label>
<input type="file" id="profileImage" th:field="*{profileImage}" class="form-control">
</div>
===생략===
</form>
</div> <!-- /container -->
</body>
</html>
- Form 태그 안에 enctype="multipart/form-data" 꼭 넣어주기
- input type="file" 로 해주기
(+) 뻘짓한 것
안되길래 GPT한테 물어봄.
알고보니 Form 태그 안에 enctype="multipart/form-data" 을 안 넣어 줌; 마 고맙다 피티야...
7. member.html
=== 생략 ===
<div>
<label>프로필 이미지</label>
<a th:if="${member.profileImage}"
th:href="|/attach/${member.id}|" th:text="${member.getProfileImage().getUploadFileName()}" /><br/>
<img th:src="|/basic/members/images/${member.getProfileImage().getStoreFileName()}|" width="300" height="300"/>
</div>
=== 생략 ===
- <a>태그는 나중에 클릭하면 다운받는 기능 넣으려고 만들어 놓았음.
(+) 뻘짓한 거
<img th:src="|/basic/members/images/${member.getProfileImage().getStoreFileName()}|" width="300" height="300"/>
src 주소부분을 영한님 강의 대로 하다보니까 /images~ 이런식으로 쓰니까 당연히 이미지가 안뜨고 엑박뜸.
하.....진짜 별거 아닌걸로 뭔 삽질을 했는지...!
엑박 뜬거 인증합니다....
이게 끝인줄 알았지만 아니였음. 제일 중요한 몇 가지들을 추가로 설정해줘야 함.
8. application.properties
file.dir=/Users/ddururiiiiiii/dev/file/bookbookclub/
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=20MB
- fileStore.java 에서 ${file.dir} 로 설정했던 것에 값을 넣어줘야 함. 즉, 이미지가 저장될 경로를 넣어줘야 함.
- 그리고 파일 업로드시 파일용량을 제한해줘야 하는데 두번째 줄이 파일당 제한, 막줄이 여러 첨부파일을 업로드 할 경우 총 용량이라고 보심 되겠다.
(+) 뻘짓한 것
파일 업로드 할라니까 갑자기 이런 에러가 듬. 413..세상 처음 보는 에러. GPT한테 물어봄.
아 파일 크기 설정 안해놔서 그렇구나....나는 어차피 테스트하는거라 어차피 큰 파일 안할 거고 크게 문제 없을 거라 생각해서 처음에 안넣었는데 저걸 넣어줘야 한다는걸 깨달음 마 고맙다 피티야....
9.Member.html
package toyproject.bookbookclub.web.validation;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import toyproject.bookbookclub.domain.Members.Member;
import toyproject.bookbookclub.domain.Members.MemberJoinForm;
@Component
public class MemberValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Member.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
MemberJoinForm member = (MemberJoinForm) target; //수정
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "id", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "nickName", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required");
}
}
- 안되길래 보니까 여기서 (Member) 로 원래 캐스팅 되어있어야 하는데 내가 memberForm을 우겨넣어서 안되는 거였음 그래서 캐스팅을 MemberForm으로 해줌.
[구현]
이번에 파일 업로드...는 좀 고생을 했다........
영한님 강의를 들은지 좀 됐기도 한데다가 강의록을 제대로 안읽은 탓도 있고 여러모로 어려웠다.
아직 수정해야 할 기능들이 많지만 그래도 한 단계 올라갔다.
728x90
320x100
'💻 뚝딱뚝딱 > (구) 북북클럽' 카테고리의 다른 글
[개발일지#028] 로그인 화면 수정하기 (레이아웃 변경 및 아이디 기억하기 기능 구현) (0) | 2024.03.20 |
---|---|
[개발일지#027] 회원 컬럼 추가하기 / 회원가입 화면 수정 (유효성검사 등) (0) | 2024.03.19 |
[개발일지#024] 인터셉터를 활용하여 로그인 구현하기 (3) | 2024.03.11 |
[개발일지#023] Validation 설정하기 (서버검증) (0) | 2024.03.08 |
[개발일지#022] 스프링 메시지화, 국제화 적용하기 (2) | 2024.03.07 |