[SPRING]영화/리뷰 프로젝트 적용하기-(1)
파일 업로드로 영화를 등록하고, 사용자들이 영화 리뷰를 기록하는 것을 예제로 작성해봅니다.
- 영화의 등록과 수정에는 파일 업로드 기능을 활용하여 영화 포스터 등을 등록할 수 있도록 구성합니다.
- 회원은 기존 회원들이 존재한다고 가정하고 데이터베이스에 존재하는 회원들을 이용합니다.
- 회원은 특정한 영화 조회 페이지에서 평점과 자신의 감상을 리뷰로 기록할 수 있게합니다.
- 조회화면에서 회원은 자신이 기록한 리뷰의 내용을 수정/삭제 할 수 있습니다.
영화 등록처리
- controller 패키지에는 movieController 클래스를 생성합니다.
- 영화 등록에 사용할 '/movie/register' 을 추가해줍니다.
@Controller
@Log4j2
@RequestMapping("/movie")
public class MovieController {
@GetMapping("/register")
public void register(){
}
}
- templates에 movie 폴더를 추가해주고 register.html을 작성합니다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<h1 class="mt-4">Movie Register Page></h1>
<form th:action="@{/movie/register}" th:method="post">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" placeholder="Entier Title">
</div>
<div class="form-group FileForm">
<label>Image Files</label>
<div class="custom-file">
<input type="file" class="custom-file-input files" id="fileInput" multiple>
<label class="custom-file-label" data-breose="Browse"></label>
</div>
</div>
<div class="box">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e){
}); //document ready
</script>
</th:block>
</th:block>
</html>
@Controller
@Log4j2
@RequestMapping("/movie")
public class MovieController {
@GetMapping("/register")
public void register(){
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<h1 class="mt-4">Movie Register Page></h1>
<form th:action="@{/movie/register}" th:method="post">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" placeholder="Entier Title">
</div>
<div class="form-group FileForm">
<label>Image Files</label>
<div class="custom-file">
<input type="file" class="custom-file-input files" id="fileInput" multiple>
<label class="custom-file-label" data-breose="Browse"></label>
</div>
</div>
<div class="box">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e){
}); //document ready
</script>
</th:block>
</th:block>
</html>
- MovieDTO/MovieImageDTO 클래스와 MovieService
- 영화와 관련해서는 이미 엔티티 클래스 처리가 완료되었기 때문에 DTO와 서비스 계층을 구성해 주는 작업만 처리합니다.
- MovieDTO는 Movie클래스를 기준으로 작성합니다.
- MOvieDTO 클래스의 내부에는 업로드된 파일들의 정보를 포함해야 하므로 MovieImageDTO 클래스도 같이 추가합니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MovieDTO {
private Long mno;
private String title;
@Builder.Default
private List<MovieImageDTO> imageDTOList = new ArrayList<>();
//화면에 영화 이미지들도 같이 수집해서 전달해야 하므로 내부적으로 리스트를 이용하여 수집합니다.
}
- 영화의 이미지를 의미한느 MovieImageDTO는 이전의 UploadResultDTO와 동일합니다.
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MovieImageDTO {
private String uuid;
private String imgName;
private String path;
public String getImageURL(){
try{
return URLEncoder.encode(path + File.separator + uuid + "_" +imgName,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
public String getThumbnailURL(){
try{
return URLEncoder.encode(path+"/s"+uuid+"_"+imgName,"UTF-8");
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return "";
}
}
- 프로젝트 내에 service 패키지를 추가하고 MovieService 인터페이스와 MovieServiceImpl 클래스를 추가합니다.
- 추가된 dtoToEntity()는 Map 타입으로 Movie객체와 MovieImage 객체의 리스트를 처리합니다.
public interface MovieService {
Long register(MovieDTO movieDTO);
default Map<String,Object> dtoToEntity(MovieDTO movieDTO){ //Map타입으로 반환
Map<String,Object> entityMap = new HashMap<>();
//key값을 String, Value값을 Object형으로
Movie movie = Movie.builder()
.mno(movieDTO.getMno())
.title(movieDTO.getTitle())
.build();
entityMap.put("movie",movie);
//Map에 키,값 저장
List<MovieImageDTO> imageDTOList = movieDTO.getImageDTOList();
//MovieImageDTO 처리
if(imageDTOList != null && imageDTOList.size() > 0){
//리스트는 stream()으로 해결하는것이 편하다.
List<MovieImage> movieImageList = imageDTOList.stream().map(movieImageDTO -> {
MovieImage movieImage = MovieImage.builder()
.path(movieImageDTO.getPath())
.imgName(movieImageDTO.getImgName())
.uuid(movieImageDTO.getUuid())
.movie(movie)
.build();
return movieImage;
}).collect(Collectors.toList());//List로 변환
entityMap.put("imageList",movieImageList);
//Map에 키,값 저장
}
return entityMap;
}
}
-
HashMap의 중요한 특성
-HashMap은 또한 '복제 가능'및 '직렬화 가능'인터페이스를 구현합니다.
-HashMap은 중복 값을 허용하지만 중복 키는 허용하지 않습니다.
-HashMap은 또한 여러 널값을 허용하지만 널 키는 하나만 될 수 있습니다.
-HashMap은 동기화되지 않으며 요소의 순서도 보장하지 않습니다. -
MovieServiceImpl은 MovieRepository와 MovieImageRepository를 주입받도록 구성하고, dtoToEntity()에서 반환한 객체들을 이용해서 save()를 처리해줍니다.
@Serviceva
@Log4j2
@RequiredArgsConstructor
public class MovieServiceImpl implements MovieService{
private final MovieRepository movieRepository;
private final MovieImageRepository movieImageRepository;
@Override
@Transactional
public Long register(MovieDTO movieDTO) {
Map<String,Object> entityMap = dtoToEntity(movieDTO);
Movie movie = (Movie) entityMap.get("movie");
List<MovieImage> movieImageList =(List<MovieImage>) entityMap.get("imgList");
//Map에서 꺼내온 값들을 Movie,List<MovieImage>로 형변환
movieRepository.save(movie);
movieImageList.forEach(movieImage -> {
movieImageRepository.save(movieImage);
});
return movie.getMno();
}
}
- MovieController 에서는 Post 방식으로 전달된 파라미터들을 MovieDTO로 수집하여 MovieService객체의 register()를 호출합니다
@Controller
@Log4j2
@RequestMapping("/movie")
@RequiredArgsConstructor
public class MovieController {
private final MovieService movieService;
@GetMapping("/register")
public void register(){
}
@PostMapping("/register")
public String register(MovieDTO movieDTO, RedirectAttributes redirectAttributes){
log.info("movieDTO: "+movieDTO);
Long mno = movieService.register(movieDTO);
redirectAttributes.addFlashAttribute("msg",mno);
return "redirect:/movie/list";
}
}
- 첨부파일을 보여주는 영역 처리하기
- 화면에서 가장 먼저 처리할 것은 첨부파일을 업로드 하는 것입니다.
- 이 과정에서 업로드된 파일을 화면에 보여주는 부분도 같이 처리해 주어야합니다.
- register.html에서 파일 업로드를 하는 부분의 클래스는 'custom-file-input'이므로 이를 이용하여 이벤트 처리를 해줍니다.
- 화면에서 첨부파일의 업로드는 별도의 버튼 없이 파일을 선택하면 자동으로 이루어지도록하기 위해서 change 이벤트를 처리합니다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content} )}">
<th:block th:fragment="content">
<h1 class="mt-4">Movie Register Page</h1>
<form th:action="@{/movie/register}" th:method="post">
<div class="form-group">
<label>Title</label>
<input type="text" class="form-control" name="title" placeholder="Enter Title">
</div>
<div class="form-group fileForm">
<label >Image Files</label>
<div class="custom-file">
<input type="file" class="custom-file-input files" id="fileInput" multiple>
<label class="custom-file-label" data-browse="Browse"></label>
</div>
</div>
<div class="box">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
$(document).ready(function(e){
var regex =new RegExp("(.*?)\.(exe|sh|zip|alz|tiff)$");
// .*? : 줄 바꿈 문자를 제외한 모든 문자를 0개 이상 찾되, 최소 크기로 찾는다.
var maxSize = 10485760; //10MB
function checkExtension(fileName, fileSize){
if(fileSize>maxSize){
alert("파일 사이즈 초과")
return false;
}
if(regex.test(fileName)){ //RegExp.test() => 패턴이 있는지 없는지를 테스트하는데 목적
alert("해당 종류의 파일은 업로드 할 수 없습니다.")
return false;
}
return true;
}
$(".custom-file-input").on("change",function(){
var fileName = $(this).val().split("\\").pop();
//pop() : 배열에서 마지막 요소를 제거하고 그 요소를 반환합니다.
$(this).siblings(".custom-file-label").addClass("selected").html(fileName);
//siblings() : 자신을 제외한 형제 요소를 찾습니다.
//addClass() : 새로운 클래스를 추가(이미 가지고 있는 클래스는 추가되지 않습니다.)
//html() : 요소안의 내용을 지우고 새로운 내용을 넣습니다.
var formData = new FormData();
var inputFile = $(this);
var files = inputFile[0].files;
var append = false;
console.log(formData);
console.log(inputFile);
console.log(files);
console.log(append);
for(var i=0; i<files.length; i++){
if(!checkExtension(files[i].name, files[i].size)){
return false;
}
console.log(files[i]);
formData.append("uploadFiles",files[i]);
appended = true;
}
//upload를 하지 않는다.
if(!append){
return;
}
for(var value of formData.values()){
console.log(value);
}
//실제 업로드 부분
$.ajax({
url: '/uploadAjax',
processData: false,
contentType : false,
data : formData,
type : 'POST',
dataType : 'json',
success : function(result){
console.log(result);
},
error : function(jqXHR,textStatus,errorThrown){
conlose.log(textStatus);
}
});//$.ajax
});//end change event
}); //document ready
</script>
</th:block>
</th:block>
- 업로드가 정상적으로 처리된 후에는 화면에서 섬네일 이미지를 보여줄 영역을 이용하여 섬네일을 추가해주어야 합니다.
- register.htmldml
<form>
태그아래 쪽에 다음과 같은 스타일과<div>
를 추가해줍니다.
<style>
.uploadResult {
width: 100%;
background-color: gray;
margin-top: 10px;
}
.uploadResult ul {
display: flex;
flex-flow: row;
justify-content: center;
align-items: center;
vertical-align: top;
overflow: auto;
}
.uploadResult ul li {
list-style: none;
padding: 10px;
margin-left: 2em;
}
.uploadResult ul li img {
width: 100px;
}
</style>
<div class="uploadResult">
<ul>
</ul>
</div>
- Ajax의 호출 결과는 showResult()라는 별도의 함수로 처리해줍니다.
- register.html일부를 수정하고 showResult()를 호출하도록 수정해줍니다.
function showResult(uploadResultArr){
var uploadUL = $(".uploadResult ul");
var str ="";
$(uploadResultArr).each(function(i,obj){
str += "<li data-name='" + obj.fileName + "' data-path='"+obj.folderPath+"' data-uuid='"+obj.uuid+"'>";
str + " <div>";
str += "<button type='button' data-file=\'" + obj.imageURL + "\' "
str += "class='btn-warning btn-sm'>X</button><br>";
str += "<img src='/display?fileName=" + obj.thumbnailURL + "'>";
str += "</div>";
str + "</li>";
});
uploadUL.append(str);
}
//실제 업로드 부분
$.ajax({
url: '/uploadAjax',
processData: false,
contentType : false,
data : formData,
type : 'POST',
dataType : 'json',
success : function(result){
console.log(result);
<---------추가---------->
showResult(result);
},
error : function(jqXHR,textStatus,errorThrown){
conlose.log(textStatus);
}
- 각 이미지는
<li>
태그로 구성되는데 이때 ImageDTO에 필요한 속성들을 구성하게 됩니다.
Author And Source
이 문제에 관하여([SPRING]영화/리뷰 프로젝트 적용하기-(1)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jyyoun1022/SPRING영화리뷰-프로젝트-적용하기-1저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)