thumbnail

CKEditor를 이용해서 글을 작성하지만,

글을 작성중 이미지를 업로드하면 오직 1장씩 밖에 되지 않습니다. N개의 이미지 업로드에 N번의 행위를 해야합니다.

따라서, CKEditor를 벗어나 여러 이미지를 한번에 올릴 수 있는 모듈을 개발하고자 합니다.

<br>

어떤 게시판에 이미지를 업로드 하는 것을 어떻게 처리해야 할까요?

이미지는 업로드 시, 사용자의 글 내용에 즉각적으로 보여져야 합니다.

즉 업로드가 비동기로 동작하여, 이미지파일이 서버로 즉시 전송되어야한다.

비록 CHANGOOS의 글을 올릴 수 있는 권한은 저에게만 있지만, 저 또한 친절하지 않은 사용자 입니다.?

친절하지 않은 사용자를 위해 다음 상황에 대해서 처리해주어야 합니다.

  • 사용자가 글을 작성하던중 글쓰기 페이지를 나간다면, 이미 업로드된 이미지는 어떻게 처리해야할까?
  • 사용자가 글을 작성하던중, 이미지를 업로드하였지만 실제로 글내용에서 이미지 태그를 지웠다면, 업로드된 이미지를 어떻게 처리해야 할까?
  • 사용자가 글을 수정하면서, 내용에서 기존의 업로드된 이미지를 지웠다면, 어떻게 처리 할까?
  • 내용에서 이미지를 지웠다면, 그것을 어떻게 파악할 수 있을까?

<br>
<br>
<br>

<span style="color:null"><b>그럼 해결책을 연구해야합니다.</b></span>

<span style="color:null">앞서 프론트 단은 다음과 같이 구성하였습니다.</span>

input file 태그에 onChange에 이벤트를 부여하여, 값 변경시 파일을 Ajax로 서버에 전송하게 하였습니다.

<img alt="pasteImage.png" pathname="BPYDPM190109133410.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/BPYDPM190109133410.PNG" style="border-style:solid; border-width:1px; width:600px" title="pasteImage.png">

<img alt="pasteImage.png" pathname="EFQ2ZY190109133447.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/EFQ2ZY190109133447.PNG" style="width:601px" title="pasteImage.png">

<br>
<br>
<br>
우선 이미지를 업로드하고, 사용자가 글 작성을 중지한 상황을 생각해봅시다.

예를들어, 인터넷 창을 그냥 꺼버리거나 그 페이지를 무자비하게 나간 상황입니다. 무자비한 사용자..

사용자가 업로드한 이미지는 불필요한 데이터가 되었습니다. 서버의 용량은 한계가있고, 이런 가비지 이미지를 처리해야할 것 입니다.

<br>
따라서 TEMP 폴더를 사용하기로 하였습니다. 서버로 전송된 이미지파일을 일단 TEMP폴더에 임시로 저장합니다.

글 작성 버튼을 눌렀을대, 이미지를 TEMP 폴더에서 업로드 폴더로 이동하는 것 입니다.

<br>
그럼 TEMP 폴더에 남아있는 이미지 파일은 언제 삭제해야 할까요?

- 세션리스너를 사용하기로 하였습니다

작성자는 한 명이 아닙니다. 따라서 맘대로 TEMP 폴더를 비울 수 없습니다.(CHANGOOS는 나 혼자 작성하지만..)

세션 생성 시에는 세션값에 폴더명을 랜덤하게 갖도록 합니다. KEY로써의 역할입니다.

사용자의 최초의 업로드에는, TEMP폴더 밑에 폴더명 값으로 폴더를 생성합니다. 사용자별로 고유 폴더를 생성하는 것입니다.

<br>
사용자 세션 종료 시에는, 사용자의 고유 TEMP 폴더를 삭제합니다.

사용자가 무자비하게 페이지를 종료하더라도, 세션 타임아웃 된다면 깔끔하게 TEMP폴더를 정리 할 수 있습니다.

<br>
비동기로 서버에 이미지 파일 저장 요청을 보내면, 결과값으로 사진에 대한 파일정보등을 전송 받습니다.

