상품 등록 기능 구현 -2-

jihan kong·2023년 2월 21일
0
post-thumbnail

상품 등록 기능 구현 2

상품을 등록하기 위해서 본격적으로 Controller 와 Service 단을 구현했다. 이미지 업로드에 관한 기능들을 구현하는데에도 많은 설정들이 필요했다. 상품 등록 기능 구현을 위해 어떤 로직들을 구현해야하는지 리스트를 먼저 작성해보았다.

ItemImg에 관한 Service, ItemService 를 중점적으로 구현하는 것에 초점을 맞췄다.

1. 이미지 업로드 기능 구현

상품을 등록할 때, 상품 대표 이미지 파일을 업로드할 수 있도록 해주었다. 따라서 이미지 파일 경로를 설정해주고 프로젝트 내부가 아닌 자신의 컴퓨터에서 파일을 찾는 경로를 설정해주어야한다.

1-1. application.properties 설정 추가

# 파일 한 개당 최대 사이즈
spring.servlet.multipart.max-file-size=20MB

# 요청당 최대 파일 크기
spring.servlet.multipart.max-request-size=100MB

# 상품 이미지 업로드 경로
itemImgLocation=C:/Spring_Shop/item

# 리소스 업로드 경로
uploadPath=file:///C:/Spring_Shop/

위와 같이 application.properties 설정을 통해 서버에서 각 파일의 최대 사이즈와 한 번에 다운을 요청할 수 있는 파일의 크기를 정할 수 있다. 또한, 컴퓨터에서 어떤 경로에 저장할지를 지정해주었다.


WebMvcConfig.java

package com.shop.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${uploadPath}")
    String uploadPath;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**")
                .addResourceLocations(uploadPath);
    }
}

정적인 리소스 (사진)을 외부 디렉토리에서 가져오기 위해선WebMvcConfigurer 를 implements한 WebMvcconfig 파일에서 addResourceHandlers 메소드를 구현하면 가능했다. /images로 시작하는 경우, uploadPath 에 설정한 폴더를 기준으로 파일을 읽어오게끔 했다.


2. 파일처리 기능 구현

다음으로는 파일을 처리할 수 있는 FileService 클래스를 만들었다.

FileService.java

package com.shop.service;

import lombok.extern.java.Log;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.util.UUID;

@Service
@Log
public class FileService {

    public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception {
        UUID uuid = UUID.randomUUID();                                                     
        String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
        String savedFileName = uuid.toString() + extension;                                   
        String fileUploadFullUrl = uploadPath + "/" + savedFileName;
        FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);                      
        fos.write(fileData);
        fos.close();
        return savedFileName;
    }

    public void deleteFile(String filePath) throws Exception {
        File deleteFile = new File(filePath);

        if(deleteFile.exists()) {
            deleteFile.delete();
            log.info("파일을 삭제하였습니다.");
        } else {
            log.info("파일이 존재하지 않습니다.");
        }
    }
}
  • UUID는 서로 다른 개체들을 구별하기 위해 이름을 부여할 때 사용한다.
  • UUID로 받은 값과 원래 파일의 이름의 확장자를 조합해서 저장될 파일 이름을 만든다.
  • FileOutputStream 클래스는 바이트 단위의 출력을 내보내는 클래스.
  • 파일이 저장된 경로를 이용하여 파일 객체를 생성하고 해당 파일이 존재하면 파일을 삭제하게끔 하였다.

상품의 이미지 정보를 저장하기 위해 ItemImgRepository 를 생성하고 JpaRepository 를 상속받게끔 한다. 상품 이미지를 업로드하고, 상품 이미지 정보를 저장하는 ItemImgService 클래스를 생성하였다.

3. ItemImgService 생성

ItemImgService.java

package com.shop.service;

import com.shop.entity.ItemImg;
import com.shop.repository.ItemImgRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.util.StringUtils;

