국비교육(22-23)

51일차(2)/Spring(9) : 파일 업로드 기능 구현 / SmartEditor 적용

서리/Seori 2022. 12. 19. 23:20

51일차(2)/Spring(9) : 파일 업로드 기능 구현 / SmartEditor 적용

 

 

- home에 파일업로드 링크추가

 

com.sy.spring03.file.controller 컨트롤러추가

FileController (최종)

package com.sy.spring03.file.controller;

import java.io.File;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;

import com.sy.spring03.file.dto.FileDto;
/*
 * [ spring mvc 파일 업로드 처리 ]
 * 
 * 1. pom.xml 에 commons-io, commons-fileupload가 dependency에 명시되어 있어야 한다.
 * 2. servlet-context.xml에 MultipartResolver bean 설정이 있어야 한다.
 * 3. MultipartFile 객체를 컨트롤러에서 받아서 사용하면 된다.
 * 
 */
@Controller
public class FileController {
   
   @RequestMapping("/file/insertform")
   public String insertform() {
      
      return "file/insertform";
   }
   
   //FileDto에는 폼 전송된 title, myFile 정보가 들어 있다.
   @RequestMapping("/file/upload2")
   public String upload2(FileDto dto, HttpServletRequest request) {
	   //FileDto 로부터 업로드된 파일의 정보를 담고 있는 MultipartFile 객체의 참조값 얻어내기 
	   MultipartFile myFile=dto.getMyFile();
	   
	 //1. 원본 파일명
      String orgFileName=myFile.getOriginalFilename();
      //2. 파일의 크기
      long fileSize=myFile.getSize();
      
      // webapp/upload 폴더 까지의 실제 경로(서버의 파일시스템 상에서의 경로)
      String realPath=request.getServletContext().getRealPath("/upload");
      //저장할 파일의 상세 경로
      String filePath=realPath+File.separator;
      //디렉토리를 만들 파일 객체 생성
      File upload=new File(filePath);
      if(!upload.exists()) {//만일 디렉토리가 존재하지 않으면 
         upload.mkdir(); //만들어 준다.
      }
      //저장할 파일 명을 구성한다.
      String saveFileName=
            System.currentTimeMillis()+orgFileName;
      try {
         //3. 임시폴더에 저장된 업로드된 파일을 원하는곳에 원하는 이름으로 이동시키기(전송하기)
         myFile.transferTo(new File(filePath+saveFileName));
         System.out.println(filePath+saveFileName);
      }catch(Exception e) {
         e.printStackTrace();
      }
      return "file/upload";
	   
   }
   
   @RequestMapping("/file/upload")
   public String upload(String title, MultipartFile myFile, HttpServletRequest request) {
      //1. 원본 파일명
      String orgFileName=myFile.getOriginalFilename();
      //2. 파일의 크기
      long fileSize=myFile.getSize();
      
      // webapp/upload 폴더 까지의 실제 경로(서버의 파일시스템 상에서의 경로)
      String realPath=request.getServletContext().getRealPath("/upload");
      //저장할 파일의 상세 경로
      String filePath=realPath+File.separator;
      //디렉토리를 만들 파일 객체 생성
      File upload=new File(filePath);
      if(!upload.exists()) {//만일 디렉토리가 존재하지 않으면 
         upload.mkdir(); //만들어 준다.
      }
      //저장할 파일 명을 구성한다.
      String saveFileName=
            System.currentTimeMillis()+orgFileName;
      try {
         //3. 임시폴더에 저장된 업로드된 파일을 원하는곳에 원하는 이름으로 이동시키기(전송하기)
         myFile.transferTo(new File(filePath+saveFileName));
         System.out.println(filePath+saveFileName);
      }catch(Exception e) {
         e.printStackTrace();
      }
      return "file/upload";
   }
}

 

/views/file/insertform.jsp (최종)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/file/insertform.jsp</title>
</head>
<body>
	<div class="container">
		<h3>파일 업로드 폼1</h3>
		<form action="${pageContext.request.contextPath}/file/upload" method="post"
			enctype="multipart/form-data">
			제목 <input type="text" name="title" />
			첨부파일 <input type="file" name="myFile" /><br />
			<button type="submit">업로드</button>
		</form>
		
		<h3>파일 업로드 폼2</h3>
		<form action="${pageContext.request.contextPath}/file/upload2" method="post"
			enctype="multipart/form-data">
			제목 <input type="text" name="title" />
			첨부파일 <input type="file" name="myFile" /><br />
			<button type="submit">업로드</button>
		</form>
	</div>
