-
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] 파일(이미지) 업로드 구현하기 (velog.io)
'SOLUX-완숙이' 카테고리의 다른 글
Access token으로 사용자 정보 가져오기 (1) 2022.01.28 spring boot 파일, 이미지 조회(multipartfile) (0) 2022.01.25 spring security+jwt 회원가입, 로그인 #4 (0) 2022.01.23 spring security+jwt 회원가입, 로그인 #3 (0) 2022.01.17 JWT(Json Web Token) (0) 2022.01.17