같은 파일을 두 번 전송하면 덮어씌워진다.
파일명도 동일해지면 안 된다.
UUID 사용
UUID를 테스트하기 위해 테스트 폴더 아래 파일을 하나 만들어준다.
원래 폴더는 main을 실행하면 서버가 실행되어 오래 걸리기 때문에
테스트할 때는 따로 빼서 해주자.
package site.metacoding.fileupload;
import java.util.UUID;
import org.junit.jupiter.api.Test;
public class UUIDTest {
public void 유유아이디_연습() {
UUID uuid = UUID.randomUUID();
System.out.println(uuid.toString());
}
}
결국 유유아이디_연습 메서드를 호출하려면 main이 실행되어야 하는데
단위 테스트하기 위해 Junit 어노테이션인 @Test를 붙여준다.
그러면 해당 메서드만 실행이 가능하다.
Junit만의 스레드를 만들어 독단적으로 실행하는 것이다.
실행한 것을 보기 위해서는 그냥 콘솔이 아닌 DEBUG CONSOLE에서 확인이 가능하다.
메서드를 실행시킬 때마다 값이 계속해서 바뀐다.
도대체 UUID는 뭘까?
UUID(Universally Unique IDentifier) : 네트워크 상에서 고유성이 보장되는 id를 만들기 위한 표준 규약
범용 고유 식별자라고 한다.
랜덤 하게 생성되는데 충돌될 확률이 거의 없다.
이 uuid를 파일명 앞에 붙여주면 된다.
파일명 뒤에 붙여주게 되면 파일의 확장자가 날아가니까 주의하자.
이제 이 이름을 path에 걸어주자.
package site.metacoding.fileupload;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.Controller;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserRepository userRepository;
// 파일을 받아서 DB에 INSERT : POST 요청
@PostMapping("/join")
public String join(JoinDto joinDto) {
UUID uuid = UUID.randomUUID();
String requestFileName = joinDto.getFile().getOriginalFilename();
System.out.println("전송받은 파일명 : " + requestFileName);
String imgurl = uuid + "_" + requestFileName;
try {
// Path 객체 생성
Path filePath = Paths.get("C:/upload/" + imgurl);
Files.write(filePath, joinDto.getFile().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return "joinComplete";
}
}
이미 같은 파일이 저장되어 있는데 다시 한번 더 send 해보자.
충돌이 일어나지 않고 잘 들어간다.
우리는 파일의 path를 지정할 때 상대 경로를 사용할 것이다.
상대 경로를 사용하면 최상위 폴더가 현재 내 프로젝트 폴더가 된다.
/를 붙이지 않으면 상대 경로는 내 프로젝트이고,
붙이면 최상단 폴더가 C드라이브이다.
보통 정적인 파일은 static 폴더 내부에 저장한다.
머스태치에서 /만 적으면 정적인 파일은 무조건 static 폴더에서 찾기 때문이다.
자바에서 /는 파일 시스템에서 최상위이고,
템플릿 엔진에서는 최상위가 static 폴더이다.
패스를 다음과 같이 설정하고 포스트맨에서 send 해보자.
Path filePath = Paths.get("src/main/resources/static/upload/" + imgurl);
파일이 잘 저장되었다!!
이제 머스태치 파일을 만들어서 머스태치로 전송해서
DB에 입력하고 입력된 것을 머스태치 화면에서 뷰로 보여줄 것이다.
우선 form 태그를 사용해보자.
form 태그가 지원하는 MIME 타입이 3개가 있는데
디폴트가 x-www-form-urlencoded이기 때문에 설정해주지 않았었는데
multipart/form-data를 보낼 것이기 때문에 enctype을 설정해준다.
<body>
<h1>회원가입 페이지</h1>
<hr />
<!-- postman과 동일 -->
<form action="/join" method="post" enctype="multipart/form-data">
<input type="text" name="username" placeholder="Enter username">
<input type="file" name="file" placeholder="choose file"> <br /> <br />
<button type="submit">회원가입</button>
</form>
</body>
여기서 전송하면 DB에 저장되어야 한다.
DB에는 imgUrl을 전송해야 한다!
그럼 이제 JoinDto에서 toEntity 메서드만 만들면 된다.
package site.metacoding.fileupload;
import org.springframework.web.multipart.MultipartFile;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class JoinDto {
private String username; // form 태그 name="username"
private MultipartFile file; // form 태그 name="file"
public User toEntity(String imgurl) {
User user = new User();
user.setUsername(username);
user.setImgurl(imgurl);
return user;
}
}
컨트롤러에서 toEntity 메서드를 호출하고 DB에 save 하면 끝!
userRepository.save(joinDto.toEntity(imgUrl));
main.mustache
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>사진 보기 페이지</h1>
<hr />
<img src="#" width="300" height="300">
</body>
</html>
@GetMapping("/joinForm")
public String joinForm() {
return "joinForm";
}
@GetMapping("/main")
public String main() {
return "main";
}
joinComplete.mustache
<body>
<h1>회원가입 완료되었습니다.</h1>
<hr>
<a href="/main">사진보려면 클릭하세요</a>
</body>
파일 저장은 잘됐는데 디비에 잘 저장되었는지 확인해보자.
주소창에 localhost:8080/h2-console 입력하여 요청해보자.
h2는 설치하는 게 아닌 인메모리 데이터베이스이다!!
잘 들어왔다.
이제 DB에서 값을 가져와서 뿌려주면 끝난다!!
이때 메인 페이지로 이동할 때 모델에 데이터를 들고 가야 한다.
@GetMapping("/main")
public String main(Model model) {
User user = userRepository.findById(1).get();
model.addAttribute("user", user);
return "main";
}
main.mustache에서 경로만 설정해주면 된다.
<img src="/upload/{{user.imgurl}}" width="300" height="300">
머스태치 파일이 변경되어도 서버가 재실행하지 않는다.
서버 재실행한다는 것은 컴파일한다는 말이기 때문이다.
재실행하고 다시 회원 가입해보면 잘 뜰 것이다.
이제 얘들을 jar 파일로 패키징 해보자.
배포하기 위해서는 프로젝트를 실행파일로 바꿔줘야 한다.
실행파일이 jar파일이다.
fileupload.jar로 구우면
.java -> .class 되어야 하는데
관련 라이브러리들이 다 포함되어 있어야 한다.
gradlew.bat이 윈도우
gradlew이 리눅스
터미널 창에 그냥 gradlew라고 치면 윈도우가 알아서 .bat파일을 실행한다.
새 터미널창에 gradle이라고 치면 gradle을 찾지 못한다.
환경변수 설정이 되어있지 않기 때문이다.
현재 폴더에서 검색한다는 명령어 ./
./gradlew build라고만 입력하면 jar파일로 구워진다.
굽고 나면 build 폴더가 생성된다.
구워진 파일을 확인해보자.
Reveal in File Explorer를 클릭해 창을 열어준다.
그리고 build 폴더 안에 들어가면 libs라는 폴더가 있다.
plain은 관련 라이브러리 없이 구운 파일,
위에는 관련 라이브러리 포함시켜서 구운 파일이다.
!!jar 파일을 실행시키는 방법!!
폴더에 마우스 우클릭해서 Git Bash Here 클릭
java -jar f까지 입력하고 탭 키를 누르면 파일명이 자동 완성된다.
다시 localhost:8080/joinForm으로 들어가서 확인해보자.
실행해보니 에러가 난다.
NoSuchFileException 에러가 났다.
경로를 인식하지 못한 것이다.
우리가 설정해준 path 경로는
내 워크 스페이스 환경에서만 통하는 것이다.
jar파일로 굽고 나면 폴더 구조가 모두 바뀌게 된다.
그래서 경로 잡을 때는 이렇게 잡지 않는다.
어떻게 하는지는 다음에 배포 배우면서 알려주신대요..
너무 궁금한데..
[출처]
https://cafe.naver.com/metacoding
메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9