본문 바로가기

안녕하세요!

프로그래밍 언어/JSP

JSP_22-11-28_[상품 이미지 업로드]

[ 상품 이미지 업로드 ]

○ 순서

    - [뷰] 사용자가 이미지를 선택

    - [뷰] 선택된 이미지 서버로 전송

    - [서버] 전송 받은 이미지 저장 후, 저장한 이미지 정보를 [뷰]로 재전송

    - [뷰] 전송 받은 데이터 활용해 이미지 미리보기, <input> 태그에 데이터 저장

    - [뷰] '등록 버튼' 클릭

    - [서버] 이미지 정보 DB에 저장

 

[ 이미지 파일 업로드 기본 설정 ]

○ 라이브러리 추가

    - javax.servlet 3.0.0 혹은 3.1.0 추가 dependency 추가

 

○ web.xml 설정

    - namespace 3.1 코드로 변경

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	version="3.1">

    - multipart-config 설정 코드 <servlet> 태그 내부에 삽입

<!-- 업로드 관련 설정 -->
<multipart-config>
    <location>C:\\upload\\temp</location>		
    <max-file-size>20971520</max-file-size>					<!-- 1MB * 20 -->
    <max-request-size>41943040</max-request-size>			<!-- 40MB -->
    <file-size-threshold>20971520</file-size-threshold>		<!-- 20MB -->
</multipart-config>

    - enctype : 웹 클라이언트와 웹 서버 간의 데티어 주고 받을 때 해당 데이터의 인코딩 되는 방식

    - <location> : 업로드한 파일 임시로 저장되는 경로

    - <max-file-size> : 업로드 가능한 최대 파일 그키

    - <max-request-size> : 업로드 되는 파일의 최대 크기

    - <file-size-threshold> : 임시 파일로 저장 여부를 결정할 데이터 크기

 

○ servlet-context.xml 설정

    - 스프링에서는 multipart 데이터 처리 불가능하므로 multipartResolver 등록

<!-- 업로드 관련 빈 -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"></beans:bean>

 

[ productEnrollForm.jsp ]

○ 업로드 UI 추가

    - 'file' type <input> 태그 추가

<div class="form_section">
    <div class="form_section_title">
        <label>상품 이미지</label>
    </div>
    <div class="form_section_content">
    	<input type="file" id ="fileItem" name='uploadFile' style="height: 30px;">
    </div>
</div>

 

○ 이미지 업로드

    - fileList 객체가 맞는지 확인하기 위해 변수 선언 및 fileList로 초기화

    - formData : FromData.append(key, value) 메서드 사용

    - url : 서버로 요청을 보낼 url

    - processData : 서버로 전송할 데이터를 queryStirng 형태로 변환할지 여부

    - contentType : 서버로 전송되는 데이터의 content-type

    - data : 서버로 전송할 데이터

    - type : 서보 요청 타입(GET, POST)

    - dataType : 서버로부터 반환받을 데이터 타입

/* 이미지 업로드 */
$("input[type='file']").on("change", function(e){

		let formData = new FormData();
		let fileInput = $('input[name="uploadFile"]');
		let fileList = fileInput[0].files;
		let fileObj = fileList[0];

		if(!fileCheck(fileObj.name, fileObj.size)){
			return false;
		}
		formData.append("uploadFile", fileObj);
        
		$.ajax({
                    url: '/product/uploadAjaxAction',
                    processData : false,
                    contentType : false,
                    data : formData,
                    type : 'POST',
                    dataType : 'json',
                    success : function(result){
	    				console.log(result);
	    	},
            error : function(result) {
            	    alert("이미지 파일이 아닙니다.");
            }
		});

        
		// multiple 설정
		//for(let i = 0; i < fileList.length; i++){
		//	formData.append("uploadFile", fileList[i]);
		//}
        
		// alert("통과");
});

○ fileCheck

    - fileCheck 메서드에 fileName, fileSize 파라미터 부여

    - 두 조건 불만족 시, 경고문구, false

    - 두 조건 만족 시, 경고문구, true

	/* var, method related with attachFile */
	let regex = new RegExp("(.*?)\.(jpg|png)$");
	let maxSize = 1048576; //1MB	
	
	function fileCheck(fileName, fileSize){

		if(fileSize >= maxSize){
			alert("파일 사이즈 초과");
			return false;
		}
			  
		if(!regex.test(fileName)){
			alert("해당 종류의 파일은 업로드할 수 없습니다.");
			return false;
		}
		
		return true;		
		
	}

[ ProductController.java ]

○ c 경로에 upload 폴덩 생성

    - 설정한 용량 초과하는 파일 c/upload/temp에 저장

