SOLUX-완숙이

spring boot 파일, 이미지 업로드(multipartfile)

leeeehhjj 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