본문 바로가기
IT/Spring boot

[Spring boot] Ck Editor5 파일 업로드 최적화 과정

by kyu-nahc 2024. 8. 12.
반응형

개발 환경

  • Spring boot 3.3.0
  • Java 21
  • IntelliJ
  • Maven
  • Ck Editor 5
  • Thymeleaf

 

파일 업로드 최적화 구현 방안

 

저번 포스트에서 CK Editor 설정 및 Java Api 코드를 설명하였다.

 

[Spring boot] Ck Editor5 파일 업로드 구현

개발 환경Spring boot 3.3.0Java 21IntelliJMavenCk Editor 5Thymeleaf CK Editor 설정CK Editor는 게시글을 작성 시 글쓰기 툴을 제공하는 JavaScript 프레임워크이다.CK Editor를 통해 글씨 크기와 굵기, 도표, 사진 및 링

kyu-nahc.tistory.com

하지만 제출된 게시물의 이미지만 저장하는 것이 아닌,

CK Editor를 통해 업로드한 이미지를 모두 서버에 저장하게 되는 문제점이 존재하였다.

이에 대한 문제점을 해결하기 위해 파일 업로드 최적화 과정에 대한 코드이다.

저번 포스팅에서는 CK Editor를 통해 업로드한 이미지 파일은 모두 temp 디렉터리에 저장하였다.

최적화 과정의 순서는 다음과 같다.

 

  1. 클라이언트가 게시물의 저장 버튼을 누르면, Controller에서 해당 Rest API 요청을 받는다.
  2. Controller로 전달받은 Content(본문) 부분에서 <img src=" "/> 부분의 파일명을 모두 추출한다.
  3. temp 폴더에서 사용하지 않는 이미지 파일은 모두 삭제한다.
  4. 필터를 거친 temp 디렉터리의 모든 파일들을 postImage라는 디렉터리로 이관한다.
  5. Content(본문) 부분에서 "/upload/temp"라는 경로를 모두 "upload/postImage"로 변경한다.

 

다음과 같은 과정을 통해 최종 포스팅한 이미지만 postImage라는 디렉터리에 보관할 수 있다.

즉 작성자가 글을 쓰는 중에는 이미지 파일이 모두 temp 디렉터리에 보관된다.

그 후 작성자가 글을 모두 써서 제출하거나, 중간에 글 작성을 취소할 경우

사용하는 이미지에 대해서만 temp에서 postImage 디렉터리로 이관한다.

이와 같은 과정을 통해 서버 혹은 클라우드에 필요 없는 파일들이 저장되는 것을 막을 수 있다. 

 

Java API 생성

Controller 생성

@PostMapping("/add")
public ResponseEntity<Post> addNewPost(@RequestBody Post newPost) throws IOException {
    return ResponseEntity.ok(postService.addNewPostProcess(
            memberService.getLoginMemberProcess(),newPost
    ));
}

 

Controller를 통해 새로운 게시물 등록에 대한 Rest API 요청을 받는다.

@RequestBody를 이용하여 Json 형태의 데이터를 자바 객체로 변환하여 받는다.

그 후 PostService의 addNewPostProcess() 메소드를

현재 로그인 한 유저의 객체와, 새로운 Post 객체를 매개변수로 전달하여 호출한다.

Service 생성

PostService

@Transactional
public Post addNewPostProcess(Member member, Post newPost) throws IOException {
    newPost.setMember(member);
    newPost.setTotalCount(0L);
    // temp File 에서 사용하지 않은 파일은 삭제
    fileService.filterTempImageList(extractImageFileNames(newPost.getContent(),"temp"));
    // 해당 글의 경로를 postImage로 변경
    newPost.setContent(fetchImagePaths(newPost.getContent()));
    // temp의 모든 파일을 PostImage로 이관
    fileService.transferTempToDirectory();
    return postRepository.save(newPost);
}
private String fetchImagePaths(String content){
    return content.replaceAll("/upload/temp/", "/upload/postImage/");
}
private List<String> extractImageFileNames(String content,String ext) {
    List<String> fileNames = new ArrayList<>();
    String patternString = String.format("<img\\s+src=\"/upload/%s/([^\"]+)\"", ext);
    Pattern pattern = Pattern.compile(patternString);
    Matcher matcher = pattern.matcher(content);
    while (matcher.find()) {
        fileNames.add(matcher.group(1));
    }
    return fileNames;
}

 