○ 첨부파일 업로드

    - MultipartFile 타입의 uploadFile 변수 매개변수 부여

    - 오늘 날짜 'yyyy/MM/dd' 형식의 String 데이터 얻기 위해 SimpleDateForamt, Date 클래스 사용

    - SimpleDateForamt : Date 클래스 통해 얻은 오늘의 날짜 지정된 형식의 문자열 데이터 생성 위해 사용

○ 썸네일 이미지 구성

"s_" + "uuid_" + "원본파일 이름. 이미지 타입"

○ ImageIO

    - ImageIO : 이미지 읽어오거나 생성하는 메서드

    - BufferedImage : 이미지 데이터 처리하거나 조작에 필요한 값과 메서드 제공

    - Graphics2D : 그림을 그리는 데 필요로 하는 설정값과 메서드 제공

/* 첨부 파일 업로드 */
@PostMapping(value="/uploadAjaxAction", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// 업로드 수행하는 url 매핑 메서드의 리턴 타입
public ResponseEntity<List<AttachImageVO>> uploadAjaxActionPOST(MultipartFile[] uploadFile) {

    logger.info("uploadAjaxActionPOST..........");
    
		/* 이미지 파일 체크 */
		for(MultipartFile multipartFile: uploadFile) {
			
        File checkfile = new File(multipartFile.getOriginalFilename());
        String type = null;

        try {
            type = Files.probeContentType(checkfile.toPath());
            logger.info("MIME TYPE : " + type);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(!type.startsWith("image")) {

            List<AttachImageVO> list = null;
            return new ResponseEntity<>(list, HttpStatus.BAD_REQUEST);

        }

    }// for
    
    String uploadFolder = "C:\\upload";
    
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
    // 오늘의 날짜 데이터를 얻기 위해 Date 클래스 타입 변수 선언 및 초기화
    Date date = new Date();
    
    // date 변수를 'yyyy-MM-dd' 형식의 문자열로 변환
    String str = sdf.format(date);
    
    // '-'를 경로를 replace 사용해 구분자 '\'로 변경
    String datePath = str.replace("-", File.separator);
    
    /* 폴더 생성 */
    // 개체화 해주는 코드에 인자 부여
    // 부모 경로 : uploadFolder
    // 자식 경로 : datePath
    File uploadPath = new File(uploadFolder, datePath);
	
    // File 클래스에서 대상 파일 혹은 디렉터리가 존재하는지 유무 반환
    if(uploadPath.exists() == false) {
        // mkdirs : 여러 개의 폴더 생성을 위한 메서드
        uploadPath.mkdirs();
    }
    
    /* 이미저 정보 담는 객체 */
    List<AttachImageVO> list = new ArrayList();
    
    // 향상된 for
    for(MultipartFile multipartFile : uploadFile) {
    
    	/* 이미지 정보 객체 */
    	AttachImageVO vo = new AttachImageVO();
    
        /* 파일 이름 */
        // getOriginalFilename() : 뷰로부터 전달받은 파일 이름 그대로 사용 위한 메서드
        String uploadFileName = multipartFile.getOriginalFilename();
        vo.setFileName(uploadFileName);
		vo.setUploadPath(datePath);
        
        /* uuid 적용 파일 이름 */
        String uuid = UUID.randomUUID().toString();
		vo.setUuid(uuid);

        uploadFileName = uuid + "_" + uploadFileName;

        /* 파일 위치, 파일 이름을 합친 File 객체 */
        // 파일 경로와 파일 이름 포함하는 File 객체로 초기화
        File saveFile = new File(uploadPath, uploadFileName);

        /* 파일 저장 */
        // transferTo : 파일을 저장하는 메서드
        try {
            multipartFile.transferTo(saveFile);
            
            File thumbnailFile = new File(uploadPath, "s_" + uploadFileName);
			
            // ImageIO의 read() 메서드 호출해 BufferedImage 타입으로 변경
            // BufferedImage 타입의 참조 변수 선언해 해당 변수에 대입
			BufferedImage bo_image = ImageIO.read(saveFile);
            
            /* 비율 */
            double ratio = 3;
            /*넓이 높이*/
            int width = (int) (bo_image.getWidth() / ratio);
            int height = (int) (bo_image.getHeight() / ratio);
            
            // 썸네일 이미지 설정(넓이, 높이, 생성될 이미지 타입)
			BufferedImage bt_image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
			
            // bt_image에서 createGraphics() 메서드 호출해 Graphic2D 객체 생성
            // Graphic2D 타입 참조 변수에 대입
			Graphics2D graphic = bt_image.createGraphics();
			
            // drawImage 메서드 호출해 원본 이미지(원본 BufferedImage)를
            // 썸네일 BufferdImage에 지정한 크기로 변경해 왼쪽 상단 "0,0" 좌표로부터 그려줌
			graphic.drawImage(bo_image, 0, 0, width, height, null);
			
            // 제작한 썸네일 이미지(bt_image)를 파일로 만들기
            // ImageIO의 write 메서드 호출해 파일로 저장
            // (파일로 저장할 이미지(bt_image), 어떠한 이미지 형식으로 저장할지 String 타입, 이미지 저장 경로와 이름 생성한 File 객체(thumbnailFile))
			ImageIO.write(bt_image, "jpg", thumbnailFile);
            
            
        } catch (Exception e) {
            e.printStackTrace();
        } 
        // AttachImageVO 객체 LIst 요소로 추가
        list.add(vo);
    
    
        //logger.info("-----------------------------------------------");
        //logger.info("파일 이름 : " + multipartFile.getOriginalFilename());
        //logger.info("파일 타입 : " + multipartFile.getContentType());
        //logger.info("파일 크기 : " + multipartFile.getSize());			
    }
    
    ResponseEntity<List<AttachImageVO>> result = new ResponseEntity<List<AttachImageVO>>(list, HttpStatus.OK);

	return result;
    

}

 

 

[ pom.xml ]

<!-- thumbnail -->
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.13</version>
</dependency>

○ uuid 사용

'유동적 경로', 'uuid', '원본 파일 이름'

원본 파일 : c:\upload + '유동적 경로' + 'uuid' + '_' + '원본 파일 이름'
썸네일 이미지 : c:\upload + '유동적 경로' + 's_' + 'uuid' + '_' + '원본 파일 이름'

 

[ AttachImageVO.java ]

@Data
public class AttachImageVO {

	/* 경로 */
	private String uploadPath;
	
	/* uuid */
	private String uuid;
	
	/* 파일 이름 */
	private String fileName;
	
	/* 상품 id */
	private int bookId;

}

 

[ pom.xml ]

○ jackson-databind : java 객체 데이터를 JSON 타입 테이터 변환해 뷰로 전송

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml -->
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.12.1</version>
</dependency>

[ ProductController.java ]

@GetMapping("/display")
public ResponseEntity<byte[]> getImage(String fileName){

	logger.info("getImage()............+ + fileName);
    // '\\' : 특수문자 '\' 인식할 수 있도록 이스케이프 문자 사용
	File file = new File("c:\\upload\\" + fileName);
    
    // 뷰로 반환할 ResponseEntity 객체 주소를 저장할 참조 변수 선언, null로 초기화
    ResponseEntity<byte[]> result = null;

    try {
		// Response의 header 관련 설정 객체 추가 위해 HttpHeaders 인스턴스화한 후 참조 변수 선언해 대입
        HttpHeaders header = new HttpHeaders();
		
        // File 클래스의 proveContentType()
        // header의 'Content Type' 속성 값에 이미지 파일 MIME TYPE 추가 위해 HttpHeader 클래스의 add() 메서드 사용
        // add(Response header의 속성명, 속성명에 부여할 값)
        // header의 'Content Type'에 대상 파일의 MIME TYPE 부여해주는 코드 추가
        header.add("Content-type", Files.probeContentType(file.toPath()));
        
		// FileCopyUtils : 파일과 stream 복사에 사용할 수 있는 메서드 제공하는 클래스
        // copyToByteArray() : 파라미터로 부여하는 File 객체 즉, 대상 파일 복사해 Byte 배열로 반환해주는 클래스
        result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file), header, HttpStatus.OK);

    }catch (IOException e) {
        e.printStackTrace();
    }

    return result;

}

○ ResponseEntity(body에 첨부할 데이터, header 설정 부여된 객체, 전송하고자 하는 상태 코드)

ResponseEntity(T body, MultiValueMap<String,String> headers, HttpStatus status)

[ productEnrollForm.java ]

<div class="form_section">
    <div class="form_section_title">
        <label>상품 이미지</label>
    </div>
    <div class="form_section_content">
    	<input type="file" id ="fileItem" name='uploadFile' style="height: 30px;">
        <div id="result_card">
			<div class="imgDeleteBtn">x</div>
		</div>
    </div>
</div>

 

[ productEnroll.css ]

<style type="text/css">
	#result_card img{
		max-width: 100%;
	    height: auto;
	    display: block;
	    padding: 5px;
	    margin-top: 10px;
	    margin: auto;	
	}
	#result_card {
		position: relative;
	}
	.imgDeleteBtn{
	    position: absolute;
	    top: 0;
	    right: 5%;
	    background-color: #ef7d7d;
	    color: wheat;
	    font-weight: 900;
	    width: 30px;
	    height: 30px;
	    border-radius: 50%;
	    line-height: 26px;
	    text-align: center;
	    border: none;
	    display: block;
	    cursor: pointer;	
	}
	
</style>
728x90
반응형

loading