Spring

Spring 30 - adminlte3을 사용한 CRUD (파일업로드)

inderrom 2023. 2. 17. 18:55
파일 업로드를 해보자

 

<create.jsp>

  • 파일 업로드 추가하기
  • form 태그안에 enctype="multipart/form-data" 추가
  • <form name="frm" action="/product/create?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
<div class="form-group"> <label for="exampleInputFile">상품 이미지</label> <div class="input-group"> <div class="custom-file"> <input type="file" class="custom-file-input" id="productImage" name="productImage" multiple /> <label class="custom-file-label" for="exampleInputFile">이미지 선택</label> </div> </div> </div> </div>

 

등록버튼 눌렀을 때 항목 추가됨

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<script type="text/javascript" src="/resources/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/resources/js/jquery-3.6.0.js"></script>
<script type="text/javascript">
$(function(){
	$("#btnAuto").on("click",function(){
		$.ajax({
			url:"/product/getproductId",
			type:"post",
			beforeSend : function(xhr) {   // 데이터 전송 전  헤더에 csrf값 설정
                xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
			},
			success:function(res){
				$("input[name='productId']").val(res);
			}
		});
	});
})
</script>

<div class="card card-primary">
	<div class="card-header">
		<h3 class="card-title">상품 등록</h3>
	</div>
	<!-- 
	요청URI : /product/create
	요청방식 : post
	메소드명 : createPost
	PRODUCT 테이블에 입력해보자
	 -->
	<form name="frm" action="/product/create?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
		<div class="card-body">
			<div class="form-group">
				<label for="productId">상품아이디</label> <input
					type="text" name="productId" class="form-control" id="productId"
					placeholder="상품아이디를 입력해주세요" required  readonly />
				<button type="button" id="btnAuto" class="btn bg-gradient-primary">자동생성</button>
			</div>

			<div class="form-group">
				<label for="pname">상품명</label> <input
					type="text" name="pname" class="form-control" id="pname"
					placeholder="상품명을 입력해주세요" required />
			</div>
			<div class="form-group">
				<label for="unitPrice">판매 가격</label> <input
					type="number" name="unitPrice" class="form-control" id="unitPrice"
					placeholder="판매 가격을 입력해주세요" required />
			</div>
			<div class="form-group">
				<label for="description">상품 설명</label> 
					<textarea cols="30" rows="5" name="description" class="form-control" id="description"
					placeholder="판매 가격을 입력해주세요"></textarea>
			</div>
			<div class="form-group">
				<label for="manufacturer">제조사</label> <input
					type="text" name="manufacturer" class="form-control" id="manufacturer"
					placeholder="제조사를 입력해주세요" required />
			</div>
			<div class="form-group">
				<label for="category">카테고리</label> <input
					type="text" name="category" class="form-control" id="category"
					placeholder="카테고리를 입력해주세요" required />
			</div>
			<div class="form-group">
				<label for="unitsInStock">재고수</label> <input
					type="number" name="unitsInStock" class="form-control" id="unitsInStock"
					placeholder="재고수를 입력해주세요" value="0" />
			</div>
			<div class="form-group">
				<label for="category">상태</label> 
				<div class="col-sm-6">
					<div class="form-group">
						<select id="condition" name="condition" class="form-control">
							<option value="New">New</option>
							<option value="Old">Old</option>
							<option value="Refurbished">Refurbished</option>
						</select>
					</div>
				</div>
			</div>
				<div class="form-group">
				<label for="exampleInputFile">상품 이미지</label>
				<div class="input-group">
					<div class="custom-file">
						<input type="file" class="custom-file-input" 
							id="productImage" name="productImage" multiple />
						<label class="custom-file-label" for="exampleInputFile">이미지 선택</label>
					</div>
				</div>
			</div>
		</div>
		<div class="card-footer">
			<button type="submit" class="btn btn-primary">등록</button>
		</div>
		<!-- Cross Site Request Forgery -->
		<sec:csrfInput/>
	</form>
</div>
<script type="text/javascript">
CKEDITOR.replace("description");
</script>

 

 

- resourecs 안에 uplaod 폴더 만들어주기

 

<ProductController.java>

 

- getFolder 메서드 생성

