[Toy – Smart]

CustomFileUtils 살펴보기

CustomFileUtil은 파일 저장 처리 기능을 구현하는 클래스입니다. 종속성을 줄이고 재사용성을 높이기 위해 매개변수는 String 및 MultipartFile과 같은 유형만 수신하도록 설계되었습니다.

– 사용자 정의 util 클래스로 모든 메소드는 정적으로 선언됩니다.

– PropertyUtil의 get 메소드를 사용하여 application.properties에 정의된 환경 값에 액세스합니다.

– 이미지를 확인할 수 없는 경우 “images/empty/noimg.jpeg” 파일로 대체됩니다.

– 메소드 getAccessUrl, getAccessUrls, findFiles, findFile, saveFiles, saveFile, deleteDirectory 및 deleteAllFiles가 구현됩니다.

– 별도로 설정한 리소스 경로를 받아 리소스 URL 경로 또는 로컬 접근 경로를 자동으로 추가합니다.

@Slf4j
public class CustomFileUtils {
    private static final String EMPTY_PATH = "empty/noimg.jpeg";

    public static String getAccessUrl(String filePath) {
        return getAccessImgUrl() + (findFile(filePath) == null ? EMPTY_PATH : filePath);
    }

    public static List<String> getAccessUrls(String directoryPath) {
        return findFiles(directoryPath).stream()
                .map(f -> getAccessImgUrl() + (f == null ? EMPTY_PATH : directoryPath + f))
                .collect(Collectors.toList());
    }

    public static String findFile(String filePath) {
        try {
            File file = new File(getImgFolderPath() + filePath);

            if (file.isFile())
                return file.getName();
        } catch (NullPointerException ex) {
            log.error("(파일 경로가 유효하지 않습니다.) : {}", "요청 경로 : " + filePath);
        }
        return null;
    }

    public static List<String> findFiles(String directoryPath) {
        List<String> found = new ArrayList<>();

        try {
            File() files = new File(getImgFolderPath() + directoryPath).listFiles();

            for (File f : files) {
                if (f.isFile()) {
                    found.add(f.getName());
                }
            }
        } catch (NullPointerException ex) {
            log.error("(디렉토리 경로가 유효하지 않습니다.) : {}", "요청 경로 : " + directoryPath);
            found.add(null);
        }
        return found;
    }

    public static List<String> saveFiles(List<MultipartFile> files, String directoryPath) {
        List<String> uploadNames = new ArrayList<>();

        for (MultipartFile f : files) {
            uploadNames.add(saveFile(f, directoryPath));
        }
        return uploadNames;
    }

    public static String saveFile(MultipartFile file, String directoryPath) {
        String extension = file.getContentType().replaceFirst(".*/", ".");
        String uploadName = UUID.randomUUID() + extension;
        String path = getImgFolderPath() + directoryPath;

        try {
            Files.createDirectories(Paths.get(path));
            file.transferTo(new File(path, uploadName));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        return uploadName;
    }

    public static void deleteDirectory(String directoryPath) {
        deleteAllFiles(getImgFolderPath() + directoryPath);
    }

    private static void deleteAllFiles(String directoryPath) {
        File dir = new File(directoryPath);
        File() files = dir.listFiles();

        try {
            for (File f : files) {
                String target = directoryPath + f.getName();
                log.info(target);
                if (f.isDirectory())
                    deleteAllFiles(target + "/");
                Files.deleteIfExists(Paths.get(target));
                log.info("(파일을 삭제했습니다.) : {}", "삭제 파일 : " + target);
            }
            Files.deleteIfExists(Paths.get(directoryPath));
        } catch (NullPointerException ex1) {
            log.error("(디렉토리 경로가 유효하지 않습니다.) : {}", "요청 경로 : " + directoryPath);
        } catch (IOException ex2) {
            throw new RuntimeException(ex2);
        }
    }
}

관습FileUtils 구문 분석

1. 참고사항

— @Slf4j

기능 : 로그 관련 기능을 편리하게 사용할 수 있습니다.

분석하다 : 현재 예외 관련 로그를 처리하는 클래스는 구현되어 있지 않습니다. 이후에 로그 처리를 분리하는 경우 해당 주석을 제거해야 합니다.


두 번째 필드

개인 최종 문자열 EMPTY_PATH = “빈/noimg.jpeg”

기능 : 이미지 확인이 불가능할 때 대체 이미지로 사용됩니다.

분석하다 : 로컬에 저장된 이미지를 찾을 수 없거나 손상되어 열 수 없는 경우 논리 자체를 중지하는 것보다 문제가 발생했음을 시각적으로 나타내는 대체 이미지를 사용하는 것이 적절하다고 생각했습니다. 변환기 클래스인 경우


세 번째 방법

공개 정적 문자열 getAccessUrl(문자열 파일 경로)

기능 : 파라미터로 받은 파일 경로를 이용하여 파일에 접근할 수 있는 URL을 빌드하여 반환합니다.

분석하다 :

