48일차 내용 정리

채공부·2025년 7월 30일

JavaScript

업로드

mime type : 특정 파일이 어떤 종류의 파일인지 지정 가능
➜ ex ) accept="image/*"

매개변수 e : event object 전달
e.target : 해당 이벤트가 일어나는 요소의 참조값

여러개의 이미지 업로드

<div class="container">
	<h1>선택한 이미지 데이터 다루기</h1>
	<!-- accept 속성을 활용하면 이미지 파일만 선택할 수 있다 -->
	<!-- 이미지 선택 시 change 이벤트 발생-->
	<input type="file" id="myImage" accept="image/*" multiple>
	<div id="dropZone">Click or Drag Drop</div>
	<div id="wrapper"></div>
</div>
document.querySelector("#myImage").addEventListener("change", (e) => {
    // e.target.files 에는 여러 개의 파일 객체가 들어 있을 수 있다
    const files = Array.from(e.target.files);
    // 배열의 filter() 함수를 이용해서 이미지 파일만 들어 있는 배열을 얻어낸다
    const imageFiles = files.filter(item => item.type.startsWith("image/"));
    // 반복문 돌면서 모든 이미지 파일을 로딩
    for (let i = 0; i < imageFiles.length; i++) {
        const reader = new FileReader();
        reader.onload = () => {
            const dataUrl = reader.result;
            const img = `<img src="${dataUrl}">`;
            document.querySelector("#wrapper").insertAdjacentHTML("beforeend", img);
        };
        // i번 방에 들어 있는 이미지 파일을 읽어들인다
        reader.readAsDataURL(imageFiles[i]);
    }
});
  • 드래그로 이미지 삽입
#dropZone {
    min-height: 300px;
    border: 3px solid #cecece;
    border-radius: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
}

#dropZone img {
    width: 120px;
    height: auto;
    margin: 10px;
    border-radius: 8px;
    object-fit: cover;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
const dropZone = document.querySelector("#dropZone");

// drag 해서 위에 올라가 있을 때
dropZone.addEventListener("dragover", (e) => {
    e.preventDefault(); // 기본 동작 막기
});

// drop 했을 때
dropZone.addEventListener("drop", (e) => {
    e.preventDefault(); // 기본 동작 막기

    // e.dataTransfer.files 는 drop 된 파일의 정보가 들어 있는 배열 (일반 배열이 아니다)
    // 일반 배열처럼 사용하고 싶으면 Array.from() 함수에 넣어서 리턴되는 배열은 일반 배열
    const files = Array.from(e.dataTransfer.files);

    // 배열의 filter() 함수를 이용해서 이미지 파일만 들어 있는 배열을 얻어낸다
    const imageFiles = files.filter(item => item.type.startsWith("image/"));
    if (imageFiles.length == 0) {
        alert("이미지 파일을 drop 하세요!");
        return; // 함수를 여기서 종료하기
    }
  
	// drop zone 클리어
	dropZone.innerHTML = "";

    for (let i = 0; i < imageFiles.length; i++) {
        const reader = new FileReader();
        reader.onload = () => {
            const dataUrl = reader.result;
            const img = `<img src="${dataUrl}">`;
            dropZone.insertAdjacentHTML("beforeend", img);
        };
        reader.readAsDataURL(imageFiles[i]);
    }

    document.querySelector("#myImage").files = e.dataTransfer.files;
});

// drop zone 을 클릭했을 때 input type="file" 을 강제로 클릭해서 파일을 선택할 수 있게 한다
dropZone.addEventListener("click", () => {
    document.querySelector("#myImage").click();
});

비동기 방식 (async)

const reader = xxx;
reader.read(이미지 파일)
reader.onload = ()=>{
	다 읽었을 때 호출되는 함수
}
또 다른 동작...

동기 방식 (멀티스레딩)

const reader = xxx;
// 다 읽을 때까지 여기서 대기
const result = reader.read(이미지 파일)
// result 에는 읽은 정보가 들어 있다
또다른 동작...

JAVA & JSP

사진 업로드

  • index.html 코드 추가
<li><a href="${pageContext.request.contextPath}/test/file-form.jsp">파일 업로드 테스트</a></li>
  • SecurityFilter 화이트 리스트에 "/test" 추가

  • webapp 폴더 안에 test 폴더 생성 후 file-form.jsp 생성

