init
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DeployTargetType implements EnumType {
|
||||
// @formatter:off
|
||||
GUKU("GUKU", "국토교통부"),
|
||||
PROD("PROD", "운영계");
|
||||
// @formatter:on
|
||||
|
||||
private final String id;
|
||||
private final String text;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DetectionClassification {
|
||||
BUILDING("building", "건물", 10),
|
||||
CONTAINER("container", "컨테이너", 20),
|
||||
FIELD("field", "경작지", 30),
|
||||
FOREST("forest", "숲", 40),
|
||||
GRASS("grass", "초지", 50),
|
||||
GREENHOUSE("greenhouse", "비닐하우스", 60),
|
||||
LAND("land", "일반토지", 70),
|
||||
ORCHARD("orchard", "과수원", 80),
|
||||
ROAD("road", "도로", 90),
|
||||
STONE("stone", "모래/자갈", 100),
|
||||
TANK("tank", "물탱크", 110),
|
||||
TUMULUS("tumulus", "토분(무덤)", 120),
|
||||
WASTE("waste", "폐기물", 130),
|
||||
WATER("water", "물", 140),
|
||||
ETC("ETC", "기타", 200); // For 'etc' (miscellaneous/other)
|
||||
|
||||
private final String id;
|
||||
private final String desc;
|
||||
private final int order;
|
||||
|
||||
/**
|
||||
* Optional: Helper method to get the enum from a String, case-insensitive, or return ETC if not
|
||||
* found.
|
||||
*/
|
||||
public static DetectionClassification fromString(String text) {
|
||||
if (text == null || text.trim().isEmpty()) {
|
||||
return ETC;
|
||||
}
|
||||
|
||||
try {
|
||||
return DetectionClassification.valueOf(text.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
// If the string doesn't match any enum constant name, return ETC
|
||||
return ETC;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Desc 한글명 get 하기
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String fromStrDesc(String text) {
|
||||
DetectionClassification dtf = fromString(text);
|
||||
return dtf.getDesc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LearnDataRegister implements EnumType {
|
||||
READY("준비"),
|
||||
UPLOADING("업로드중"),
|
||||
UPLOAD_FAILED("업로드 실패"),
|
||||
COMPLETED("완료");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LearnDataType implements EnumType {
|
||||
DELIVER("납품"),
|
||||
PRODUCTION("제작");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ModelMngStatusType implements EnumType {
|
||||
READY("준비"),
|
||||
IN_PROGRESS("진행중"),
|
||||
COMPLETED("완료");
|
||||
|
||||
private String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ProcessStepType implements EnumType {
|
||||
// @formatter:off
|
||||
STEP1("STEP1", "학습 중"),
|
||||
STEP2("STEP2", "테스트 중"),
|
||||
STEP3("STEP3", "완료");
|
||||
// @formatter:on
|
||||
|
||||
private final String id;
|
||||
private final String text;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleType implements EnumType {
|
||||
ROLE_ADMIN("시스템 관리자"),
|
||||
ROLE_LABELER("라벨러"),
|
||||
ROLE_REVIEWER("검수자");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum StatusType implements EnumType {
|
||||
ACTIVE("사용"),
|
||||
INACTIVE("미사용"),
|
||||
PENDING("계정등록");
|
||||
|
||||
private final String desc;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.kamco.cd.training.common.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.CodeExpose;
|
||||
import com.kamco.cd.training.common.utils.enums.EnumType;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@CodeExpose
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum TrainStatusType implements EnumType {
|
||||
// @formatter:off
|
||||
READY("READY", "대기"),
|
||||
ING("ING", "진행중"),
|
||||
COMPLETED("COMPLETED", "완료"),
|
||||
STOPPED("STOPPED", "중단됨"),
|
||||
ERROR("ERROR", "오류");
|
||||
// @formatter:on
|
||||
|
||||
private final String id;
|
||||
private final String text;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.training.common.enums.error;
|
||||
|
||||
import com.kamco.cd.training.common.utils.ErrorCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
@Getter
|
||||
public enum AuthErrorCode implements ErrorCode {
|
||||
LOGIN_ID_NOT_FOUND("LOGIN_ID_NOT_FOUND", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
LOGIN_PASSWORD_MISMATCH("LOGIN_PASSWORD_MISMATCH", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
LOGIN_PASSWORD_EXCEEDED("LOGIN_PASSWORD_EXCEEDED", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
REFRESH_TOKEN_EXPIRED_OR_REVOKED("REFRESH_TOKEN_EXPIRED_OR_REVOKED", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
REFRESH_TOKEN_MISMATCH("REFRESH_TOKEN_MISMATCH", HttpStatus.UNAUTHORIZED);
|
||||
|
||||
private final String code;
|
||||
private final HttpStatus status;
|
||||
|
||||
AuthErrorCode(String code, HttpStatus status) {
|
||||
this.code = code;
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.training.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class BadRequestException extends CustomApiException {
|
||||
|
||||
public BadRequestException(String message) {
|
||||
super("BAD_REQUEST", HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.kamco.cd.training.common.exception;
|
||||
|
||||
import com.kamco.cd.training.common.utils.ErrorCode;
|
||||
import lombok.Getter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
@Getter
|
||||
public class CustomApiException extends RuntimeException {
|
||||
|
||||
private final String codeName; // ApiResponseCode enum name과 맞추는 용도 (예: "UNPROCESSABLE_ENTITY")
|
||||
private final HttpStatus status; // 응답으로 내려줄 HttpStatus
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status, String message) {
|
||||
super(message);
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public CustomApiException(String codeName, HttpStatus status) {
|
||||
this.codeName = codeName;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public CustomApiException(ErrorCode errorCode) {
|
||||
this.codeName = errorCode.getCode();
|
||||
this.status = errorCode.getStatus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.training.common.exception;
|
||||
|
||||
public class DuplicateFileException extends RuntimeException {
|
||||
public DuplicateFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.training.common.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class NotFoundException extends CustomApiException {
|
||||
|
||||
public NotFoundException(String message) {
|
||||
super("NOT_FOUND", HttpStatus.NOT_FOUND, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.training.common.exception;
|
||||
|
||||
public class ValidationException extends RuntimeException {
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.kamco.cd.training.common.service;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* Base Core Service Interface
|
||||
*
|
||||
* <p>CRUD operations를 정의하는 기본 서비스 인터페이스
|
||||
*
|
||||
* @param <T> Entity 타입
|
||||
* @param <ID> Entity의 ID 타입
|
||||
* @param <S> Search Request 타입
|
||||
*/
|
||||
public interface BaseCoreService<T, ID, S> {
|
||||
|
||||
/**
|
||||
* ID로 엔티티를 삭제합니다.
|
||||
*
|
||||
* @param id 삭제할 엔티티의 ID
|
||||
*/
|
||||
void remove(ID id);
|
||||
|
||||
/**
|
||||
* ID로 단건 조회합니다.
|
||||
*
|
||||
* @param id 조회할 엔티티의 ID
|
||||
* @return 조회된 엔티티
|
||||
*/
|
||||
T getOneById(ID id);
|
||||
|
||||
/**
|
||||
* 검색 조건과 페이징으로 조회합니다.
|
||||
*
|
||||
* @param searchReq 검색 조건
|
||||
* @return 페이징 처리된 검색 결과
|
||||
*/
|
||||
Page<T> search(S searchReq);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.kamco.cd.training.code.dto.CommonCodeDto.Basic;
|
||||
import com.kamco.cd.training.code.service.CommonCodeService;
|
||||
import com.kamco.cd.training.config.api.ApiResponseDto;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 공통코드 조회 유틸리티 클래스 애플리케이션 전역에서 공통코드를 조회하기 위한 유틸리티입니다. training 서버는 Redis 사용하고 Spring 내장 메모리 캐시
|
||||
* 사용합니다.
|
||||
*/
|
||||
//
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CommonCodeUtil {
|
||||
|
||||
private final CommonCodeService commonCodeService;
|
||||
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
public CommonCodeUtil(CommonCodeService commonCodeService) {
|
||||
this.commonCodeService = commonCodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 모든 공통코드 조회
|
||||
*
|
||||
* @return 캐시된 모든 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getAllCommonCodes() {
|
||||
try {
|
||||
return commonCodeService.getFindAll();
|
||||
} catch (Exception e) {
|
||||
log.error("공통코드 전체 조회 중 오류 발생", e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 코드로 공통코드 조회
|
||||
*
|
||||
* @param code 코드값
|
||||
* @return 해당 코드의 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getCommonCodesByCode(String code) {
|
||||
if (code == null || code.isEmpty()) {
|
||||
log.warn("유효하지 않은 코드: {}", code);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
return commonCodeService.findByCode(code);
|
||||
} catch (Exception e) {
|
||||
log.error("코드 기반 공통코드 조회 중 오류 발생: {}", code, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 ID로 공통코드 단건 조회
|
||||
*
|
||||
* @param id 공통코드 ID
|
||||
* @return 조회된 공통코드
|
||||
*/
|
||||
public Optional<Basic> getCommonCodeById(Long id) {
|
||||
if (id == null || id <= 0) {
|
||||
log.warn("유효하지 않은 ID: {}", id);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(commonCodeService.getOneById(id));
|
||||
} catch (Exception e) {
|
||||
log.error("ID 기반 공통코드 조회 중 오류 발생: {}", id, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상위 코드와 하위 코드로 공통코드명 조회
|
||||
*
|
||||
* @param parentCode 상위 코드
|
||||
* @param childCode 하위 코드
|
||||
* @return 공통코드명
|
||||
*/
|
||||
public Optional<String> getCodeName(String parentCode, String childCode) {
|
||||
if (parentCode == null || parentCode.isEmpty() || childCode == null || childCode.isEmpty()) {
|
||||
log.warn("유효하지 않은 코드: parentCode={}, childCode={}", parentCode, childCode);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return commonCodeService.getCode(parentCode, childCode);
|
||||
} catch (Exception e) {
|
||||
log.error("코드명 조회 중 오류 발생: parentCode={}, childCode={}", parentCode, childCode, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 상위 코드를 기반으로 하위 코드 조회
|
||||
*
|
||||
* @param parentCode 상위 코드
|
||||
* @return 해당 상위 코드의 하위 공통코드 목록
|
||||
*/
|
||||
public List<Basic> getChildCodesByParentCode(String parentCode) {
|
||||
if (parentCode == null || parentCode.isEmpty()) {
|
||||
log.warn("유효하지 않은 상위 코드: {}", parentCode);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
return commonCodeService.getFindAll().stream()
|
||||
.filter(code -> parentCode.equals(code.getCode()))
|
||||
.findFirst()
|
||||
.map(Basic::getChildren)
|
||||
.orElse(List.of());
|
||||
} catch (Exception e) {
|
||||
log.error("상위 코드 기반 하위 코드 조회 중 오류 발생: {}", parentCode, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 사용 가능 여부 확인
|
||||
*
|
||||
* @param parentId 상위 코드 ID -> 1depth 는 null 허용 (파라미터 미필수로 변경)
|
||||
* @param code 확인할 코드값
|
||||
* @return 사용 가능 여부 (true: 사용 가능, false: 중복 또는 오류)
|
||||
*/
|
||||
public boolean isCodeAvailable(Long parentId, String code) {
|
||||
if (parentId <= 0 || code == null || code.isEmpty()) {
|
||||
log.warn("유효하지 않은 입력: parentId={}, code={}", parentId, code);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
ApiResponseDto.ResponseObj response = commonCodeService.getCodeCheckDuplicate(parentId, code);
|
||||
// ResponseObj의 code 필드 : OK이면 성공, 아니면 실패
|
||||
return response.getCode() != null
|
||||
&& response.getCode().equals(ApiResponseDto.ApiResponseCode.OK);
|
||||
} catch (Exception e) {
|
||||
log.error("코드 중복 확인 중 오류 발생: parentId={}, code={}", parentId, code, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import com.kamco.cd.training.auth.BCryptSaltGenerator;
|
||||
import java.util.regex.Pattern;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
public class CommonStringUtils {
|
||||
|
||||
/**
|
||||
* 영문, 숫자, 특수문자를 모두 포함하여 8~20자 이내의 비밀번호
|
||||
*
|
||||
* @param password 벨리데이션 필요한 패스워드
|
||||
* @return
|
||||
*/
|
||||
public static boolean isValidPassword(String password) {
|
||||
String passwordPattern =
|
||||
"^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$";
|
||||
return Pattern.matches(passwordPattern, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 암호화
|
||||
*
|
||||
* @param password 암호화 필요한 패스워드
|
||||
* @param employeeNo salt 생성에 필요한 사원번호
|
||||
* @return
|
||||
*/
|
||||
public static String hashPassword(String password, String employeeNo) {
|
||||
String salt = BCryptSaltGenerator.generateSaltWithEmployeeNo(employeeNo.trim());
|
||||
return BCrypt.hashpw(password.trim(), salt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public interface ErrorCode {
|
||||
|
||||
String getCode();
|
||||
|
||||
HttpStatus getStatus();
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import static java.lang.String.CASE_INSENSITIVE_ORDER;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.geotools.coverage.grid.GridCoverage2D;
|
||||
import org.geotools.gce.geotiff.GeoTiffReader;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public class FIleChecker {
|
||||
|
||||
static SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public static boolean isValidFile(String pathStr) {
|
||||
|
||||
Path path = Paths.get(pathStr);
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Files.isReadable(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Files.size(path) <= 0) {
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean verifyFileIntegrity(Path path, String expectedHash)
|
||||
throws IOException, NoSuchAlgorithmException {
|
||||
|
||||
// 1. 알고리즘 선택 (SHA-256 권장, MD5는 보안상 비추천)
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
|
||||
try (InputStream fis = Files.newInputStream(path)) {
|
||||
byte[] buffer = new byte[8192]; // 8KB 버퍼
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 계산된 바이트 배열을 16진수 문자열로 변환
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest.digest()) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
String actualHash = sb.toString();
|
||||
|
||||
return actualHash.equalsIgnoreCase(expectedHash);
|
||||
}
|
||||
|
||||
public static boolean checkTfw(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 파일의 모든 라인을 읽어옴
|
||||
List<Double> lines = new ArrayList<>();
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (!line.trim().isEmpty()) { // 빈 줄 제외
|
||||
lines.add(Double.parseDouble(line.trim()));
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 6줄이 맞는지 확인
|
||||
if (lines.size() < 6) {
|
||||
// System.out.println("유효하지 않은 TFW 파일입니다. (데이터 부족)");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkGeoTiff(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GeoTiffReader reader = null;
|
||||
try {
|
||||
// 1. 파일 포맷 및 헤더 확인
|
||||
reader = new GeoTiffReader(file);
|
||||
|
||||
// 2. 실제 데이터 로딩 (여기서 파일 깨짐 여부 확인됨)
|
||||
// null을 넣으면 전체 영역을 읽지 않고 메타데이터 위주로 체크하여 빠름
|
||||
GridCoverage2D coverage = reader.read(null);
|
||||
|
||||
if (coverage == null) return false;
|
||||
|
||||
// 3. GIS 필수 정보(좌표계)가 있는지 확인
|
||||
// if (coverage.getCoordinateReferenceSystem() == null) {
|
||||
// GeoTIFF가 아니라 일반 TIFF일 수도 있음(이미지는 정상이지만, 좌표계(CRS) 정보가 없습니다.)
|
||||
// }
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("손상된 TIF 파일입니다: " + e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
// 리소스 해제 (필수)
|
||||
if (reader != null) reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean cmmndGdalInfo(String filePath) {
|
||||
|
||||
File file = new File(filePath);
|
||||
|
||||
if (!file.exists()) {
|
||||
System.err.println("파일이 존재하지 않습니다: " + filePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasDriver = false;
|
||||
|
||||
// 운영체제 감지
|
||||
String osName = System.getProperty("os.name").toLowerCase();
|
||||
boolean isWindows = osName.contains("win");
|
||||
boolean isMac = osName.contains("mac");
|
||||
boolean isUnix = osName.contains("nix") || osName.contains("nux") || osName.contains("aix");
|
||||
|
||||
// gdalinfo 경로 찾기 (일반적인 설치 경로 우선 확인)
|
||||
String gdalinfoPath = findGdalinfoPath();
|
||||
if (gdalinfoPath == null) {
|
||||
System.err.println("gdalinfo 명령어를 찾을 수 없습니다. GDAL이 설치되어 있는지 확인하세요.");
|
||||
System.err.println("macOS: brew install gdal");
|
||||
System.err.println("Ubuntu/Debian: sudo apt-get install gdal-bin");
|
||||
System.err.println("CentOS/RHEL: sudo yum install gdal");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> command = new ArrayList<>();
|
||||
|
||||
if (isWindows) {
|
||||
// 윈도우용
|
||||
command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행
|
||||
command.add("/c"); // 명령어를 수행하고 종료한다는 옵션
|
||||
command.add("gdalinfo");
|
||||
command.add(filePath);
|
||||
command.add("|");
|
||||
command.add("findstr");
|
||||
command.add("/i");
|
||||
command.add("Geo");
|
||||
} else if (isMac || isUnix) {
|
||||
// 리눅스, 맥용
|
||||
command.add("sh");
|
||||
command.add("-c");
|
||||
command.add(gdalinfoPath + " \"" + filePath + "\" | grep -i Geo");
|
||||
} else {
|
||||
System.err.println("지원하지 않는 운영체제: " + osName);
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(command);
|
||||
processBuilder.redirectErrorStream(true);
|
||||
|
||||
Process process = null;
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
System.out.println("gdalinfo 명령어 실행 시작: " + filePath);
|
||||
process = processBuilder.start();
|
||||
|
||||
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// System.out.println("gdalinfo 출력: " + line);
|
||||
if (line.contains("Driver: GTiff/GeoTIFF")) {
|
||||
hasDriver = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = process.waitFor();
|
||||
System.out.println("gdalinfo 종료 코드: " + exitCode);
|
||||
|
||||
// 프로세스가 정상 종료되지 않았고 Driver를 찾지 못한 경우
|
||||
if (exitCode != 0 && !hasDriver) {
|
||||
System.err.println("gdalinfo 명령 실행 실패. Exit code: " + exitCode);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("gdalinfo 실행 중 I/O 오류 발생: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("gdalinfo 실행 중 인터럽트 발생: " + e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
System.err.println("gdalinfo 실행 중 예상치 못한 오류 발생: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
// 리소스 정리
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("BufferedReader 종료 중 오류: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
if (process != null) {
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
return hasDriver;
|
||||
}
|
||||
|
||||
public static boolean mkDir(String dirPath) {
|
||||
Path uploadTargetPath = Paths.get(dirPath);
|
||||
try {
|
||||
Files.createDirectories(uploadTargetPath);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<Folder> getFolderAll(String dirPath, String sortType, int maxDepth) {
|
||||
|
||||
Path startPath = Paths.get(dirPath);
|
||||
|
||||
List<Folder> folderList = List.of();
|
||||
|
||||
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
|
||||
|
||||
folderList =
|
||||
stream
|
||||
.filter(Files::isDirectory)
|
||||
.filter(p -> !p.toString().equals(dirPath))
|
||||
.map(
|
||||
path -> {
|
||||
int depth = path.getNameCount();
|
||||
|
||||
String folderNm = path.getFileName().toString();
|
||||
String parentFolderNm = path.getParent().getFileName().toString();
|
||||
String parentPath = path.getParent().toString();
|
||||
String fullPath = path.toAbsolutePath().toString();
|
||||
|
||||
boolean isValid =
|
||||
!NameValidator.containsKorean(folderNm)
|
||||
&& !NameValidator.containsWhitespaceRegex(folderNm);
|
||||
|
||||
File file = new File(fullPath);
|
||||
int childCnt = getChildFolderCount(file);
|
||||
String lastModified = getLastModified(file);
|
||||
|
||||
return new Folder(
|
||||
folderNm,
|
||||
parentFolderNm,
|
||||
parentPath,
|
||||
fullPath,
|
||||
depth,
|
||||
childCnt,
|
||||
lastModified,
|
||||
isValid);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (sortType.equals("name") || sortType.equals("name asc")) {
|
||||
folderList.sort(
|
||||
Comparator.comparing(
|
||||
Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이
|
||||
));
|
||||
} else if (sortType.equals("name desc")) {
|
||||
folderList.sort(
|
||||
Comparator.comparing(
|
||||
Folder::getFolderNm, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이
|
||||
)
|
||||
.reversed());
|
||||
} else if (sortType.equals("dttm desc")) {
|
||||
folderList.sort(
|
||||
Comparator.comparing(
|
||||
Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이
|
||||
)
|
||||
.reversed());
|
||||
} else {
|
||||
folderList.sort(
|
||||
Comparator.comparing(
|
||||
Folder::getLastModified, CASE_INSENSITIVE_ORDER // 대소문자 구분 없이
|
||||
));
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return folderList;
|
||||
}
|
||||
|
||||
public static List<Folder> getFolderAll(String dirPath) {
|
||||
return getFolderAll(dirPath, "name", 1);
|
||||
}
|
||||
|
||||
public static List<Folder> getFolderAll(String dirPath, String sortType) {
|
||||
return getFolderAll(dirPath, sortType, 1);
|
||||
}
|
||||
|
||||
public static int getChildFolderCount(String dirPath) {
|
||||
File directory = new File(dirPath);
|
||||
File[] childFolders = directory.listFiles(File::isDirectory);
|
||||
|
||||
int childCnt = 0;
|
||||
if (childFolders != null) {
|
||||
childCnt = childFolders.length;
|
||||
}
|
||||
|
||||
return childCnt;
|
||||
}
|
||||
|
||||
public static int getChildFolderCount(File directory) {
|
||||
File[] childFolders = directory.listFiles(File::isDirectory);
|
||||
|
||||
int childCnt = 0;
|
||||
if (childFolders != null) {
|
||||
childCnt = childFolders.length;
|
||||
}
|
||||
|
||||
return childCnt;
|
||||
}
|
||||
|
||||
public static String getLastModified(String dirPath) {
|
||||
File file = new File(dirPath);
|
||||
return dttmFormat.format(new Date(file.lastModified()));
|
||||
}
|
||||
|
||||
public static String getLastModified(File file) {
|
||||
return dttmFormat.format(new Date(file.lastModified()));
|
||||
}
|
||||
|
||||
public static List<Basic> getFilesFromAllDepth(
|
||||
String dir,
|
||||
String targetFileNm,
|
||||
String extension,
|
||||
int maxDepth,
|
||||
String sortType,
|
||||
int startPos,
|
||||
int endPos) {
|
||||
|
||||
Path startPath = Paths.get(dir);
|
||||
String dirPath = dir;
|
||||
|
||||
int limit = endPos - startPos + 1;
|
||||
|
||||
Set<String> targetExtensions = createExtensionSet(extension);
|
||||
|
||||
List<Basic> fileList = new ArrayList<>();
|
||||
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
Predicate<Path> isTargetName =
|
||||
p -> {
|
||||
if (targetFileNm == null
|
||||
|| targetFileNm.trim().isEmpty()
|
||||
|| targetFileNm.trim().equals("*")) {
|
||||
return true; // 전체 파일 허용
|
||||
}
|
||||
return p.getFileName().toString().contains(targetFileNm);
|
||||
};
|
||||
|
||||
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
|
||||
|
||||
fileList =
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(
|
||||
p ->
|
||||
extension == null
|
||||
|| extension.equals("")
|
||||
|| extension.equals("*")
|
||||
|| targetExtensions.contains(extractExtension(p)))
|
||||
.sorted(getFileComparator(sortType))
|
||||
.filter(isTargetName)
|
||||
.skip(startPos)
|
||||
.limit(limit)
|
||||
.map(
|
||||
path -> {
|
||||
// int depth = path.getNameCount();
|
||||
|
||||
String fileNm = path.getFileName().toString();
|
||||
String ext = FilenameUtils.getExtension(fileNm);
|
||||
String parentFolderNm = path.getParent().getFileName().toString();
|
||||
String parentPath = path.getParent().toString();
|
||||
String fullPath = path.toAbsolutePath().toString();
|
||||
|
||||
File file = new File(fullPath);
|
||||
long fileSize = file.length();
|
||||
String lastModified = dttmFormat.format(new Date(file.lastModified()));
|
||||
|
||||
return new Basic(
|
||||
fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
public static List<Basic> getFilesFromAllDepth(
|
||||
String dir, String targetFileNm, String extension) {
|
||||
|
||||
return FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension, 100, "name", 0, 100);
|
||||
}
|
||||
|
||||
public static int getFileCountFromAllDepth(String dir, String targetFileNm, String extension) {
|
||||
|
||||
List<FIleChecker.Basic> basicList =
|
||||
FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension);
|
||||
|
||||
return (int)
|
||||
basicList.stream().filter(dto -> dto.getExtension().toString().equals(extension)).count();
|
||||
}
|
||||
|
||||
public static Long getFileTotSize(List<FIleChecker.Basic> files) {
|
||||
|
||||
Long fileTotSize = 0L;
|
||||
if (files != null || files.size() > 0) {
|
||||
fileTotSize = files.stream().mapToLong(FIleChecker.Basic::getFileSize).sum();
|
||||
}
|
||||
|
||||
return fileTotSize;
|
||||
}
|
||||
|
||||
public static boolean multipartSaveTo(MultipartFile mfile, String targetPath) {
|
||||
Path tmpSavePath = Paths.get(targetPath);
|
||||
|
||||
boolean fileUpload = true;
|
||||
try {
|
||||
mfile.transferTo(tmpSavePath);
|
||||
} catch (IOException e) {
|
||||
// throw new RuntimeException(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static boolean multipartChunkSaveTo(MultipartFile mfile, String targetPath, int chunkIndex) {
|
||||
File dest = new File(targetPath, String.valueOf(chunkIndex));
|
||||
|
||||
boolean fileUpload = true;
|
||||
try {
|
||||
mfile.transferTo(dest);
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean deleteFolder(String path) {
|
||||
return FileSystemUtils.deleteRecursively(new File(path));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static boolean validationMultipart(MultipartFile mfile) {
|
||||
// 파일 유효성 검증
|
||||
if (mfile == null || mfile.isEmpty() || mfile.getSize() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean checkExtensions(String fileName, String ext) {
|
||||
if (fileName == null) return false;
|
||||
|
||||
if (!fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase().equals(ext)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Set<String> createExtensionSet(String extensionString) {
|
||||
if (extensionString == null || extensionString.isBlank()) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
// "java, class" -> ["java", " class"] -> [".java", ".class"]
|
||||
return Arrays.stream(extensionString.split(","))
|
||||
.map(ext -> ext.trim())
|
||||
.filter(ext -> !ext.isEmpty())
|
||||
.map(ext -> "." + ext.toLowerCase())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static String extractExtension(Path path) {
|
||||
String filename = path.getFileName().toString();
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
|
||||
// 확장자가 없거나 파일명이 .으로 끝나는 경우
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return ""; // 빈 문자열 반환
|
||||
}
|
||||
|
||||
// 확장자 추출 및 소문자 변환
|
||||
return filename.substring(lastDotIndex).toLowerCase();
|
||||
}
|
||||
|
||||
public static Comparator<Path> getFileComparator(String sortType) {
|
||||
|
||||
// 파일 이름 비교 기본 Comparator (대소문자 무시)
|
||||
Comparator<Path> nameComparator =
|
||||
Comparator.comparing(path -> path.getFileName().toString(), CASE_INSENSITIVE_ORDER);
|
||||
|
||||
Comparator<Path> dateComparator =
|
||||
Comparator.comparing(
|
||||
path -> {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path);
|
||||
} catch (IOException e) {
|
||||
return FileTime.fromMillis(0);
|
||||
}
|
||||
});
|
||||
|
||||
if ("name desc".equalsIgnoreCase(sortType)) {
|
||||
return nameComparator.reversed();
|
||||
} else if ("date".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator;
|
||||
} else if ("date desc".equalsIgnoreCase(sortType)) {
|
||||
return dateComparator.reversed();
|
||||
} else {
|
||||
return nameComparator;
|
||||
}
|
||||
}
|
||||
|
||||
private static String findGdalinfoPath() {
|
||||
// 일반적인 설치 경로 확인
|
||||
String[] possiblePaths = {
|
||||
"/usr/local/bin/gdalinfo", // Homebrew (macOS)
|
||||
"/opt/homebrew/bin/gdalinfo", // Homebrew (Apple Silicon macOS)
|
||||
"/usr/bin/gdalinfo", // Linux
|
||||
"gdalinfo" // PATH에 있는 경우
|
||||
};
|
||||
|
||||
for (String path : possiblePaths) {
|
||||
if (isCommandAvailable(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isCommandAvailable(String command) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(command, "--version");
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
|
||||
// 프로세스 완료 대기 (최대 5초)
|
||||
boolean finished = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
|
||||
|
||||
if (!finished) {
|
||||
process.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 종료 코드가 0이면 정상 (일부 명령어는 --version에서 다른 코드 반환할 수 있음)
|
||||
return process.exitValue() == 0 || process.exitValue() == 1;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "Folder", description = "폴더 정보")
|
||||
@Getter
|
||||
public static class Folder {
|
||||
private final String folderNm;
|
||||
private final String parentFolderNm;
|
||||
private final String parentPath;
|
||||
private final String fullPath;
|
||||
private final int depth;
|
||||
private final long childCnt;
|
||||
private final String lastModified;
|
||||
private final Boolean isValid;
|
||||
|
||||
public Folder(
|
||||
String folderNm,
|
||||
String parentFolderNm,
|
||||
String parentPath,
|
||||
String fullPath,
|
||||
int depth,
|
||||
long childCnt,
|
||||
String lastModified,
|
||||
Boolean isValid) {
|
||||
this.folderNm = folderNm;
|
||||
this.parentFolderNm = parentFolderNm;
|
||||
this.parentPath = parentPath;
|
||||
this.fullPath = fullPath;
|
||||
this.depth = depth;
|
||||
this.childCnt = childCnt;
|
||||
this.lastModified = lastModified;
|
||||
this.isValid = isValid;
|
||||
}
|
||||
}
|
||||
|
||||
@Schema(name = "File Basic", description = "파일 기본 정보")
|
||||
@Getter
|
||||
public static class Basic {
|
||||
|
||||
private final String fileNm;
|
||||
private final String parentFolderNm;
|
||||
private final String parentPath;
|
||||
private final String fullPath;
|
||||
private final String extension;
|
||||
private final long fileSize;
|
||||
private final String lastModified;
|
||||
|
||||
public Basic(
|
||||
String fileNm,
|
||||
String parentFolderNm,
|
||||
String parentPath,
|
||||
String fullPath,
|
||||
String extension,
|
||||
long fileSize,
|
||||
String lastModified) {
|
||||
this.fileNm = fileNm;
|
||||
this.parentFolderNm = parentFolderNm;
|
||||
this.parentPath = parentPath;
|
||||
this.fullPath = fullPath;
|
||||
this.extension = extension;
|
||||
this.fileSize = fileSize;
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class NameValidator {
|
||||
|
||||
private static final String HANGUL_REGEX = ".*\\p{IsHangul}.*";
|
||||
private static final Pattern HANGUL_PATTERN = Pattern.compile(HANGUL_REGEX);
|
||||
|
||||
private static final String WHITESPACE_REGEX = ".*\\s.*";
|
||||
private static final Pattern WHITESPACE_PATTERN = Pattern.compile(WHITESPACE_REGEX);
|
||||
|
||||
public static boolean containsKorean(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Matcher matcher = HANGUL_PATTERN.matcher(str);
|
||||
return matcher.matches();
|
||||
}
|
||||
|
||||
public static boolean containsWhitespaceRegex(String str) {
|
||||
if (str == null || str.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Matcher matcher = WHITESPACE_PATTERN.matcher(str);
|
||||
// find()를 사용하여 문자열 내에서 패턴이 일치하는 부분이 있는지 확인
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
public static boolean isNullOrEmpty(String str) {
|
||||
if (str == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.kamco.cd.training.common.utils;
|
||||
|
||||
import com.kamco.cd.training.auth.CustomUserDetails;
|
||||
import com.kamco.cd.training.members.dto.MembersDto;
|
||||
import com.kamco.cd.training.postgres.entity.MemberEntity;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UserUtil {
|
||||
|
||||
public MembersDto.Member getCurrentUser() {
|
||||
return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
|
||||
.filter(auth -> auth.getPrincipal() instanceof CustomUserDetails)
|
||||
.map(
|
||||
auth -> {
|
||||
CustomUserDetails user = (CustomUserDetails) auth.getPrincipal();
|
||||
MemberEntity m = user.getMember();
|
||||
return new MembersDto.Member(m.getId(), m.getName(), m.getEmployeeNo());
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
MembersDto.Member user = getCurrentUser();
|
||||
return user != null ? user.getId() : null;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
MembersDto.Member user = getCurrentUser();
|
||||
return user != null ? user.getName() : null;
|
||||
}
|
||||
|
||||
public String getEmployeeNo() {
|
||||
MembersDto.Member user = getCurrentUser();
|
||||
return user != null ? user.getEmployeeNo() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
public class CodeDto {
|
||||
|
||||
private String code;
|
||||
private String name;
|
||||
|
||||
public CodeDto(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CodeExpose {}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
public interface EnumType {
|
||||
|
||||
String getId();
|
||||
|
||||
String getText();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
import com.kamco.cd.training.common.utils.interfaces.EnumValid;
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EnumValidator implements ConstraintValidator<EnumValid, String> {
|
||||
|
||||
private Set<String> acceptedValues;
|
||||
|
||||
@Override
|
||||
public void initialize(EnumValid constraintAnnotation) {
|
||||
acceptedValues =
|
||||
Arrays.stream(constraintAnnotation.enumClass().getEnumConstants())
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
return value != null && acceptedValues.contains(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.kamco.cd.training.common.utils.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
public class Enums {
|
||||
|
||||
private static final String BASE_PACKAGE = "com.kamco.cd.training";
|
||||
|
||||
/** 노출 가능한 enum만 모아둔 맵 key: enum simpleName (예: RoleType) value: enum Class */
|
||||
private static final Map<String, Class<? extends Enum<?>>> exposedEnumMap = scanExposedEnumMap();
|
||||
|
||||
// code로 enum 찾기
|
||||
public static <E extends Enum<E> & EnumType> E fromId(Class<E> enumClass, String id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (E e : enumClass.getEnumConstants()) {
|
||||
if (id.equalsIgnoreCase(e.getId())) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// enum -> CodeDto list
|
||||
public static List<CodeDto> toList(Class<? extends Enum<?>> enumClass) {
|
||||
Object[] enums = enumClass.getEnumConstants();
|
||||
|
||||
return Arrays.stream(enums)
|
||||
.map(e -> (EnumType) e)
|
||||
.map(e -> new CodeDto(e.getId(), e.getText()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/** 특정 타입(enum)만 조회 /codes/{type} -> type = RoleType 같은 값 */
|
||||
public static List<CodeDto> getCodes(String type) {
|
||||
Class<? extends Enum<?>> enumClass = exposedEnumMap.get(type);
|
||||
if (enumClass == null) {
|
||||
throw new IllegalArgumentException("지원하지 않는 코드 타입: " + type);
|
||||
}
|
||||
return toList(enumClass);
|
||||
}
|
||||
|
||||
/** 전체 enum 코드 조회 */
|
||||
public static Map<String, List<CodeDto>> getAllCodes() {
|
||||
Map<String, List<CodeDto>> result = new HashMap<>();
|
||||
for (Map.Entry<String, Class<? extends Enum<?>>> e : exposedEnumMap.entrySet()) {
|
||||
result.put(e.getKey(), toList(e.getValue()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CodeExpose + EnumType 인 enum만 스캔해서 Map 구성
|
||||
*/
|
||||
private static Map<String, Class<? extends Enum<?>>> scanExposedEnumMap() {
|
||||
Reflections reflections = new Reflections(BASE_PACKAGE);
|
||||
|
||||
Set<Class<?>> types = reflections.getTypesAnnotatedWith(CodeExpose.class);
|
||||
|
||||
Map<String, Class<? extends Enum<?>>> result = new HashMap<>();
|
||||
|
||||
for (Class<?> clazz : types) {
|
||||
if (clazz.isEnum() && EnumType.class.isAssignableFrom(clazz)) {
|
||||
result.put(clazz.getSimpleName(), (Class<? extends Enum<?>>) clazz);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.kamco.cd.training.common.utils.geometry;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import java.io.IOException;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonReader;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
|
||||
|
||||
public GeometryDeserializer(Class<T> targetType) {
|
||||
super(targetType);
|
||||
}
|
||||
|
||||
// TODO: test code
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JacksonException {
|
||||
String json = jsonParser.readValueAsTree().toString();
|
||||
|
||||
if (!StringUtils.hasText(json)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
GeoJsonReader reader = new GeoJsonReader();
|
||||
return (T) reader.read(json);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.kamco.cd.training.common.utils.geometry;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.io.geojson.GeoJsonWriter;
|
||||
|
||||
public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
|
||||
|
||||
// TODO: test code
|
||||
public GeometrySerializer(Class<T> targetType) {
|
||||
super(targetType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(
|
||||
T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
if (Objects.nonNull(geometry)) {
|
||||
// default: 8자리 강제로 반올림시킴. 16자리로 늘려줌
|
||||
GeoJsonWriter writer = new GeoJsonWriter(16);
|
||||
String json = writer.write(geometry);
|
||||
jsonGenerator.writeRawValue(json);
|
||||
} else {
|
||||
jsonGenerator.writeNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.kamco.cd.training.common.utils.html;
|
||||
|
||||
import com.fasterxml.jackson.core.JacksonException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import java.io.IOException;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
public class HtmlEscapeDeserializer extends JsonDeserializer<Object> {
|
||||
|
||||
@Override
|
||||
public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException, JacksonException {
|
||||
String value = jsonParser.getValueAsString();
|
||||
return value == null ? null : HtmlUtils.htmlEscape(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.kamco.cd.training.common.utils.html;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import java.io.IOException;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
public class HtmlUnescapeSerializer extends JsonSerializer<String> {
|
||||
@Override
|
||||
public void serialize(
|
||||
String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
|
||||
throws IOException {
|
||||
if (value == null) {
|
||||
jsonGenerator.writeNull();
|
||||
} else {
|
||||
jsonGenerator.writeString(HtmlUtils.htmlUnescape(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.kamco.cd.training.common.utils.interfaces;
|
||||
|
||||
import com.kamco.cd.training.common.utils.enums.EnumValidator;
|
||||
import jakarta.validation.Constraint;
|
||||
import jakarta.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Constraint(validatedBy = EnumValidator.class)
|
||||
public @interface EnumValid {
|
||||
|
||||
String message() default "올바르지 않은 값입니다.";
|
||||
|
||||
Class<?>[] groups() default {};
|
||||
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
|
||||
Class<? extends Enum<?>> enumClass();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.kamco.cd.training.common.utils.interfaces;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@JacksonAnnotationsInside
|
||||
@JsonFormat(
|
||||
shape = JsonFormat.Shape.STRING,
|
||||
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
|
||||
timezone = "Asia/Seoul")
|
||||
public @interface JsonFormatDttm {}
|
||||
Reference in New Issue
Block a user