  • findFile 메서드를 호출하여 파일의 유효성을 검사합니다. 유효성에 따라 리소스 액세스 URL이 대체 파일 경로 또는 매개 변수로 수신 및 반환되는 파일 경로에 추가됩니다.
  • getAccessImgUrl 메서드를 사용하여 PropertyUtil에 정의된 리소스 액세스 URL의 값을 가져옵니다.
  • 중복을 제거하고 getAccessImgUrl 호출에 대한 코드 가독성을 향상시키기 위해 개인 변환기 방법을 구현할 수 있다고 생각합니다.
  • 함수의 특성상 CustomFileUtils 클래스에 구현하는 것보다 함수를 분리하여 다른 클래스에 구현하는 것이 적절하다.

공개 정적 목록 getAccessUrls(문자열 디렉터리 경로)

기능 : 파라미터로 받은 디렉토리 아래의 모든 파일에 접근할 수 있는 URL을 빌드하여 반환합니다.

분석하다 :

  • 디렉토리의 모든 파일을 찾으려면 findFiles 메소드를 호출하십시오.
  • getAccessImgUrl 메서드를 사용하여 PropertyUtil에 정의된 리소스 액세스 URL의 값을 가져옵니다.
  • 개인 변환기 메서드를 구현하면 getAccessImgUrl 호출에 대한 중복을 제거하고 코드 가독성을 향상시킬 수 있습니다.
  • 함수의 특성상 CustomFileUtils 클래스에 구현하는 것보다 함수를 분리하여 다른 클래스에 구현하는 것이 적절하다.

– 공개 정적 문자열 findFile(문자열 파일 경로)

기능 : 매개변수로 받은 파일경로에 있는 파일을 검증하고 결과에 따라 파일명 또는 null을 반환한다.

분석하다 :

  • 리소스 액세스 경로를 자동으로 마운트하여 서비스 계층의 노출을 제거할 수 있습니다.
  • 파일 유효성 검사 처리가 잘못되었습니다.
  • 중복을 제거하고 getImgFolderPath 호출에 대한 코드 가독성을 개선하기 위해 전용 변환기 메서드를 구현할 수 있습니다.

– 공개 정적 목록 findFiles(문자열 디렉토리 경로)

기능 : 매개변수화된 디렉터리의 모든 파일은 유효성이 검사되고 결과에 따라 파일 이름 목록 또는 null로 반환됩니다.

분석하다 :

