회원가입시에, 프로필사진을 등록할 수 있도록 처리를 하고자 합니다. 현재는 로컬 서버에서 동작하고 있기 때문에 컴퓨터 하드 디스크에 저장하도록 하겠습니다.
🔥이미지 파일을 DB(MySQL)에 저장하지 않습니다. DB에 저장하면 용량을 많이 차지하기 때문에 로컬 스토리지 저장경로만 DB에 저장하고, 필요 시 하드디스크에서 호출하도록 만들어야 합니다.
앞선 글에서 Domain과 Repository는 미리 생성하였으므로 이번엔 생략하겠습니다. 다음 글을 참고해주세요.
파일 관련 처리를 할 서비스를 별도로 생성하고, 이곳에서 ImageDTO를 생성하여 반환할 수 있도록 구성하겠습니다.
@Service
@Log4j2
public class FileService {
@Transactional
public ImageDTO createImageDTO(String originalSourceName, Path path) throws IOException {
String fileName = originalSourceName.substring(originalSourceName.lastIndexOf("\\") + 1);
String uuid = UUID.randomUUID().toString();
String fileUrl = getDirectory(path) + File.separator + uuid + "_" + fileName;
return ImageDTO.builder()
.fileName(fileName)
.uuid(uuid)
.fileUrl(fileUrl)
.build();
}
private String getDirectory(Path path) throws IOException {
if(!Files.exists(path)) {
Files.createDirectory(path);
}
return path.toString();
}
}
createImageDTO
의 파라미터로 originalSourceName
을 받아오는데, 이는 MultipartFile의 경로입니다. 원본 경로~파일명까지 저장되어 있는 문자열 값입니다. path
는 이미지를 저장할 로컬 저장소 경로입니다.
로컬 저장소에 저장될 이미지의 파일명은 "UUID_파일명.확장자"형태로 저장됩니다.
해당 경로에 폴더가 없으면 폴더를 생성해주는 getDirectory
메서드는 서비스 안에서만 동작하도록 private 설정을 해줍니다.
다음으로 회원가입 기능을 하는 서비스 부분을 변경합니다.
@Service
@Log4j2
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {
private final MemberRepository memberRepository;
private final TokenService tokenService;
private final AuthService authService;
//추가
private final FileService fileService;
private final MemberImageRepository imageRepository;
//추가
@Value("${spring.servlet.multipart.location}")
private String uploadPath;
@Override
public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
return memberRepository.findByUserId(userId)
.map(this::createUserDetails)
.orElseThrow(() -> new UsernameNotFoundException("userId: " + userId + "를 데이터베이스에서 찾을 수 없습니다."));
}
private UserDetails createUserDetails(Member member) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(member.getRoles().stream().map(Role::getType).collect(Collectors.joining(",")));
return new User(
member.getUserId(),
member.getPassword(),
Collections.singleton(grantedAuthority)
);
}
public Member findMemberByUserId(String userId) {
return memberRepository.findByUserId(userId).orElseThrow(() -> new RuntimeException("해당 ID를 가진 사용자가 존재하지 않습니다."));
}
public MemberDTO findMemberByEmail(String email) {
Member member = memberRepository.findByEmail(email).orElseThrow(() -> new RuntimeException("해당 email을 가진 사용자가 존재하지 않습니다."));
return MemberMapper.INSTANCE.memberToMemberDTO(member);
}
public MemberDTO getMember(String userId) {
return MemberMapper.INSTANCE.memberToMemberDTO(findMemberByUserId(userId));
}
@Transactional
public void saveMember(MemberDTO memberDTO) {
memberRepository.save(MemberMapper.INSTANCE.memberDTOToMember(memberDTO));
}
/**
* UsernamePasswordAuthenticationToken을 통한 Spring Security인증 진행
* 이후 tokenService에 userId값을 전달하여 토큰 생성
* @param requestDTO
* @return TokenDTO
*/
@Transactional
public TokenDTO login(LoginRequestDTO requestDTO) {
authService.authenticateLogin(requestDTO);
Member member = memberRepository.findByUserId(requestDTO.getUserId()).get();
MemberDTO memberDTO = MemberMapper.INSTANCE.memberToMemberDTO(member);
return tokenService.createToken(memberDTO);
}
@Transactional
public void signup(MemberRequestDTO requestDTO) {
if(memberRepository.existsByUserId(requestDTO.getUserId())) {
throw new RuntimeException("이미 존재하는 아이디입니다.");
}
Member member = MemberMapper.INSTANCE.memberRequestDTOToMember(requestDTO);
member.updateRole(Role.ROLE_USER);
memberRepository.save(member);
//추가
if(!(requestDTO.getMemberImage() == null)) {
MemberImage memberImage = saveMemberImage(requestDTO.getMemberImage());
member.updateMemberImage(memberImage);
}
}
//추가
@Transactional(readOnly = false)
private MemberImage saveMemberImage(MultipartFile file) {
if(file.getContentType().startsWith("image") == false) {
log.warn("이미지 파일이 아닙니다.");
return null;
}
String originalName = file.getOriginalFilename();
Path root = Paths.get(uploadPath, "member");
try {
ImageDTO imageDTO = fileService.createImageDTO(originalName, root);
MemberImage memberImage = MemberImage.builder()
.uuid(imageDTO.getUuid())
.fileName(imageDTO.getFileName())
.fileUrl(imageDTO.getFileUrl())
.build();
file.transferTo(Paths.get(imageDTO.getFileUrl()));
return imageRepository.save(memberImage);
} catch (IOException e) {
log.warn("업로드 폴더 생성 실패: " + e.getMessage());
}
return null;
}
}
file.transferTo
에서 파일이 로컬 스토리지에 저장됩니다.🔥
spring.servlet.multipart.location
은 yml파일에 추가한 값입니다. 이미지 파일이 저장될 로컬 경로를 나타낸 것으로, 이외에도 파일 최대 크기 설정도 진행합니다.
✅ application.ymlspring: servlet: multipart: max-file-size: 4MB max-request-size: 4MB location: 지정할 경로
참고자료
https://hello-bryan.tistory.com/343
https://chb2005.tistory.com/102
https://ssdragon.tistory.com/99