7. My First Solo Project(이미지 addImageBlobHook 및 multiple)

혼빈·2024년 11월 14일

개인프로젝트

목록 보기
7/7
  • toast UI 에디터를 작성할 때 이미지를 업로드하면 다음과 같은 base64로 인코딩된 문자열 데이터가 입력된다.

이걸 짧게 만들기 위해서 사용되는게 addImageBlobHook 인데
사진을 한번 업로드 하고 저장할때 이름을 설정하고 다시 가져오면서 URL로 표시하게하는 방식을 쓰기로 했다.

        const editor = new toastui.Editor({
            el: document.querySelector('#editor'),
            height: '500px',
            initialEditType: 'markdown',
            previewStyle: 'vertical',
            hooks: {
                addImageBlobHook: function (blob, callback) {
                    // 이미지 파일을 업로드하는 FormData 생성
                    const formData = new FormData();
                    formData.append('file', blob);

                    // 서버로 이미지를 업로드 (여러 이미지를 처리할 수 있도록 변경)
                    fetch('/api/images/upload', {
                        method: 'POST',
                        body: formData,
                    })
                        .then(response => response.json())
                        .then(data => {
                            if (data.url) {
                                // 업로드된 이미지 URL을 에디터에 삽입
                                callback(data.url, '');
                            } else {
                                console.error('Image upload failed: ', data);
                            }
                        })
                        .catch(error => {
                            console.error('Error uploading image:', error);
                        });

                    return false;  // 기본 이미지 업로드 동작 방지
                }
            }
        });

        // 이미지 업로드 기능을 활성화하기 위해 클릭 시 파일 선택
        document.querySelector(".image")

            .addEventListener("click", function() {

            const imageInput = document.createElement('input');
            imageInput.type = 'file';
            imageInput.accept = 'image/*';
            imageInput.multiple = true;  // 여러 개의 이미지 파일 선택 가능

            imageInput.addEventListener('change', function(event) {
                const files = event.target.files;
                if (files && files.length > 0) {
                    Array.from(files).forEach(file => {
                        const formData = new FormData();
                        formData.append('file', file);

                        fetch('/api/images/upload', {
                            method: 'POST',
                            body: formData,
                        })
                            .then(response => response.json())
                            .then(data => {
                                if (data.url) {
                                    // 업로드된 이미지 URL을 에디터에 삽입
                                    editor.insertText('![](' + data.url + ')');
                                } else {
                                    console.error('Image upload failed: ', data);
                                }
                            })
                            .catch(error => {
                                console.error('Error uploading image:', error);
                            });
                    });
                }
            });

            imageInput.click();  // 파일 선택 창 강제 팝업
        });

Toast에 있는 사진 넣기 기능은 사진이 한장씩만 넣을 수가 있는데
나는 한장이 아니라 여러 사진을 한번에 올리고 싶어서 Toast사진입력을 누르면 따로 input을 띄어서 Multiple기능을 사용했다.

짠 이렇게 짧아진 문자열을 볼수가 있다 !!

        // 이미지 URL들을 hidden 필드에 추가
        const imageUrls = [];
        document.querySelectorAll('.editor-image-url').forEach(function(img) {
            imageUrls.push(img.src);  // 업로드된 이미지 URL을 배열에 저장
        });

        // 이미지 URL들을 hidden input 필드에 저장
        form.imageUrls.value = imageUrls.join(',');  // 쉼표로 구분하여 전송

Form으로 전송이 되기 때문에 toast에 넣은 이미지들을 배열로 url을 저장해서 ,로 구분하여 전송처리 했다.

여기까지 하기까지 한가지 문제를 해결을 못해서 오래 걸렸었는데
지금까지 프로젝트들을 하면서 발생한 문제들을 살펴보면 정말 사소한거 간단한거 하나때문에
문제가 되는 경우가 많은거 같다.

<div id="editor" class="toast-ui-editor">

기존에 toast ui를 쓰기위해서 작성했던 div인데
이게이게 잘못된거다 저렇게 class에 넣어서 사용하면 왠지 모르지만
script 실행이 안되고 충돌? 오류가 나는거같다.

<div id="editor"></div>

이렇게 바꿔준 후에 toast UI를 인클루드 해서 사용했더니 아주 잘된다.
난 저거 하나 때문에 엄청 시간을 버렸다 ㅠㅠ

@RestController
@RequestMapping("/api/images")
public class ImageController {

    @Autowired
    private ImageService imageService;

    @PostMapping("/upload")
    public ResponseEntity<ImageUploadResponse> uploadImage(@RequestParam("file") MultipartFile file) {
        try {
            // 이미지 업로드 처리
            String imageUrl = imageService.uploadImage(file);
            ImageUploadResponse response = new ImageUploadResponse(imageUrl);

            return ResponseEntity.ok(response);
        } catch (Exception e) {
            return ResponseEntity.status(500).body(new ImageUploadResponse("Upload failed"));
        }
    }


}

AJAX에서 보낸 백엔드에서는

    @Value("${custom.genFileDirPath}")  // application.yml에서 지정한 경로를 주입
    private String uploadDir;  // 이미지 업로드 디렉토리 경로

    public String uploadImage(MultipartFile file) throws IOException {
        // 파일 이름을 고유하게 만들기 위해 UUID를 사용
        String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();

        // 파일을 저장할 경로
        Path path = Paths.get(uploadDir, fileName);
        File directory = new File(uploadDir);
        
        // 디렉토리가 없으면 생성
        if (!directory.exists()) {
            directory.mkdirs();
        }

        // 파일 저장
        file.transferTo(path.toFile());

        // 저장된 이미지 파일의 URL 생성 (서버의 URL 형식에 맞게)
        return "/images/" + fileName;  // 반환할 URL 형식
    }

이렇게 UUID를 사용해서 이미지 이름이 겹치지않게 해줬고

form 제출 시에는 이 저장된걸 Board에 따라서 article id로 이름을 바꾸어 저장이 되도록 구현하는 중이다.

form 저장으로 제출할 때
article Controller에서 MultipartFile로 가져올 수가 없어서
Url로 이름값을 저장했고 그걸 가져와서 처리하는 방식인거다

게시글 저장까지 완료하면 다시 오겠다. !!
뿅!

profile
코딩중

0개의 댓글