  • 리소스 액세스 경로를 자동으로 마운트하여 서비스 계층의 노출을 제거할 수 있습니다.
  • 하위 디렉토리가 존재하는지 여부를 판별하는 논리를 추가해야 합니다.
  • 파일 유효성 검사 처리가 잘못되었습니다.
  • 중복을 제거하고 getImgFolderPath 호출에 대한 코드 가독성을 개선하기 위해 전용 변환기 메서드를 구현할 수 있습니다.

– 공개 정적 목록 saveFiles(목록 파일, 문자열 디렉토리경로)

기능 : 매개변수로 받은 모든 파일은 지정된 디렉토리에 저장됩니다.

분석하다 : saveFile 메소드로 모든 파일을 저장하고 저장된 파일명을 리스트로 반환합니다.

– 공개 정적 문자열 saveFile(MultipartFile 파일, String directoryPath)

기능 : 파라미터로 받은 파일을 지정된 디렉토리에 저장합니다.

분석하다 :

  • 파일에서 확장자를 추출하고 UUID를 사용하여 임의의 파일 이름을 생성합니다.
  • 파일 및 디렉터리에 대한 유효성 검사 논리를 추가해야 합니다.
  • 중복을 제거하고 getImgFolderPath 호출에 대한 코드 가독성을 개선하기 위해 전용 변환기 메서드를 구현할 수 있습니다.

– 공개 정적 무효 deleteDirectory(String directoryPath)

기능 : 리소스 접근 경로를 디렉터리 경로에 추가합니다.

분석하다 : 리소스 액세스 경로를 추가한 다음 deleteAllFiles 메서드를 호출하기만 하면 됩니다. 중복을 제거하고 getImgFolderPath 호출에 대한 코드 가독성을 개선하기 위해 전용 변환기 메서드를 구현할 수 있습니다.

– private static void deleteAllFiles(문자열 디렉토리 경로)

기능 : 디렉토리 아래의 모든 파일과 디렉토리를 제거한 다음 해당 디렉토리도 제거합니다.

분석하다 : 파일 및 디렉터리에 대한 유효성 검사 논리를 추가해야 합니다.


관습FileUtils 리팩토링

변경된 경우 추가된 경우 색상 삭제된 경우 색상 색상으로 구분됩니다. 수정된 코드는 다음과 같습니다.

/**
 * 해당 클래스는 파일에 대한 저장, 조회, 제거 기능을 처리하기 위해 구현되었다.
 */
public class CustomFileUtils {

    /**
     * 유효 파일 탐색 과정에서 발생할 수 있는 비효율적인 저장 방식의 개선과 스프링의 멀티 쓰레드 환경을 고려한
     * 필드이다.
     */
    private static final ThreadLocal<List<String>> foundImage = new ThreadLocal<>();

    /**
     * recursivelyFind 메서드를 호출하기 전과 후에 ThreadLocal에 대해 처리를 수행하는 메서드이다.
     * @param path : 파일을 탐색할 경로
     * @return : 탐색 과정에서 확인한 모든 파일들의 전체 경로
     */
    public static List<String> findFilePaths(String path) {
        foundImage.set(new ArrayList<>());
        recursivelyFind(path);

        List<String> found = new ArrayList<>(foundImage.get());

        foundImage.remove();
        return found;
    }

    /**
     * 해당 경로 하위에 존재하는 모든 파일들을 재귀적으로 탐색 및 찾은 파일의 전체 경로를 저장한다.
     * @param path : 파일을 탐색할 경로
     */
    private static void recursivelyFind(String path) {
        File() files = new File(path).listFiles();

        if (files == null) {
            return;
        }
        for (File f : files) {
            String target = path + "/" + f.getName();

            if (f.isDirectory()) {
                recursivelyFind(target);
            }
            foundImage.get().add(target);
        }
    }

    /**
     * 요청으로 받은 MultipartFile 타입의 파일 각각을 saveFile 메서드의 매개변수로 전달한다.
     * @param files : 클라이언트 요청으로 받은 MultipartFile 타입의 파일들
     * @param path : 파일을 저장할 경로
     * @return 저장된 파일들의 파일명 반환
     */
    public static List<String> saveFiles(List<MultipartFile> files, String path) {
        List<String> uploadNames = new ArrayList<>();

        for (MultipartFile f : files) {
            uploadNames.add(saveFile(f, path));
        }
        return uploadNames;
    }

