카테고리 없음

[위니아에이드] 팀 프로젝트 - 관리자 Solution 게시글 작성

코딩딩코 2022. 11. 6.

클론 웹 사이트에서 자주 하는 질문과 자가진단과 같은 해결책을 알려주는 게시판이 있습니다.

이번에는 이 데이터를 관리자 페이지에서 작성을 구현하고 구현함에 있어서 고민을 했던 부분과

해결하고 완성한 로직을 리뷰하고자 합니다.

 

목표

이러한 게시글을 작성하여야 합니다.

 

 

구현

위와 같이 간단하게 뷰를 만들고 기능을 구현하려 합니다.

(썸머노트 에디터를 사용했습니다.)

function setSummerNote() {
    $(document).ready(function() {
        $('#summernote').summernote({
              height: 600,                
              minHeight: null,             
              maxHeight: null,             
              focus: true,                  
              lang: "ko-KR",
              callbacks: {
                onImageUpload: (files) => {	// 이미지 업로드 시
                    uploadImage(files[0]);
                }
              }
        });
    });
}

function uploadImage(file) {
    let formData = setFormData(file);

	// 이미지 로컬에 저장하기 위한 Ajax
    $.ajax({
        async: false,
        type: "post",
        url: `/api/v1/manager/temp/solution/file`,
        data: formData,
        enctype: "multipart/form-data",
        contentType: false,
        processData: false,
        success: (response) => {
        	// 파일 원본 이름
            let fileName = response.data.substring(response.data.lastIndexOf("_") + 1);
            
            // 파일 경로
            let fileUrl = response.data;
            
            // 이미지 삽입
            $('#summernote').summernote('insertImage',  fileUrl, fileName);
            
            // Map을 생성하고 Key: 파일 경로, Value: File Object
            let fileMap = new Map().set(fileUrl, file);
            
            해당 Map을 Array에 Push
            fileList.push(fileMap);
        },
        error: errorMessage
    });
}

function setFormData(file) {
    let formData = new FormData();
    formData.append("file", file);

    return formData;
}

요청 Ajax입니다.

 

@Log
@PostMapping("/temp/solution/file")
public ResponseEntity<?> uploadTempSolutionFile(MultipartFile file) {
    String fileUrl = null;

    try {
        fileUrl = uploadSolutionFileAndReturnFileUrl(file);
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.internalServerError().body(new CustomResponseDto<>(-1, "Failure to clear trouble symptom", fileUrl));
    }

    return ResponseEntity.ok(new CustomResponseDto<>(1, "Trouble Symptom Deletion Successful", fileUrl));
}


private String uploadSolutionFileAndReturnFileUrl(MultipartFile file) throws IOException {
        String customPath = "temp_solution_files";

        return "/image/" + customPath + "/" + fileService.createFileByFileAndPath(file, customPath);
}

Controller입니다.

 

위의 요청을 처리하면 temp_solution_files라는 폴더에 이미지가 3개가 생겼고 Array에도 3개의 Map이 push 되었습니다.

 

썸머노트의 value를 가져오면 위와 같은 String 값을 가져올 수 있습니다.

여기서 이제 구현해야 할 것은 게시글에 이미지를 3개를 삽입했지만 저 중에서 테스트.png 이미지를 지우고

게시글을 작성한다면 의도했던 것과는 다르게 3개의 이미지가 DB에 저장될 것입니다.

그래서 Map과 썸머노트 value를 이용해서 현재 게시글에 남아있는 이미지만 골라서 DB에 Insert 요청을 할 것입니다.

 

function writeRequest() {
    let title = document.querySelector(".title").value;
    let content = document.querySelector("#summernote").value;
    let fileList = setFileList(content);
    let solutionTypeCode = solutionTypeSelect.value;

    let formData = new FormData();

    formData.append("solutionTypeCode", solutionTypeCode);
    formData.append("solutionTypeBoardCode", solutiontypeBoardCode);
    formData.append("solutionTitle", title);
    formData.append("solutionContent", content);
    fileList.forEach(file => formData.append("fileList", file));

    $.ajax({
        async: false,
        type: "post",
        url: `/api/v1/manager/solution`,
        enctype: 'multipart/form-data',
        processData: false,
        contentType: false,
        data: formData,
        dataType: "json",
        success: (response) => {
            if(response.data) {
                alert("작성 완료");
                location.replace("/manager/solution/registration");
            }
        },
        error: errorMessage
    });
}

