File
업로드 커맨드 객체에 파일 추가
form.setFiles(files);
파일 업로드 전 처리
beforeProcess.process(form);
//process
이미지만 포함되어있어야함
이미지가 아닌 경우 예외 발생
// 업로드된 파일에서 이미지만 포함되어 있는지 체크
if (form.isImageOnly()) {
for (MultipartFile file : files) {
String contentType = file.getContentType();
// 이미지이면 image/png, image/gif ..
// 이미지가 아닌 파일이 포함된 경우
if (!contentType.contains("image/")) {
throw new FileTypeException(HttpStatus.BAD_REQUEST);
}
}
}
단일 파일업로드 인경우 기존 파일 삭제 후 새로운 파일 set
if (form.isSingle()) {
deleteService.delete(form.getGid(), form.getLocation(), FileStatus.All);
form.setFiles(new MultipartFile[] { files[0] });
}
List<FileInfo> items = uploadService.upload(files, form.getGid(), form.getLocation());
/**
1. 파일 정보 저장
2. 파일을 서버로 이동
3. 이미지이면 썸네일 생성
4. 업로드한 파일 목록 반환
*/
gid 설정(form.getGid()) 비어있으면 랜덤으로
gid = StringUtils.hasText(gid) ? gid : UUID.randomUUID().toString();
매개변수로 가져온 파일의 정보들 build 후 엔티티 데이터베이스로 플러시 (seq는 GeneratedValue)
// 1. 파일 정보 저장
for (MultipartFile file : files) {
String fileName = file.getOriginalFilename(); // 업로드 파일 원래 이름
String contentType = file.getContentType(); // 파일 형식 (image/jpeg)
String extension = fileName.substring(fileName.lastIndexOf(".")); // 확장자
FileInfo fileInfo = FileInfo.builder()
.gid(gid)
.location(location)
.fileName(fileName)
.contentType(contentType)
.extension(extension)
.build();
fileInfoRepository.saveAndFlush(fileInfo);
uploadPath 지정
// 2. 파일을 서버로 이동
long seq = fileInfo.getSeq();
String uploadDir = properties.getPath() + "/" + (seq % 10L); // 디렉토리 분산
File dir = new File(uploadDir); // 경로와 파일 사이 연결을 표현
if (!dir.exists() || !dir.isDirectory()) {
dir.mkdir();
}
String uploadPath = uploadDir + "/" + seq + extension;
uploadPath 경로로 저장
만들어 뒀던 uploadedFiles 리스트에 저장
try { // transferTo 메서드는 파일을 지정된 경로로 이동, 복사
file.transferTo(new File(uploadPath)); // 업로드된 파일을 uploadPath 경로에 저장
uploadedFiles.add(fileInfo); // 업로드 성공 파일 정보
} catch (IOException e) {
e.printStackTrace();
// 파일 이동 실패시 정보 삭제
fileInfoRepository.delete(fileInfo);
fileInfoRepository.flush();
}
infoservice로 리스트 각각 요소에 대해서 addFileInfo 실행
uploadedFiles.forEach(fileInfoService::addFileInfo);
매개변수 item은 아까 builder했던 FileInfo 엔티티
item entities에 fileUrl, filePath 저장
public void addFileInfo(FileInfo item) {
String fileUrl = getFileUrl(item);
String filePath = getFilePath(item);
item.setFileUrl(fileUrl);
item.setFilePath(filePath);
}
// 브라우저 접근 주소
public String getFileUrl(FileInfo item) {
return request.getContextPath() + properties.getUrl() + getFolder(item.getSeq()) + "/" + getFileName(item);
}
// 서버 업로드 경로
public String getFilePath(FileInfo item) {
return properties.getPath() + "/" + getFolder(item.getSeq()) + "/" + getFileName(item);
}
public String getFolder(long seq) {
return String.valueOf(seq % 10L);
}
public String getFileName(FileInfo item) {
String fileName = item.getSeq() + Objects.requireNonNullElse(item.getExtension(), "");
return fileName;
}
uploadPath: 파일 업로드 시 파일이 저장될 구체적인 경로를 나타내며, 업로드 작업 중에만 사용. 파일을 서버에 저장하는 시점에 활용되는 경로.
filePath: 파일이 서버에 저장된 후, 파일의 위치를 추적하고 관리하기 위해 사용되는 경로. 데이터베이스나 다른 시스템에서 파일의 위치를 참조할 때 사용.
서버에 저장은 됐음.
afterProcess.process(form); // 파일 업로드 후처리
사용자가 입력했던 파일 form 을 가지고 gid, location 가지고 doneService.process 실행
public void process(RequestUpload form) {
// 파일 업로드 직후 완료 처리
if (form.isDone()) {
doneService.process(form.getGid(), form.getLocation());
}
}
: 파일 업로드의 위치나 목적지를 나타내는 데 사용
: 파일이 저장될 경로, 논리적 위치, 업로드 대상, 또는 컨텍스트 정보를 지정하는 데 활용
infoService.getList 로부터 FileInfo 리스트 가져옴
, 그룹 작업 완료 여부를 업데이트함
public void process(String gid, String location) {
List<FileInfo> items = infoService.getList(gid, location, FileStatus.All);
사용자가 입력한 파일의 form. gid, location 를 통해서 andBuilder 패턴으로 gid, location, status를(DONE)인것만 걸러서 담아줌
그리고 생성 날짜로 정렬함.
public List<FileInfo> getList(String gid, String location, FileStatus status) {
QFileInfo fileInfo = QFileInfo.fileInfo;
BooleanBuilder andBuilder = new BooleanBuilder();
andBuilder.and(fileInfo.gid.eq(gid));
if (StringUtils.hasText(location)) {
andBuilder.and(fileInfo.location.eq(location));
}
if (status != FileStatus.All) {
andBuilder.and(fileInfo.done.eq(status == FileStatus.DONE));
}
List<FileInfo> items = (List<FileInfo>)infoRepository.findAll(andBuilder, Sort.by(asc("createdAt")));
// 2차 추가 데이터 처리
items.forEach(this::addFileInfo);
return items;
}
public List<FileInfo> getList(String gid, String location) {
return getList(gid, location, FileStatus.DONE);
}
public List<FileInfo> getList(String gid) {
return getList(gid, null, FileStatus.DONE);
}
FileInfo 엔티티에 항목을 status가 DONE 인 항목들만 걸렀으니
done을 true로 설정해주고
플러시함
// process에서
items.forEach(i -> i.setDone(true));
repository.saveAllAndFlush(items);
}
public void process(RequestUpload form) {
// 파일 업로드 직후 완료 처리
if (form.isDone()) {
// status가 DONE인 항목들만 거르고 , 그 요소들에 대해서 setDone(true)로 설정함
doneService.process(form.getGid(), form.getLocation());
}
}
FileManager.js 의 존재 이유
마치 spring 프로젝트 백엔드 단계에서 validation과 커맨드객체를 활용하여 검증하듯이
JS는 프론트 단계에서 파일이 백엔드로 이루어지기전에 검증하여 사용자에게 즉각적인 피드백을 보여준다.
프론트와 백엔드 단계에서 적절한 검증이 이루어짐에 따라 서버로의 불필요한 데이터 전송과 더욱 빠른 데이터 처리를 보여준다.
요약
commonLib: 클라이언트 측에서 서버로 HTTP 요청을 보내고, 서버의 응답을 처리하는 공통 기능을 제공. 이 함수는 요청의 메소드, URL, 본문 데이터, 헤더 등을 설정하며, 서버와의 통신을 간소화하고 일관성 있게 처리할 수 있도록
도움.
Ajax 기능 (실시간으로 페이지를 필요한 부분만 로드함)
CSRF 토큰을 포함시키고, fetch를 통해 서버로 비동기 요청을 보냄. 서버는 이 토큰을 검증하여 요청의 유효성을 확인.