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>