</body>
</html>

 

- 파일 업로드를 위해 예전에 java에서는 cos.jar를 사용했는데 스프링에서는 어떻게 하는지 살펴볼 예정.

 

 

- 파일 업로드용 객체 생성을 위해 Servlet-context.xml 에 추가

<!-- 
    Multipart 폼 전송 처리를 위한 bean 설정
    최대 업로드 사이즈 제한하기 
    name="maxUploadSize" value="byte단위"
-->
<beans:bean id="multipartResolver"
  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
  <beans:property name="maxUploadSize" value="102400000"/>
</beans:bean>

 

- 파일 전송을 위해서는 이 객체가 생성되어 있어야 한다. CommonsMultipart 해결사!

 

name="maxUploadSize" value="byte단위"

- 단, value="1024*100" 형태로는 쓸 수 없다.(연산자x) 연산한 값으로 넣어주어야 한다.

 

 

- 컨트롤러 수정

- 위와 같이 문자열(String)으로는 받을 수 없다. 파일은 여러가지 정보를 담고 있기 때문에!

→ file은 dataType을 MultipartFile 으로 넣어주면 된다.

 

- insertform에 있는 input 속성의 name 값과 일치시켜주기! 중요!!!

 

- 이전과 같이 webapp에 upload라는 폴더를 만들어준다.

 

- 컨트롤러에 예전에 이클립스에서 사용했던 코드를 그대로 붙여넣어주기

 

- 이 코드에서 얻어낼 수 있는 정보

1) 원본 파일명(getOriginalFileName)

2) 파일의 크기(getSize) : 나중에 다운로드시키려면 필요하다.

 

3) 임시폴더에 저장된 파일(transferTo)

- 임시폴더에 저장된 업로드된 파일을 원하는 곳에 원하는 이름으로 이동시키는(전송하는) 메소드

 

 

- 웹서버는 파일을 전송받으면 시스템의 temporary 폴더(임시폴더) 에 해당파 일을 복잡한 파일명으로 일단 저장해 놓는다.

 해당 폴더에 저장된 파일은 사용하지 않으면 일정시간 이후에 자동으로 삭제된다.

- 따라서 해당 파일을 사용하기 위해서는 다른 곳으로 이동시켜야 한다.

 이동시키기 위해서는 어떤 경로에 어떤 파일명으로 이동시킬지 결정해야 한다.

- 위의 작업을 java에서는 cos.jar가 대신해줬는데, 여기서는 메소드를 사용해서 해주려고 한다.

 

- 임시폴더에 이상한 파일명으로 저장한 것을 이런 방식으로 upload 폴더안에 다시 저장하게 된다.

- 바꿔서 저장할때 겹치지 않도록 파일명 앞에다가 숫자를 붙인다.(빨간박스 위치)

 

- chat.txt 는 원본 파일, 아래는 이름이 바뀐 파일! 

- 다운로드를 위해서는 둘다 잘 관리해주어야한다.

 

- transferTo() 라는 메소드 안에 이렇게 저장할 수 있는 파일 객체를 만들어서 전달해주면 된다.

- 목적지 대상이 되는 파일 객체를 전달해주면 그 메소드안에서 파일 객체를 활용해서 옮겨주는 기능을 한다.

 

 

- 파일 업로드를 위해 이렇게 3개의 메소드를 활용하고 있다.

 

- 어디에 어떻게 저장할지 그 정보를 알고 있는 파일 객체

- currentTimeMillis() 에다가 파일명을 붙여서(절대 겹칠 일이 없는 숫자) 파일명을 생성해준다.

- 다른 값을 써도 되지만, 저것도 라이브러리 없이 편리하게 사용할 수 있는 방법이다.

 

 

upload.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/file/upload.jsp</title>
</head>
<body>
	<p>파일을 업로드 했습니다.</p>
	<a href="${pageContext.request.contextPath}/file/insertform">다시 테스트</a>
</body>
</html>

 

- 파일을 업로드하면 저장된 주소가 찍히고, 업로드된다.

 

- 만약 100mb이상의 파일을 올리면 upload 페이지로의 이동이 거부된다.

 


 

myFile.transferTo(new File(filePath+saveFileName));

 