extractImageFileNames() 메소드를 통해 Post 객체의 Content(본문)에서

<img src="/upload/temp"/>라는 문자열 형식을 정의한다.

그 후 정규표현식이 컴파일된 클래스인 Pattern 객체를 이용하여, 인스턴스를 만들어준다.

Post의 Content 문자열을 패턴에 맞는지 확인하거나 패턴과 일치하는 문자열을 반복해 추출하기 위해,

find() 메소드와 group() 메소드를 사용하여 만약 일치한다면, List에 넣어준다. 

그 후 FileServicefilterTempImageList()를 호출하여 사용하지 않는 파일은 삭제한다.

 

그 후 temp라는 파일을 postImage로 이관하기 전 새로운 Post의 Content String에서

"/upload/temp/" 부분의 경로를 "/upload/postImage"로 변경해야 한다.

따라서 fetchImagePaths()를 호출해 준다.

 

마지막으로 FileServicetransferTempToDirectory()를 호출하여

temp의 모든 파일들을 postImage 디렉터리로 이관한다.

 

FileService

public void filterTempImageList(List<String> pathList) throws IOException {

    String tempPath = servletContext.getRealPath("/upload/temp/");
    File tempDir = new File(tempPath);
    File[] targetFiles = tempDir.listFiles();

    if (targetFiles != null){
        for(File file : targetFiles){
            if (!pathList.contains(file.getName())){
                String savePath = servletContext.getRealPath("/upload/temp/") + file.getName();
                deleteFile(savePath);
            }
        }
    }
}
public void deleteFile(String path) throws IOException {
    Files.delete(Paths.get(path));
}
public void transferTempToDirectory(){
    String tempPath = servletContext.getRealPath("/upload/temp/");
    String postImagePath = servletContext.getRealPath("/upload/postImage/");

    File tempDir = new File(tempPath);
    File[] targetFiles = tempDir.listFiles();
    if (targetFiles != null) {
        for(File targetFile : targetFiles){
            Path sourcePath = targetFile.toPath();
            Path targetPath = Paths.get(postImagePath,targetFile.getName());
            try {
                Files.move(sourcePath,targetPath);
            } catch (IOException e) {
                log.info("Move to PostImage Dir -{}",targetFile.getName());
            }
        }
    }
}

 

filterTempImageList() 메소드는 현재 사용 중인 파일 이미지명을 List로 받아서,

temp 파일에서 사용하지 않는 파일은 삭제하는 메소드이다. 

마찬가지로 ServletContext 객체의 getRealPath()를 통해

절대 경로를 생성하여 사용하지 않는 파일은 삭제하도록 한다.

 

transferTempToDirectory() 메소드는 temp 디렉터리의 모든 파일을 postImage 디렉터리로 이관한다.

이미 filterTempImageList()를 통해 필터를 거쳐 사용하지 않는 파일은 삭제하였으므로,

temp에 있는 모든 파일을 가져와 옮기기만 하면 된다.

 

결과 화면

 

HeidiSQL을 통해 데이터베이스에 저장된 데이터를 보면

다음과 같이 src가 "/upload/temp"에서 "/upload/postImage/"로 변경된 것을 볼 수 있다.

CK Editor에서 글을 쓰거나 이미지를 첨부하면,

다음과 같이 HTML 형태의 문자열이 서버 쪽으로 넘어온다.

 

위와 같이 본문에 대한 글 및 이미지까지 잘 나오는 것을 볼 수 있다.

CK Editor를 사용하여 게시물 혹은 글을 작성해야 하는 프로젝트를 진행한다면,

이미 구현되어 있는 프레임워크를 사용하여, 쉽게 여러 가지 툴 및 이미지 첨부를 사용할 수 있다.

또한 소요 시간을 줄이고 좀 더 기능적인 완성도를 높일 수 있을 것이다.

 

 

반응형

loading