스프링부트 게시판 만들기 제10강 파일다운로드

  스프링 부트 게시판 만들기

제10강 File Download

 

파일을 업로드 처리를 했으니, 이제 파일 다운로드 차례입니다.

게시글 뷰화면에서 다운로드 링크를 만들어주고,

링크를 클릭했을 때 저장 된 파일이 다운로드 되도록 처리하는 것입니다.

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!DOCTYPE html>
<html lang="ko">
<head>
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" type="text/css" />
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
	<div class="container">
		<form action="boardarticleview.sd"  id="frm" name="frm" method="post">
	
			<table class="table table-bordered" >
				<colgroup>
					<col style="width: 20%;">
					<col style="width: 80%">
				</colgroup>
				<tr>
					<th>고유번호</th>
					<td><c:out value='${boardarticle.seq}' /></td>
				</tr>
				<tr>
					<th>제목</th>
					<td><c:out value='${boardarticle.title}' /></td>
				</tr>
				<tr>
					<th>내용</th>
					<td><c:out value='${boardarticle.content}' /></td>
				</tr>
				<tr>
					<th>첨부파일</th>
					<td>
						<!-- 첨부파일 다운로드를 위한 링크삽입 -->
						<a href="/boardarticle/filedown.sd?seq=${boardarticle.seq}">${boardarticle.filename}</a>
					</td>
				</tr>
				<tr>
					<th>조회수</th>
					<td><c:out value='${boardarticle.readcnt}' /></td>
				</tr>
				<tr>
					<th>코멘트수</th>
					<td><c:out value='${boardarticle.commentcnt}' /></td>
				</tr>
				<tr>
					<th>추천수</th>
					<td><c:out value='${boardarticle.recommendcnt}' /></td>
				</tr>
				<tr>
					<th>그룹번호</th>
					<td><c:out value='${boardarticle.groupnum}' /></td>
				</tr>
				<tr>
					<th>레벨번호</th>
					<td><c:out value='${boardarticle.levelnum}' /></td>
				</tr>
				<tr>
					<th>스텝번호</th>
					<td><c:out value='${boardarticle.stepnum}' /></td>
				</tr>
				<tr>
					<th>등록IP</th>
					<td><c:out value='${boardarticle.inip}' /></td>
				</tr>
				<tr>
					<th>등록일</th>
					<td>${ boardarticle.indate }</td>
				</tr>
			</table>
			<div class="ln_solid"></div>
	
			<div class="form-group">
				<div class="col-sm-12">
					<a class="btn btn-info" href="boardarticleadd.sd" role="button" title="등록"><i class="fa fa-plus"></i> 등록</a>
					<a class="btn btn-primary" href="boardarticlelist.sd" role="button" title="리스트"><i class="fa fa-list-ul"></i> 리스트</a>
				</div>
			</div>
	
		</form>
	</div>

</body>
</html>

 

get방식으로 boardarticle 테이블의 고유값(seq)를 parameter로 전달해서 다운로드 처리 합니다.

전달되는 Parameter는 게시글의 파일명을 확인하기 위해서입니다. 파일명을 바로 전달해서 다운로드 처리할 수도 있겠습니다.

원래 보안을 위해서는 파일을 저장할 때,

파일명을 변경하여 저장하고 변경된 파일명을 이용해서 다운로드 처리하는것이 좋습니다.

이 글은 기본예제에 충실하기 위해서 부수적인 설정들을 제외했기 때문에 실제 개발을 할 때는 고민할 부분들이 더 있습니다.

보안이나, 다운로드 링크를 계속해서 호출했을 때의 처리 등등

실제 운영에서는 여러 변수들에 대해 고려해야 합니다.

다음으로 Controller를 수정해봅시다.

이번 파일다운로드의 핵심은 이 곳에 있습니다.

 

package com.soledot.board.boardarticle;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLConnection;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author soledot
 * @since 20191108
 * @version soledot
 * @description boardarticle Controller
 *
*/

@Controller
@RequestMapping("/boardarticle/*")
public class BoardarticleCtrl {

	private static final Logger logger = LoggerFactory.getLogger(BoardarticleCtrl.class);

	@Autowired BoardarticleSvc boardarticleSvc;