- MultipartFile myFile 이라고 하면,

  이 객체는 우리가 직접 생성하는것이 아니라 받아와서 쓰는 것이다.

 

- myFile.transferTo( 임시파일을 어디에 저장할지 정보를 가지고있는 File 객체 )

 

 

- 만일 업로드된 파일을 c:\aaa\bbb\...\upload\1234aaa.txt 에 저장하고 싶다면

 

File f = new File("c:\aaa\bbb\...\upload\1234aaa.txt");

myFile.transforTo(f);

 

- 위와 같이 파일 객체를 생성해서

 transferTo() 메소드의 인자로 전달해주면 메소드 내부에서 알아서 처리된다.

 


 

- 그런데 지금은 너무 여러개의 정보를 인자로 받는데, dto로 한번에 모아서 받을수는 없을까?

 

com.sy.spring03.file.dto 패키지생성

FileDto

package com.sy.spring03.file.dto;

import org.springframework.web.multipart.MultipartFile;

public class FileDto {
	private int num;
	private String title;
	private String orgFileName;
	private String saveFileName;
	//업로드된 파일의 정보를 담을 필드 추가***
	private MultipartFile myFile; //<input type="file" name="myFile"> name 속성의 value값과 일치시키기
	
	public FileDto() {}

	public FileDto(int num, String title, String orgFileName, String saveFileName, MultipartFile myFile) {
		super();
		this.num = num;
		this.title = title;
		this.orgFileName = orgFileName;
		this.saveFileName = saveFileName;
		this.myFile = myFile;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getOrgFileName() {
		return orgFileName;
	}

	public void setOrgFileName(String orgFileName) {
		this.orgFileName = orgFileName;
	}

	public String getSaveFileName() {
		return saveFileName;
	}

	public void setSaveFileName(String saveFileName) {
		this.saveFileName = saveFileName;
	}

	public MultipartFile getMyFile() {
		return myFile;
	}

	public void setMyFile(MultipartFile myFile) {
		this.myFile = myFile;
	}
	
}

 

- 이전에 만들었던 것과 비슷하게 필드를 만들지만, 하나가 추가된다.

- myFile 필드를 만들어준다.

 

- uploadform 페이지에 업로드폼을 하나 추가해주고, 컨트롤러 추가

 

- FileDto에는 폼 전송된 title, myFile 정보가 들어 있다.

 아래의 처리는 같다!

- dto로 받는 것 이외에는 차이가 없다.

 

- 똑같이 console 창에 저장되는 경로가 찍히고, 잘 업로드된다.

 


 

- 파일 업로드도 로그인해야만 할 수 있도록 하려면?

→ /file/ 경로도 interceptor 처리하기

 

sevlet-context.xml 에 작성한 interceptor (전체)

<!-- MyInterceptor가 bean이 되도록 한다. -->
<beans:bean id="myInterceptor" class="com.sy.spring03.interceptor.MyInterceptor" />

<beans:bean id="loginInterceptor" class="com.sy.spring03.interceptor.LoginInterceptor" />



<!-- 인터셉터 목록 -->
<interceptors>
    <!-- myInterceptor가 /play 요청에 대해 끼어들도록 설정한다. -->
    <interceptor>
        <mapping path="/play"/>
        <beans:ref bean="myInterceptor" />
    </interceptor>
    <!-- /users/하위의 모든 요청에 대해 loginInterceptor가 끼어들도록 설정한다. -->
    <interceptor>
        <mapping path="/users/*" />
        <exclude-mapping path="/users/loginform"/>
        <exclude-mapping path="/users/login"/>			
        <beans:ref bean="loginInterceptor"/>
    </interceptor>		
    <!-- 또다른 경로에 대해서 맵핑하고 싶다면 interceptor 설정을 하나 추가 한다. -->
    <interceptor>
        <mapping path="/file/*"/>
        <beans:ref bean="loginInterceptor"/>
    </interceptor>		
</interceptors>

 

- 한 interceptor 안에 mapping이 하나 있는데 또 입력하려고 하면 에러가 난다.

 

- 이렇게 새 interceptor로 작성해주기