그 정보는 Input 태그에 저장합니다. JSON 형태로 저장하였습니다.

	@ResponseBody
	@RequestMapping(value = "/board/post/image", method = RequestMethod.POST, consumes = "multipart/form-data")
	public String imageDoUpload(HttpSession session, MultipartFile image) throws IOException{
		String pathname = boardImageService.saveImage((String)session.getAttribute("tempDirId"), image);
		
		JSONObject result = new JSONObject();
		result.put("pathname", pathname);
		
		return result.toString();
	}

<img alt="pasteImage.png" pathname="GKODLB190109132744.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/GKODLB190109132744.PNG" style="border-style:solid; border-width:1px; width:600px" title="pasteImage.png">

이미지를 업로드하고, 사용자는 글 작성을 완료하였습니다.

이미지 파일정보도 같이 전송될 것입니다.

<br>
<br>
Controller.java

	/** 공부 업로드  **/
	@RequestMapping(value = "/mgnt/study/upload.do", method = RequestMethod.POST, params = "!seq")
	public String studyDoUpload(HttpSession session, Model model, StudyVo study, String imageValues, String fileValues) throws SQLException, IllegalStateException, IOException{
		int seq = studyService.insert(study, imageValues, fileValues);
		return "redirect:" + "/study/view?seq=" + seq;
	}

<br>
<br>
Service.java

	@Transactional(rollbackFor = {IllegalStateException.class, IOException.class })
	public int insert(StudyVo study, String imageValues, String fileValues) throws IllegalStateException, IOException {
		study.setDate(Formatter.toDate(new Date()));
		
		int seq = studyDao.insert(study);
		boardFileService.insertFiles(fileTB, fileDir, seq, fileValues);
		
		String contents = boardImageService.insertImages(imageTB, imageDir, seq, study.getContents(), imageValues);
		study.setContents(contents);
		
		studyDao.update(study);
		
		return seq;
	}
<br data-tomark-pass  data-tomark-pass  data-tomark-pass />
  • 5 : 게시글을 DB에 저장함으로서, 게시글에 대한 SEQ값을 가져왔습니다.
  • 8 : 저장된 이미지 파일에 대하여 처리합니다.

<br>
<br>
BoardImageService.insertImage()

	public String insertImages(String TB, String tempDirId, String dir, int boardSeq, String contents, String imageValues) throws JsonParseException, JsonMappingException, IOException {
		List<BoardImageVo> images = new ObjectMapper().readValue(imageValues, new TypeReference<List<BoardImageVo>>(){});
		
		BoardImageVo image;
		
		for (int i = 0; i < images.size(); i++) {
			image = images.get(i);
			image.setBoardSeq(boardSeq);
			
			String pathname = image.getPathname();
			ImageStatus status = image.getStatus();
			
			switch(status){
			case NEW:
				if(boardImageDao.insert(TB, image)) {
					//임시폴더에서 본 폴더로 이동
					File existFile  = new File(realPath + tempDir + tempDirId, pathname);
					File newFile	= new File(realPath + dir, pathname);
					if(fileHandler.move(existFile, newFile)) {
						contents = contents.replaceAll(tempDir + tempDirId + pathname, dir + pathname);
					}
				}
				break;
			case UNNEW:
				fileHandler.delete(realPath + tempDir + tempDirId, pathname);
				break;
			case REMOVE:
				if(boardImageDao.delete(TB, image.getSeq())) {
					fileHandler.delete(realPath + dir, pathname);
				}
				break;
			case BE: 
				break;
			}
		}
		
		return contents;
	}
  • 2 : Input 태그에 저장된 JSON형태의 이미지 정보를 VO로 매핑합니다.
  • 8 : 이미지 정보에, 게시글 SEQ값을 주입합니다.
  • 13 : *상태값에 따른 처리
  • 15 : 이미지 정보를 DB에 저장합니다.
  • 19 : 이미지를 TEMP 폴더에서 업로드 폴더로 이동합니다.
  • 20 : 게시글 내용중 이미지 태그 src 링크를, TEMP폴더에서 업로드폴더 링크로 바꿉니다.

<br>
<br>
<br>
13번줄의 상태값에 대해서 거론하였습니다.

다음 과제인 글의 수정에 관한처리를 하려고 합니다.

