아니 파일 올리고 받는 거 너무 신기하네..
<form action="/" method="post" enctype="multipart/form-data">
MultipartFile 형태
(신규 클래스)로 파일을 수신 # application.properties
# multipart resolver
spring.servlet.multipart.max-file-size=1MB //낱개로 1mb
spring.servlet.multipart.max-request-size=10MB //전체 10mb 제한(총 10개 전송 가능)
@PostMapping("/")
public String upload(@RequestParam String uploader,
@RequestParam MultipartFile attachment) {
System.out.println("uploader = " + uploader);
System.out.println("attachment = " + attachment);
return "redirect:/";
}
//attachment 분석
System.out.println("content type = " + attachment.getContentType());
System.out.println("name = " + attachment.getName());
System.out.println("original file name = " + attachment.getOriginalFilename());
System.out.println("size = " + attachment.getSize());
File directory = new File("D:/upload");
//OS 무관한 홈 폴더 경로:
//System.getProperty("user.home") + "/upload");
directory.mkdirs(); //폴더 생성 명령
File target = new File(directory,
attachment.getOriginalFilename()); //저장될 파일 생성
attachment.transferTo(target); //예외 (전가: 첫 번째 옵션 선택) //실제 저장 처리 명령
변조 필요
)아래 세 가지 방법 중 3번 선택
PK로 이름
을 설정정보
도 DB
에 따로 저장겹치지 않는 이름
을 생성회원가입 시, 프로필 사진을 업로드할 수 있도록 처리하려고 함. 회원가입에서 에러 발생 시 파일이 저장되면 안되므로 DB에 회원 정보를 등록하는 구문보다 뒤에 파일 저장 명령을 추가함
유저가 프로필 이미지 안 올렸을 때 폴더에 저장되면 안되므로 첨부파일이 있는 경우에만 upload/member 폴더 생기고 거기에 아이디가 이름인 파일 추가하도록 처리
파일을 열어볼 게 아니므로 확장자를 지정하지 않음
//(+) 첨부파일(프로필 이미지)을 받아서 저장
@PostMapping("/join")
public String join(@ModelAttribute MemberDto dto,
@RequestParam MultipartFile memberProfile) throws IllegalStateException, IOException {
//DB 등록
memberDao.join(dto);
if(! memberProfile.isEmpty()) {//첨부파일이 있다면
//프로필 저장
File directory = new File("D:/upload/member");
directory.mkdirs();
File target = new File(directory, dto.getMemberId());
memberProfile.transferTo(target);
}
return "redirect:join_finish";
}
특정 사용자의 프로필 이미지를 다운로드하는 매핑
만들어서 반환해라
와 같이 말하듯이 만들 수 있음<!-- appache-commons io 파일 입출력 자동화시켜주는 의존성 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
@GetMapping("/download")
@ResponseBody
public ResponseEntity<ByteArrayResource> download(//반환형:ResponseEntity
@RequestParam String memberId) throws IOException {
//A. 파일 찾기
File directory = new File("D:/upload/member");
File target = new File(directory, memberId); //directory 내에서 memberId 파일명 조회
if(target.exists()) {//파일 존재
//B. 해당 파일 내용 불러오기(apache commons io 의존성 수동 추가 필요)
byte[] data = FileUtils.readFileToByteArray(target);//예외 전가
ByteArrayResource resource = new ByteArrayResource(data);//포장
//C. 사용자에게 보낼 응답 생성
return ResponseEntity.ok().body(resource); //ok:200번
}else {
//1. 우리가 정한 예외를 발생시키는 방법
throw new TargetNotFoundException("프로필 이미지 없음");
//2. 사용자에게 못찾음(404) 전송
//return ResponseEntity.notFound().build();
}
}
return ResponseEntity.ok().header("Content-Encoding", "UTF-8")
.header("Content-Length", String.valueOf(data.length))
//사이즈 알려주면 다운로드 진행률 알 수 있음
//html 통신이므로 String 타입만 보낼 수 있음
.header("Content-Disposition", "attachment; filename=" + memberId)
//파일을 다루는 방법을 알려줌
//브라우저에 인라인으로 표시될 것인지,
//다운로드 된 파일로 표시될 것인지 등
.header("Content-Type","application/octet-stream")
//파일 유형
//image/jpg로 적을 수 있으나
//db에 저장하지 않았으므로 확장자를 모름
//이미지로 지정하지 않고 무조건 다운로드 받으라고 지정
//DB가 없으면 알려줄 수 있는 정보가 제한적이고 못하는 게 많음
.body(resource); //ok:200번
<!-- 프로필 이미지 출력 -->
<img src="download?memberId=${dto.memberId}" width="100" height="100">
파일 크기
를 int로 하면 안됨. long
으로 저장-- 파일 테이블
create table attachment(
attachment_no number primary key,
attachment_name varchar2(256) not null,
attachment_type varchar2(30) not null,
attachment_size number not null check(attachment_size>=0),
attachment_time date default sysdate not null
);
create sequence attachment_seq;
//Dao
int sequence();
void insert(AttachmentDto attachmentDto);
List<AttachmentDto> selectList();
AttachmentDto selectOne(int attachmentNo);
boolean delete(int attachmentNo);
<h1>DB 사용하는 파일 업로드</h1>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="attachment"><br><br>
<button type="submit">등록</button>
</form>
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile attachment) throws IllegalStateException, IOException {
//DB 저장
int attachmentNo = attachmentDao.sequence();
attachmentDao.insert(AttachmentDto.builder()
.attachmentNo(attachmentNo)
.attachmentName(attachment.getOriginalFilename())
.attachmentType(attachment.getContentType())
.attachmentSize(attachment.getSize())
.build());
//file 저장
File dir = new File("D:/upload");
dir.mkdirs();
File target = new File(dir, String.valueOf(attachmentNo));//파일명이 시퀀스 번호로 생성됨
attachment.transferTo(target); //예외 전가
return "redirect:/";
}
@GetMapping("/list")
public String list(Model model) {
model.addAttribute("list", attachmentDao.selectList());
return "list";
}
<c:forEach var="attachementDto" items="${list}">
<h1>
[${attachmentDto.attachmentType}]
${attachmentDto.attachmentName}
(${attachmentDto.attachmentSize} bytes)
</h1>
</c:forEach>
파일이 없는 경우 필터링
가능해짐@GetMapping("/download")
public ResponseEntity<ByteArrayResource> download(
@RequestParam int attachmentNo) throws IOException{
//[1] 파일 탐색(DB)
AttachmentDto dto = attachmentDao.selectOne(attachmentNo);
if(dto == null) {//파일이 없으면
return ResponseEntity.notFound().build(); //404 error
}
//[2] 파일 불러오기
File dir = new File("D:/upload");
File target = new File(dir, String.valueOf(attachmentNo));
byte[] data = FileUtils.readFileToByteArray(target);
ByteArrayResource resource = new ByteArrayResource(data);
//[3] 응답 객체를 만들어 데이터를 전송
return ResponseEntity.ok()//ok:200번
.header("Content-Encoding", "UTF-8")
//.header("Content-Length", String.valueOf(data.length))
.header("Content-Length",
String.valueOf(dto.getAttachmentSize()))
.header("Content-Disposition",
"attachment; filename=" + dto.getAttachmentName())
//.header("Content-Type","application/octet-stream")
.header("Content-Type",dto.getAttachmentType())
.body(resource);
}
//[3] 응답 객체를 만들어 데이터를 전송
return ResponseEntity.ok()//ok:200번
//.header("Content-Encoding", "UTF-8")
.header(HttpHeaders.CONTENT_ENCODING,
StandardCharsets.UTF_8.name())
//.header("Content-Length", String.valueOf(data.length))
//.header("Content-Length", String.valueOf(dto.getAttachmentSize()))
.contentLength(dto.getAttachmentSize())//알아서 스트링 변환해줌
//.header("Content-Disposition",
//"attachment; filename=" + dto.getAttachmentName())
.header(HttpHeaders.CONTENT_DISPOSITION,
ContentDisposition.attachment()
.filename(dto.getAttachmentName(),
StandardCharsets.UTF_8)
.build().toString())
//파일명 한글, 띄어쓰기 다 처리 가능(위에 건 안됨)
//.header("Content-Type","application/octet-stream")
//.header("Content-Type",dto.getAttachmentType())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
//다양한 타입 사용하려면 위처럼 사용
.body(resource);