From 372a1364b2251ece2adc3508ad06cbd35da5b5e7 Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 16 Dec 2025 14:12:29 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EC=83=81=ED=83=9C=EA=B0=92=20enum=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20spotlessApply=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/common/enums/StatusType.java | 4 +- .../common/utils/CommonStringUtils.java | 2 +- .../kamcoback/common/utils/FIleChecker.java | 4 +- .../mapsheet/MapSheetMngApiController.java | 2 +- .../cd/kamcoback/members/dto/MembersDto.java | 11 +++-- .../mapsheet/MapSheetMngRepositoryImpl.java | 41 +++++++++++-------- .../members/MembersRepositoryImpl.java | 3 +- 7 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/common/enums/StatusType.java b/src/main/java/com/kamco/cd/kamcoback/common/enums/StatusType.java index 029e85ae..6d83bac7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/enums/StatusType.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/enums/StatusType.java @@ -7,9 +7,9 @@ import lombok.Getter; @Getter @AllArgsConstructor public enum StatusType implements EnumType { - ACTIVE("활성"), + ACTIVE("사용"), INACTIVE("미사용"), - PENDING("보류"); + PENDING("계정등록"); private final String desc; diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/CommonStringUtils.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/CommonStringUtils.java index ee94b72c..c891325f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/CommonStringUtils.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/CommonStringUtils.java @@ -14,7 +14,7 @@ public class CommonStringUtils { */ public static boolean isValidPassword(String password) { String passwordPattern = - "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?]).{8,20}$"; + "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-\\[\\]{};':\"\\\\|,.<>/?=]).{8,20}$"; return Pattern.matches(passwordPattern, password); } diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java index f7e76691..f6d53588 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java @@ -253,8 +253,8 @@ public class FIleChecker { List fileList = new ArrayList<>(); SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - //int fileTotCnt = 0; - //long fileTotSize = 0; + // int fileTotCnt = 0; + // long fileTotSize = 0; try (Stream stream = Files.walk(startPath, maxDepth)) { diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java index 1f1c4413..9869992f 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java @@ -43,7 +43,7 @@ public class MapSheetMngApiController { public ApiResponseDto> findMapSheetMngList( @RequestBody MapSheetMngDto.MngSearchReq searchReq) { - System.out.println("kkkkkkkkkkkkkkkkkkkkkkkkk"); + System.out.println("kkkkkkkkkkkkkkkkkkkkkkkkk"); return ApiResponseDto.ok(mapSheetMngService.findMapSheetMngList(searchReq)); } diff --git a/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java b/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java index 557be142..55494625 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/dto/MembersDto.java @@ -46,7 +46,8 @@ public class MembersDto { ZonedDateTime createdDttm, ZonedDateTime firstLoginDttm, ZonedDateTime lastLoginDttm, - ZonedDateTime statusChgDttm) { + ZonedDateTime statusChgDttm, + Boolean pwdResetYn) { this.id = id; this.uuid = uuid; this.userRole = userRole; @@ -54,7 +55,7 @@ public class MembersDto { this.name = name; this.employeeNo = employeeNo; this.status = status; - this.statusName = getStatusName(status); + this.statusName = getStatusName(status, pwdResetYn); this.createdDttm = createdDttm; this.firstLoginDttm = firstLoginDttm; this.lastLoginDttm = lastLoginDttm; @@ -66,8 +67,12 @@ public class MembersDto { return type.getText(); } - private String getStatusName(String status) { + private String getStatusName(String status, Boolean pwdResetYn) { StatusType type = Enums.fromId(StatusType.class, status); + pwdResetYn = pwdResetYn != null && pwdResetYn; + if (type.equals(StatusType.PENDING) && pwdResetYn) { + type = StatusType.ACTIVE; + } return type.getText(); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index e8bde29a..305e466b 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -54,12 +54,13 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport NumberExpression totalCount = mapSheetMngHstEntity.count().as("syncTotCnt"); - NumberExpression doneCount = new CaseBuilder() - .when(mapSheetMngHstEntity.dataState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum() - .as("syncStateDoneCnt"); + NumberExpression doneCount = + new CaseBuilder() + .when(mapSheetMngHstEntity.dataState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum() + .as("syncStateDoneCnt"); List foundContent = queryFactory @@ -87,22 +88,28 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .otherwise(0L) .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("NOFILE") - .or( mapSheetMngHstEntity.syncState.eq("NOTPAIR"))) + .when( + mapSheetMngHstEntity + .syncState + .eq("NOFILE") + .or(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))) .then(1L) .otherwise(0L) .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) - .then(1L) - .otherwise(0L) - .sum(), + .when(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("TYPEERROR") - .or( mapSheetMngHstEntity.syncState.eq("SIZEERROR"))) - .then(1L) - .otherwise(0L) - .sum(), + .when( + mapSheetMngHstEntity + .syncState + .eq("TYPEERROR") + .or(mapSheetMngHstEntity.syncState.eq("SIZEERROR"))) + .then(1L) + .otherwise(0L) + .sum(), mapSheetMngEntity.createdDttm, mapSheetMngHstEntity.syncEndDttm.max())) .from(mapSheetMngEntity) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java index a57d9204..0cf06001 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/members/MembersRepositoryImpl.java @@ -125,7 +125,8 @@ public class MembersRepositoryImpl implements MembersRepositoryCustom { memberEntity.createdDttm, memberEntity.firstLoginDttm, memberEntity.lastLoginDttm, - memberEntity.statusChgDttm)) + memberEntity.statusChgDttm, + memberEntity.pwdResetYn)) .from(memberEntity) .where(builder) .offset(pageable.getOffset()) From 77931aa97ab648ff11b9c5055baf4a6f55257e8e Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 16 Dec 2025 14:12:53 +0900 Subject: [PATCH 2/7] =?UTF-8?q?spotlessApply=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapsheet/MapSheetMngRepositoryImpl.java | 94 +++++++++++-------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 6407bfd5..16125bf5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -54,12 +54,13 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport NumberExpression totalCount = mapSheetMngHstEntity.count().as("syncTotCnt"); - NumberExpression doneCount = new CaseBuilder() - .when(mapSheetMngHstEntity.dataState.eq("DONE")) - .then(1L) - .otherwise(0L) - .sum() - .as("syncStateDoneCnt"); + NumberExpression doneCount = + new CaseBuilder() + .when(mapSheetMngHstEntity.dataState.eq("DONE")) + .then(1L) + .otherwise(0L) + .sum() + .as("syncStateDoneCnt"); List foundContent = queryFactory @@ -87,46 +88,63 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .otherwise(0L) .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("NOFILE") - .or( mapSheetMngHstEntity.syncState.eq("NOTPAIR"))) + .when( + mapSheetMngHstEntity + .syncState + .eq("NOFILE") + .or(mapSheetMngHstEntity.syncState.eq("NOTPAIR"))) .then(1L) .otherwise(0L) .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncCheckState.eq("DONE") - .and( mapSheetMngHstEntity.syncState.eq("NOFILE") - .or(mapSheetMngHstEntity.syncState.eq("NOTPAIR")) ) - ) - .then(1L) - .otherwise(0L) - .sum(), - + .when( + mapSheetMngHstEntity + .syncCheckState + .eq("DONE") + .and( + mapSheetMngHstEntity + .syncState + .eq("NOFILE") + .or(mapSheetMngHstEntity.syncState.eq("NOTPAIR")))) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) - .then(1L) - .otherwise(0L) - .sum(), + .when(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncCheckState.eq("DONE") - .and(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) ) - .then(1L) - .otherwise(0L) - .sum(), - + .when( + mapSheetMngHstEntity + .syncCheckState + .eq("DONE") + .and(mapSheetMngHstEntity.syncState.eq("DUPLICATE"))) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncState.eq("TYPEERROR") - .or( mapSheetMngHstEntity.syncState.eq("SIZEERROR"))) - .then(1L) - .otherwise(0L) - .sum(), + .when( + mapSheetMngHstEntity + .syncState + .eq("TYPEERROR") + .or(mapSheetMngHstEntity.syncState.eq("SIZEERROR"))) + .then(1L) + .otherwise(0L) + .sum(), new CaseBuilder() - .when(mapSheetMngHstEntity.syncCheckState.eq("DONE") - .and(mapSheetMngHstEntity.syncState.eq("TYPEERROR") - .or( mapSheetMngHstEntity.syncState.eq("SIZEERROR"))) ) - .then(1L) - .otherwise(0L) - .sum(), - + .when( + mapSheetMngHstEntity + .syncCheckState + .eq("DONE") + .and( + mapSheetMngHstEntity + .syncState + .eq("TYPEERROR") + .or(mapSheetMngHstEntity.syncState.eq("SIZEERROR")))) + .then(1L) + .otherwise(0L) + .sum(), mapSheetMngEntity.createdDttm, mapSheetMngHstEntity.syncEndDttm.max())) .from(mapSheetMngEntity) From b5b7fc79a872de3887964f4421a3d5ad5a66a5d3 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:36:16 +0900 Subject: [PATCH 3/7] error-list api upgarde and sync api change to stable --- .../mapsheet/MapSheetMngApiController.java | 35 +++++++++++++++- .../mapsheet/dto/MapSheetMngDto.java | 40 +++++++++++++++++++ .../mapsheet/MapSheetMngRepositoryImpl.java | 40 ++++++++++++++----- .../MapSheetMngFileJobRepositoryImpl.java | 18 ++++++--- src/main/resources/application-prod.yml | 2 +- src/main/resources/application.yml | 2 +- 6 files changed, 119 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java index 9869992f..9e49513a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/MapSheetMngApiController.java @@ -71,9 +71,40 @@ public class MapSheetMngApiController { /** * 오류데이터 목록 조회 * - * @param searchReq - * @return + *

도엽파일 동기화 시 발생한 오류 데이터를 조회합니다. + * + *

오류 타입: + * + *

    + *
  • NOFILE - 파일없음 + *
  • NOTPAIR - 페어없음 (tif/tfw 중 하나만 존재) + *
  • DUPLICATE - 중복 (동일한 파일이 여러개 존재) + *
  • SIZEERROR - size 0 (파일 크기가 0) + *
  • TYPEERROR - 형식오류 (tfw 검증 실패 또는 GeoTIFF 검증 실패) + *
+ * + *

sync_check_strt_dttm과 sync_check_end_dttm은 동일한 값으로 설정됩니다. + * + * @param searchReq 검색 조건 (년도, 검색어, syncStateFilter 등) + * @return 오류 데이터 목록 */ + @Operation( + summary = "오류데이터 목록 조회", + description = + "도엽파일 동기화 시 발생한 오류 데이터를 조회합니다. " + + "오류 타입: NOFILE(파일없음), NOTPAIR(페어없음), DUPLICATE(중복), SIZEERROR(size 0), TYPEERROR(형식오류)") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = MapSheetMngDto.ErrorDataDto.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) @PostMapping("/error-list") public ApiResponseDto> findMapSheetErrorList( @RequestBody @Valid MapSheetMngDto.ErrorSearchReq searchReq) { diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java index 95dba9e6..a6044414 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/dto/MapSheetMngDto.java @@ -128,6 +128,11 @@ public class MapSheetMngDto { @Schema(description = "년도", example = "2025") private Integer mngYyyy; + @Schema( + description = "동기화 상태 필터 (NOFILE, NOTPAIR, DUPLICATE, SIZEERROR, TYPEERROR)", + example = "NOFILE") + private String syncStateFilter; + public Pageable toPageable() { if (sort != null && !sort.isEmpty()) { String[] sortParams = sort.split(","); @@ -153,6 +158,18 @@ public class MapSheetMngDto { private Integer mapCodeSrc; private String createdDttm; private DataState dataState; + + @Schema(description = "동기화 상태 (NOFILE, NOTPAIR, DUPLICATE, SIZEERROR, TYPEERROR)") + private String syncState; + + @Schema(description = "동기화 체크 상태") + private String syncCheckState; + + @Schema(description = "동기화 체크 시작 시간") + private java.time.LocalDateTime syncCheckStrtDttm; + + @Schema(description = "동기화 체크 종료 시간") + private java.time.LocalDateTime syncCheckEndDttm; } @Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴") @@ -224,4 +241,27 @@ public class MapSheetMngDto { return desc; } } + + @Getter + @AllArgsConstructor + public enum SyncErrorState implements EnumType { + NOFILE("파일없음"), + NOTPAIR("페어없음"), + DUPLICATE("중복"), + SIZEERROR("size 0"), + TYPEERROR("형식오류"), + DONE("정상"); + + private final String desc; + + @Override + public String getId() { + return name(); + } + + @Override + public String getText() { + return desc; + } + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java index 16125bf5..7e026ed7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/mapsheet/MapSheetMngRepositoryImpl.java @@ -172,6 +172,30 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport MapSheetMngDto.@Valid ErrorSearchReq searchReq) { Pageable pageable = PageRequest.of(searchReq.getPage(), searchReq.getSize()); + + BooleanBuilder whereBuilder = new BooleanBuilder(); + whereBuilder.and(mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy())); + + // syncStateFilter 조건 추가 + if (searchReq.getSyncStateFilter() != null && !searchReq.getSyncStateFilter().isEmpty()) { + whereBuilder.and(mapSheetMngHstEntity.syncState.eq(searchReq.getSyncStateFilter())); + } else { + // 기본: 오류 상태만 조회 (NOFILE, NOTPAIR, DUPLICATE, SIZEERROR, TYPEERROR) + whereBuilder.and( + mapSheetMngHstEntity + .syncState + .eq("NOFILE") + .or(mapSheetMngHstEntity.syncState.eq("NOTPAIR")) + .or(mapSheetMngHstEntity.syncState.eq("DUPLICATE")) + .or(mapSheetMngHstEntity.syncState.eq("SIZEERROR")) + .or(mapSheetMngHstEntity.syncState.eq("TYPEERROR"))); + } + + // 검색어 조건 추가 + if (searchReq.getSearchValue() != null && !searchReq.getSearchValue().isEmpty()) { + whereBuilder.and(mapSheetErrorSearchValue(searchReq)); + } + List foundContent = queryFactory .select( @@ -188,16 +212,17 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport mapSheetMngHstEntity.mapSheetCodeSrc, Expressions.stringTemplate( "to_char({0}, 'YYYY-MM-DD')", mapSheetMngHstEntity.createdDate), - mapSheetMngHstEntity.dataState)) + mapSheetMngHstEntity.dataState, + mapSheetMngHstEntity.syncState, + mapSheetMngHstEntity.syncCheckState, + mapSheetMngHstEntity.syncCheckStrtDttm, + mapSheetMngHstEntity.syncCheckEndDttm)) .from(mapSheetMngHstEntity) .innerJoin(mapInkx5kEntity) .on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid)) .leftJoin(mapInkx50kEntity) .on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue())) - .where( - mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()), - // mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색 - mapSheetErrorSearchValue(searchReq)) + .where(whereBuilder) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .orderBy(mapSheetMngHstEntity.createdDate.desc()) @@ -211,10 +236,7 @@ public class MapSheetMngRepositoryImpl extends QuerydslRepositorySupport .on(mapSheetMngHstEntity.mapSheetCode.eq(mapInkx5kEntity.fid)) .leftJoin(mapInkx50kEntity) .on(mapInkx5kEntity.fidK50.eq(mapInkx50kEntity.fid.longValue())) - .where( - mapSheetMngHstEntity.mngYyyy.eq(searchReq.getMngYyyy()), - // mapSheetMngHstEntity.dataState.eq(MapSheetMngDto.DataState.FAIL), // 오류만 검색 - mapSheetErrorSearchValue(searchReq)) + .where(whereBuilder) .fetchOne(); return new PageImpl<>(foundContent, pageable, countQuery); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/MapSheetMngFileJobRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/MapSheetMngFileJobRepositoryImpl.java index 3029a053..116696e9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/MapSheetMngFileJobRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/scheduler/MapSheetMngFileJobRepositoryImpl.java @@ -108,14 +108,19 @@ public class MapSheetMngFileJobRepositoryImpl extends QuerydslRepositorySupport public void mngHstDataSyncStateUpdate(MapSheetMngDto.MngHstDto updateReq) { + ZonedDateTime now = ZonedDateTime.now(); + if (updateReq.getDataState().equals("DONE")) { long updateCount = queryFactory .update(mapSheetMngHstEntity) .set(mapSheetMngHstEntity.dataState, updateReq.getDataState()) - .set(mapSheetMngHstEntity.dataStateDttm, ZonedDateTime.now()) + .set(mapSheetMngHstEntity.dataStateDttm, now) .set(mapSheetMngHstEntity.syncState, updateReq.getSyncState()) - .set(mapSheetMngHstEntity.syncEndDttm, ZonedDateTime.now()) + .set(mapSheetMngHstEntity.syncEndDttm, now) + .set(mapSheetMngHstEntity.syncCheckState, "DONE") + .set(mapSheetMngHstEntity.syncCheckStrtDttm, now) + .set(mapSheetMngHstEntity.syncCheckEndDttm, now) .where(mapSheetMngHstEntity.hstUid.eq(updateReq.getHstUid())) .execute(); } else { @@ -123,10 +128,13 @@ public class MapSheetMngFileJobRepositoryImpl extends QuerydslRepositorySupport queryFactory .update(mapSheetMngHstEntity) .set(mapSheetMngHstEntity.dataState, updateReq.getDataState()) - .set(mapSheetMngHstEntity.dataStateDttm, ZonedDateTime.now()) + .set(mapSheetMngHstEntity.dataStateDttm, now) .set(mapSheetMngHstEntity.syncState, updateReq.getSyncState()) - .set(mapSheetMngHstEntity.syncStrtDttm, ZonedDateTime.now()) - .set(mapSheetMngHstEntity.syncEndDttm, ZonedDateTime.now()) + .set(mapSheetMngHstEntity.syncStrtDttm, now) + .set(mapSheetMngHstEntity.syncEndDttm, now) + .set(mapSheetMngHstEntity.syncCheckState, "PROCESSING") + .set(mapSheetMngHstEntity.syncCheckStrtDttm, now) + .set(mapSheetMngHstEntity.syncCheckEndDttm, now) .where(mapSheetMngHstEntity.hstUid.eq(updateReq.getHstUid())) .execute(); } diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index b330d1d8..e989c2b4 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,7 +1,7 @@ spring: config: activate: - on-profile: dev + on-profile: prod jpa: show-sql: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 289b414c..6b592c27 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: application: name: kamco-change-detection-api profiles: - active: dev # 사용할 프로파일 지정 (ex. dev, prod, test) + active: local # 사용할 프로파일 지정 (ex. dev, prod, test) datasource: driver-class-name: org.postgresql.Driver From 5a40c29eecffc68b1f0c750985f9f5aa834e5404 Mon Sep 17 00:00:00 2001 From: DanielLee Date: Tue, 16 Dec 2025 15:37:05 +0900 Subject: [PATCH 4/7] change to dev setting --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6b592c27..289b414c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: application: name: kamco-change-detection-api profiles: - active: local # 사용할 프로파일 지정 (ex. dev, prod, test) + active: dev # 사용할 프로파일 지정 (ex. dev, prod, test) datasource: driver-class-name: org.postgresql.Driver From 9f1e4de18c296dee8c0f7d740e5956e07887ddef Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 16 Dec 2025 16:19:48 +0900 Subject: [PATCH 5/7] =?UTF-8?q?userId=20->=20=EC=82=AC=EB=B2=88=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/CustomAuthenticationProvider.java | 6 +++--- .../cd/kamcoback/members/dto/SignInRequest.java | 2 +- .../postgres/core/MembersCoreService.java | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java b/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java index 333b673a..9698b1e1 100644 --- a/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java +++ b/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java @@ -26,9 +26,9 @@ public class CustomAuthenticationProvider implements AuthenticationProvider { // 유저 조회 MemberEntity member = - membersRepository - .findByUserId(username) - .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); + membersRepository + .findByEmployeeNo(username) + .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); // 미사용 상태 if (member.getStatus().equals(StatusType.INACTIVE.getId())) { diff --git a/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java b/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java index 1ad47961..25f59beb 100644 --- a/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java +++ b/src/main/java/com/kamco/cd/kamcoback/members/dto/SignInRequest.java @@ -11,7 +11,7 @@ import lombok.ToString; @ToString(exclude = "password") public class SignInRequest { - @Schema(description = "사용자 ID", example = "admin2") + @Schema(description = "사용자 ID", example = "1234567") private String username; @Schema(description = "비밀번호", example = "Admin2!@#") diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java index b59f71fc..df0bcdc9 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java @@ -67,7 +67,7 @@ public class MembersCoreService { */ public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); + membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); if (StringUtils.isNotBlank(updateReq.getName())) { memberEntity.setName(updateReq.getName()); @@ -85,7 +85,7 @@ public class MembersCoreService { } String password = - CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); + CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); memberEntity.setStatus(StatusType.PENDING.getId()); memberEntity.setLoginFailCount(0); @@ -103,7 +103,7 @@ public class MembersCoreService { */ public void resetPassword(String id, MembersDto.InitReq initReq) { MemberEntity memberEntity = - membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); + membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); // 기존 패스워드 확인 if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) { @@ -111,7 +111,7 @@ public class MembersCoreService { } String password = - CommonStringUtils.hashPassword(initReq.getNewPassword(), memberEntity.getEmployeeNo()); + CommonStringUtils.hashPassword(initReq.getNewPassword(), memberEntity.getEmployeeNo()); memberEntity.setPassword(password); memberEntity.setStatus(StatusType.ACTIVE.getId()); @@ -141,9 +141,9 @@ public class MembersCoreService { */ public String getUserStatus(SignInRequest request) { MemberEntity memberEntity = - membersRepository - .findByUserId(request.getUsername()) - .orElseThrow(MemberNotFoundException::new); + membersRepository + .findByEmployeeNo(request.getUsername()) + .orElseThrow(MemberNotFoundException::new); return memberEntity.getStatus(); } @@ -154,7 +154,7 @@ public class MembersCoreService { */ public void saveLogin(UUID uuid) { MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); if (memberEntity.getFirstLoginDttm() == null) { memberEntity.setFirstLoginDttm(ZonedDateTime.now()); From 827c056d02ccacd0ac8e26fb8ac3f209149e5b2f Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 16 Dec 2025 16:23:46 +0900 Subject: [PATCH 6/7] =?UTF-8?q?spotlessApply=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/CustomAuthenticationProvider.java | 6 +++--- .../postgres/core/MembersCoreService.java | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java b/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java index 9698b1e1..df3c70cf 100644 --- a/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java +++ b/src/main/java/com/kamco/cd/kamcoback/auth/CustomAuthenticationProvider.java @@ -26,9 +26,9 @@ public class CustomAuthenticationProvider implements AuthenticationProvider { // 유저 조회 MemberEntity member = - membersRepository - .findByEmployeeNo(username) - .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); + membersRepository + .findByEmployeeNo(username) + .orElseThrow(() -> new CustomApiException(AuthErrorCode.LOGIN_ID_NOT_FOUND)); // 미사용 상태 if (member.getStatus().equals(StatusType.INACTIVE.getId())) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java index df0bcdc9..cb040aa6 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/MembersCoreService.java @@ -67,7 +67,7 @@ public class MembersCoreService { */ public void updateMembers(UUID uuid, MembersDto.UpdateReq updateReq) { MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); + membersRepository.findByUUID(uuid).orElseThrow(MemberNotFoundException::new); if (StringUtils.isNotBlank(updateReq.getName())) { memberEntity.setName(updateReq.getName()); @@ -85,7 +85,7 @@ public class MembersCoreService { } String password = - CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); + CommonStringUtils.hashPassword(updateReq.getPassword(), memberEntity.getEmployeeNo()); memberEntity.setStatus(StatusType.PENDING.getId()); memberEntity.setLoginFailCount(0); @@ -103,7 +103,7 @@ public class MembersCoreService { */ public void resetPassword(String id, MembersDto.InitReq initReq) { MemberEntity memberEntity = - membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); + membersRepository.findByEmployeeNo(id).orElseThrow(() -> new MemberNotFoundException()); // 기존 패스워드 확인 if (!BCrypt.checkpw(initReq.getOldPassword(), memberEntity.getPassword())) { @@ -111,7 +111,7 @@ public class MembersCoreService { } String password = - CommonStringUtils.hashPassword(initReq.getNewPassword(), memberEntity.getEmployeeNo()); + CommonStringUtils.hashPassword(initReq.getNewPassword(), memberEntity.getEmployeeNo()); memberEntity.setPassword(password); memberEntity.setStatus(StatusType.ACTIVE.getId()); @@ -141,9 +141,9 @@ public class MembersCoreService { */ public String getUserStatus(SignInRequest request) { MemberEntity memberEntity = - membersRepository - .findByEmployeeNo(request.getUsername()) - .orElseThrow(MemberNotFoundException::new); + membersRepository + .findByEmployeeNo(request.getUsername()) + .orElseThrow(MemberNotFoundException::new); return memberEntity.getStatus(); } @@ -154,7 +154,7 @@ public class MembersCoreService { */ public void saveLogin(UUID uuid) { MemberEntity memberEntity = - membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); + membersRepository.findByUUID(uuid).orElseThrow(() -> new MemberNotFoundException()); if (memberEntity.getFirstLoginDttm() == null) { memberEntity.setFirstLoginDttm(ZonedDateTime.now()); From 9f0c55fd0ce670985d03ebbc536a4e829f492be0 Mon Sep 17 00:00:00 2001 From: DanielLee <198891672+sanghyeonhd@users.noreply.github.com> Date: Tue, 16 Dec 2025 18:05:59 +0900 Subject: [PATCH 7/7] Gdal Support mac,linux,win os --- .../kamcoback/common/utils/FIleChecker.java | 142 +++++++++++++++--- .../config/GlobalExceptionHandler.java | 22 ++- .../kamcoback/config/api/ApiResponseDto.java | 1 + .../MapSheetMngFileCheckerService.java | 100 +++++++++++- src/main/resources/application-dev.yml | 8 +- src/main/resources/application-local.yml | 16 +- src/main/resources/application.yml | 2 +- .../common/utils/FIleCheckerTest.java | 110 ++++++++++++++ 8 files changed, 359 insertions(+), 42 deletions(-) create mode 100644 src/test/java/com/kamco/cd/kamcoback/common/utils/FIleCheckerTest.java diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java index f6d53588..34f34152 100644 --- a/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/FIleChecker.java @@ -152,55 +152,97 @@ public class FIleChecker { 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 command = new ArrayList<>(); - // 윈도우용 - command.add("cmd.exe"); // 윈도우 명령 프롬프트 실행 - command.add("/c"); // 명령어를 수행하고 종료한다는 옵션 - command.add("gdalinfo"); - command.add(filePath); - command.add("|"); - command.add("findstr"); - command.add("/i"); - command.add("Geo"); - - /* - command.add("sh"); // 리눅스,맥 명령 프롬프트 실행 - command.add("-c"); // 명령어를 수행하고 종료한다는 옵션 - command.add("gdalinfo"); - command.add(filePath); - command.add("|"); - command.add("grep"); - command.add("-i"); - command.add("Geo"); - */ + if (isWindows) { + // 윈도우용 + command.add("cmd.exe"); + command.add("/c"); + command.add(gdalinfoPath + " \"" + filePath + "\" | findstr /i 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 { - Process process = processBuilder.start(); + System.out.println("gdalinfo 명령어 실행 시작: " + filePath); + process = processBuilder.start(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { - System.out.println("line == " + line); + System.out.println("gdalinfo 출력: " + line); if (line.contains("Driver: GTiff/GeoTIFF")) { hasDriver = true; break; } } - process.waitFor(); + int exitCode = process.waitFor(); + System.out.println("gdalinfo 종료 코드: " + exitCode); - } catch (Exception e) { + // 프로세스가 정상 종료되지 않았고 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; @@ -349,4 +391,54 @@ public class FIleChecker { return nameComparator; } } + + /** + * gdalinfo 실행 파일 경로를 찾습니다. + * + * @return gdalinfo 경로 (찾지 못하면 null) + */ + 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; + } + + /** + * 명령어가 사용 가능한지 확인합니다. + * + * @param command 명령어 경로 + * @return 사용 가능 여부 + */ + 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; + } + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java index 403de33b..a0cf0b7d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java @@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; @Slf4j @Order(value = 1) @@ -396,7 +397,8 @@ public class GlobalExceptionHandler { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public ApiResponseDto handlerException(Exception e, HttpServletRequest request) { - log.warn("[Exception] resource :{} ", e.getMessage()); + log.error("[Exception] resource: {}, message: {}", request.getRequestURI(), e.getMessage()); + log.error("Exception stacktrace: ", e); String codeName = "INTERNAL_SERVER_ERROR"; ErrorLogEntity errorLog = @@ -504,4 +506,22 @@ public class GlobalExceptionHandler { // return new ResponseEntity<>(body, status); } + + @ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE) + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ApiResponseDto handleMaxUploadSizeExceeded( + MaxUploadSizeExceededException e, HttpServletRequest request) { + log.warn("[MaxUploadSizeExceededException] resource :{} ", e.getMessage()); + ApiResponseCode code = ApiResponseCode.PAYLOAD_TOO_LARGE; + ErrorLogEntity errorLog = + saveErrorLogData( + request, + code, + HttpStatus.PAYLOAD_TOO_LARGE, + ErrorLogDto.LogErrorLevel.WARNING, + e.getStackTrace()); + + return ApiResponseDto.createException( + code, code.getText(), HttpStatus.PAYLOAD_TOO_LARGE, errorLog.getId()); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java index ca53fab6..fb5beaa0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java @@ -172,6 +172,7 @@ public class ApiResponseDto { "You can only reset your password within 24 hours from when the email was sent.\n" + "To reset your password again, please submit a new request through \"Forgot" + " Password.\""), + PAYLOAD_TOO_LARGE("업로드 용량 제한을 초과했습니다."), ; // @formatter:on private final String message; diff --git a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java index 0659c8df..7c175d70 100644 --- a/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java +++ b/src/main/java/com/kamco/cd/kamcoback/mapsheet/service/MapSheetMngFileCheckerService.java @@ -34,11 +34,13 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FilenameUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +@Slf4j @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -318,11 +320,34 @@ public class MapSheetMngFileCheckerService { @Transactional public String uploadFile(MultipartFile file, String targetPath, boolean overwrite, Long hstUid) { try { + // 파일 유효성 검증 + if (file == null || file.isEmpty()) { + throw new ValidationException("업로드 파일이 비어있습니다."); + } + if (file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) { + throw new ValidationException("파일명이 유효하지 않습니다."); + } + Path path = Paths.get(targetPath); - if (Files.isDirectory(path)) { + + // targetPath가 존재하지 않으면 파일 경로로 가정하고 부모 디렉토리 생성 + if (!Files.exists(path)) { + // 경로가 확장자로 끝나면 파일로 간주 + if (targetPath.matches(".*\\.[a-zA-Z]{3,4}$")) { + if (path.getParent() != null) { + Files.createDirectories(path.getParent()); + } + } else { + // 확장자가 없으면 디렉토리로 간주 + Files.createDirectories(path); + path = path.resolve(file.getOriginalFilename()); + } + } else if (Files.isDirectory(path)) { path = path.resolve(file.getOriginalFilename()); } - if (path.getParent() != null) { + + // 최종 파일의 부모 디렉토리 생성 + if (path.getParent() != null && !Files.exists(path.getParent())) { Files.createDirectories(path.getParent()); } @@ -403,8 +428,13 @@ public class MapSheetMngFileCheckerService { saveUploadMeta(path, hstUid); return "업로드 성공"; + } catch (ValidationException | DuplicateFileException e) { + // 비즈니스 예외는 그대로 던짐 + throw e; } catch (IOException e) { - throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage()); + throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage(), e); + } catch (Exception e) { + throw new IllegalArgumentException("파일 업로드 처리 중 오류 발생: " + e.getMessage(), e); } } @@ -416,8 +446,45 @@ public class MapSheetMngFileCheckerService { boolean overwrite, Long hstUid) { try { + log.info( + "uploadPair 시작 - targetPath: {}, overwrite: {}, hstUid: {}", + targetPath, + overwrite, + hstUid); + + // 파일 유효성 검증 + if (tfwFile == null || tfwFile.isEmpty()) { + throw new ValidationException("TFW 파일이 비어있습니다."); + } + if (tifFile == null || tifFile.isEmpty()) { + throw new ValidationException("TIF 파일이 비어있습니다."); + } + if (tfwFile.getOriginalFilename() == null || tfwFile.getOriginalFilename().isEmpty()) { + throw new ValidationException("TFW 파일명이 유효하지 않습니다."); + } + if (tifFile.getOriginalFilename() == null || tifFile.getOriginalFilename().isEmpty()) { + throw new ValidationException("TIF 파일명이 유효하지 않습니다."); + } + + log.info( + "파일명 - TFW: {}, TIF: {}", tfwFile.getOriginalFilename(), tifFile.getOriginalFilename()); + Path basePath = Paths.get(targetPath); + + // targetPath가 존재하지 않으면 디렉토리로 생성 + if (!Files.exists(basePath)) { + log.info("대상 경로가 존재하지 않아 디렉토리 생성: {}", basePath); + Files.createDirectories(basePath); + } + + // 파일인 경우 부모 디렉토리를 basePath로 사용 + if (Files.isRegularFile(basePath)) { + log.info("대상 경로가 파일이므로 부모 디렉토리 사용"); + basePath = basePath.getParent(); + } + if (Files.isDirectory(basePath)) { + log.info("디렉토리 확인됨: {}", basePath); // 디렉토리인 경우 파일명 기준으로 경로 생성 Path tfwPath = basePath.resolve(tfwFile.getOriginalFilename()); Path tifPath = basePath.resolve(tifFile.getOriginalFilename()); @@ -427,9 +494,9 @@ public class MapSheetMngFileCheckerService { if (!tfwBase.equalsIgnoreCase(tifBase)) { throw new ValidationException("TFW/TIF 파일명이 동일한 베이스가 아닙니다."); } - // 디렉토리 생성 - if (tfwPath.getParent() != null) Files.createDirectories(tfwPath.getParent()); - if (tifPath.getParent() != null) Files.createDirectories(tifPath.getParent()); + // 디렉토리는 이미 생성되었으므로 추가 생성 불필요 + // if (tfwPath.getParent() != null) Files.createDirectories(tfwPath.getParent()); + // if (tifPath.getParent() != null) Files.createDirectories(tifPath.getParent()); // DB 중복 체크 및 overwrite 처리 (각 파일별) String parentPathStr = basePath.toString(); @@ -450,32 +517,51 @@ public class MapSheetMngFileCheckerService { } // 파일 저장 + log.info("파일 저장 시작 - TFW: {}, TIF: {}", tfwPath, tifPath); tfwFile.transferTo(tfwPath.toFile()); tifFile.transferTo(tifPath.toFile()); + log.info("파일 저장 완료"); // 검증 + log.info("TFW 파일 검증 시작: {}", tfwPath); boolean tfwOk = FIleChecker.checkTfw(tfwPath.toString()); if (!tfwOk) { + log.warn("TFW 파일 검증 실패: {}", tfwName); Files.deleteIfExists(tfwPath); Files.deleteIfExists(tifPath); throw new ValidationException("유효하지 않은 TFW 파일입니다 (6줄 숫자 형식 검증 실패): " + tfwName); } + log.info("TFW 파일 검증 성공"); + + log.info("TIF 파일 검증 시작: {}", tifPath); boolean isValidTif = FIleChecker.cmmndGdalInfo(tifPath.toString()); if (!isValidTif) { + log.warn("TIF 파일 검증 실패: {}", tifName); Files.deleteIfExists(tfwPath); Files.deleteIfExists(tifPath); throw new ValidationException("유효하지 않은 TIF 파일입니다 (GDAL 검증 실패): " + tifName); } + log.info("TIF 파일 검증 성공"); // 메타 저장 (두 파일 각각 저장) + log.info("메타 데이터 저장 시작"); saveUploadMeta(tfwPath, hstUid); saveUploadMeta(tifPath, hstUid); + log.info("메타 데이터 저장 완료"); return "TFW/TIF 페어 업로드 성공"; } else { throw new ValidationException("targetPath는 디렉토리여야 합니다."); } + } catch (ValidationException | DuplicateFileException e) { + // 비즈니스 예외는 그대로 던짐 + log.warn("업로드 비즈니스 예외 발생: {}", e.getMessage()); + throw e; } catch (IOException e) { - throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage()); + log.error("파일 I/O 처리 실패: {}", e.getMessage(), e); + throw new IllegalArgumentException("파일 I/O 처리 실패: " + e.getMessage(), e); + } catch (Exception e) { + log.error("파일 업로드 처리 중 예상치 못한 오류 발생: {}", e.getMessage(), e); + throw new IllegalArgumentException("파일 업로드 처리 중 오류 발생: " + e.getMessage(), e); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index bf8026a7..6c7b17f0 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -38,13 +38,14 @@ spring: servlet: multipart: enabled: true - max-file-size: 1024MB - max-request-size: 2048MB + max-file-size: 4GB + max-request-size: 4GB file-size-threshold: 10MB server: tomcat: - max-swallow-size: 2097152000 # 약 2GB + max-swallow-size: 4GB + max-http-form-post-size: 4GB jwt: secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b" @@ -73,4 +74,3 @@ mapsheet: upload: skipGdalValidation: true - diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 8ab311f2..a5395888 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -29,6 +29,18 @@ spring: port: 6379 password: 1234 + servlet: + multipart: + enabled: true + max-file-size: 4GB + max-request-size: 4GB + file-size-threshold: 10MB + +server: + tomcat: + max-swallow-size: 4GB + max-http-form-post-size: 4GB + jwt: secret: "kamco_token_9b71e778-19a3-4c1d-97bf-2d687de17d5b" access-token-validity-in-ms: 86400000 # 1일 @@ -41,7 +53,3 @@ token: springdoc: swagger-ui: persist-authorization: true # 스웨거 새로고침해도 토큰 유지, 로컬스토리지에 저장 - - - - diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 289b414c..6b592c27 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -5,7 +5,7 @@ spring: application: name: kamco-change-detection-api profiles: - active: dev # 사용할 프로파일 지정 (ex. dev, prod, test) + active: local # 사용할 프로파일 지정 (ex. dev, prod, test) datasource: driver-class-name: org.postgresql.Driver diff --git a/src/test/java/com/kamco/cd/kamcoback/common/utils/FIleCheckerTest.java b/src/test/java/com/kamco/cd/kamcoback/common/utils/FIleCheckerTest.java new file mode 100644 index 00000000..d15bfd1b --- /dev/null +++ b/src/test/java/com/kamco/cd/kamcoback/common/utils/FIleCheckerTest.java @@ -0,0 +1,110 @@ +package com.kamco.cd.kamcoback.common.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.io.TempDir; + +class FIleCheckerTest { + + @Test + void testOsDetection() { + // 운영체제 감지 테스트 + String osName = System.getProperty("os.name").toLowerCase(); + System.out.println("현재 운영체제: " + osName); + + boolean isWindows = osName.contains("win"); + boolean isMac = osName.contains("mac"); + boolean isUnix = osName.contains("nix") || osName.contains("nux") || osName.contains("aix"); + + // 최소한 하나의 OS는 감지되어야 함 + assertTrue(isWindows || isMac || isUnix, "지원되는 운영체제를 감지해야 합니다"); + + System.out.println("Windows: " + isWindows); + System.out.println("Mac: " + isMac); + System.out.println("Unix/Linux: " + isUnix); + } + + @Test + void testCheckTfw_ValidFile(@TempDir File tempDir) throws IOException { + // 임시 유효한 TFW 파일 생성 + File tfwFile = new File(tempDir, "test.tfw"); + try (FileWriter writer = new FileWriter(tfwFile)) { + writer.write("0.25\n"); // pixel size x + writer.write("0.0\n"); // rotation x + writer.write("0.0\n"); // rotation y + writer.write("-0.25\n"); // pixel size y + writer.write("127.5\n"); // upper left x + writer.write("37.5\n"); // upper left y + } + + boolean result = FIleChecker.checkTfw(tfwFile.getAbsolutePath()); + assertTrue(result, "유효한 TFW 파일은 true를 반환해야 합니다"); + } + + @Test + void testCheckTfw_InvalidFile(@TempDir File tempDir) throws IOException { + // 잘못된 TFW 파일 생성 (5줄만) + File tfwFile = new File(tempDir, "invalid.tfw"); + try (FileWriter writer = new FileWriter(tfwFile)) { + writer.write("0.25\n"); + writer.write("0.0\n"); + writer.write("0.0\n"); + writer.write("-0.25\n"); + writer.write("127.5\n"); + // 6번째 줄 누락 + } + + boolean result = FIleChecker.checkTfw(tfwFile.getAbsolutePath()); + assertFalse(result, "잘못된 TFW 파일은 false를 반환해야 합니다"); + } + + @Test + void testCheckTfw_NonExistentFile() { + boolean result = FIleChecker.checkTfw("/non/existent/path/file.tfw"); + assertFalse(result, "존재하지 않는 파일은 false를 반환해야 합니다"); + } + + @Test + void testCmmndGdalInfo_MethodExists() throws NoSuchMethodException { + // cmmndGdalInfo 메서드가 존재하는지 확인 + assertNotNull(FIleChecker.class.getMethod("cmmndGdalInfo", String.class)); + System.out.println("cmmndGdalInfo 메서드 존재 확인"); + } + + @Test + void testCmmndGdalInfo_NonExistentFile() { + // 존재하지 않는 파일은 false를 반환해야 함 + boolean result = FIleChecker.cmmndGdalInfo("/non/existent/path/file.tif"); + assertFalse(result, "존재하지 않는 파일은 false를 반환해야 합니다"); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + void testWindowsCommand() { + System.out.println("Windows OS 감지됨 - GDAL 명령어 형식 확인"); + String osName = System.getProperty("os.name").toLowerCase(); + assertTrue(osName.contains("win")); + } + + @Test + @EnabledOnOs(OS.MAC) + void testMacCommand() { + System.out.println("Mac OS 감지됨 - GDAL 명령어 형식 확인"); + String osName = System.getProperty("os.name").toLowerCase(); + assertTrue(osName.contains("mac")); + } + + @Test + @EnabledOnOs(OS.LINUX) + void testLinuxCommand() { + System.out.println("Linux OS 감지됨 - GDAL 명령어 형식 확인"); + String osName = System.getProperty("os.name").toLowerCase(); + assertTrue(osName.contains("nux") || osName.contains("nix")); + } +}