 (이 안에 입력하면 위의 loginInterceptor의 interceptor가 같이 적용될 수 있다.)

 


 

[ spring mvc 파일 업로드 처리 ] 
1. pom.xml 에 commons-io, commons-fileupload가 dependency에 명시되어 있어야 한다.
2. servlet-context.xml에 MultipartResolver bean 설정이 있어야 한다.
3. MultipartFile 객체를 컨트롤러에서 받아서 사용하면 된다.

- 위 3가지를 기억하기!

 

- pom.xml에 이게 있어야 파일 업로드, 스마트에디터를 사용할 수 있다.

 


 

 

- webapp/resources/ 에 images 폴더 만들어두고 이 안에 파일하나 넣어두기

- 이 이미지를 home.jsp에서 표시하려면?

 

<img src="${pageContext.request.contextPath}/resources/images/puff.png" />

- 이렇게만 경로를 지정해도 이미지가 나온다.

- resources라는 폴더는 무엇인지? 어디에 경로 설정이 되어있기에 바로 나오는 것인지?

 

- servlet-context.xml 을 보면 resources 라는 설정이 있다.

 

<resources mapping="/resources/**" location="/resources/" />

- 모든 요청은 Spring DispatcherServlet을 거치도록 했는데, 
 거기에서 배제할 파일에 대한 요청을 편하게 할 수 있도록 설정한 것이다.

 

- mapping을 "/" 으로 해두어서 모두 컨트롤러를 거치게 되었다.

 

하지만 위는 컨트롤러를 거치는 요청이어야하지만,

아래는 그럴필요가없다.그냥 get방식 요청으로 응답만 하면되는데 다 거치도록 해버린 것.

하나하나 배제시키려면 너무 불편하므로 /resources/ 하위에 모아놓으면 controller를 거치지않아도 그냥 통과시켜주도록  한 것!

 

 

 

2번으로 설정하면 마치 webapp안에 해당 파일이 있는 것처럼 사용할 수 있다.

Spring boot에서는 이런 형태를 더 많이 사용한다.(static이라는 이름의 폴더 사용)

 

 

- 모든 요청은 Spring DispatcherServlet을 거치도록 했는데, 
 거기에서 배제할 파일에 대한 요청을 편하게 할 수 있도록 설정한 것이다.
- html, css, js, image 등의 정적인 파일은 webapp/resources/ 폴더 안의 특정 경로에 넣어두고 사용하도록 되어 있다.

1. mapping="/resources/**" location="/resources/"  이게 기본설정이다.
따라서 /resources/images/kim1.png를 클라이언트가 받아가기 위해서는 
<img src="/spring03/resources/images/kim.png" /> 경로로 요청하면 된다.

2. mapping="/**"  location="/resources/" 만일 이렇게 변경하면
/resources/images/kim1.png를 클라이언트가 받아가기 위해서는 
<img src="/spring03/images/kim.png" /> 경로로 요청하면 된다.

 

- 만약 2번으로 사용하려면 이렇게 설정 변경이 필요하다.

 그럼 resources 폴더안에 들어있는 정적인 자원들을 웹앱폴더에 있는 것처럼 사용할 수 있다.

- home에 있는 링크를 <img src="${pageContext.request.contextPath}/images/puff.png" /> 이렇게 바꾸어도 잘 나온다.

- 이렇게 하면 파일 업로드시에 사용했던 upload 폴더도 resources 안에 넣어두어도 사용할 수 있게 된다.

 

 

- SmartEditor폴더를 webapp-resources 폴더 안에 생성.

- 컨트롤러를 거치지 않고도 로딩가능한 자원으로 하기 위해.

- upload 폴더도 넣어준다. 앞으로는 webapp의 upload는 사용X

  resources 안에 넣어두고 웹앱에 있는 것처럼 사용할 예정!!

 

- SmartEditor 폴더 안의 jsp 두개를 수정할 예정

 

- 첫번째 jsp 파일은 /upload → /resources/upload 로 수정

 

- 두번째 jsp파일도 똑같이 수정

 


 

- home.jsp 에서 cafe 링크추가 / HomeController에 컨트롤러추가

 

- insertform에 대한 controller추가

 

 

/views/cafe/insertform.jsp 뷰페이지 추가

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>/views/cafe/insertform.jsp</title>
<style>
   #content{
      height: 500px;
   }