//연/월/일 폴더 생성
	public static String getFolder() {
		// 날짜 형식
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		//날짜 객체 생성
		Date date = new Date();
		//2023-02-17
		String str = sdf.format(date);
		
		return str.replace("-", File.separator);
	}

  • File saveFile = new File(uploadPath, uploadFileName);
  • 여기에서 , 를 써주는 이유는 ,를 써야 파일 안으로 들어가기 때문이다 + 를 쓰는 경우 폴더 밖에 저장됨

 

- checkImageType 매서드 생성

	public static boolean checkImageType(File file) {
	      /*
	       .jpeg / .jpg(JPEG 이미지)의 MIME 타입 : image/jpeg
	       */
	      //MIME 타입을 통해 이미지 여부 확인
	      //file.toPath() : 파일 객체를 path객체로 변환
	      try {
	    	  // Files : java.nio.file
	         String contentType = Files.probeContentType(file.toPath());
	         log.info("contentType : " + contentType);
	         //MIME 타입 정보가 image로 시작하는지 여부를 return
	         return contentType.startsWith("image");
	      } catch (IOException e) {
	         e.printStackTrace();
	      }
	      //이 파일이 이미지가 아닐 경우
	      return false;
	   }
  • createPost 매서드 수정
	@PostMapping("/create")
	public String createPost(@ModelAttribute ProductVO productVO) {
		/*
		 productVO : ProductVO(productId=P1237, pname=Galaxy Note 20, unitPrice=1000
		 , description=<p>상품 설명</p>
		, manufacturer=Samsung, category=Smart Phone, unitsInStock=1000, condition=New
		, filename=null, quantity=0, productImage=null)
		 */
		log.info("productVO : " + productVO);
		
		
		//2) 파일이 저장되는 경로 세팅(/resuorces/upload)
		//	String uploadFolder = ...
		String uploadFolder = "C:\\eGovFrameDev-3.10.0-64bit\\workspace\\egovProj\\src\\main\\webapp\\resources\\upload";
		
		//2) 연/월/일 폴더가 없으면 생성
		// File uploadPath = new File(uplaodFolder, getFolder());
		File uploadPath = new File(uploadFolder, getFolder());
		if(uploadPath.exists()==false) {
			uploadPath.mkdirs();
		}
		
		//3) MultipartFile[] productImage = productVO.getProductImage();
		//		for(MultipartFile multipartFile : productImage)
		MultipartFile[] productImage = productVO.getProductImage();
		
		
		String uploadFileName ="";
		
		for(MultipartFile multipartFile : productImage) {
			log.info("---------------");
			log.info("upload file name : " + multipartFile.getOriginalFilename());
			log.info("upload file size :" + multipartFile.getSize() );
			
			//3-1) UUID 처리
			uploadFileName = multipartFile.getOriginalFilename();
			
			//3-2) uploadFileName = uuid.toString() + "_" + uploadFileName;
			UUID uuid = UUID.randomUUID();
			uploadFileName = uuid.toString() + "_" + uploadFileName;
			
			//3-3) File객체 설계(복사할 대상 경로, 파일명)
			//		File saveFile = new File(uplaodPath, uploadFileName)
			File saveFile = new File(uploadPath, uploadFileName);
			//3-4) 파일 복사
			// 		multipartFile.transferTo(saveFile);
			try {
				multipartFile.transferTo(saveFile);
				
				//3-5) 썸네일 처리
				if(checkImageType(saveFile)) {
					FileOutputStream thumbnail = new FileOutputStream(
							new File(uploadPath, "s_"+uploadFileName)
							);
					Thumbnailator.createThumbnail(multipartFile.getInputStream(),thumbnail,100,100);
					thumbnail.close();
				}
			} catch (IllegalStateException | IOException e) {
				log.error(e.getMessage());
			}
		}
		//4) productVO.setFilename(파일full경로+명)
		//2024/02/17/파일명.jpg
		String filename = "/" + getFolder().replaceAll("\\", "/") + "/" + uploadFileName;
		productVO.setFilename(filename);
		
		int result = this.productService.createPost(productVO);
		log.info("result : " + result);
		
		if(result > 0) { // 등록 성공
			//redirect : URI 재요청
			return "redirect:/product/list";
		}else { //등록 실패
			return "redirect:/product/create";
		}
		
	}

 


