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로 수정해서 사용하기
'국비교육(22-23)' 카테고리의 다른 글
52일차(2)/Spring(11) : Interceptor 추가, 비밀번호 암호화 및 수정, 회원 삭제 기능 구현 (1) | 2022.12.20 |
---|---|
52일차(1)/Spring(10) : 회원가입, 로그인, 로그아웃, 회원정보 보기 기능 구현 (1) | 2022.12.20 |
51일차(1)/Spring(8) : Interceptor (0) | 2022.12.19 |
50일차(4)/Spring(7) : Service 메소드 / Todo 테이블로 실습 (1) | 2022.12.18 |
50일차(3)/Spring(6) : MyBatis / 회원정보 수정 기능 구현 (getData, update) (0) | 2022.12.18 |