    /**
     * MultipartFile에서 추출한 확장자와 UUID로 생성한 문자열을 이용해 파일명을 정의하고, 매개변수로 입력받은
     * 경로에 파일을 저장한다.
     * @param file : 클라이언트 요청으로 받은 MultipartFile 타입의 파일
     * @param path : 파일을 저장할 경로
     * @return 저장된 파일의 파일명 반환
     */
    public static String saveFile(MultipartFile file, String path) {
        if (file.isEmpty() || file.getContentType() == null) {
            throw new IllegalArgumentException();
        }

        String extension = file.getContentType().replaceFirst(".*/", ".");
        String uploadName = UUID.randomUUID() + extension;

        try {
            Files.createDirectories(Paths.get(path));
            file.transferTo(new File(path, uploadName));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        return uploadName;
    }

    /**
     * 디렉토리 내에 존재하는 모든 파일을 재귀적으로 탐색 및 삭제 후 해당 디렉토리도 삭제한다.
     * @param path : 삭제하려는 파일 경로
     */
    public static void deleteDirectory(String path) {
        File() files = new File(path).listFiles();

        if (files == null) {
            return;
        }
        try {
            for (File f : files) {
                String target = path + "/" + f.getName();

                if (f.isDirectory()) {
                    deleteDirectory(target);
                }
                Files.deleteIfExists(Paths.get(target));
            }
            Files.deleteIfExists(Paths.get(path));
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }
}

1. 참고사항

@Slf4j

로그 처리 기준이 마련되면 다시 시행할 예정이다.


두 번째 필드

개인 최종 문자열 EMPTY_PATH = “빈/noimg.jpeg”

PathConverter 클래스의 새로운 구현으로 AccessUrl과 관련된 메서드가 제거되어 필드가 더 이상 사용되지 않습니다.

비공개 상태 최종 ThreadLocal> foundImage = 새 ThreadLocal<>()

ThreadLocal 방식은 Spring이 스레드 풀을 사용하는 요청 처리 방식을 사용한다는 사실과 재귀적으로 파일 경로를 찾는 효율성을 고려하여 선택되었다.


세 번째 방법

공개 정적 문자열 getAccessUrl(문자열 파일 경로)

해당 절차를 파일 관련 처리가 아닌 변환기 기능으로 분리하는 것이 적절하다고 판단되었습니다.

공개 정적 목록 getAccessUrls(문자열 디렉터리 경로)

해당 절차를 파일 관련 처리가 아닌 변환기 기능으로 분리하는 것이 적절하다고 판단되었습니다.

공개 정적 문자열 findFile(문자열 파일 경로)

파일명을 포함한 경로는 현재 DB에 저장되어 있지 않아 불필요한 것으로 판단되어 삭제되었습니다.

공개 정적 목록 findFilePaths(문자열 경로)

ThreadLocal을 사용했기 때문에 재귀적 Find 메서드 호출과 LocalThread 핸들러를 수행하도록 해당 메서드를 수정했다.

private static void recursivelyFind(문자열 경로)

기존 findFiles 함수에서 예외 처리 방식과 대상 경로 내의 모든 파일을 재귀적으로 찾는 방식이 변경되었습니다.

공개 정적 문자열 saveFile(MultipartFile 파일, 문자열 경로)

MultipartFile의 경우 컨트롤러 수준에서 유효성 검사를 수행할 수 없습니다. 따라서 저장 시 파일에 유효성 검사 논리가 추가되었습니다.

공공 정적 무효 deleteDirectory(문자열 디렉토리 경로)

대상 경로 아래의 모든 파일을 재귀적으로 제거합니다.

개인 정적 무효 deleteAllFiles(String directoryPath)

PathConverter 클래스가 구현되었기 때문에 기본 리소스 접근 경로를 추가하는 기능은 더 이상 필요하지 않으므로 메서드에서 처리하는 기능은 deleteDirectroy에서 처리합니다.

지름길