function setFileList(content) {
    let fileResultList = new Array();


	// Array에 들어있는 Map을 하나씩 꺼내서 content에 String값을 비교하면서
	// 값이 있다면 다시 Array에 새롭게 push합니다.
    fileList.forEach(file => {
        if(content.indexOf(file.keys().next().value) != -1) {
            fileResultList.push(file.values().next().value);
        }
    });

    return fileResultList;
}

Array를 forEach를 통해서 값(Map)을 하나씩 꺼냅니다.

Map에는 어차피 하나의 데이터만이 Key와 Value로 들어가 있기 때문에

keys()를 통해서 Iterator로 만들어주고 next().value()로 첫 번째 요소의 값(이미지 경로)을 가져와서 그 String값이

content에 있다면 새로운 Array에 value를 담아주는 과정을 반복해줘서 최종적으로 Array를 반환합니다.

이렇게 해준다면 content에 남아있는 이미지만 Array에 담겨있습니다.

 

 

이 두 가지의 값을 비교하는 것입니다.

 

이제 Ajax를 통해 서버에 요청을 보내줍니다.

 

@Log
@PostMapping("/solution")
public ResponseEntity<?> insertSolutionBoard(InsertSolutionRequestDto insertSolutionRequestDto) {
    boolean status = false;

    try {
        status = managerService.insertSolution(insertSolutionRequestDto);
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.internalServerError().body(new CustomResponseDto<>(-1, "Failed to create solution", status));
    }

    return ResponseEntity.ok(new CustomResponseDto<>(1, "Solution creation successful", status));
}

@Transactional
@Override
public boolean insertSolution(InsertSolutionRequestDto insertSolutionRequestDto) throws Exception {
    boolean status = false;
    ManagerSolution managerSolution = null;

    managerSolution = insertSolutionRequestDto.toManagerSolution();

    status = managerRepository.insertSolution(managerSolution) > 0;

	// 업로드 이미지가 하나라도 있다면
    if(insertSolutionRequestDto.getFileList().size() > 0) {
        status = insertSolutionFile(insertSolutionRequestDto, managerSolution);
    }

    return status;
}


private boolean insertSolutionFile(InsertSolutionRequestDto insertSolutionRequestDto, ManagerSolution managerSolution) throws Exception {
        List<String> fileNameList = null;

        try {
            fileNameList = fileService.createFileByFileAndPath(insertSolutionRequestDto.getFileList(), "solution_files");
            managerSolution.setFile_name_list(fileNameList);
            
            // 임시로 업로드 된 이미지 디렉토리 삭제
            fileService.deleteTempFolderByPath("temp_solution_files");

            return managerRepository.insertSolutionFile(managerSolution) > 0;
        }catch (Exception e) {
            e.printStackTrace();
            
            // 만약 이미지 DB Insert 실패시 위에서 업로드 했던 파일 다시 삭제 후 롤백을 위해 예외 던지기
            deleteFileDueToInsertSolutionError(fileNameList);
            throw new Exception("Insert solution file failed");
        }
    }

private void deleteFileDueToInsertSolutionError(List<String> fileNameList) throws IOException {
    for(String fileName : fileNameList) {
        fileService.deleteFileByFileNameAndPath(fileName, "solution_files");
    }
}

s서버에서 처리되는 과정입니다.

게시글이 먼저 DB에 Insert가 되고 그 후에 이미지가 있으면 이미지도 DB에 Insert 되는 순서라서

이미지 Insert시 에러가 난다면 롤백을 위해 @Transactional을 달아주었습니다.

 

최종적으로 이미지 두 개만 업로드되었고 temp_solution_files 폴더는 삭제되었습니다.

 

 

 

DB에 들어간 데이터도 확인을 했습니다. 이제 Insert 한 데이터를 바탕으로

어느 제품에 대한 Solution인지 정하는 기능과 게시글 수정, 삭제도 구현하려고 합니다.

처음 업로드되었던 이미지를 어떻게 구분하고 서버로 요청 보내야 할지 고민을 많이 했습니다.

시행착오도 있었으며 예외를 Try Catch문에서 잡아버려서 @Transactional이 정상 작동하지 않기도 했기 때문에

예외를 던져주어야 예외가 발생하면서 @Transactional이 정상 작동하는 것도 배웠습니다.

여러 가지 시도하면서 많은 것을 배운 시간이 되었습니다.

댓글