package kr.or.ddit.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import kr.or.ddit.service.ProductService;
import kr.or.ddit.vo.ProductVO;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnailator;

@Slf4j
@RequestMapping("/product")
@Controller
public class ProductController {
	@Autowired
	ProductService productService;
	
	@GetMapping("/list")
	public String list(Model model){
		List<ProductVO> productVOList = this.productService.list();
		
		log.info("productVOList : " + productVOList);
		
		model.addAttribute("pvo", productVOList);
		
		//forwarding. product폴더의 list.jsp를 리턴
		return "product/list";
	}
	
	//요청URI : /product/create
		//요청방식 : get
		@GetMapping("/create")
		public String create() {

			//fowarding
			//product폴더의 create.jsp를 forwarding
			return "product/create";
		}
		
	@PostMapping("/create")
	public String createPost(@ModelAttribute ProductVO productVO) {
		/*
		 productVO : ProductVO(productId=P1237, pname=Galaxy Note 20, unitPrice=1000
		 , description=<p>상품 설명</p>
		, manufacturer=Samsung, category=Smart Phone, unitsInStock=1000, condition=New
		, filename=null, quantity=0, productImage=null)
		 */
		log.info("productVO : " + productVO);
		
		
		//2) 파일이 저장되는 경로 세팅(/resuorces/upload)
		//	String uploadFolder = ...
		String uploadFolder = "C:\\eGovFrameDev-3.10.0-64bit\\workspace\\egovProj\\src\\main\\webapp\\resources\\upload";
		
		//2) 연/월/일 폴더가 없으면 생성
		// File uploadPath = new File(uplaodFolder, getFolder());
		File uploadPath = new File(uploadFolder, getFolder());
		if(uploadPath.exists()==false) {
			uploadPath.mkdirs();
		}
		
		//3) MultipartFile[] productImage = productVO.getProductImage();
		//		for(MultipartFile multipartFile : productImage)
		MultipartFile[] productImage = productVO.getProductImage();
		
		
		String uploadFileName ="";
		
		for(MultipartFile multipartFile : productImage) {
			log.info("---------------");
			log.info("upload file name : " + multipartFile.getOriginalFilename());
			log.info("upload file size :" + multipartFile.getSize() );
			
			//3-1) UUID 처리
			uploadFileName = multipartFile.getOriginalFilename();
			
			//3-2) uploadFileName = uuid.toString() + "_" + uploadFileName;
			UUID uuid = UUID.randomUUID();
			uploadFileName = uuid.toString() + "_" + uploadFileName;
			
			//3-3) File객체 설계(복사할 대상 경로, 파일명)
			//		File saveFile = new File(uplaodPath, uploadFileName)
			File saveFile = new File(uploadPath, uploadFileName);
			//3-4) 파일 복사
			// 		multipartFile.transferTo(saveFile);
			try {
				multipartFile.transferTo(saveFile);
				
				//3-5) 썸네일 처리
				if(checkImageType(saveFile)) {
					FileOutputStream thumbnail = new FileOutputStream(
							new File(uploadPath, "s_"+uploadFileName)
							);
					Thumbnailator.createThumbnail(multipartFile.getInputStream(),thumbnail,100,100);
					thumbnail.close();
				}
			} catch (IllegalStateException | IOException e) {
				log.error(e.getMessage());
			}
		}
		//4) productVO.setFilename(파일full경로+명)
		//2024/02/17/파일명.jpg
		String filename = "/" + getFolder().replaceAll("\\\\", "/") + "/" + uploadFileName;
		productVO.setFilename(filename);
		
		int result = this.productService.createPost(productVO);
		log.info("result : " + result);
		
		if(result > 0) { // 등록 성공
			//redirect : URI 재요청
			return "redirect:/product/list";
		}else { //등록 실패
			return "redirect:/product/create";
		}
		
	}
	
	// 상품아이디 자동생성
	//json으로 응답 시 ResponseBody
	//1)String
	//2)List<..VO>, ..VO, Map<String,String>,List<Map<String,String>> : dataType="json"
	@ResponseBody
	@PostMapping("/getproductId")
	public String getproductId() {
		
		String productId = this.productService.getproductId();
		//dataType="json" 생략
		//List<..VO> =>$.each(result,function(index,data)
		//result : List<ProductVO>
		//data : ProductVO
		//index : 0부터1씩 증가
		return productId;
	}
	
