커뮤니티 프로젝트

이미지 업로드 구현

쭈녁 2023. 1. 11. 21:39

게시물 포스팅 시 이미지를 첨부파일로 업로드하는 기능을 구현해보았다.

 

첨부 파일 Entity

@Data
@Entity
public class Attachment {
    public Attachment() {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String originName;
    private String storeName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    public Attachment(String originName, String storeName) {
        this.originName = originName;
        this.storeName = storeName;
    }
}

첨부파일의 원본 이름과 저장할 이름을 변수로 갖도록 작성하였고 첨부파일과 게시물이 다대일 연관관계가 되도록 @ManyToOne으로 묶어주었다.

 

AttachmentAddForm

@Data
public class AttachmentAddForm {

    private List<MultipartFile> imageFiles;

}

엔티티에 파일을 직접 저장하는 것이 아닌 원본명과 저장된 이름을 저장할 것이기 때문에 파일을 전달할 목적으로 AttachmentAddForm을 만든다.

 

Repository

public interface AttachmentRepository extends JpaRepository<Attachment, Long> {

}

기본적인 CRUD를 사용할 것임으로 JPA Repository를 상속 받아 사용하였다.  

 

FileStore(Service)

@Service
@RequiredArgsConstructor
@Slf4j
public class FileStore {
    private final AttachmentRepository attachmentRepository;

    @Value("${file.dir}")
    private String fileDir;

    public void storeFiles(List<MultipartFile> multipartFiles, Post post) throws IOException {
        List<Attachment> attachments = new ArrayList<>();
        for (MultipartFile multipartFile : multipartFiles) {
            if (!multipartFiles.isEmpty()) {
                Attachment attachment = storeFile(multipartFile);
                attachment.setPost(post);
                attachments.add(attachment);

                Attachment saved = attachmentRepository.save(attachment);
                log.info("이미지파일 ID ={}", saved.getId());
            }
        }
    }

    public String getFullPath(String fileName) {
        return fileDir + fileName;
    }

    public Attachment storeFile(MultipartFile multipartFile) throws IOException {
        if (multipartFile.isEmpty()) {
            return null;
        }
        String originalFilename = multipartFile.getOriginalFilename();
        String storeFileName = createFileName(originalFilename);

        multipartFile.transferTo(new File(getFullPath(storeFileName)));

        return new Attachment(originalFilename, storeFileName);
    }

    private String createFileName(String originalFilename) {
        String uu = UUID.randomUUID().toString();
        String extracted = extract(originalFilename);

        String storeFileName = uu + "." + extracted;
        return storeFileName;
    }

    private String extract(String originalFilename) {
        int pos = originalFilename.lastIndexOf(".");
        String ext = originalFilename.substring(pos + 1);
        return ext;
    }

}

 

서비스에서의 메서드는 크게 3가지로 구성하였다.

  • 확장자를 분리해오는 extract 메서드
  • 파일명의 중복 방지를 위해 UUID로 새로운 이름을 생성하는 createFileName 메서드
  • Requst로 받은 MultipartFile을 특정 경로에 저장하고 attachment 객체를 만드는 storeFile 메서드 -> 여기서 multipartFile이 Attachment객체로 바뀌어 생성된다.

그리고 위 3 메서드를 활용하는 메서드

  • List로 여러 MultipartFile이 들어왔을때 하나씩 꺼내 storeFile메서드를 수행하도록 하는 void storeFiles 메서드
  • 저장된 파일명을 포함한 모든 경로명을 return 하는 getFullPath 메서드를 만들었다.

application.properties에 아래와 같이 설정해주면

file.dir= /C:/Users/사용자명/Downloads/imageFile/

@Value(${flie.dir}) 를 사용하여 fileDir이라는 변수에 해당 경로값을 지정 할 수 있다.

 

MultiFile

MultiFile클래스의 경우 getOriginalFileName을 제공해준다.

아래 코드를 통해 특정 경로/ 파일명으로 해당 파일을 저장하도록 한다.

  • multipartFile.transferTo(new File(getFullPath(storeFileName)));

Controller(게시물 등록)

@GetMapping("/posts/add")
public String addForm(@ModelAttribute Post post, @ModelAttribute AttachmentAddForm attachmentForm) {
    return "post/addForm";
}

@PostMapping("/posts/add")
public String addPost(@Valid @ModelAttribute Post post, @ModelAttribute AttachmentAddForm attachmentForm, BindingResult bindingResult, RedirectAttributes redirectAttributes, @AuthenticationPrincipal PrincipalDetail principalDetail) throws IOException {
    if (bindingResult.hasErrors()) {
        log.info("error={}", bindingResult);
        return "post/addForm";
    }

    Post savedPost = postService.save(post, principalDetail.getMember());
    redirectAttributes.addAttribute("postId", savedPost.getId());
    redirectAttributes.addAttribute("status", true);

    List<MultipartFile> imageFiles = attachmentForm.getImageFiles();
    fileStore.storeFiles(imageFiles, post);


    return "redirect:/post/{postId}";
}

 

PostDTO를 만들어 이 객체에 MultipartFile을 담아서 전달해도 된다. 나의 경우는 이미 Post객체로 저장하도록 만들어 놓은 코드가 있어서 모두 수정하기엔 복잡했기에 AttachmentAddForm을 모델로 받아 받은 데이터를 Attachment객체에 저장하도록 컨트롤러를 짰다.

 

Controller(이미지 페이지)


@ResponseBody
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
    return new UrlResource("file:" + fileStore.getFullPath(filename));
}

위 코드는 파일명에 해당하는 URL을 생성하여 보여주는 컨트롤러이다. 이미지 파일을 저장하면 해당 이미지 파일의 이름을 파라미터로 URL을 생성하도록 하였다.

'커뮤니티 프로젝트' 카테고리의 다른 글

좋아요 List up 구현  (0) 2022.12.13
좋아요 기능 개발  (0) 2022.12.12
댓글 삭제 개발  (0) 2022.12.07
댓글 기능 개발 (댓글 저장 부분)  (0) 2022.12.02
회원정보 수정  (0) 2022.11.30