사용자가 글을 수정과정에서 이미지를 삭제한다면? 어떻게 처리 해야할까요?

삭제된 이미지가 새로 올린 이미지라면 TEMP 폴더에 삭제해야 할 것입니다.

삭제된 이미지가 기존에 글이 가지고 있던 이미지라면 업로드 폴더에서 삭제하고, 또한 이미지정보가 DB에서도 삭제 되어야 합니다.

<br>
<br>
따라서 이미지 정보에 관하여 상태값을 정의하고, 상태를 4가지로 구분하였습니다.

  • BE : 기존에 첨부되었던 이미지 파일
  • REMOVE : 기존에 첨부되었다가, 삭제된 이미지.
  • NEW : 새롭게 추가된 이미지
  • UNNEW : 새롭게 추가되었다, 삭제된 이미지

<br>
<br>
사용자가 글의 수정 과정에서 기존에 있던 이미지를 삭제 하려고 하다면, BE -> REMOVE 상태로 변경 될 것 입니다.

사용자가 글의 작성/수정 과정에서 새롭게 업로드된 이미지를 삭제하려고 한다면, NEW -> UNNEW 상태로 변경될 것입니다.

<br>
<br>
ImageUpload.js 파일중 일부입니다.

	if(status == imageUploader.status.BE){
		wrapImage.addClass("remove");
		status.val(imageUploader.status.REMOVE);
	} else if(status == imageUploader.status.NEW){
		wrapImage.addClass("remove");
		status.val(imageUploader.status.UNNEW);
	}

<br>
<br>
위의 코드를 다시보면, 상태값에 따라 다음 처리가 실행됩니다.

			switch(status){
			case NEW:
				if(boardImageDao.insert(TB, image)) {
					//임시폴더에서 본 폴더로 이동
					File existFile  = new File(realPath + tempDir + tempDirId, pathname);
					File newFile	= new File(realPath + dir, pathname);
					if(fileHandler.move(existFile, newFile)) {
						contents = contents.replaceAll(tempDir + tempDirId + pathname, dir + pathname);
					}
				}
				break;
			case UNNEW:
				fileHandler.delete(realPath + tempDir + tempDirId, pathname);
				break;
			case REMOVE:
				if(boardImageDao.delete(TB, image.getSeq())) {
					fileHandler.delete(realPath + dir, pathname);
				}
				break;
			case BE: 
				break;
			}

<br>
<br>
<br>
그럼 이제 다음 과제 입니다.

글의 이미지를 삭제를 하진 않았지만 글의 내용에서 이미지를 삭제해버렸습니다.

즉 글의 내용에서 이미지 태그를 삭제 했다면 어떻게 처리 해야 할까요?

결론은 아무 처리도 않습니다.

게시글에 대한 이미지정보는 DB에 저장되어 있기 때문에 다음과 같이 글에 첨부된 이미지를 파악 할 수 있습니다.

추후에, 글 삭제 시에도 이미지 정보/파일을 삭제 하는데 어려움이 없습니다.

따라서 친절한 사용자라면 사용하지 않는 이미지는 삭제버튼을 눌러 DB정보, 파일을 삭제 하겠지만, 그렇지 않아도 무방합니다.

NAVER의 카페 업로드 또한 동일하게 처리 했음을 확인 할 수 있습니다.

<img alt="undefined" pathname="ULVLWI190114150439.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/ULVLWI190114150439.PNG" style="border-style:solid; border-width:1px; width:720px" title="undefined">

<br>
<br>
<br>
# 추가 작업

  • 파일 첨부 또한 비슷한 로직으로 처리하였습니다.
  • CKEditor AutoSave 시, 일정주기 간격으로 게시글 내용을 세션에 저장해주는데, 이미지정보 값은 저장하지 않습니다.
    따라서 이를 수정하여, autosave시에 이미지정보도 같이 저장하도록 하였습니다.
    물론 임시 저장된 게시글내용을 불러올때 같이 이미지정보를 불러옵니다.
    너무 입맛대로 플러그인을 수정한듯 싶습니다... :'(...
  • CKEditor에서 붙여넣기를 통한 이미지 첨부 시, 동일하게 처리해주었습니다.
CommentCount 0
이전 댓글 보기
등록
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
TOP