<div class="container">
	<h1>파일 업로드 테스트</h1>
	<form action="${pageContext.request.contextPath}/test/fileup"
		enctype="multipart/form-data" method="post">
		<input type="text" name="caption" placeholder="설명 입력"/>
		<br />
		<input type="file" name="myFile"/>
		<br />
		<button type="submit">업로드</button>
	</form>
</div>
⭐ - file 을 전송해야 하기 때문에 필요한 form 의 속성
	enctype="multipart/form-data
	input type="file"

	- 이 요청을 처리할 서블릿이 필요 (@WebServlet("/test/fileup")
	/test/fileup
    
    - 전송되는 parameter의 이름
	myFile , caption
  • FileupServlet 생성
@WebServlet("/test/fileup")
@MultipartConfig(
		fileSizeThreshold = 1024*1024*10, // 업로드 처리하기 위한 메모리 사이즈(10 Mega byte)
		maxFileSize = 1024*1024*50, // 업로드되는 최대 파일 사이즈 (50 Mega byte)
		maxRequestSize = 1024*1024*60) // 이 요청의 최대 사이즈(60 Mega byte), 파일 외의 다른 문자열도 전송되기 때문에
public class FileupServlet extends HttpServlet{
	// 업로드된 파일 저장경로를 저장할 필드 선언
	String fileLocation;
		
	// 이 서블릿이 초기화되는 시점에 최초 1번 호출되는 메소드
	@Override
	public void init() throws ServletException {
		// 무언가 초기화 작업을 여기서 하면 된다
		ServletContext context = getServletContext();
		// web.xml 파일에 "fileLocation" 이라는 이름으로 저장된 정보를 읽어와서 필드에 저장하기
		fileLocation = context.getInitParameter("fileLocation");
		// "C:/playground/upload" 에 저장될 예정
	}
	
	// post 방식 요청만 처리할 거라면 doPost() 메소드를 오버라이드 한다
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		/*
		 * "caption" 이라는 파라미터 명으로 문자열이 전송되고
		 * "myFile" 이라는 파라미터 명으로 파일이 전송된다 (주출 방식이 다름)
		 * */
		String caption = req.getParameter("caption");
		// 파일이 하나라면 getPart, 여러 개의 파일이라며 Parts (list 형태)
		// <input type="file" name="myFile">
		Part filePart = req.getPart("myFile");
		if(filePart != null && filePart.getSize() > 0) {
			// 원본 파일의 이름 얻어내기
			String orgFileName = filePart.getSubmittedFileName();
			// 파일명이 겹치지 않게 랜덤한 id 값 얻어내기
			String uid = UUID.randomUUID().toString();
			// 저장될 파일명을 구성한다
			String saveFileName = uid+orgFileName;
			// 저장할 파일의 경로 구성하기
			String filePath = fileLocation+"/"+saveFileName;
			/*
			 * 업로드된 파일은 임시 폴더에 임시 파일로 저장할 수 있다
			* 해당 파일에서 byte 알갱이를 읽어들일 수 있는 InputStream 객체를 얻어내서
			* */
			InputStream is = filePart.getInputStream();
			// 원하는 목적지에 copy 를 해야 한다
			Files.copy(is, Paths.get(filePath));
		}
	}
}
⭐ 파일 다운로드 처리 시
	업로드된 파일은 임시 폴더에 변경된 파일명으로 저장
    원래 파일이름과 크기 등의 정보는 DB에 저장
    다운로드 시 사용자에게 원본 정보로 표시

응답 위임

💡 요청받은 서블릿에서 최종 응답 출력을 JSP에 위임 가능

➜ 서블릿에서 복잡한 처리를 수행한 후 응답에 필요한 데이터를 JSP로 전달해 응답 처리를 맡긴다

  • FileupServlet 코드 추가
// 요청을 전달한 객체 얻어내기
RequestDispatcher rd = req.getRequestDispatcher("/test/응답.jsp");
// 응답을 위임하기
rd.forward(req, resp);
  • 응담.jsp 생성
<div class="container">
	<p>응답합니다</p>
</div>
  • FileupServlet if문 안에 코드 추가
/*
* 원래는 DB 에 저장해야 하지만 테스트로 응답에 필요한 데이터를
* HttpServletRequest 객체의 .setAttribute(키, 값) 메소도를 이용해서 저장
* jsp 페이지가 사용할 수 있도록 한다
* jsp 페이지에서 해당 정보를 얻어날 때는
* HttpServletRequest 객체의 .getAttribute(키) 메소드를 이용해서 얻어낸다
* Object type 으로 리턴되기 때문에 원래 type 으로 casting 을 해야 한다
* */
req.setAttribute("orgFileName", orgFileName); // String type
req.setAttribute("saveFileName", saveFileName); // String type
req.setAttribute("fileSize", fileSize); // long type
  • 응답.jsp 코드 추가