</style>
</head>
<body>
<div class="container">
   <h1>새글 작성 폼</h1>
   <form action="insert" method="post" id="insertForm">
      <div class="mb-3">
         <label class="form-label" for="title">제목</label>
         <input class="form-control" type="text" name="title" id="title"/>
      </div>
      <div class="mb-3">
         <label class="form-label" for="content">내용</label>
         <textarea class="form-control"  name="content" id="content"></textarea>
      </div>
      <button class="btn btn-primary" type="submit">저장</button>
   </form>
</div>
<%--
   [ SmartEditor 를 사용하기 위한 설정 ]
   
   1. webapp/resources 폴더에 SmartEditor 폴더를 복사해서 붙여 넣기
   2. WebContent 에 upload 폴더 만들어 두기   
   3. <textarea id="content" name="content"> 
      content 가 아래의 javascript 에서 사용 되기때문에 다른 이름으로 바꾸고 
         싶으면 javascript 에서  content 를 찾아서 모두 다른 이름으로 바꿔주면 된다. 
   5. textarea 의 크기가 SmartEditor  의 크기가 된다.
   6. 폼을 제출하고 싶으면  submitContents(this) 라는 javascript 가 
         폼 안에 있는 버튼에서 실행되면 된다.
 --%>
<!-- SmartEditor 에서 필요한 javascript 로딩  -->
<script src="${pageContext.request.contextPath }/resources/SmartEditor/js/HuskyEZCreator.js"></script>
<script>
   var oEditors = [];
   
   //추가 글꼴 목록
   //var aAdditionalFontSet = [["MS UI Gothic", "MS UI Gothic"], ["Comic Sans MS", "Comic Sans MS"],["TEST","TEST"]];
   
   nhn.husky.EZCreator.createInIFrame({
      oAppRef: oEditors,
      elPlaceHolder: "content",
      sSkinURI: "${pageContext.request.contextPath}/resources/SmartEditor/SmartEditor2Skin.html",   
      htParams : {
         bUseToolbar : true,            // 툴바 사용 여부 (true:사용/ false:사용하지 않음)
         bUseVerticalResizer : true,      // 입력창 크기 조절바 사용 여부 (true:사용/ false:사용하지 않음)
         bUseModeChanger : true,         // 모드 탭(Editor | HTML | TEXT) 사용 여부 (true:사용/ false:사용하지 않음)
         //aAdditionalFontList : aAdditionalFontSet,      // 추가 글꼴 목록
         fOnBeforeUnload : function(){
            //alert("완료!");
         }
      }, //boolean
      fOnAppLoad : function(){
         //예제 코드
         //oEditors.getById["ir1"].exec("PASTE_HTML", ["로딩이 완료된 후에 본문에 삽입되는 text입니다."]);
      },
      fCreator: "createSEditor2"
   });
   
   function pasteHTML() {
      var sHTML = "<span style='color:#FF0000;'>이미지도 같은 방식으로 삽입합니다.<\/span>";
      oEditors.getById["content"].exec("PASTE_HTML", [sHTML]);
   }
   
   function showHTML() {
      var sHTML = oEditors.getById["content"].getIR();
      alert(sHTML);
   }

   
   function setDefaultFont() {
      var sDefaultFont = '궁서';
      var nFontSize = 24;
      oEditors.getById["content"].setDefaultFont(sDefaultFont, nFontSize);
   }
   
   //폼에 submit 이벤트가 일어났을때 실행할 함수 등록
   document.querySelector("#insertForm")
      .addEventListener("submit", function(e){
         //에디터에 입력한 내용이 textarea 의 value 값이 될수 있도록 변환한다. 
         oEditors.getById["content"].exec("UPDATE_CONTENTS_FIELD", []);
         //textarea 이외에 입력한 내용을 여기서 검증하고 
         const title=document.querySelector("#title").value;
         
         //만일 폼 제출을 막고 싶으면  
         //e.preventDefault();
         //을 수행하게 해서 폼 제출을 막아준다.
         if(title.length < 5){
            alert("제목을 5글자 이상 입력하세요!");
            e.preventDefault();
         }
         
      });
</script>
</body>
</html>

 

- SmartEditor 관련한 내용을 넣어준다.

- spring에는 SmartEditor 관련 정보가 dependency에 들어있기 때문에 별도의 라이브러리로는 필요없다.

 

servlet-context.xml 에서 resources 설정

- smartEditor 업로드에 문제가 생겨서 1번(기본설정) 그대로 놓고 사용하기로 했다.

 

- insertform의 경로를 resources로 수정해서 사용하기