	// 요청URI : /product/detail?productId=P1234
	// 요청URI : /product/detail
	// 요청파라미터 : productId = P1234
	@GetMapping("/detail")
	public String detail(@ModelAttribute ProductVO productVO, Model model) {
		log.info("productVO : " + productVO);
		
		//detail서비스를 구현해보자
		//구현전 : productVO{productId=P1234,pname=null,unitPrice=0...}
		productVO = this.productService.detail(productVO);
		//구현후 : productVO{productId=P1234,pname=iphone 6s, unitPrice=800000...}
		
		model.addAttribute("data", productVO);
		
		//forwarding -> /views/product/폴더 안의 detail.jsp를 리턴
		return "product/detail";
	}
	
	//요청URI : /product/updatePost
	//요청파라미터 : {productId=P1234,pname=iPhone 6s,unitPrice=800000...}
	//요청방식 : post
	@PostMapping("/updatePost")
	public String updatePost(@ModelAttribute ProductVO productVO) {
		log.info("productVO : " + productVO);
		//기본키 데이터 백업
		String oldProductId = productVO.getProductId();
		
		//서비스 처리(merge into로 처리해보자)
		int result = this.productService.updatePost(productVO);
		log.info("result : " + result);
		
		//redirect
		return "redirect:/product/detail?productId="+oldProductId;
	}
	
	@PostMapping("/deletePost")
	public String deletePost(@ModelAttribute ProductVO productVO) {
		
		int result = this.productService.deletePost(productVO);
		
		if(result >0) {
			return "redirect:/product/list";
		}else {
			return "redirect:/detail?productId"+productVO.getProductId();
		}
	}
	
	//연/월/일 폴더 생성
	public static String getFolder() {
		// 날짜 형식
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		//날짜 객체 생성
		Date date = new Date();
		//2023-02-17
		String str = sdf.format(date);
		
		return str.replace("-", File.separator);
	}
	
	public static boolean checkImageType(File file) {
	      /*
	       .jpeg / .jpg(JPEG 이미지)의 MIME 타입 : image/jpeg
	       */
	      //MIME 타입을 통해 이미지 여부 확인
	      //file.toPath() : 파일 객체를 path객체로 변환
	      try {
	    	  // Files : java.nio.file
	         String contentType = Files.probeContentType(file.toPath());
	         log.info("contentType : " + contentType);
	         //MIME 타입 정보가 image로 시작하는지 여부를 return
	         return contentType.startsWith("image");
	      } catch (IOException e) {
	         e.printStackTrace();
	      }
	      //이 파일이 이미지가 아닐 경우
	      return false;
	   }
	
}

 

 

<product_SQL.xml>

  • createPost 수정
  • filename 을 추가해준다.
	
	<!-- 상품 등록 
	productVO
	생략(int)
	-->
	<insert id="createPost" parameterType="productVO">
		<selectKey resultType="String" order="BEFORE" keyProperty="productId">
			SELECT SUBSTR(MAX(PRODUCT_ID),1,1)
			     || (SUBSTR(MAX(PRODUCT_ID),2) + 1)
			FROM   PRODUCT
		</selectKey>
	
		INSERT INTO PRODUCT(PRODUCT_ID, PNAME, UNIT_PRICE, DESCRIPTION, MANUFACTURER
		, CATEGORY, UNITS_IN_STOCK, CONDITION, FILENAME) 
		VALUES(#{productId},#{pname},#{unitPrice},#{description},#{manufacturer}
		,#{category},#{unitsInStock},#{condition},#{filename})
	</insert>

 

이렇게 처리하면 데이터 베이스에 추가가 된다.


상세보기에서 파일이미지를 보기 위해

<detail.jsp> 추가

<div class="form-group">
        <label for="unitsInStock">상품 이미지</label>
        <div class="col-sm-6">
            <img src="/resources/upload${data.filename}" />
        </div>
    </div>
</div>

 

등록을 하고 상세보기를 누르면 이미지가 나온다.