πŸ“Œ [Java Servlet 파일 μ—…λ‘œλ“œ κ΅¬ν˜„ - FileSystem μ €μž₯ + λͺ©λ‘ 좜λ ₯ + Drag & Drop + Axios 비동기 처리]

My Pale Blue DotΒ·2025λ…„ 4μ›” 21일

JSP/SERVLET

λͺ©λ‘ 보기
25/25
post-thumbnail

πŸ“… λ‚ μ§œ

2025λ…„ 4μ›” 18일


πŸ“ ν•™μŠ΅ λ‚΄μš©

βœ… 파일 μ—…λ‘œλ“œ 방식 비ꡐ

방식섀λͺ…μž₯점단점
DB (BLOB)DB에 파일 자체λ₯Ό μ €μž₯νŠΈλžœμž­μ…˜/λ°±μ—… μš©μ΄λŒ€μš©λŸ‰ 처리 뢀적합
FileSystem + 경둜만 DBμ €μž₯νŒŒμΌμ€ μ„œλ²„μ— μ €μž₯, DBμ—λŠ” 경둜만 μ €μž₯μ„±λŠ₯ 우수, λ‹¨μˆœ 처리경둜 관리 μ‹€νŒ¨ μ‹œ λΆˆμ•ˆμ •

βœ… 전체 μ—…λ‘œλ“œ/λ‹€μš΄λ‘œλ“œ 흐름 μš”μ•½

[1] μ‚¬μš©μž 파일 선택 or Drag & Drop
 ↓
[2] JSμ—μ„œ FormData ꡬ성 ν›„ Axios둜 μ„œλ²„μ— POST μš”μ²­
 ↓
[3] UploadController β†’ FileServiceImpl 호좜 β†’ 파일 μ €μž₯
 ↓
[4] λͺ©λ‘ μš”μ²­ β†’ ListController β†’ 파일 μ‹œμŠ€ν…œμ—μ„œ λͺ©λ‘ 쑰회
 ↓
[5] λ‹€μš΄λ‘œλ“œ μš”μ²­ μ‹œ β†’ DownloadController β†’ FileServiceImpl.download()
 ↓
[6] 파일 슀트리밍 ν›„ μ‚¬μš©μž λΈŒλΌμš°μ €λ‘œ 전솑

βœ… @MultipartConfig μ„€μ •

@WebServlet("/file/upload")
@MultipartConfig(
  fileSizeThreshold = 1024 * 1024,
  maxFileSize = 1024 * 1024 * 5,
  maxRequestSize = 1024 * 1024 * 20
)

βœ… UploadController.java

  • POST μš”μ²­ μ‹œ μ—…λ‘œλ“œ 처리
  • μ—…λ‘œλ“œ 성곡 μ‹œ λͺ©λ‘ νŽ˜μ΄μ§€λ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈ
  • μ‹€νŒ¨ μ‹œ μ—…λ‘œλ“œ 뷰둜 λ‹€μ‹œ ν¬μ›Œλ”©
  • μ‚¬μš©μžμ—κ²Œ 성곡 λ©”μ‹œμ§€ 전달 κ°€λŠ₯ (req.setAttribute("message", "μ—…λ‘œλ“œ 성곡!") λ“±)

βœ… FileServiceImpl.java - μ—…λ‘œλ“œ, λͺ©λ‘, λ‹€μš΄λ‘œλ“œ 톡합

πŸ”Ή 파일 μ—…λ‘œλ“œ

String folderName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
String UploadPath = ROOT_PATH + "/" + UPLOAD_PATH + "/" + folderName + "/";
File dir = new File(UploadPath);
if (!dir.exists()) dir.mkdirs();

String filename = ... // 원본 이름
String uuid = UUID.randomUUID().toString();
String newFileName = uuid + "_" + filename; // UUID 적용
part.write(UploadPath + newFileName);

πŸ”Ή 파일 ν™•μž₯자 μ œν•œ μ˜ˆμ‹œ

String ext = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
List<String> blocked = List.of("exe", "bat", "sh");
if (blocked.contains(ext)) throw new ServletException("ν—ˆμš©λ˜μ§€ μ•ŠλŠ” 파일 ν˜•μ‹μž…λ‹ˆλ‹€.");

πŸ”Ή 파일 λͺ©λ‘ 쑰회

