상품(item) 엔티티
상품 사진(item_image) 엔티티
public interface ItemRepository extends JpaRepository<Item, Long> {
}
public interface ItemImageRepository extends JpaRepository<ItemImage, Long> {
}
file.dir=C:/Users/Woomin/Desktop/study/ImageStorage/
@Component
public class FileHandler {
@Value("${file.dir}")
private String fileDir;
//파일 경로명
public String getFullPath(String filename) {
return fileDir + filename;
}
//파일 경로명(스토리지)에 사진 저장
public List<ItemImage> storeImages(List<MultipartFile> multipartFiles) throws IOException {
List<ItemImage> storeResult = new ArrayList<>();
for (MultipartFile multipartfile : multipartFiles) {
storeResult.add(storeImage(multipartfile));
}
return storeResult;
}
public ItemImage storeImage(MultipartFile multipartFile) throws IOException {
if (multipartFile.isEmpty()) {
return null;
}
// ex) image1.jpeg
String oriImageName = multipartFile.getOriginalFilename();
//서버에 저장될 파일명
String storeImageName = createStoreImageName(oriImageName);
//스토리지에 저장
multipartFile.transferTo(new File(getFullPath(storeImageName)));
return ItemImage.builder()
.originalName(oriImageName)
.storeName(storeImageName)
.build();
}
private String createStoreImageName(String oriImageName) {
String ext = extractExt(oriImageName); //jpeg
String uuid = UUID.randomUUID().toString();
return uuid + "." + ext;
}
//확장자 추출
private String extractExt(String oriImageName) {
int pos = oriImageName.lastIndexOf(".");
return oriImageName.substring(pos + 1);
}
}
@Value("${file.dir}")
로 application.properties에 작성해둔 이미지 업로드 경로를 쉽게 불러올 수 있습니다.public ItemImage storeImage(MultipartFile multipartFile)
메소드는 업로드된 이미지인 multipartFile로 원본 파일명, 서버에 저장될 파일명을 추출하고 스토리지에 저장하는 업무를 처리합니다.굳이 원본 파일명과 서버에 저장하는 파일명을 구분하는 이유?
사용자가 얼마 없다면 파일명이 중복되는 문제가 발생할 가능성이 없지만 만약 사용자가 수십만명이 된다면 파일명이 충분히 중복될 가능성이 높고 기존 파일 이름과 충돌이 나기 때문에 문제가 발생될 수 있습니다. 때문에 중복이 불가능한 UUID를 생성하고 서버에서 관리하는 별도의 파일명으로 저장합니다.
@Service
@RequiredArgsConstructor
@Transactional
public class ItemService {
private final ItemRepository itemRepository;
private final ItemImageRepository itemImageRepository;
private final FileHandler filehandler;
public Long saveItem(ItemServiceDTO itemServiceDTO, List<MultipartFile> multipartFileList) throws IOException {
Item item = Item.createItem(itemServiceDTO.getName(),
itemServiceDTO.getDescription(),
itemServiceDTO.getPrice(),
itemServiceDTO.getStockQuantity());
List<ItemImage> itemImages = filehandler.storeImages(multipartFileList);
for (ItemImage itemImage : itemImages) {
item.addItemImage(itemImageRepository.save(itemImage));
}
return itemRepository.save(item).getId();
}
}
public void addItemImage(ItemImage itemImage) {
itemImageList.add(itemImage);
itemImage.changeItem(this);
}
@Controller
@Slf4j
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/items/new")
public String createItemForm(Model model) {
model.addAttribute("itemForm", new ItemForm());
return "item/itemForm";
}
@PostMapping("/items/new")
public String createItem(@Valid @ModelAttribute ItemForm itemForm, BindingResult bindingResult, Model model,
@RequestPart(name = "itemImages") List<MultipartFile> multipartFiles
) throws IOException {
if (bindingResult.hasErrors()) return "item/itemForm";
//상품 이미지를 등록안하면
if (multipartFiles.get(0).isEmpty()) {
model.addAttribute("errorMessage", "상품 사진을 등록해주세요!");
return "item/itemForm";
}
itemService.saveItem(itemForm.toServiceDTO(), multipartFiles);
return "redirect:/userHome";
}
/**
* 컨트롤러와 서비스간 통신을 할 때, 컨트롤러가 뷰와 통신할 때 사용한 DTO를 그대로 사용하면
* 강한 의존이 생겨 위험!!
*/
}
@PostMapping("/items/new")
@ResponseBody
public ResponseEntity<String> createItem(@ModelAttribute(name = "itemForm") ItemForm form,
@RequestPart(name = "itemImages") List<MultipartFile> multipartFiles) throws IOException {
itemService.saveItem(form.toServiceDTO(), multipartFiles);
return ResponseEntity.ok("이미지 업로드 성공");
}
상품 등록 화면 없이 PostMan을 이용해서 테스트를 진행해보았습니다.
성공적으로 스토리지에도 사진들이 업로드 된것을 확인하였습니다.