ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spring boot 파일, 이미지 업로드(multipartfile)
    SOLUX-완숙이 2022. 1. 25. 13:23

    게시판을 만들 때 필수적인 사진 저장 코드를 구현해봤다.

     

    1. application.properties

    #Multipart file
    spring.servlet.multipart.enabled=true
    spring.servlet.multipart.max-file-size=200MB
    spring.servlet.multipart.max-request-size=215MB

    2. Photo 클래스

    import com.fasterxml.jackson.annotation.JsonIgnore;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.NoArgsConstructor;
    import solux.wansuki.OurNeighbor_BE.domain.UsedGoods.UsedGoods;
    
    import javax.persistence.*;
    
    @Getter
    @NoArgsConstructor
    @Entity
    public class Photo {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "photo_id")
        private Long id;
    
        @Column(nullable = false)
        private String originalName;
    
        @Column(nullable = false)
        private String filePath;
    
        private Long fileSize;
    
        @JsonIgnore
        @ManyToOne
        @JoinColumn(name = "usedGoods_id")
        private UsedGoods usedGoods;
    
        @Builder
        public Photo(String originalName, String filePath, Long fileSize) {
            this.originalName = originalName;
            this.filePath = filePath;
            this.fileSize = fileSize;
        }
    
        public void setUsedGoods(UsedGoods usedGoods) {
            if (this.usedGoods != null) {
                this.usedGoods.getPhotos().remove(this);
            }
            this.usedGoods = usedGoods;
            //무한 루프 빠지지 않도록
            if (!usedGoods.getPhotos().contains(this))
                usedGoods.getPhotos().add(this);
        }
    }

    사진의 이름, 경로, 사이즈를 저장할 클래스를 만든다.

    그 후 usedGoods라는 중고 거래 게시판의 글 하나당 사진 여러개를 저장할 수 있기 때문에

    다대일 양방향 연관관계를 만들어준다. 

     

    3. UsedGoods 클래스

     @OneToMany(mappedBy = "usedGoods")
        private List<Photo> photos = new ArrayList<>();
    
        public void addPhoto(Photo photo) {
            this.photos.add(photo);
    
            if (photo.getUsedGoods() != this)
                photo.setUsedGoods(this);
        }

    양방향이기 때문에 usedGoods 클래스에도 photo와 연관 관계를 맺을 수 있도록 하는 addPhoto 메소드를 쓴다.

    - usedGoods 클래스의 addPhoto 메소드를 실행하면 photo의 setUsedGoods 메소드도 같이 실행되도록 편의 메소드를 작성했다.

     

    4. UsedGoodsController

    @PostMapping("/usedGoods")
        public Long save(
                @RequestParam(value = "file", required = false) List<MultipartFile> files,
                @RequestParam("title") String title,
                @RequestParam(value = "content", required = false) String content
        ) throws Exception{
            UsedGoodsSaveDto saveDto = UsedGoodsSaveDto.builder()
                    .title(title)
                    .content(content)
                    .build();
            return usedGoodsService.save(saveDto, files);
        }

    multipartfile은 @RequestBody로 받으면 오류가 발생하기 때문에 이처럼 @RequestParam으로 받아줬다. 

    중고 거래 게시판 작성글의 제목, 내용도 마찬가지로 @RequestParam으로 받았다.

     

    5. FileHandler

    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.multipart.MultipartFile;
    import solux.wansuki.OurNeighbor_BE.domain.Photo.Photo;
    
    import java.io.File;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.ArrayList;
    import java.util.List;
    
    @Component
    public class FileHandler {
    
        public List<Photo> parseFileInfo(List<MultipartFile> multipartFiles) throws Exception {
            List<Photo> fileList = new ArrayList<>();
            if (multipartFiles.isEmpty())
                return fileList;
    
            if (!CollectionUtils.isEmpty(multipartFiles)) {
                LocalDateTime now = LocalDateTime.now();
                DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
                String currentDate = now.format(dateTimeFormatter);
    
                String absolutePath = new File("").getAbsolutePath() + File.separator + File.separator;
                
                //프로젝트 폴더 내에 photo 폴더 생성 후 그 안에 현재 날짜로 파일 생성
                String path = "photo" + File.separator + currentDate;
                File file = new File(path);
    
    			//경로가 존재하지 않으면 디렉토리 생성
                if (!file.exists()) {
                    file.mkdirs();
                }
    
                for (MultipartFile multipartFile : multipartFiles) {
                    String originalFileExtension;
                    String contentType = multipartFile.getContentType();
    
                    if (ObjectUtils.isEmpty(contentType))
                        break;
                    else {
                        if (contentType.contains("image/jpeg"))
                            originalFileExtension = ".jpg";
                        else if (contentType.contains("image/png"))
                            originalFileExtension = ".png";
                        else
                            break;
                    }
    
    				//파일 이름이 중복되지 않도록 생성 시간의 나노초까지 구해서 이름으로 사용
                    String newFileName = System.nanoTime() + originalFileExtension;
                    Photo photo = Photo.builder()
                            .originalName(multipartFile.getOriginalFilename())
                            //photo 파일 아래 날짜 파일 아래 파일을 저장
                            .filePath(path + File.separator + newFileName)
                            .fileSize(multipartFile.getSize())
                            .build();
    
                    fileList.add(photo);
    
                    file = new File(absolutePath + path + File.separator + newFileName);
                    multipartFile.transferTo(file);
    
                    file.setWritable(true);
                    file.setReadable(true);
                }
            }
            return fileList;
        }
    }

    파일 핸들러를 통해 여러 개의 파일들의 이름을 각각 system의 나노초로 변환 후 photo 객체 생성 후 fileList에 넣어 반환한다.

     

    6. UsedGoodsService

    @Transactional
        public Long save(UsedGoodsSaveDto saveDto, List<MultipartFile> files) throws Exception{
            UsedGoods usedGoods = saveDto.toEntity();
            List<Photo> photoList = fileHandler.parseFileInfo(files);
            if (!photoList.isEmpty()) {
                for (Photo photo : photoList)
                    usedGoods.addPhoto(photoRepository.save(photo));
            }
            return usedGoodsRepository.save(usedGoods).getId();
        }

    fileHandler를 통해 반환받은 포토 리스트들을 각각 usedGoods 객체의 photo list에 추가한 후 usedGoods 객체를 저장한다.

     

     


    참고

    [Spring Boot] 게시판 구현 4 - 다중 파일(이미지) 업로드 MultipartFile (velog.io)

     

    [Spring Boot] 게시판 구현 4 - 다중 파일(이미지) 업로드 MultipartFile

    들어가기 앞서 이 글에서는 Spring Boot 초기 설정 및 FrontEnd 관련 내용은 다루지 않습니다. 또한 파일 중에서도 이미지 처리에 중점을 둔 점 참고해주시길 바랍니다. 🔎 구현해야 할 기능 게시글

    velog.io

    [Spring Boot] 파일(이미지) 업로드 구현하기 (velog.io)

     

    [Spring Boot] 파일(이미지) 업로드 구현하기

    MultipartFile 을 이용해 여러 개의 이미지 받기

    velog.io

     

Designed by Tistory.