<%
	// request 객체에 특정 key 값을로 담긴 정보 추출하기
	String orgFileName = (String)request.getAttribute("orgFileName");
	String saveFileName = (String)request.getAttribute("saveFileName");
	long fileSize = (long)request.getAttribute("fileSize");
%>
<div class="container">
	<p>응답합니다</p>
	<p>원본 파일명 : <%=orgFileName%></p>
	<p>저장된 파일명 : <%=saveFileName%></p>
	<p>파일의 크기 : <%=fileSize%></p>
		
	<!-- el 을 이용하면 casting 필요없이 바로 추출할 수도 있다 -->
	<p>원본 파일명 : ${requestScope.orgFileName}</p>
	<p>저장된 파일명 : ${requestScope.saveFileName}</p>
	<!-- requestScope. 은 생략 가능 -->
	<p>파일의 크기 : ${fileSize}</p>
</div>

파일 업로드 & 다운로드

원본 파일명 , 저장된 파일명 , 파일의 크기
➜ 3가지를 알면 다운로드 가능한 로직 처리 가능

  • 응답.jsp 코드 추가
<a href="${pageContext.request.contextPath}/test/download?orgFileName=${orgFileName}&saveFileName=${saveFileName}&fileSize=${fileSize}">다운로드</a>
  • TestDownloadServlet 생성
@WebServlet("/test/download")
public class TestDownloadServlet extends HttpServlet{
	// 이미지 저장 경로 
    private  String fileLocation;
    
    @Override
    public void init() throws ServletException {
        ServletContext context = getServletContext();
        fileLocation = context.getInitParameter("fileLocation");
    }
	
	
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// get 방식 파라미터로 전달되는 다운로드해 줄 파일의 정보를 얻어온다 (원래는 pk 를 이용해서 DB 에서 읽어와야 한다)
		String orgFileName = request.getParameter("orgFileName");
		String saveFileName = request.getParameter("saveFileName");
		// 여기서에서는 굳이 long type 이 필요 없지만 테스트로 바꿔봄
		long fileSize = Long.parseLong(request.getParameter("fileSize"));
		
		//응답 헤더 정보 설정
        // application/octet-stream : 형식을 알 수 없는 파일 지칭
		response.setHeader("Content-Type", "application/octet-stream; charset=UTF-8");
		//다운로드 시켜줄 파일명 인코딩  
		String encodedName=URLEncoder.encode(orgFileName, "utf-8");
		//파일명에 공백이있는 경우 처리 (공백을 +로 전환 후 다시 +를 공백으로 치환)
		encodedName=encodedName.replaceAll("\\+"," ");
				
		response.setHeader("Content-Disposition", "attachment;filename="+encodedName);
		response.setHeader("Content-Transfer-Encoding", "binary");
				
		//다운로드할 파일의 크기
		response.setContentLengthLong(fileSize);
				
		//다운로드 시켜줄 파일의 실제 경로
		String path=fileLocation+File.separator+saveFileName;
				
		FileInputStream fis=null;
		BufferedOutputStream bos=null;
		try {
			//파일에서 byte 을 읽어들일 객체
			fis=new FileInputStream(path);
			//클라이언트에게 출력할수 있는 스트림 객체 얻어오기
			bos=new BufferedOutputStream(response.getOutputStream());
			//한번에 최대 1M byte 씩 읽어올수 있는 버퍼
			byte[] buffer=new byte[1024*1024];
			int readedByte=0;
			//반복문 돌면서 출력해주기
			while(true) {
				//byte[] 객체를 이용해서 파일에서 byte 알갱이 읽어오기
				readedByte = fis.read(buffer);
				if(readedByte == -1)break; //더이상 읽을 데이터가 없다면 반복문 빠져 나오기
					//읽은 만큼 출력하기
				   	bos.write(buffer, 0, readedByte);
				   	bos.flush(); //출력
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			if(fis!=null)fis.close();
			if(bos!=null)bos.close();
		}		
	}
}
⚠️ DB에서 파일 정보를 사용할 경우, FileDto에 파일명과 크기 정보를 포함
profile
학원 공부 내용 정리

0개의 댓글