[Spring] 자료실 만들기 (File upload, download)

sua_ahn·2023년 3월 2일
0

Spring

목록 보기
5/8
post-thumbnail

파일 업로드 및 다운로드

웹 서버에 파일을 업로드하기 위해서
HTML <form> 태그 내 <input type='file'> 태그를 사용하게 된다.
일반적으로 다른 input 태그를 추가하여, 파일 외 여러 종류의 데이터도 같이 전송하게 된다.
이를 처리하기 위하여 multipart라는 형식을 이용한다.

form 태그 속성으로 enctype="multipart/form-data"을 명시하면,
HTTP Request Header에서 Context-type: multipart/form-data를 확인할 수 있고,
이는 HTTP Request Body에 여러 종류의 데이터를 구분하여 넣었음을 의미한다.

업로드한 파일을 Http 요청으로 받은 후 복사하여 웹 서버에 저장해놓고,
다운로드 시에도 파일을 복사하여 Http 응답으로 보내게 된다.

 


자료실 만들기

자료실 게시판에 파일 업로드와 다운로드 기능을 구현해보자!

1. 의존성 추가

  • commons-fileupload
  • commons-io

pom.xml

: <dependecies> 태그 내 추가

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
</dependency>

 

2. xml 설정

file-context.xml

  1. upload 설정

    • CommonsMultipartResolver
      : multipart 형식으로 전송된 데이터를 핸들링할 수 있는 기능을 지원
    • webapp/upload 디렉토리 생성
  2. download 설정

    • 다운로드 기능을 처리할 객체 생성
    • BeanNameViewResolver
      : 컨드롤러에서 리턴받은 뷰의 이름과 빈에 등록되어 있는 id의 값이 같은 클래스를 찾아 실행시키는 역할
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- upload -->
	<bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 최대 파일 크기: 1MB -->
		<property name="maxUploadSize" value="104857600"/>
        <!-- 최대 메모리 크기: 1kB -->
		<property name="maxInMemorySize" value="102400"/>
        <!-- 요청 파싱할 때의 인코딩 설정 -->
		<property name="defaultEncoding" value="utf-8"/>
        <!-- 디렉토리 경로: webapp/upload -->
		<property name="uploadTempDir" value="upload"/>
	</bean>
	
	<!-- download -->
	<bean id="downloadView" class="com.mypack.sample.util.DownloadView"></bean>
	
	<bean id="downloadViewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
		<property name="order">
			<value>0</value>
		</property>
	</bean>
	
</beans>

web.xml

: 위에서 설정한 file-context.xml 파일을 등록

<init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/spring/servlet-context.xml
        /WEB-INF/spring/file-context.xml
    </param-value>
</init-param>

 

3. View 생성

  • enctype : form data가 서버로 제출될 때, 해당 데이터가 인코딩되는 방법을 명시
    → "multipart/form-data"
    method가 "post"인 경우에만 사용 가능!
<form action="pdsupload.do" method="post" enctype="multipart/form-data">

	<input type="file" name="fileload" required="required">
    
    <input type="hidden" name="id" value="<%= login.getId() %>">
    <input type="text" name="title" required="required">
    <textarea name="content" rows="10" required="required"></textarea>
    <input type="submit" value="작성">
    
</form>

 

4. Utility & DB 준비

Utility

: upload 디렉토리 내 파일이름의 충돌을 막기 위해
시스템 시간으로 새로운 파일명 생성

public class PdsUtility {
	
	// 새 파일명 생성 newfile.txt -> 3237534.txt
	public static String getNewFileName(String filename) {
    
		String newfilename = "";	// 새 파일이름
		String extension = "";		// 파일 확장자명
		
		if(filename.indexOf('.') >= 0) {	// 확장자명 있음
			extension = filename.substring(filename.indexOf('.'));
			newfilename = new Date().getTime() + extension;
            
		} else {							// 확장자명 없음(-1)
			newfilename = new Date().getTime() + ".back";
		}
		return newfilename;
	}
}

DB 테이블