import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class ItemImgService {

    @Value("${itemImgLocation}")
    private String itemImgLocation;

    private final ItemImgRepository itemImgRepository;

    private final FileService fileService;

    public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception {
        String oriImgName = itemImgFile.getOriginalFilename();
        String imgName = "";
        String imgUrl = "";

        // 파일 업로드
        if (!StringUtils.isEmpty(oriImgName)) {
            imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());
            imgUrl = "/images/item/" + imgName; 
        }

        // 상품 이미지 정보 저장
        itemImg.updateItemImg(oriImgName, imgName, imgUrl);
        itemImgRepository.save(itemImg);
    }
}
  • 상품의 이미지를 등록하면 저장할 경로와 파일의 이름, 파일을 파일의 바이트 배열을 파라미터로 uploadFile 메소드 호출하고 imgName 변수에 저장하게끔 한다.
  • 저장한 상품 이미지를 불러올 경로도 설정해주었다.
  • imgName : 실제 로컬에 저장된 상품 이미지 파일의 이름
    oriImgName : 업로드했던 상품 이미지 파일의 원래 이름
    imgUrl : 업로드 결과 로컬에 저장된 상품 이미지 파일을 불러오는 경로

4. ItemService 생성

ItemService.java

package com.shop.service;

// ..import 생략

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;
    private final ItemImgService itemImgService;
    private final ItemImgRepository itemImgRepository;

    public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList) throws Exception{

        //상품 등록
        Item item = itemFormDto.createItem();
        itemRepository.save(item);

        //이미지 등록
        for(int i=0;i<itemImgFileList.size();i++){
            ItemImg itemImg = new ItemImg();
            itemImg.setItem(item);

            if(i == 0)
                itemImg.setRepimgYn("Y");
            else
                itemImg.setRepimgYn("N");

            itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));
        }

        return item.getId();
    }
  • 상품 등록 폼으로부터 입력 받은 데이터를 이용해 item 객체를 생성했다.
  • 첫 번째 이미지일 경우 대표 상품 이미지 여부 값을 "Y"로 세팅했다. 나머지 상품 이미지는 "N"으로 설정하였다.

5. 상품 등록 url 추가

마지막으로 상품을 등록하는 url을 ItemController 클래스에 추가하였다.

ItemContoller.java

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemService itemService;

    @GetMapping(value = "/admin/item/new")
    public String itemForm(Model model) {
        model.addAttribute("itemFormDto", new ItemFormDto());
        return "item/itemForm";
    }

    @PostMapping(value = "/admin/item/new")
    public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, Model model, @RequestParam("itemImgFile")
            List<MultipartFile> itemImgFileList) {

        if (bindingResult.hasErrors()) { 
            return "item/itemForm";
        }

        if (itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null) {
            model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값입니다.");
            return "item/itemForm";
        }

        try {
            itemService.saveItem(itemFormDto, itemImgFileList);     
        } catch (Exception e) {
            model.addAttribute("errorMessage", "상품 등록 중 에러가 발생하였습니다.");
            return "item/itemForm";
        }

        return "redirect:/";
    }
  • 상품 등록 시에 필수 값이 없다면 다시 상품 등록 페이지로 전환하도록 했다.
  • 상품 등록 시 첫 번째 이미지가 없어도 에러메시지와 함께 상품 등록 페이지로 전환하게끔 했다.
  • try, catch 문으로 상품 저장 로직을 호출. 매개변수로 상품 정보와 이미지 정보를 담고 있는 itemImgFileList 를 넘겨주었다.

6. 동작화면🕹️

ADMIN Role 로 회원가입된 계정으로 로그인을 하면 위 내비게이션 메뉴에 상품 등록 탭이 있는 것을 확인할 수 있다.

강아지 물품 정보를 다음과 같이 입력하고,

열심히 구현한 상품 이미지 기능을 사용하기 위해 상품 이미지에서 Browse 버튼을 누르면 파일을 업로드할 수 있다. 미리 다운받아 놓은 이미지를 사용할 것이다.


저장 버튼을 누르고 메인 화면으로 돌아가면 다음과 같이 방금 등록한 상품 내용을 볼 수 있다.


느낀점💡

상품 등록 기능 구현을 마쳤다. 상품을 등록하고, 이미지를 첨부하는 기능 하나를 구현하기 위해서도 Repository, Service, Controller 를 일일히 만들어주어야하는데 실제 서비스를 운영한다고 하면 수 많은 Controller, Entity들이 존재하겠다는 생각을 했다. 구현하는데도 처음이라 익숙치 않은 부분들이 있었는데, 각 계층과 도메인들을 익히고 숙지해야겠다.

profile
학습하며 도전하는 것을 즐기는 개발자

2개의 댓글

comment-user-thumbnail
2023년 11월 1일

책 내용같은데 출처 안남겨도 되나요?

1개의 답글