	//---*파일 다운로드
	@RequestMapping( "filedown.sd" )
	public void filedown( HttpServletResponse response, @RequestParam Map<String, Object> param, ModelMap model ){
		try{
			boardarticleSvc.one(param, model);	//--- 게시글 정보를 가져옴. param에 seq이 담겨 있기 때문에 해당 seq값으로 boardarticle model에 담아 가져옵니다.
			Map<String, Object> boardarticle = (Map<String, Object>) model.get( "boardarticle" );		//--- model에 담긴 boardarticle을 가져옵니다.
			String filename =  new String( boardarticle.get( "filename" ).toString().getBytes("euc-kr"),"iso-8859-1");		//--- 한글 파일명이 깨지지 않도록 처리합니다. toString의 경우 객체가 null이면 에러가 발생되니 주의해서 사용하세요~
			File file = new File( "f:/"+boardarticle.get( "filename" ) );		//--- 파일명과 경로를 이용해서 File객체를 가져옵니다.
	        String mimeType= URLConnection.guessContentTypeFromName(filename);		//--- 파일의 mime타입을 확인합니다.
	        if(mimeType==null){			//--- 마임타입이 없을 경우 application/octet-stream으로 설정합니다. 
	            mimeType = "application/octet-stream";
	        }
	        response.setContentType(mimeType);	//--- reponse에 mimetype을 설정합니다.
			response.setHeader("Content-Disposition", "attachment; filename=\""+filename+"\"");		//--- Content-Disposition를 attachment로 설정하여 다운로드 받을 파일임을 브라우저에게 알려줍니다.
	        response.setContentLength((int)file.length());		//--- response content length를 설정합니다.
	        InputStream inputStream = new BufferedInputStream(new FileInputStream(file));	//--- inputstream 객체를 얻습니다.
	        FileCopyUtils.copy(inputStream, response.getOutputStream());		//--- inputstream으로 파일을 읽고 outputsream으로 파일을 씁니다.
	        
		}catch( Exception e ){
			logger.error( e.toString(), e );
		}
	}
	
}

 

본 예제 이외의 불필요한 부분은 제외한 BoardarticleCtrl 내용입니다.

 

package com.soledot.board.boardarticle;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author soledot
 * @since 20191108
 * @version soledot
 * @description boardarticle Service Interface
 *
*/

public interface BoardarticleSvc {

	public boolean one( Map<String, Object> param, ModelMap model )throws Exception;
}

 

interface BoardarticleSvc입니다. 오버로딩으로 in method를 추가했습니다.

 

package com.soledot.board.boardarticle;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;
import org.springframework.web.multipart.MultipartFile;

/**
 * @author soledot
 * @since 20191108
 * @version soledot
 * @description boardarticle Service 구현 클래스
 *
*/

@Service("BoardarticleSvc")
public class BoardarticleSvcImpl implements BoardarticleSvc {

	private static final Logger logger = LoggerFactory.getLogger(BoardarticleSvcImpl.class);

	@Autowired HttpSession ss;

	@Autowired BoardarticleDao boardarticleDao;


	@Override
	public boolean one( Map<String, Object> param, ModelMap model ) throws Exception {

		Map<String, Object> svcMap = new HashMap<>();
		svcMap.put( "seq", param.get( "seq" ) );
		model.put("boardarticle", boardarticleDao.one( svcMap ) );

		return true;
	}
	

}

 

BoardarticleSvcImpl의 one method에서는 seq값 전달하여 database boardarticle의 seq값을 가진 row를 하나 가져오는 역할입니다.

 

package com.soledot.board.boardarticle;

import java.util.List;
import java.util.Map;

/**
 * @author soledot
 * @since 20191108
 * @version soledot
 * @description boardarticle Dao Interface
 *
*/

public interface BoardarticleDao {

	//---*저장
	public Map<String, Object> one( Map<String, Object> param );

}

 

인터페이스 BoardarticleDao내용입니다.

 

package com.soledot.board.boardarticle;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

/**
 * @author soledot
 * @since 20191108
 * @version soledot
 * @description boardarticle Dao 구현 클래스
 *
*/

@Repository("BoardarticleDao")
public class BoardarticleDaoImpl implements BoardarticleDao {

	@Autowired private SqlSession sqlSession;

	@Override
	public Map<String, Object> one( Map<String, Object> param ){
		return (Map<String, Object>)sqlSession.selectOne( "boardarticle.one", param );
	}

}

 

BoardarticleDaoImpl 소스입니다.

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="boardarticle">

	<select id="one" parameterType="hashmap" resultType="hashmap">
		SELECT * FROM BOARDARTICLE
		<where>
			<if test="null != seq and '' != seq">AND SEQ = #{seq}</if> <!-- 전달된 seq값이 조건문에 입력됩니다. -->
			<if test="null != title and '' != title">AND TITLE = #{title}</if>
			<if test="null != content and '' != content">AND CONTENT = #{content}</if>
		</where>
	</select>


</mapper>

 

boardarticle.xml 내용입니다.

이제, 테스트만 진행해보면 됩니다.

http://localhost:8080/boardarticle/boardarticlelist.sd로 접속해서

파일을 등록한 게시글 상세화면으로 이동합니다.

 

 

 

 

파일이 정상적으로 다운로드 되었습니다~

공유하기:

스프링부트 카테고리 글 :

1 Comments

  1. DaynightHacker2021-04-30 20:38:46.0

    마임타입을 셋팅하는 전반적인 코드들과 흐름이 에바터지네 좀 더 우아하게 개선하도록.

Comment