상품 등록 기능 구현 2
상품을 등록하기 위해서 본격적으로 Controller 와 Service 단을 구현했다. 이미지 업로드에 관한 기능들을 구현하는데에도 많은 설정들이 필요했다. 상품 등록 기능 구현을 위해 어떤 로직들을 구현해야하는지 리스트를 먼저 작성해보았다.
ItemImg에 관한 Service
, ItemService
를 중점적으로 구현하는 것에 초점을 맞췄다.
상품을 등록할 때, 상품 대표 이미지 파일을 업로드할 수 있도록 해주었다. 따라서 이미지 파일 경로를 설정해주고 프로젝트 내부가 아닌 자신의 컴퓨터에서 파일을 찾는 경로를 설정해주어야한다.
# 파일 한 개당 최대 사이즈
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
에 설정한 폴더를 기준으로 파일을 읽어오게끔 했다.
다음으로는 파일을 처리할 수 있는 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("파일이 존재하지 않습니다.");
}
}
}
FileOutputStream
클래스는 바이트 단위의 출력을 내보내는 클래스.상품의 이미지 정보를 저장하기 위해 ItemImgRepository
를 생성하고 JpaRepository
를 상속받게끔 한다. 상품 이미지를 업로드하고, 상품 이미지 정보를 저장하는 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);
}
}
imgName
변수에 저장하게끔 한다.imgName
: 실제 로컬에 저장된 상품 이미지 파일의 이름oriImgName
: 업로드했던 상품 이미지 파일의 원래 이름imgUrl
: 업로드 결과 로컬에 저장된 상품 이미지 파일을 불러오는 경로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();
}
마지막으로 상품을 등록하는 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
를 넘겨주었다.ADMIN Role
로 회원가입된 계정으로 로그인을 하면 위 내비게이션 메뉴에 상품 등록 탭이 있는 것을 확인할 수 있다.
강아지 물품 정보를 다음과 같이 입력하고,
열심히 구현한 상품 이미지 기능을 사용하기 위해 상품 이미지에서 Browse 버튼을 누르면 파일을 업로드할 수 있다. 미리 다운받아 놓은 이미지를 사용할 것이다.
저장 버튼을 누르고 메인 화면으로 돌아가면 다음과 같이 방금 등록한 상품 내용을 볼 수 있다.
상품 등록 기능 구현을 마쳤다. 상품을 등록하고, 이미지를 첨부하는 기능 하나를 구현하기 위해서도 Repository
, Service
, Controller
를 일일히 만들어주어야하는데 실제 서비스를 운영한다고 하면 수 많은 Controller, Entity들이 존재하겠다는 생각을 했다. 구현하는데도 처음이라 익숙치 않은 부분들이 있었는데, 각 계층과 도메인들을 익히고 숙지해야겠다.
책 내용같은데 출처 안남겨도 되나요?