ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 게시판 파일 첨부 (1. 파일을 임시 보관소에 저장하기 )
    Spring 2023. 4. 14. 15:09

    게시판 글쓰기에 파일 첨부 및 파일 불러오기

     : 유저들이 게시글에 파일을 첨부하거나 이미지를 불러올 수 있는 기능을 구현해보자.

    사실, 이 부분은 textarea의 에디터를 사용하면 더욱 편리하고, 유저가 원한는 위치에 배치 하는 것도 가능하다.

    하지만, 스프링에서 해당 기능을 구현해보는 것에 의의를 둔다.

     


    파일을 임시 보관소에 저장하기

     : 보내고자 하는 파일을 게시글 작성 브라우저에 미리 등록 시킨 다음 게시글 작성 버튼을 클릭하면

    게시글 본문과 함께 데이터베이스에 저장하는 기능을 구현할 것이다.

     

     

    1) jsp 작성 , AttachFileVO.java 작성

     그러기 위해서 upload할 파일을 불러오는 버튼과 임시 저장소로 전송하는 버튼, 임시 저장소에 저장 된 파일을 미리 확인할 수 있도록 썸네일을 불러오는 div를 작성했다.

     

    파일 업로드 경로, 파일 이름, uuid , 파일이 이미지인지 아닌지 판단하는 boolean 을 필드로 선언하고

    Getter, Setter를 생성했다.

     

     

    2) jquery 작성

    $(document).ready(function(){
    	
    	/* 4. 첨부파일 공격에 대비하기 위한 업로드 파일 확장자 제한.(함수)
    
    	  .exe .zip .alz -> 첨부X
                특정 크기 이상의 파일 -> 첨부X
    
    	*/
    	// 함수 선언(checkExtension)
    	// 정규식을 이용하여 확장자 제한
    	var reg = new RegExp("(.*?)\.(exe|zip|alz)$")
    	// 최대 크기를 설정하여 그 이상이면 제한
    	var maxSize=5242880;	// 5MB
    	//                파일이름, 파일크기
    	function checkExtension(fileName, fileSize){
    		// 파일크기 제한
    		// 실제파일의 크기 > 최대 크기
    		if(fileSize >= maxSize){
    			alert("파일 사이즈 초과");
    			return false;
    		}
    		// 확장자 제한
    		// 실제파일명의 확장자와 정규식 비교
    		// 정규식이면
    		if(reg.test(fileName)){
    			alert("해당 종류의 파일은 업로드 할 수 없습니다.");
    			return false;
    		}
    		return true;
    	}
    	
    	// 파일전송버튼(id="uploadBtn")을 클릭하면
    	$("#uploadBtn").on("click",function(e){
    		e.preventDefault();
    		alert("aaaa");
    		// 파일 업로드 관련 로직 처리
    		// .jsp의 form태그를 대체(FormData함수)
    		var formData=new FormData();
    		
    		var inputFile=$("input[name='uploadFile']");
    		console.log(inputFile);
    		var files=inputFile[0].files;
    		
    		console.log(files);
    		
    		for(var i=0;i<files.length;i++){
    			
    			// 함수 호출(checkExtension)
    			if(!checkExtension(files[i].name,files[i].size)){
    				return false;
    			}
    			// .jsp의 파일선택을 통해 선택한 파일들을 form태그에 추가 
    			formData.append("uploadFile",files[i]);
    			
    		}
    		// ajax를 통해서 UploadController에 파일 관련 데이터 전송.
    		$.ajax({
    			type:"post",
    			url:"/uploadAjaxAction",
    			data:formData,
    			contentType:false,
    			processData:false,
    			dataType:"json",
    			success:function(result){
    				console.log(result)
    				
    				var str="";
    				var input="";
    				$(result).each(function(i,obj){
    					console.log(obj)
    					//console.log(obj.fileName)
    					input+="<input type='text' class='dis-none' name='attach["+i+"].fileName' value='" +obj.fileName+ "'>";
    					input+="<input type='text' class='dis-none' name='attach["+i+"].uuid' value='" +obj.uuid+ "'>";
    					input+="<input type='text' class='dis-none' name='attach["+i+"].uploadPath' value='" +obj.uploadPath+ "'>";
    					input+="<input type='text' class='dis-none' name='attach["+i+"].image' value='" +obj.image+ "'>";
    					// 만약 image결과가 true면
    					if(obj.image){
    						// 아래에 있는거 실행
    						var filePath=encodeURIComponent(obj.uploadPath+"/s_"+obj.uuid+"_"+obj.fileName)
    						console.log(filePath)
    						
    						str+="<li><img src='/display?fileName="+filePath+"'></li>"
    					}else{// 그렇지 않으면
    						// 다운로드 할 수 있도록 실행
    						var filePath=encodeURIComponent(obj.uploadPath+"/"+obj.uuid+"_"+obj.fileName)
    						str+="<li><a href='/download?fileName="+filePath+"'>"+obj.fileName+"</a></li>"	
    					}
    					
    				})
    				$("#uploadResult ul").html(str);
    				$("#writeForm").append(input);
    			}
    		})
    	})
    	
    })

    비동기식으로 해당 파일을 임시 저장소로 보내기 위해 다음의 자바스크립트를 작성했다.

     

    코드를 부분적으로 설명하면

     - 파일의 확장자와 크기를 제한한다. 해당 코드는 exe , zip , alz 확장자를 가진 파일을 업로드 하지 않을 것이며, 파일의 최대 크기를 5MB로 제한한다. 조건들을 만족하지 않을 시, false를 리턴하고 alert로 메세지를 띄울 것이다.

     

     - 파일 전송버튼을 누르면, 함수 내에서 form 데이터를 생성하고, name = uploadFile인 인풋 태그의 files를 가져온다.

    그리고 for 문을 통해 파일들의 확장자와 크기가 조건을 만족한다면 uploadFile 배열에 파일들을 저장하고 formData에 추가한다. 이후에 해당 form에 submit 명령이 발생하면, uploadFile도 함께 전송될 것이다.

     

     - ajax를 이용해서 /uploadAjaxAction 로 이동 한 뒤, 리턴값을 가지고 돌아올 것이다.

    이 때, return 값은 업로드할 파일에 대한 이름, uuid, 경로, 이미지 파일 여부 등의 데이터를 객체 형태로 가져올 것이다.

     

    그리고 글쓰기 페이지의 form 에 파일 객체 데이터를 실어보내기 위해서 input 태그를 작성한 후

    $("#writeForm").append(input); 을 실행시켜서 form 문 내부에 추가한다.

     

    또한, 전송하기 위해 임시 폴더에 저장된 파일들이 #uploadResult ul 내부에 생성되도록 .html( ) 함수를 사용했다.

     

    ** 이로서 전송하고자 하는 파일의 데이터를 객체화하고 임시 폴더에 저장한 후, 전송준비가 완료된 파일을 글쓰기 화면에서 유저가 확인할 수 있게 됐다.

     

    또한 input 태그와 li a 태그 내부에 /display?   ,  /download?  를 선언함으로서 브라우저에 이미지와 다운로드 파일의 주소를 생성할 것이다.

     

     

    3) /uploadAjaxAction 를 실행하기 위한 Controller

    @Controller
    public class UploadController {
        @RequestMapping(value = "/uploadAjaxAction", method = RequestMethod.POST)
    
            public ResponseEntity<ArrayList<AttachFileVO>> uploadAjaxPost(MultipartFile[] uploadFile){
    
                System.out.println("uploadFile="+uploadFile[0].getOriginalFilename());
    
                // AttachFileVO클래스 포함
                ArrayList<AttachFileVO> list = new ArrayList<>();
    
                // 폴더 경로
                String uploadFolder = "D:\\upload";
    
                // 서버 업로드 경로와 getFolder 메서드의 날짜 문자열을 이어서 하나의 폴더 생성
                File uploadPath = new File(uploadFolder,getFolder());
    
                // 폴더 생성(D:\\upload\\현재날짜
                if(uploadPath.exists() == false) {	// uploadPath가 존재하지 않으면,
                    uploadPath.mkdirs();
                }
    
                // for ( 변수명 : 배열명 )
                for(MultipartFile multipartFile : uploadFile) {
    
                    // AttachFileVO 클래스의 새로운 주소를 반복적으로 생성하여 ArrayList에 저장
                    AttachFileVO attachvo = new AttachFileVO();
    
                    System.out.println(multipartFile.getOriginalFilename());
                    System.out.println(multipartFile.getSize());
    
                    // 파일 저장
                    // 실제 파일명(multiparFile.getOriginalFilename())
                    // UUID 적용(UUID_multiparFile.getOriginalFilename());
                    UUID uuid = UUID.randomUUID();
                    System.out.println("UUID="+uuid.toString());
    
                    // AttachFileVO의 uploadPath 변수에 저장()
                    attachvo.setUploadPath(getFolder());
                    // AttachFileVO의 fileName 변수에 저장()
                    attachvo.setFileName(multipartFile.getOriginalFilename());
                    // AttachFileVO의 uuid 변수에 저장()
                    attachvo.setUuid(uuid.toString());
    
                    //                      어느폴더에(D:\\upload\\현재날짜), 어떤 파일이름으로(UUID_mainlogo_new.png)  
                    File saveFile = new File(uploadPath,uuid.toString()+"_"+multipartFile.getOriginalFilename());
    
                    //                      D:\\upload\\mainlogo_new.png에 파일을 전송(transferTo)
    
                    try {// transferTo() 메소드에 예외가 있으면
                        multipartFile.transferTo(saveFile);	// 서버로 원본파일 전송
                        // 내가 서버에 올리고자 하는 파일이 이미지이면,
                        if(checkImageType(saveFile)) {
    
                            // AttachFileVO의 image 변수에 저장()
                            attachvo.setImage(true);
    
                            // 파일 생성
                            FileOutputStream thumnail = new FileOutputStream(new File(uploadPath,"s_"+uuid.toString()+"_"+multipartFile.getOriginalFilename()));
    
                            // 섬네일형식의 파일 생성
                            Thumbnailator.createThumbnail(multipartFile.getInputStream(),thumnail,100,100);
    
                            thumnail.close();
                        } // checkImageType메서드 끝
    
                        // AttachFileVO에 저장된 데이터를 배열의 추가(add메소드)
                        list.add(attachvo);
    
                    }catch(Exception e) {// 예외를 처리하라.
                        System.out.println(e.getMessage());
                    }
                } // for문 끝
    
                return new ResponseEntity<>(list,HttpStatus.OK);
            }
            
            // 년/월/일 폴더 생성하는 메서드 선언
    	private String getFolder() {
    		
    		// 현재날짜 추출(Thu Aug 24 09:23:12 KST 2022)
    		Date date = new Date();
    		System.out.println("No Format 현재날짜 : "+date);
    		// Thu Aug 24 09:23:12 KST 2022 -> 2022-08-24
    		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    		// 현재날짜와 날짜형식을 연결
    		String str=sdf.format(date);	// 2022-08-24
    		System.out.println("Format 적용 현재날짜 : "+str);
    		// 2022-08-24 -> 2022\08\24로 변경
    			
    		return str.replace("-", "\\");
    	}
    	
    	// 내가 올리고자 하는 파일이 이미지 파일인지 아닌지 구분하는 메서드 선언
    	//     반환타입  메소드명            타입 변수명
    	private boolean checkImageType(File file) {
    		// probeContentType(파일경로) : 파일경로에 있는 파일타입을 알아내는 메서드
    		try {
    			String contentType = Files.probeContentType(file.toPath());
    			System.out.println("contentType="+contentType);
    			// 파일타입이 image이면 true, 그 이외에는 false
    			return contentType.startsWith("image");
    			
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		return false;
    	}
     }

     - 자바 스크립트에서 실행한 /uploadAjaxAction 경로의 ajax는 자바스크립트에서 배열의 형태로 전달해준 파일 객체를 받아서 파일의 이름, 크기, uuid, 경로를 AttachFileVO 형식에 맞게 객체화 하고

    현재 시간 데이터를 기반으로 임시 저장 폴더를 생성하고, 파일이 이미지 파일인지 아닌지 확인하는 작업을 할 것이다.

     

    임시 저장 폴더를 생성하고, 전달받은 파일들의 이름을 재설정 하기위해 File 모듈을 이용했고

     

    최종적으로 변경이 끝난 파일들을 AttachFileVO 객체 형태로 ArrayList에 담아서 리턴한다.

    리턴된 ArrayList를 JavaScript에서 input 태그와 li 태그를 생성할 것이다.

     

     

     


    이미지, 다운로드 주소 생성 controller

    // 이미지 주소 생성
    	@RequestMapping(value = "/display", method = RequestMethod.GET)	
    	public ResponseEntity<byte[]> getFile(String fileName){
    		System.out.println(fileName);
    		
    		File file = new File("D:\\upload\\"+fileName);
    		
    		ResponseEntity<byte[]> result = null;
    		
    		HttpHeaders headers = new HttpHeaders();
    		try {
    			headers.add("Content-Type", Files.probeContentType(file.toPath()));
    			result = new ResponseEntity<>(FileCopyUtils.copyToByteArray(file),
    					headers,HttpStatus.OK);
    		}catch(Exception e) {
    			e.printStackTrace();
    		}
    		return result;
    	}
    	
    	// 다운로드 주소 생성
    	@RequestMapping(value = "/download", method = RequestMethod.GET)
    	public ResponseEntity<Resource> downloadFile(String fileName){
    		Resource resource = new FileSystemResource("D:\\upload\\"+fileName);
    		
    		// 다운로드 시 파일의 이름을 처리
    		String resourceName = resource.getFilename();
    		HttpHeaders headers = new HttpHeaders();
    		try {
    			// 다운로드 파일이름이 한글일 때, 깨지지 않게 하기 위한 설정
    			headers.add("Content-Disposition", "attachment;filename="
    					+ new String(resourceName.getBytes("utf-8"),"ISO-8859-1"));
    		}catch(Exception e) {
    			e.printStackTrace();
    		}
    		return new ResponseEntity<>(resource,headers,HttpStatus.OK);
    	}

      - 특정 경로로 부터 받아온 파일(이미지)과 리소스(파일) 을 HttpHeaders 모듈을 이용해서 주소 형태로 리턴한다.

     

    파일 선택 및 전송 전
    kakao.jpg를 선택한 후 전송한 모습

     - 임시 폴더에는 원본 이미지 파일과 썸네일이 랜덤uuid 이름을 가진 채로 저장됐다.

Designed by Tistory.