1. 화면(뷰) – 업로드 폼 & 결과 페이지
업로드 폼 main.html
<h3>single file upload</h3>
<form action="single-file" method="post" **enctype="multipart/form-data"**>
파일: <input type="file" name="singleFile"><br>
파일 설명: <input type="text" name="singleFileDescription"><br>
<input type="submit" value="업로드">
</form>
<h3>multi file upload</h3>
<form action="multi-file" method="post" **enctype="multipart/form-data"**>
파일: <input type="file" name="multiFiles" **multiple**><br>
파일 설명: <input type="text" name="multiFileDescription"><br>
<input type="submit" value="업로드">
</form>
enctype="multipart/form-data": 파일 업로드에 필수
name="singleFile" / name="multiFiles": 컨트롤러의 파라미터 이름과 매칭
multiple: 다중 선택 가능
결과 페이지 result.html
<h1 th:text="${message}"></h1>
<div th:if="${img != null}">
<img th:src="${img}" width="200" height="200"/>
<p th:text="${singleFileDescription}"></p>
</div>
<div th:if="${imgs != null}">
<th:block th:each="img: ${imgs}">
<img th:src="${img}" width="150" height="150">
</th:block>
<p th:text="${multiFileDescription}"></p>
</div>
- 컨트롤러에서
RedirectAttributes로 넣어준 Flash Attribute들을 받아 출력
- 단일:
${img} 1개 / 다중: ${imgs} 리스트를 순회
2. 설정 application.yml
server:
port: 8081
tomcat:
max-connections: 200
filepath: /Users/.../uploadFiles
spring:
servlet:
multipart:
enabled: true
max-file-size: 100MB
max-request-size: 100MB
web:
resources:
static-locations: file:///Users/.../uploadFiles/
cache-period: 3600
server.port=8081: 서버 포트
filepath: 실제 파일 저장 디렉토리 루트(컨트롤러에서 @Value로 주입받음)
multipart 한도: 파일/요청당 용량 제한
static-locations:
- 정적 리소스를 로컬 폴더에서 서빙하도록 지정
- 여기서
/Users/.../uploadFiles/가 웹 루트 /처럼 동작
- 이 경로 아래
img/single/xxx.jpg 저장 → 브라우저에서 /img/single/xxx.jpg로 접근 가능
3. 컨트롤러
@Controller
public class FileUploadController {
@Value("${filepath}")
private String filepath; // yml에서 주입 (업로드 루트 경로)
@PostMapping("single-file")
public String singleFile(@RequestParam MultipartFile singleFile,
@RequestParam String singleFileDescription,
RedirectAttributes rttr) {
// 1) 원본 이름/확장자 추출
String originFileName = singleFile.getOriginalFilename();
String ext = originFileName.substring(originFileName.lastIndexOf("."));
// 2) 서버 저장용 파일명 리네임(중복 방지)
String saveName = UUID.randomUUID().toString().replace("-", "") + ext;
// 3) 실제 저장 (로컬 디스크)
singleFile.transferTo(new File(filepath + "/img/single/" + saveName));
// 4) 결과 페이지에 보여줄 데이터(Flash Attribute)
rttr.addFlashAttribute("message", originFileName + " 파일 업로드 성공!");
rttr.addFlashAttribute("img", "/img/single/" + saveName);
rttr.addFlashAttribute("singleFileDescription", singleFileDescription);
return "redirect:/result";
}
@GetMapping("result")
public void result(){}
@PostMapping("multi-file")
public String multiFileUpload(@RequestParam List<MultipartFile> multiFiles,
@RequestParam String multiFileDescription,
RedirectAttributes rttr) {
List<Map<String,String>> files = new ArrayList<>();
List<String> imgSrcs = new ArrayList<>();
try {
for (MultipartFile mf : multiFiles) {
String originFileName = mf.getOriginalFilename();
String ext = originFileName.substring(
originFileName.lastIndexOf("."));
String saveName = UUID.randomUUID().toString()
.replace("-", "") + ext;
// 저장
mf.transferTo(new File(filepath + "/img/multi/" + saveName));
// DB 전송용 구조(예시로만 저장)
Map<String, String> file = new HashMap<>();
file.put("originFileName", originFileName);
file.put("saveName", saveName);
file.put("filePath", "/img/multi/");
file.put("multiFileDescription", multiFileDescription);
files.add(file);
// 화면 표시용 경로
imgSrcs.add("/img/multi/" + saveName);
}
rttr.addFlashAttribute("message", "다중 파일 업로드 성공!");
rttr.addFlashAttribute("imgs", imgSrcs);
rttr.addFlashAttribute("multiFileDescription", multiFileDescription);
} catch (IOException e) {
// 일부 저장 성공 후 실패 대비 → 이미 저장된 파일 롤백 삭제
for (Map<String, String> file : files) {
new File(filepath + "/img/multi/" + file.get("saveName"))
.delete();
}
rttr.addFlashAttribute("message", "다중 파일 업로드 실패!");
}
return "redirect:/result";
}
}