본문 바로가기
프로젝트/개인 프로젝트 V1

(4) 상품 등록 구현 - 이미지 업로드

by Thumper 2022. 8. 1.

스프링은 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

댓글