오늘은 사용자 프로필 저장 기능을 구현해보았다.
사용자는 하나의 프로필을 설정할 수 있기 때문에 1대1관계라 생각하고 ProfileEntity를 따로 하나 더 생성하였다.
ProfileEntity
public class ProfileEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long profileIndex;
@Column
private Long userIndex;
@Column
private String originFileName;
@Column
private String storeFileName;
public ProfileDto toDto(){
ProfileDto profileDto = ProfileDto.builder()
.userIndex(userIndex)
.originFileName(originFileName)
.storeFileName(storeFileName)
.build();
return profileDto;
}
}
private String originFileName;
private String storeFileName;
private Long userIndex;
파일을 저장하는데 있어서 특화된 메소드들을 따로 클래스로 구성하여 이를 분리해두었다.
FileStore
public class FileStore {
@Autowired
private ProfileRepository profileRepository;
// 파일 경로
@Value("${file.dir}")
private String fileDir;
// 파일 경로 구성
public String getFullPath(String storeFileName) {
return fileDir + storeFileName;
}
// 프로필을 저장 - 이미 유저인덱스로 찾아 엔티티가 있을 경우 수정 / 없으면 저장
public Optional<ProfileEntity> storeFile(MultipartFile multipartFile, Long userIndex) throws IOException {
if(multipartFile.isEmpty()){
return null;
}
String originFileName = multipartFile.getOriginalFilename();
String storeFileName = createStoreFileName(originFileName);
multipartFile.transferTo(new File(getFullPath(storeFileName)));
ProfileEntity entity = ProfileEntity.builder()
.originFileName(originFileName)
.storeFileName(storeFileName)
.userIndex(userIndex)
.build();
Optional<ProfileEntity> profileEntity = profileRepository.findByUserIndex(userIndex);
// 만약 유저 인덱스를 가진 프로필엔티티가 있으면 해당 프로필 엔티티를 수정
if(profileEntity != null){
profileEntity.map(p -> {
profileEntity.get().setOriginFileName(originFileName);
profileEntity.get().setStoreFileName(storeFileName);
profileEntity.get().setUserIndex(userIndex);
return p;
})
.map(p -> profileRepository.save(p));
return profileEntity;
}
// 없다면 프로필 엔티티를 생성해서 저장
else{
ProfileEntity saved = profileRepository.save(entity);
Optional<ProfileEntity> find =profileRepository.findById(userIndex);
return find;
}
}
// storeFile 이름 구성
private String createStoreFileName(String originFileName) {
String uuid = UUID.randomUUID().toString();
String ext = extractExt(originFileName);
String storeFileName = uuid + ext;
return storeFileName;
}
// 확장자 추출
private String extractExt(String originFileName) {
int idx = originFileName.lastIndexOf(".");
String ext = originFileName.substring(idx);
return ext;
}
}
@Value("${file.dir}")
private String fileDir;
private String extractExt(String originFileName){...}
private String createStoreFileName(String originFileName) {...}
public String getFullPath(String storeFileName) {...}
public Optional<ProfileEntity> storeFile(MultipartFile multipartFile, Long userIndex) throws IOException {...}
이 storeFile 메서드가 가장 중요한 부분이다.
파일을 저장시켜주는 메서드로써, originFileName과 storeFileName의 값을 반환해야 한다.
파라미터를 multipartFile과 회원정보를 수정하고 있는 유저의 인덱스를 받아와
multipartFile들을 String타입으로 변환 후 새로운 ProfileEntity를 생성한다.
하지만, 프로필은 하나만 있어야 하므로, 만약 동일한 userIndex를 갖고 있는 ProfileEntity가 있는 경우 해당 Entity에 덮어씌우게 하고, 동일한 userIndex가 없다면 Entity를 생성하는 방식으로 만들었다.
프로필 변경 작업은 회원정보 변경 페이지에서 진행되기 때문에 UserController에 로직을 생성하였다.
이전에 프로필이 아닌 이름, 닉네임, 전화번호를 변경하는 로직에 프로필 변경 로직을 추가하였다.
// 회원정보 수정 버튼 기능
@PostMapping("/user/edit")
public ResponseEntity<Optional<UserEntity>> userEdit(HttpSession session,@RequestParam("originFileName") MultipartFile originFileName, @ModelAttribute UserDto userDto) throws IOException{
Long userIndex = (Long) session.getAttribute("userIndex");
UserEntity user = userDto.toEntity();
// 회원 정보 수정
Optional<UserEntity> userInfo = userService.changeUserInfo(userIndex, user);
// 프로필 수정
profileService.saveProfile(originFileName, userIndex);
return new ResponseEntity<>(userInfo, HttpStatus.OK);
}
Optional<UserEntity> userInfo = userService.changeUserInfo(userIndex, user);
여기까지는 이전에 있던 로직이다.
profileService.saveProfile(originFileName, userIndex);
public ProfileEntity saveProfile(MultipartFile originFileName, Long userIndex) throws IOException {
fileStore.storeFile(originFileName, userIndex);
return null;
}
컨트롤러에서 받아온 파일과 userIndex를 StoreFile메서드로 넘겨 ProfileEntity를 생성하고, 파일을 저장한다.
아직 로직을 전부 만들지는 않았지만, 파일과 이미지 업로드 기능에 대하여 여러가지 공부를 하였다.
이 이미지 저장 로직에 대해 이해가 잘 안되었어서 오래걸리긴 했지만, 혼자 공부하는 것 치고 꽤 빠르게 이해한 것 같다.
또 이렇게 기술 회고를 작성하며 복습할 수 있어서 참 다행인것 같다.
만약 복습을 하지 않았다면, 이 프로젝트가 끝난 뒤에 나중에 또 이미지 파일을 저장할 때 다시 공부를 해야겠지만, 복습을 통해서 완전히 나의 것으로 만들었다.
오늘은 안풀리던 이미지 저장 로직을 이해해서 참 기분 좋았다.
앞으로 남은 로직과 프론트와의 데이터 렌더링을 잘 마무리 하고, 프로젝트를 성공적으로 끝낼 수 있었으면 좋겠다.