Map<String, List<File>> map = new LinkedHashMap<>();
for (File folder : uploadRoot.listFiles()) {
    map.put(folder.getName(), Arrays.stream(folder.listFiles()).toList());
}

πŸ”Ή 파일 λ‹€μš΄λ‘œλ“œ

String path = ROOT_PATH + "/" + UPLOAD_PATH + "/" + folder + "/" + filename;
File file = new File(path);
if (!file.exists()) {
  response.sendError(HttpServletResponse.SC_NOT_FOUND, "파일이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
  return false;
}
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));

βœ… ListController.java

Map<String, List<File>> map = fileService.getFileList();
req.setAttribute("map", map);
req.getRequestDispatcher("/WEB-INF/view/file/list.jsp").forward(req, resp);

βœ… DownloadController.java

boolean isDownloaded = fileService.download(req, resp);
// 직접 λ‹€μš΄λ‘œλ“œ 처리 (λ·° μ—†μŒ)

βœ… list.jsp - μ—…λ‘œλ“œλœ 파일 λͺ©λ‘ 좜λ ₯ + λ‹€μš΄λ‘œλ“œ 링크

<h1>FILE LIST</h1>
<%
	Map<String,List<File>> map = (Map<String,List<File>>)request.getAttribute("map");
	if (map != null) {
		for (String folder : map.keySet()) {
%>
	<h3><%= folder %></h3>
<%
			for (File file : map.get(folder)) {
%>
	<a href="/file/download?folder=<%= folder %>&filename=<%= file.getName() %>">
		<%= file.getName() %>
	</a><br/>
<%
			}
			out.println("<hr/>");
		}
	}
%>

πŸ“š κ°œλ… 정리

πŸ”Ή multipart/form-dataλž€?

HTML νΌμ—μ„œ νŒŒμΌμ„ ν¬ν•¨ν•œ 데이터λ₯Ό μ„œλ²„λ‘œ 전솑할 λ•Œ μ‚¬μš©ν•˜λŠ” 인코딩 방식

πŸ”Ή FormData κ°μ²΄λž€?

JSμ—μ„œ 파일 및 데이터λ₯Ό key-value 쌍으둜 κ΅¬μ„±ν•˜μ—¬ μ„œλ²„μ— λ³΄λ‚΄λŠ” 객체

πŸ”Ή UUIDλž€?

Universal Unique Identifier. μ€‘λ³΅λ˜μ§€ μ•ŠλŠ” κ³ μœ ν•œ λ¬Έμžμ—΄λ‘œ 파일λͺ… μΆ©λŒμ„ 방지함

πŸ”Ή Content-Disposition: attachmentλž€?

λΈŒλΌμš°μ €κ°€ νŒŒμΌμ„ μ—΄μ§€ μ•Šκ³  λ‹€μš΄λ‘œλ“œν•˜κ²Œ μ§€μ‹œν•˜λŠ” 응닡 헀더

πŸ”Ή Partλž€?

multipart μš”μ²­μ—μ„œ 파일 λ˜λŠ” ν•„λ“œ ν•˜λ‚˜ν•˜λ‚˜λ₯Ό μ˜λ―Έν•˜λŠ” 객체 (Servlet 3.0 이상 지원)

πŸ”Ή URLEncoder.encode()λž€?

URL에 ν•œκΈ€, 곡백, νŠΉμˆ˜λ¬Έμžκ°€ λ“€μ–΄κ°ˆ 경우 깨짐을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ 인코딩 μ²˜λ¦¬ν•˜λŠ” λ©”μ„œλ“œ


πŸ”— μ°Έκ³  자료


이제 이 λ‚΄μš©μ„ Velog에 κ·ΈλŒ€λ‘œ λ³΅λΆ™ν•΄μ„œ μ—…λ‘œλ“œ κ°€λŠ₯ν•©λ‹ˆλ‹€! πŸš€
λ‹€μŒ νŽΈμ—μ„œλŠ” 파일 μ‚­μ œ κΈ°λŠ₯ κ΅¬ν˜„, 파일 경둜 DB μ €μž₯ 및 쑰회, 파일λͺ… μ •κ·œν™”/λ³΄μ•ˆ 필터링을 μ΄μ–΄μ„œ λ‹€λ£° 수 μžˆμ–΄μš”.
ν•„μš”ν•˜λ©΄ λ°”λ‘œ λ§ν•΄μ£Όμ„Έμš”! 😊

profile
Here, My Pale Blue.🌏

0개의 λŒ“κΈ€