create table pds(
    seq int auto_increment primary key,
    id varchar(50) not null,
    title varchar(200) not null,
    content varchar(4000) not null,
    filename varchar(50) not null,
    newfilename varchar(50)  not null,
    readcount decimal(8) not null,
    downcount decimal(8) not null,
    regdate timestamp not null
);

alter table pds
add foreign key(id) references member(id);

mapper.xml

: MyBatis 사용

<insert id="uploadPds" parameterType="com.mypack.sample.dto.PdsDto">
    insert into pds(id, title, content, filename, newfilename, readcount, downcount, regdate)
    values(#{id}, #{title}, #{content}, #{filename}, #{newfilename}, 0, 0, now())
</insert>

 

5. Model 생성

DTO

: DB 테이블 참고하여 멤버, 생성자, getter/setter, toString 생성

DAO, Service

: mapper.xml 참고하여 인터페이스와 클래스에 uploadPds() 메소드 생성

 

6. 로직 생성

Controller

  1. 파일 업로드
@PostMapping(value = "pdsupload.do")
public String pdsupload(PdsDto dto, 
        @RequestParam(value = "fileload", required = false) 
        MultipartFile fileload,
        HttpServletRequest req) {

    // filename 취득
    String filename = fileload.getOriginalFilename();
    dto.setFilename(filename);

    // upload 경로 설정
    // server에 파일 저장 -> 재시작 시 refresh되면서 파일 삭제됨
    String fupload = req.getServletContext().getRealPath("/upload");

    // 새 파일명 취득
    String newfilename = PdsUtility.getNewFileName(filename);
    dto.setNewfilename(newfilename);

    // 서버에 실제 파일 생성 및 기입 (= 업로드)
    File file = new File(fupload + "/" + newfilename);
    try {
        FileUtils.writeByteArrayToFile(file, fileload.getBytes());

        // db에 저장
        service.uploadPds(dto);
        
    } catch (IOException e) {
        e.printStackTrace();
    }

    return "redirect:/pdslist.do";	// 파일 업로드 후 목록으로 이동
}
  1. 파일 다운로드
@PostMapping(value = "filedownload.do")
public String filedownload(int seq, String newfilename, String filename, 
        HttpServletRequest req, Model model) {

    // 경로 설정 (server)
    String fupload = req.getServletContext().getRealPath("/upload");

    // 다운로드 받을 파일
    File downloadFile = new File(fupload + "/" + newfilename);

    model.addAttribute("downloadFile", downloadFile);
    model.addAttribute("filename", filename);
    model.addAttribute("seq", seq);

    return "downloadView";		// file-context.xml 설정대로 이동
}

DownloadView

public class DownloadView extends AbstractView {
	
	@Autowired
	PdsService service;

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model, 
    			HttpServletRequest request,
				HttpServletResponse response) throws Exception {
		
        // 컨트롤러에서 보낸 값 받기
		File downloadFile = (File)model.get("downloadFile");
		String filename = (String)model.get("filename");
		int seq = (Integer)model.get("seq");
		
        // 파일명으로 한글 사용 시 설정
		filename = URLEncoder.encode(filename, "utf-8");
        
        // Http Response Header 설정
		response.setContentType(this.getContentType());
		response.setContentLength((int)downloadFile.length());
		
		response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\";");
		response.setHeader("Content-Transfer-Encoding", "binary;");	// 이진수로 저장
		response.setHeader("Content-Length", "" + downloadFile.length());
		response.setHeader("Pragma", "no-cache;"); 	// 캐시로 저장 안함
		response.setHeader("Expires", "-1;");		// 기한 없음
		
		OutputStream os = response.getOutputStream();
		FileInputStream fis = new FileInputStream(downloadFile);
		
		// 실제 데이터 기입 (os에 복사)
		FileCopyUtils.copy(fis, os);
		
		// downcount 증가
		service.downcountPds(seq);
		
		if(fis != null) {
			fis.close();
		}
	}
}
profile
해보자구

0개의 댓글