스프링은 MultipartFile 이라는 인터페이스로 멀티파트 파일을 매우 편리하게 지원한다.
Multipart로 넘어온 파일을 파일정보DTO로 반환해서 상품 이미지를 저장해보자.
이미지 업로드
- FileService를 통해 파일정보(이미지 경로, 이미지 이름, 저장된 이미지 파일명, 대표 이미지 여부)를 DTO로 반환한다.
- ItemImgService를 통해 DTO를 Entity로 반환해 ItemImg를 얻는다.
- DB에 이미지 엔티티를 저장한다.
application.properties 설정
file.dir=C:/newThing/chimm/
- 원하는 파일저장경로를 설정해주면 된다.
- @Value("${file.dir}")로 불러다가 사용한다.
FileService : Multipart를 파일정보 DTO로 반환해준다.
@Service
@Slf4j
public class FileService {
@Value("${file.dir}")
private String fileDir;
public String getFullPath(String filename) {
return fileDir + filename;
}
//multipartFile --> fileInfo 반환
public FileInfo storeFile(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
// 원래 파일명 추출
String originalFilename = multipartFile.getOriginalFilename();
// uuid 저장파일명 생성
String savedFileName = createStoreFileName(originalFilename);
// 파일을 불러올 때 사용할 파일 경로 (예: /file:/users/.../nameh8787bghh33.png)
String savedFilePath = fileDir + savedFileName;
FileInfo fileInfo = new FileInfo();
fileInfo.updateItmImg(originalFilename,savedFileName,savedFilePath);
// 실제로 로컬에 uuid 파일명으로 저장하기
multipartFile.transferTo(new File(savedFilePath));
log.info("fileInfo={}", fileInfo.getSavePath());
return fileInfo;
}
// uuid 파일명 생성 메서드
private String createStoreFileName(String originalFilename) {
String fileExtension = getFileExtension(originalFilename);
String uuid = UUID.randomUUID().toString();
//uuid.확장자 로 만들기
String savedFileName = uuid + "." + fileExtension;
return savedFileName;
}
// 확장자 추출 메서드
private String getFileExtension(String originalFilename) {
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
return extension;
}
}
storeFile()
multipartFile를 받아, fileInfo(상품 이미지 정보 DTO)로 반환해준다.
createStoreFileName()
서버 내부에서 관리하는 파일명은 유일한 이름을 생성하는 UUID 를 사용해서 충돌하지 않도록 한다.
getFileExtension()
확장자를 별도로 추출해서 서버 내부에서 관리하는 파일명에도 붙여준다.
ItemImgService : 상품 이미지를 저장한다.
@Service
@Transactional
@RequiredArgsConstructor
public class ItemImgService {
private final FileService fileService;
private final ItemImgRepository imgRepository;
// DTO --> ItemImg (Entity) 저장
public Long saveItemImg(ItemInfo itemInfo, MultipartFile multipartFile) throws IOException {
FileInfo fileInfo = fileService.storeFile(multipartFile);
ItemImg imgEntity = ItemImg.imgBuilder()
.originImgName(fileInfo.getOriginImgName())
.imgName(fileInfo.getImgName())
.savePath(fileInfo.getSavePath())
.reImgYn(itemInfo.getRepImgYn())
.item(itemInfo.getItem())
.build();
ItemImg saved = imgRepository.save(imgEntity);
return saved.getId();
}
}
ItemService : 상품을 저장하고 조회한다.
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class ItemService {
private final ItemRepository itemRepository;
private final ItemImgService imgService;
private final ItemImgRepository imgRepository;
public Long saveItem(User user, ItemFormDto itemFormDto, List<MultipartFile> multipartFileList) throws IOException {
Item item = itemFormDto.toEntity();
item.setUpUser(user);
Long id = itemRepository.save(item).getId();
//대표 이미지 구별
for (int i=0; i<multipartFileList.size(); i++) {
ItemInfo itemInfo = new ItemInfo();
itemInfo.setItem(item);
if (i==0)
itemInfo.setRepImgYn("Y");
else
itemInfo.setRepImgYn("N");
imgService.saveItemImg(itemInfo, multipartFileList.get(i));
}
return id;
}
@Transactional(readOnly = true)
public ItemFormDto getItemDetail(Long itemId) {
List<ItemImg> itemImgList = imgRepository.findAllByItem_id(itemId);
List<ItemImgDto> itemImgDtoList = new ArrayList<>();
for (ItemImg itemImg : itemImgList) {
ItemImgDto itemImgDto = ItemImgDto.of(itemImg);
itemImgDtoList.add(itemImgDto);
}
//item(Entity) -> itemformDto(DTO) --> itemimgdtoList (setting)
Item item = itemRepository.findById(itemId)
.orElseThrow(() -> new IllegalArgumentException("Invalid board id= " + itemId ));
ItemFormDto itemFormDto = ItemFormDto.of(item);
itemFormDto.setItemImgDtoList(itemImgDtoList);
return itemFormDto;
}
saveItem (상품저장)
- 로그인된 계정으로 상품등록한다.
- 대표 이미지는 list의 첫번째 값으로 지정한다.
getItemDetail (상품 상세페이지)
- 상품ID로 상품 이미지 엔티티를 조회한다.
- 상품 이미지 엔티티를 itemImgDto(DTO)로 변환한다.
- 상품ID로 상품 엔티티를 조회한다.
- 상품 엔티티를 itemFormDto(DTO)로 변환한다.
- ModelMapper로 간단하게 엔티티를 DTO로 변환한다.
- ItemImgDto, itemFormDto를 상품 상세페이지에 들어갈 DTO로 변환한다.
DTO
FileInfo
fileService에서 fileInfo.updateItmImg(originalFilename,savedFileName,savedFilePath)하여 데이터를 저장한다.
- originImgName : 고객이 업로드한 파일명
- imgName : 서버 내부에서 관리하는 파일명
@Data
public class FileInfo {
private String imgName;
private String originImgName;
private String savePath;
//dto :set() 메소드를 만들었다.
public void updateItmImg(String originImgName, String imgName, String savePath) {
this.originImgName = originImgName;
this.imgName = imgName;
this.savePath = savePath;
}
}
ItemInfo
- imgRepository.save(imgEntity)에 넣을 엔티티 생성할 때 dto를 넣는다.
- ItemImg 엔티티 생성 시, 해당 데이터를 넣는다.
@Data
public class ItemInfo {
private String repImgYn;
private Item item;
}
ItemImgDto
상품 상세정보를 조회할 때 이미지정보를 DTO로 넘긴다.
@Data
public class ItemImgDto {
private Long id;
private String imgName;
private String oriImgName;
private String savePath;
private String repImgYn;
private static ModelMapper modelMapper = new ModelMapper();
//ItemImg(엔티티) ---> ItemImgDto(DTO) 변환
public static ItemImgDto of(ItemImg itemImg) {
return modelMapper.map(itemImg,ItemImgDto.class);
}
}
ItemFormDto
item(엔티티)를 dto로 변환하여 사용한다.
@Data
public class ItemFormDto {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
private String itemType;
private String categoryType;
private ItemSellStatus status;
//이미지dto를 저장하는 리스트
private List<ItemImgDto> itemImgDtoList = new ArrayList<>();
//dto에서 item을 생성하는 메서드
public Item toEntity() {
return Item.itemBuilder()
.itemName(itemName)
.price(price)
.stockQuantity(quantity)
.itemType(itemType)
.categoryType(categoryType)
.status(status)
.build();
}
private static ModelMapper modelMapper = new ModelMapper();
//item(엔티티) ---> itemFormDto(DTO)로 변환
public static ItemFormDto of(Item item) {
return modelMapper.map(item,ItemFormDto.class);
}
}
Controller
@PostMapping("/item/new")
public String itemNew(@Login User loginMember, ItemFormDto itemFormDto, BindResult bindResult,
Model model, @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList, RedirectAttributes redirectAttributes) throws IOException {
if (itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null) {
model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다.");
return "item/itemForm";
}
Long id = itemService.saveItem(loginMember, itemFormDto, itemImgFileList);
log.info("itemInfo={}", itemService.findById(id).toString());
redirectAttributes.addAttribute("itemId", id);
return "redirect:/item/{itemId}";
}
@GetMapping("/item/{itemId}")
public String itemDetail(@PathVariable Long itemId, Model model) {
ItemFormDto formDto = itemService.getItemDetail(itemId);
for (ItemImgDto itemImgDto : formDto.getItemImgDtoList()) {
log.info("img imgName={} savePath={}", itemImgDto.getImgName(),itemImgDto.getSavePath());
}
model.addAttribute("item", formDto);
return "item/itemDetailV2";
}
@ResponseBody
@GetMapping("/images/{filename}")
public Resource downloadImage(@PathVariable String filename) throws MalformedURLException {
//file:/users/.../nameh8787bghh33.png 이 uuid 경로를 찾아가지고,
//urlResource가 찾는다.
return new UrlResource("file:" + fileService.getFullPath(filename));
}
@PostMapping("item/new") : 상품등록
@GetMapping("/item/{itemId}") : 상품 상세 페이지
@GetMapping("/images/{filename}") :이미지 출력
- UrlResource 로 파일을 읽어서 @ResponseBody 로 이미지 바이너리를 반환한다.
- UrlResource는 Resource 인터페이스의 구현체로 new UrlResource("file:" + "파일이 저장된 경로") 로 사용하면된다.
폼화면 작성
- form 태그의 enctype="multipart/form-data"로 설정을 해주어야 한다.
- input 태그의 multiple="multiple" : 여러 개의 파일을 선택할 수 있도록 해준다.
느낀점 / 보완사항
- 예외처리, 검증로직을 추가해야 겠다.
- 이미지 수정/삭제 로직을 추가해야 겠다.
'프로젝트 > 개인 프로젝트 V1' 카테고리의 다른 글
(6) 페이징 처리 (0) | 2022.08.02 |
---|---|
(5) domain, repository, service 계층별 설계 및 구현 (0) | 2022.08.01 |
(3) 상품 등록 계층 설계 (0) | 2022.08.01 |
(2) 엔티티와 테이블 설계 (0) | 2022.08.01 |
(1) 계획 (0) | 2022.08.01 |
댓글