This commit is contained in:
2026-02-02 15:03:50 +09:00
parent 0228ece712
commit c8167485d1
158 changed files with 7989 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
package com.kamco.cd.kamcoback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class KamcoBackApplication {
public static void main(String[] args) {
SpringApplication.run(KamcoBackApplication.class, args);
}
}

View File

@@ -0,0 +1,18 @@
package com.kamco.cd.kamcoback.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuerydslConfig {
@PersistenceContext private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}

View File

@@ -0,0 +1,46 @@
package com.kamco.cd.kamcoback.controller;
import com.kamco.cd.kamcoback.dto.ApiResponseDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "스캐쥴러 API", description = "스캐쥴러 API")
@RestController
@RequiredArgsConstructor
@RequestMapping({"/api/job"})
public class MapSheetMngFileJobApiController {
private final MapSheetMngFileJobController mapSheetMngFileJobController;
@Operation(summary = "영상관리 파일 싱크 스캐쥴러 Start/Stop", description = "영상관리 파일 싱크 스캐쥴러 Start/Stop API")
@ApiResponses(
value = {
@ApiResponse(
responseCode = "200",
description = "조회 성공",
content =
@Content(
mediaType = "application/json",
schema = @Schema(implementation = String.class))),
@ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content),
@ApiResponse(responseCode = "500", description = "서버 오류", content = @Content)
})
@PutMapping("/mng-sync-job")
public ApiResponseDto<String> mngSyncOnOff(
@RequestParam boolean jobStart, @RequestParam int pageSize) {
mapSheetMngFileJobController.setSchedulerEnabled(jobStart);
mapSheetMngFileJobController.setMngSyncPageSize(pageSize);
return ApiResponseDto.createOK("OK");
}
}

View File

@@ -0,0 +1,136 @@
package com.kamco.cd.kamcoback.controller;
import com.kamco.cd.kamcoback.service.MapSheetMngFileJobService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class MapSheetMngFileJobController {
private final MapSheetMngFileJobService mapSheetMngFileJobService;
// 현재 상태 확인용 Getter
@Getter private boolean isSchedulerEnabled = true;
@Getter private boolean isFileSyncSchedulerEnabled = false;
@Getter private int mngSyncPageSize = 20;
// 파일싱크 진행여부 확인하기
@Scheduled(fixedDelay = 1000 * 10)
public void checkMngFileSync() {
if (!isSchedulerEnabled) return;
Integer mng = 0;
// isFileSyncSchedulerEnabled = false;
if (mapSheetMngFileJobService.checkMngFileSync() != null) {
mng = mapSheetMngFileJobService.checkMngFileSync();
this.isFileSyncSchedulerEnabled = true;
System.out.println(
"MngFileSyncJob ON --> mngYyyy : "
+ mng
+ ", currentTime : "
+ System.currentTimeMillis());
} else {
this.isFileSyncSchedulerEnabled = false;
System.out.println(
"MngFileSyncJob OFF --> mngYyyy : "
+ mng
+ ", currentTime : "
+ System.currentTimeMillis());
}
}
@Scheduled(fixedDelay = 1000 * 10)
public void mngFileSyncJob00() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 00 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(0, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob01() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 01 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(1, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob02() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 02 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(2, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob03() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 03 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(3, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob04() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 04 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(4, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob05() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 05 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(5, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob06() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 06 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(6, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob07() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 07 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(7, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob08() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 08 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(8, mngSyncPageSize);
}
@Scheduled(fixedDelay = 1000 * 5)
public void mngFileSyncJob09() {
if (!isSchedulerEnabled || !isFileSyncSchedulerEnabled) return;
System.out.println("mngFileSyncJob 09 Processing currentTime : " + System.currentTimeMillis());
mapSheetMngFileJobService.checkMapSheetFileProcess(9, mngSyncPageSize);
}
// 3. 외부에서 플래그를 변경할 수 있는 Setter 메서드
public void setSchedulerEnabled(boolean enabled) {
this.isSchedulerEnabled = enabled;
this.isFileSyncSchedulerEnabled = false;
System.out.println("스케줄러 동작 상태 변경됨: " + (enabled ? "ON" : "OFF"));
}
public void setMngSyncPageSize(int pageSize) {
this.mngSyncPageSize = pageSize;
System.out.println("스케줄러 처리 개수 변경됨: " + pageSize);
}
}

View File

@@ -0,0 +1,223 @@
package com.kamco.cd.kamcoback.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kamco.cd.kamcoback.inferface.EnumType;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.http.HttpStatus;
@Getter
@ToString
public class ApiResponseDto<T> {
private T data;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Error error;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T errorData;
@JsonIgnore private HttpStatus httpStatus;
@JsonIgnore private Long errorLogUid;
public ApiResponseDto(T data) {
this.data = data;
}
private ApiResponseDto(T data, HttpStatus httpStatus) {
this.data = data;
this.httpStatus = httpStatus;
}
public ApiResponseDto(ApiResponseCode code) {
this.error = new Error(code.getId(), code.getMessage());
}
public ApiResponseDto(ApiResponseCode code, String message) {
this.error = new Error(code.getId(), message);
}
public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) {
this.error = new Error(code.getId(), message);
this.httpStatus = httpStatus;
}
public ApiResponseDto(
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
this.error = new Error(code.getId(), message);
this.httpStatus = httpStatus;
this.errorLogUid = errorLogUid;
}
public ApiResponseDto(ApiResponseCode code, String message, T errorData) {
this.error = new Error(code.getId(), message);
this.errorData = errorData;
}
// HTTP 상태 코드가 내장된 ApiResponseDto 반환 메서드들
public static <T> ApiResponseDto<T> createOK(T data) {
return new ApiResponseDto<>(data, HttpStatus.CREATED);
}
public static <T> ApiResponseDto<T> ok(T data) {
return new ApiResponseDto<>(data, HttpStatus.OK);
}
public static <T> ApiResponseDto<ResponseObj> okObject(ResponseObj data) {
if (data.getCode().equals(ApiResponseCode.OK)) {
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
} else {
return new ApiResponseDto<>(data.getCode(), data.getMessage(), HttpStatus.CONFLICT);
}
}
public static <T> ApiResponseDto<T> deleteOk(T data) {
return new ApiResponseDto<>(data, HttpStatus.NO_CONTENT);
}
public static ApiResponseDto<String> createException(ApiResponseCode code) {
return new ApiResponseDto<>(code);
}
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
return new ApiResponseDto<>(code, message);
}
public static ApiResponseDto<String> createException(
ApiResponseCode code, String message, HttpStatus httpStatus) {
return new ApiResponseDto<>(code, message, httpStatus);
}
public static ApiResponseDto<String> createException(
ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) {
return new ApiResponseDto<>(code, message, httpStatus, errorLogUid);
}
public static <T> ApiResponseDto<T> createException(
ApiResponseCode code, String message, T data) {
return new ApiResponseDto<>(code, message, data);
}
@Getter
public static class Error {
private final String code;
private final String message;
public Error(String code, String message) {
this.code = code;
this.message = message;
}
}
/** Error가 아닌 Business상 성공이거나 실패인 경우, 메세지 함께 전달하기 위한 object */
@Getter
public static class ResponseObj {
private final ApiResponseCode code;
private final String message;
public ResponseObj(ApiResponseCode code, String message) {
this.code = code;
this.message = message;
}
}
@Getter
@RequiredArgsConstructor
public enum ApiResponseCode implements EnumType {
// @formatter:off
OK("요청이 성공하였습니다."),
BAD_REQUEST("요청 파라미터가 잘못되었습니다."),
BAD_GATEWAY("네트워크 상태가 불안정합니다."),
ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."),
NOT_FOUND_MAP("지도를 찾을 수 없습니다."),
UNAUTHORIZED("권한이 없습니다."),
CONFLICT("이미 등록된 컨텐츠입니다."),
NOT_FOUND("Resource를 찾을 수 없습니다."),
NOT_FOUND_DATA("데이터를 찾을 수 없습니다."),
NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."),
FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."),
TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."),
UNAUTHENTICATED("인증에 실패하였습니다."),
INVALID_TOKEN("잘못된 토큰입니다."),
EXPIRED_TOKEN("만료된 토큰입니다."),
INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."),
FORBIDDEN("권한을 확인해주세요."),
INVALID_PASSWORD("잘못된 비밀번호 입니다."),
NOT_FOUND_CAR_IN("입차정보가 없습니다."),
WRONG_STATUS("잘못된 상태입니다."),
FAIL_VERIFICATION("인증에 실패하였습니다."),
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
WRONG_PASSWORD("잘못된 패스워드입니다."),
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
DUPLICATE_DATA("이미 등록되어 있습니다."),
DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."),
FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."),
DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."),
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
UNPROCESSABLE_ENTITY("이 데이터는 삭제할 수 없습니다."),
LOGIN_ID_NOT_FOUND("아이디를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_MISMATCH("비밀번호를 잘못 입력하셨습니다."),
LOGIN_PASSWORD_EXCEEDED("비밀번호 오류 횟수를 초과하여 이용하실 수 없습니다.\n로그인 오류에 대해 관리자에게 문의하시기 바랍니다."),
INACTIVE_ID("사용할 수 없는 계정입니다."),
INVALID_EMAIL_TOKEN(
"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("업로드 용량 제한을 초과했습니다."),
NOT_FOUND_TARGET_YEAR("기준년도 도엽을 찾을 수 없습니다."),
NOT_FOUND_COMPARE_YEAR("비교년도 도엽을 찾을 수 없습니다."),
FAIL_SAVE_MAP_SHEET("도엽 저장 중 오류가 발생했습니다."),
FAIL_CREATE_MAP_SHEET_FILE("도엽 설정파일 생성 중 오류가 발생했습니다."),
;
// @formatter:on
private final String message;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return message;
}
public static ApiResponseCode getCode(String name) {
return ApiResponseCode.valueOf(name.toUpperCase());
}
public static String getMessage(String name) {
return ApiResponseCode.valueOf(name.toUpperCase()).getText();
}
public static ApiResponseCode from(String codeName, HttpStatus status) {
if (codeName != null && !codeName.isBlank()) {
try {
return ApiResponseCode.valueOf(codeName.toUpperCase());
} catch (IllegalArgumentException ignore) {
// fallback
}
}
if (status != null) {
try {
return ApiResponseCode.valueOf(status.name());
} catch (IllegalArgumentException ignore) {
// fallback
}
}
return INTERNAL_SERVER_ERROR;
}
}
}

View File

@@ -0,0 +1,159 @@
package com.kamco.cd.kamcoback.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
public class FileDto {
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFoldersDto {
@Schema(description = "디렉토리경로(ROOT:/app/original-images)", example = "")
@NotNull
private String dirPath;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFilesDto {
@Schema(description = "디렉토리경로", example = "D:\\kamco\\2022\\캠코_2021_2022_34602060_D1")
@NotNull
private String dirPath;
@Schema(description = "전체(*), cpg,dbf,geojson등", example = "*")
@NotNull
private String extension;
@Schema(description = "전체(*), 3878687.tif", example = "*")
@NotNull
private String fileNm;
@Schema(description = "파일명(name), 최종수정일(date)", example = "name")
@NotNull
private String sortType;
@Schema(description = "파일시작위치", example = "1")
@NotNull
private Integer startPos = 0;
@Schema(description = "파일종료위치", example = "100")
@NotNull
private Integer endPos = 100;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class SrchFilesDepthDto extends SrchFilesDto {
@Schema(description = "최대폴더Depth", example = "5")
@NotNull
private Integer maxDepth;
}
@Schema(name = "FolderDto", description = "폴더 정보")
@Getter
public static class FolderDto {
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 FolderDto(
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 = "FoldersDto", description = "폴더목록 정보")
@Getter
public static class FoldersDto {
private final String dirPath;
private final int folderTotCnt;
private final int folderErrTotCnt;
private final List<FolderDto> folders;
public FoldersDto(
String dirPath, int folderTotCnt, int folderErrTotCnt, List<FolderDto> folders) {
this.dirPath = dirPath;
this.folderTotCnt = folderTotCnt;
this.folderErrTotCnt = folderErrTotCnt;
this.folders = folders;
}
}
@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;
}
}
@Schema(name = "FilesDto", description = "파일 목록 정보")
@Getter
public static class FilesDto {
private final String dirPath;
private final int fileTotCnt;
private final long fileTotSize;
private final List<Basic> files;
public FilesDto(String dirPath, int fileTotCnt, long fileTotSize, List<Basic> files) {
this.dirPath = dirPath;
this.fileTotCnt = fileTotCnt;
this.fileTotSize = fileTotSize;
this.files = files;
}
}
}

View File

@@ -0,0 +1,467 @@
package com.kamco.cd.kamcoback.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kamco.cd.kamcoback.enums.Enums;
import com.kamco.cd.kamcoback.enums.MngStateType;
import com.kamco.cd.kamcoback.enums.SyncStateType;
import com.kamco.cd.kamcoback.inferface.EnumType;
import com.kamco.cd.kamcoback.inferface.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public class MapSheetDto {
@Schema(name = "MngSearchReq", description = "영상관리 검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngSearchReq {
// 페이징 파라미터
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
private int page = 0;
@Schema(description = "페이지 크기", example = "20")
private int size = 20;
@Schema(description = "년도", example = "2025")
private Integer mngYyyy;
public Pageable toPageable() {
return PageRequest.of(page, size);
}
}
@Schema(name = "MngAddReq", description = "영상관리 생성 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class AddReq {
@Schema(description = "관리년도", example = "2022")
private int mngYyyy;
@Schema(description = "선택폴더경로", example = "D:\\app\\original-images\\2022")
private String mngPath;
@JsonIgnore private Long createdUid;
}
@Schema(name = "DeleteFileReq", description = "파일 삭제 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class DeleteFileReq {
@Schema(description = "파일 경로", example = "/app/original-images/2024/00000001.tif")
private String filePath;
}
@Schema(name = "MngDto", description = "영상관리 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngDto {
private int rowNum;
private int mngYyyy;
private String mngPath;
private String mngState;
private String syncState;
private String syncDataCheckState;
private Long syncTotCnt;
private Long syncStateDoneCnt;
private Long syncDataCheckDoneCnt;
private Long syncNotPaireCnt;
private Long syncNotPaireExecCnt;
private Long syncDuplicateCnt;
private Long syncDuplicateExecCnt;
private Long syncFaultCnt;
private Long syncFaultExecCnt;
private Long syncNoFileCnt;
private Long syncNoFileExecCnt;
@JsonFormatDttm private ZonedDateTime rgstStrtDttm;
@JsonFormatDttm private ZonedDateTime rgstEndDttm;
public String getSyncState() {
if (this.syncStateDoneCnt == 0) {
return "NOTYET";
} else if (this.syncStateDoneCnt < this.syncTotCnt) {
return "PROCESSING";
}
return "DONE";
}
public String getDataCheckState() {
if (this.syncDataCheckDoneCnt == 0) {
return "NOTYET";
} else if (this.syncDataCheckDoneCnt < this.syncTotCnt) {
return "PROCESSING";
}
return "DONE";
}
public double getSyncStateDoneRate() {
if (this.syncTotCnt == null || this.syncTotCnt == 0) {
return 0.0;
}
return (double) this.syncStateDoneCnt / this.syncTotCnt * 100.0;
}
public double getSyncDataCheckDoneRate() {
if (this.syncTotCnt == null || this.syncTotCnt == 0) {
return 0.0;
}
return (double) this.syncDataCheckDoneCnt / this.syncTotCnt * 100.0;
}
public long getSyncErrorTotCnt() {
return this.syncNotPaireCnt + this.syncDuplicateCnt + this.syncFaultCnt;
}
public long getSyncErrorExecTotCnt() {
return this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt;
}
public String getMngState() {
String mngState = "DONE";
if (this.syncStateDoneCnt == 0) {
mngState = "NOTYET";
} else if (this.syncStateDoneCnt < this.syncTotCnt) {
mngState = "PROCESSING";
}
if ((this.syncNotPaireExecCnt + this.syncDuplicateExecCnt + this.syncFaultExecCnt) > 0) {
mngState = "TAKINGERROR";
}
return mngState;
}
public String getMngStateName() {
String enumId = this.getMngState();
if (enumId == null || enumId.isEmpty()) {
enumId = "NOTYET";
}
MngStateType type = Enums.fromId(MngStateType.class, enumId);
return type.getText();
}
}
@Schema(name = "ErrorSearchReq", description = "영상관리 오류데이터 검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class ErrorSearchReq {
// 페이징 파라미터
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
private int page = 0;
@Schema(description = "페이지 크기", example = "20")
private int size = 20;
@Schema(description = "정렬", example = "id desc")
private String sort;
@Schema(description = "오류종류(페어누락:NOTPAIR,중복파일:DUPLICATE,손상파일:FAULT)", example = "NOTPAIR")
private String syncState;
@Schema(description = "처리유형(처리:DONE,미처리:NOTYET)", example = "DONE")
private String syncCheckState;
@Schema(description = "검색어", example = "부산3959")
private String searchValue;
@Schema(description = "년도", example = "2025")
private Integer mngYyyy;
public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property));
}
return PageRequest.of(page, size);
}
}
@Schema(name = "ErrorDataDto", description = "영상관리 오류데이터 검색 리턴")
@Getter
@Setter
public static class ErrorDataDto {
private Long hstUid;
private Integer mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String map50kName;
private String map5kName;
private String mapSrcName;
private Integer mapCodeSrc;
@JsonFormatDttm private ZonedDateTime createdDttm;
private String syncState;
private String syncStateName;
private String syncTfwFileName;
private String syncTifFileName;
private String errorCheckState;
private String errorCheckStateName;
private String errorCheckTfwFileName;
private String errorCheckTifFileName;
// private List<MngFIleDto> fileArray;
public ErrorDataDto(
Long hstUid,
Integer mngYyyy,
String mapSheetNum,
String refMapSheetNum,
String map50kName,
String map5kName,
String mapSrcName,
Integer mapCodeSrc,
ZonedDateTime createdDttm,
String syncState,
String syncTfwFileName,
String syncTifFileName,
String errorCheckState,
String errorCheckTfwFileName,
String errorCheckTifFileName) {
this.hstUid = hstUid;
this.mngYyyy = mngYyyy;
this.mapSheetNum = mapSheetNum;
this.refMapSheetNum = refMapSheetNum;
this.map50kName = map50kName;
this.map5kName = map5kName;
this.mapSrcName = mapSrcName;
this.mapCodeSrc = mapCodeSrc;
this.createdDttm = createdDttm;
this.syncState = syncState;
this.syncStateName = getSyncStateName(syncState);
this.syncTfwFileName = syncTfwFileName;
this.syncTifFileName = syncTifFileName;
this.errorCheckState = errorCheckState;
this.errorCheckStateName = getSyncStateName(errorCheckState);
this.errorCheckTfwFileName = errorCheckTfwFileName;
this.errorCheckTifFileName = errorCheckTifFileName;
}
private String getSyncStateName(String enumId) {
if (enumId == null || enumId.isEmpty()) {
enumId = "NOTYET";
}
SyncStateType type = Enums.fromId(SyncStateType.class, enumId);
return type.getText();
}
}
@Schema(name = "SyncCheckStateReqUpdateDto", description = "영상관리 오류처리 상태변경요청")
@Getter
@Setter
public static class SyncCheckStateReqUpdateDto {
private Long hstUid;
private String filePath;
private String syncCheckTfwFileName;
private String syncCheckTifFileName;
private String syncCheckState;
}
@Schema(name = "MngFIleDto", description = "관리파일정보")
@Getter
@Setter
public static class MngFIleDto {
private Long fileUid;
private String filePath;
private String fileName;
private Long fileSize;
private String fileState;
private Long hstUid;
}
@Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class DmlReturn {
private String flag;
private String message;
}
@Schema(name = "MngFileAddReq", description = "영상관리파일 등록 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFileAddReq {
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
private String fileState;
}
@Schema(name = "MngFilesDto", description = "영상파일내역 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFilesDto {
private long fileUid;
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
}
@Schema(name = "ResisterYearList", description = "영상파일 등록을 위한 연도 list")
@Getter
public static class ResisterYearList {
private Integer current;
private List<Integer> years;
public ResisterYearList(Integer current, List<Integer> years) {
this.current = current;
this.years = years;
}
}
@Getter
@AllArgsConstructor
public enum MapSheetState implements EnumType {
// @formatter:off
DONE("완료"),
NOTYET("처리대기");
// @formatter:on
private final String message;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return message;
}
}
// 연도리스틀 조회시 사용하는 request Dto
@Getter
@Setter
@NoArgsConstructor
public static class YearSearchReq {
private String status;
// 페이징 파라미터
private int page = 0;
private int size = 20;
private String sort;
@Builder
public YearSearchReq(String status, int page, int size, String sort) {
this.status = status;
this.page = page;
this.size = size;
this.sort = sort;
}
public Pageable toPageable() {
if (sort != null && !sort.isEmpty()) {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction =
sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property));
}
return PageRequest.of(page, size);
}
}
@Schema(name = "MngListDto", description = "영상파일내역 검색 목록")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngListDto {
private int mngYyyy;
private String mapSheetNum;
private String mapSheetName;
private Integer beforeYear;
private Boolean isSuccess;
}
@Schema(name = "MngListDto", description = "영상파일내역 검색 목록")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngListCompareDto {
private String mngYyyy;
private String mapSheetNum;
private Integer beforeYear;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class TotalListDto {
private String mapSheetNum;
private Integer beforeYear;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngYyyyDto {
private Integer yyyy;
private String mngPath;
}
}

View File

@@ -0,0 +1,138 @@
package com.kamco.cd.kamcoback.dto;
import com.kamco.cd.kamcoback.inferface.JsonFormatDttm;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.ZonedDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
public class MapSheetMngDto {
@Schema(name = "MngSearchReq", description = "영상관리 검색 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngSearchReq {
// 페이징 파라미터
@Schema(description = "페이지 번호 (0부터 시작) ", example = "0")
private int page = 0;
@Schema(description = "페이지 크기", example = "20")
private int size = 20;
@Schema(description = "년도", example = "2025")
private Integer mngYyyy;
public Pageable toPageable() {
return PageRequest.of(page, size);
}
}
@Schema(name = "MngDto", description = "영상관리 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngDto {
private int rowNum;
private int mngYyyy;
private String mngState;
private String syncState;
private String syncCheckState;
private Long syncTotCnt;
private Long syncStateDoneCnt;
private Long syncCheckStateDoneCnt;
private Long syncNotFileCnt;
private Long syncTypeErrorCnt;
private Long syncSizeErrorCnt;
@JsonFormatDttm private ZonedDateTime rgstStrtDttm;
@JsonFormatDttm private ZonedDateTime rgstEndDttm;
}
@Schema(name = "MngHstDto", description = "영상관리내역 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngHstDto {
private long hstUid;
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String dataState;
private String syncState;
private String syncCheckState;
@JsonFormatDttm private ZonedDateTime syncStrtDttm;
@JsonFormatDttm private ZonedDateTime syncEndDttm;
@JsonFormatDttm private ZonedDateTime syncCheckStrtDttm;
@JsonFormatDttm private ZonedDateTime syncCheckEndDttm;
private String mapSheetPath;
private String syncTifFileName;
private String syncTfwFileName;
private String useInference;
private String syncMngPath;
}
@Schema(name = "MngFileAddReq", description = "영상관리파일 등록 요청")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFileAddReq {
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
private String fileState;
}
@Schema(name = "MngFilesDto", description = "영상관리내역 검색 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngFilesDto {
private long fileUid;
private int mngYyyy;
private String mapSheetNum;
private String refMapSheetNum;
private String filePath;
private String fileName;
private String fileExt;
private Long hstUid;
private Long fileSize;
}
@Schema(name = "MngListCompareDto", description = "영상파일 비교가능 이전년도정보")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class MngListCompareDto {
private String mngYyyy;
private String mapSheetNum;
private Integer beforeYear;
}
@Schema(name = "DmlReturn", description = "영상관리 DML 수행 후 리턴")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class DmlReturn {
private String flag;
private String message;
}
}

View File

@@ -0,0 +1,22 @@
package com.kamco.cd.kamcoback.enums;
import lombok.EqualsAndHashCode;
import lombok.Getter;
public class ApiConfigEnum {
@Getter
@EqualsAndHashCode(of = "enumValue")
public static class EnumDto<T> {
private final T enumValue;
private final String id;
private final String text;
public EnumDto(T enumValue, String id, String text) {
this.enumValue = enumValue;
this.id = id;
this.text = text;
}
}
}

View File

@@ -0,0 +1,20 @@
package com.kamco.cd.kamcoback.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;
}
}

View File

@@ -0,0 +1,46 @@
package com.kamco.cd.kamcoback.enums;
import com.kamco.cd.kamcoback.enums.ApiConfigEnum.EnumDto;
import com.kamco.cd.kamcoback.inferface.EnumType;
import java.util.Arrays;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Common usage status used across the system.
*
* <p>This enum represents whether a resource is active, excluded from processing, or inactive. It
* is commonly used for filtering, business rules, and status management.
*/
@Getter
@AllArgsConstructor
public enum CommonUseStatus implements EnumType {
// @formatter:off
USE("USE", "사용중", 100)
/** Actively used and available */
,
EXCEPT("EXCEPT", "영구 추론제외", 200)
/** Explicitly excluded from use or processing */
,
AUTO_EXCEPT("AUTO_EXCEPT", "자동추론 제외", 300),
NOT_USE("NOT_USE", "사용안함", 999)
/** Not used or disabled */
;
// @formatter:on
private String id;
private String text;
private int ordering;
public static CommonUseStatus getEnumById(String id) {
return Arrays.stream(CommonUseStatus.values())
.filter(x -> x.getId().equals(id))
.findFirst()
.orElse(CommonUseStatus.NOT_USE);
}
public EnumDto<CommonUseStatus> getEnumDto() {
return new EnumDto<>(this, this.id, this.text);
}
}

View File

@@ -0,0 +1,86 @@
package com.kamco.cd.kamcoback.enums;
import com.kamco.cd.kamcoback.inferface.CodeExpose;
import com.kamco.cd.kamcoback.inferface.CodeHidden;
import com.kamco.cd.kamcoback.inferface.EnumType;
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.kamcoback";
/** 노출 가능한 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)
.filter(e -> !isHidden(enumClass, (Enum<?>) e))
.map(e -> new CodeDto(e.getId(), e.getText()))
.toList();
}
private static boolean isHidden(Class<? extends Enum<?>> enumClass, Enum<?> e) {
try {
return enumClass.getField(e.name()).isAnnotationPresent(CodeHidden.class);
} catch (NoSuchFieldException ex) {
return false;
}
}
/** 특정 타입(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;
}
}

View File

@@ -0,0 +1,26 @@
package com.kamco.cd.kamcoback.enums;
import com.kamco.cd.kamcoback.inferface.EnumType;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MngStateType implements EnumType {
NOTYET("동기화 시작"),
PROCESSING("데이터 체크"),
DONE("동기화 작업 종료"),
TAKINGERROR("오류 데이터 처리중");
private final String desc;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
}

View File

@@ -0,0 +1,33 @@
package com.kamco.cd.kamcoback.enums;
import com.kamco.cd.kamcoback.inferface.CodeExpose;
import com.kamco.cd.kamcoback.inferface.CodeHidden;
import com.kamco.cd.kamcoback.inferface.EnumType;
import lombok.AllArgsConstructor;
import lombok.Getter;
@CodeExpose
@Getter
@AllArgsConstructor
public enum SyncStateType implements EnumType {
@CodeHidden
NOTYET("미처리"),
NOFILE("파일없음"),
NOTPAIR("페어파일누락"),
DUPLICATE("파일중복"),
TYPEERROR("손상파일"),
@CodeHidden
DONE("완료");
private final String desc;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return desc;
}
}

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.inferface;
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 {}

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.inferface;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CodeHidden {}

View File

@@ -0,0 +1,8 @@
package com.kamco.cd.kamcoback.inferface;
public interface EnumType {
String getId();
String getText();
}

View File

@@ -0,0 +1,19 @@
package com.kamco.cd.kamcoback.inferface;
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@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 {}

View File

@@ -0,0 +1,83 @@
package com.kamco.cd.kamcoback.postgres.core;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngHstDto;
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetMngFileJobRepository;
import com.kamco.cd.kamcoback.postgres.repository.MapSheetMngYearRepository;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MapSheetMngFileJobCoreService {
private final MapSheetMngFileJobRepository mapSheetMngFileJobRepository;
private final MapSheetMngYearRepository mapSheetMngYearRepository;
public Page<MngDto> findMapSheetMngList(
MapSheetMngDto.@Valid MngSearchReq searchReq) {
return mapSheetMngFileJobRepository.findMapSheetMngList(searchReq);
}
public List<MngHstDto> findTargetMapSheetFileList(long targetNum, int pageSize) {
return mapSheetMngFileJobRepository.findTargetMapSheetFileList(targetNum, pageSize);
}
public MapSheetMngDto.DmlReturn mngHstDataSyncStateUpdate(
@Valid MapSheetMngDto.MngHstDto updateReq) {
mapSheetMngFileJobRepository.mngHstDataSyncStateUpdate(updateReq);
return new MapSheetMngDto.DmlReturn("success", updateReq.getHstUid() + "");
}
public MapSheetMngDto.DmlReturn mngFileSave(@Valid MapSheetMngDto.MngFileAddReq addReq) {
MapSheetMngFileEntity entity = new MapSheetMngFileEntity();
entity.setMngYyyy(addReq.getMngYyyy());
entity.setMapSheetNum(addReq.getMapSheetNum());
entity.setRefMapSheetNum(addReq.getRefMapSheetNum());
entity.setFilePath(addReq.getFilePath());
entity.setFileName(addReq.getFileName());
entity.setFileExt(addReq.getFileExt());
entity.setHstUid(addReq.getHstUid());
entity.setFileSize(addReq.getFileSize());
entity.setFileState(addReq.getFileState());
MapSheetMngFileEntity saved = mapSheetMngFileJobRepository.save(entity);
// int hstCnt = mapSheetMngRepository.insertMapSheetOrgDataToMapSheetMngHst(saved.getMngYyyy());
return new MapSheetMngDto.DmlReturn("success", saved.getFileUid().toString());
}
public Long findByMngYyyyTargetMapSheetNotYetCount(int mngYyyy) {
return mapSheetMngFileJobRepository.findByMngYyyyTargetMapSheetNotYetCount(mngYyyy);
}
public void mngDataState(int mngYyyy, String mngState) {
mapSheetMngFileJobRepository.mngDataState(mngYyyy, mngState);
}
public Integer findNotYetMapSheetMng() {
return mapSheetMngFileJobRepository.findNotYetMapSheetMng();
}
public Long findByHstMapSheetBeforeYyyyListCount(int strtYyyy, int endYyyy, String mapSheetNum) {
return mapSheetMngFileJobRepository.findByHstMapSheetBeforeYyyyListCount(
strtYyyy, endYyyy, mapSheetNum);
}
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus) {
mapSheetMngFileJobRepository.updateException5kMapSheet(mapSheetNum, commonUseStatus);
}
public void saveSheetMngYear() {
mapSheetMngYearRepository.saveFileInfo();
}
}

View File

@@ -0,0 +1,34 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import java.time.ZonedDateTime;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
@Getter
@MappedSuperclass
public class CommonDateEntity {
@CreatedDate
@Column(name = "created_dttm", updatable = false, nullable = false)
private ZonedDateTime createdDate;
@LastModifiedDate
@Column(name = "updated_dttm", nullable = false)
private ZonedDateTime modifiedDate;
@PrePersist
protected void onPersist() {
this.createdDate = ZonedDateTime.now();
this.modifiedDate = ZonedDateTime.now();
}
@PreUpdate
protected void onUpdate() {
this.modifiedDate = ZonedDateTime.now();
}
}

View File

@@ -0,0 +1,48 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
@Getter
@Setter
@Table(name = "tb_map_inkx_50k")
@Entity
@NoArgsConstructor
public class MapInkx50kEntity extends CommonDateEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_50k_fid_seq_gen")
@SequenceGenerator(
name = "tb_map_inkx_50k_fid_seq_gen",
sequenceName = "tb_map_inkx_50k_fid_seq",
allocationSize = 1)
private Integer fid;
@Column(name = "mapidcd_no")
private String mapidcdNo;
@Column(name = "mapid_nm")
private String mapidNm;
@Column(name = "mapid_no")
private String mapidNo;
@Column(name = "geom")
private Geometry geom;
public MapInkx50kEntity(String mapidcdNo, String mapidNm, String mapidNo, Geometry geom) {
this.mapidcdNo = mapidcdNo;
this.mapidNm = mapidNm;
this.mapidNo = mapidNo;
this.geom = geom;
}
}

View File

@@ -0,0 +1,69 @@
package com.kamco.cd.kamcoback.postgres.entity;
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.locationtech.jts.geom.Geometry;
@Getter
@Setter
@Table(name = "tb_map_inkx_5k")
@Entity
@NoArgsConstructor
public class MapInkx5kEntity extends CommonDateEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tb_map_inkx_5k_fid_seq_gen")
@SequenceGenerator(
name = "tb_map_inkx_5k_fid_seq_gen",
sequenceName = "tb_map_inkx_5k_fid_seq",
allocationSize = 1)
private Integer fid;
@Column(name = "mapidcd_no")
private String mapidcdNo;
@Column(name = "mapid_nm")
private String mapidNm;
@Column(name = "geom")
private Geometry geom;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fid_k50", referencedColumnName = "fid")
private MapInkx50kEntity mapInkx50k;
// 사용상태 USE,
@Column(name = "use_inference")
@Enumerated(EnumType.STRING)
private CommonUseStatus useInference;
// Constructor
public MapInkx5kEntity(
String mapidcdNo, String mapidNm, Geometry geom, MapInkx50kEntity mapInkx50k) {
this.mapidcdNo = mapidcdNo;
this.mapidNm = mapidNm;
this.geom = geom;
this.mapInkx50k = mapInkx50k;
// 생성시 default 사용함 (사용,제외,사용안함)
this.useInference = CommonUseStatus.USE;
}
// 변경 사용상태 (추론사용여부)
public void updateUseInference(CommonUseStatus useInference) {
this.useInference = useInference;
}
}

View File

@@ -0,0 +1,72 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_mng")
public class MapSheetMngEntity {
@Id
@Column(name = "mng_yyyy", nullable = false)
private Integer mngYyyy;
@Size(max = 20)
@ColumnDefault("'NOTYET'")
@Column(name = "mng_state", length = 20)
private String mngState = "NOTYET";
@Size(max = 20)
@ColumnDefault("'NOTYET'")
@Column(name = "sync_state", length = 20)
private String syncState = "NOTYET";
@Column(name = "mng_state_dttm")
private ZonedDateTime mngStateDttm = ZonedDateTime.now();
@Column(name = "sync_state_dttm")
private ZonedDateTime syncStateDttm = ZonedDateTime.now();
@Column(name = "created_dttm")
private ZonedDateTime createdDttm = ZonedDateTime.now();
@Column(name = "created_uid")
private Long createdUid;
@Column(name = "updated_dttm")
private ZonedDateTime updatedDttm = ZonedDateTime.now();
@Column(name = "updated_uid")
private Long updatedUid;
@Size(max = 255)
@ColumnDefault("'NULL::character varying'")
@Column(name = "mng_path")
private String mngPath;
@Size(max = 20)
@ColumnDefault("'NOTYET'")
@Column(name = "sync_check_state", length = 20)
private String syncCheckState = "NOTYET";
@Column(name = "sync_strt_dttm")
private ZonedDateTime syncStrtDttm;
@Column(name = "sync_end_dttm")
private ZonedDateTime syncEndDttm;
@Column(name = "sync_check_strt_dttm")
private ZonedDateTime syncCheckStrtDttm;
@Column(name = "sync_check_end_dttm")
private ZonedDateTime syncCheckEndDttm;
}

View File

@@ -0,0 +1,63 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_mng_files")
public class MapSheetMngFileEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "file_uid", nullable = false)
private Long fileUid;
@NotNull
@Column(name = "mng_yyyy", nullable = false)
private Integer mngYyyy;
@NotNull
@Column(name = "map_sheet_num", nullable = false)
private String mapSheetNum;
@Column(name = "ref_map_sheet_num")
private String refMapSheetNum;
@Size(max = 255)
@Column(name = "file_path")
private String filePath;
@Size(max = 100)
@Column(name = "file_name", length = 100)
private String fileName;
@Size(max = 20)
@Column(name = "file_ext", length = 20)
private String fileExt;
@Column(name = "hst_uid")
private Long hstUid;
@Column(name = "file_size")
private Long fileSize;
@Size(max = 20)
@Column(name = "file_state", length = 20)
private String fileState;
@NotNull
@ColumnDefault("false")
@Column(name = "file_del", nullable = false)
private Boolean fileDel = false;
}

View File

@@ -0,0 +1,169 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import java.time.ZonedDateTime;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* This class represents the entity for managing the history of map sheets. It is mapped to the
* database table "tb_map_sheet_mng_hst" and contains various properties related to the 1:5k map
* sheet information, as well as metadata for file synchronization and management.
*
* <p>This entity: - Includes a primary key (hstUid) for unique identification. - Maintains
* information associated with map sheets such as code, name, scale ratio, and paths. - Tracks
* states, timestamps, and data synchronization details. - Maintains relationships with the
* `MapInkx5kEntity` entity through a many-to-one association. - Provides functionality to update
* file information and sizes (`tifSizeBytes`, `tfwSizeBytes`, and `totalSizeBytes`).
*
* <p>It extends the `CommonDateEntity` class to include common date management fields, such as
* creation and modification timestamps.
*
* <p>The `@Getter` annotation generates getter methods for all fields, while the access to setters
* is restricted to enforce controlled modifications. The entity uses `@NoArgsConstructor` with
* `AccessLevel.PROTECTED` to restrict direct instantiation. The `updateFileInfos` method allows
* dynamic updates of specific file information.
*
* <p>Fields include: - hstUid: Unique identifier for the history record. - mngYyyy: Year associated
* with the management record. - mapInkx5kByCode: Reference to the related `MapInkx5kEntity` object.
* - mapSheetNum: Map sheet number identifying specific map. - mapSheetName: Name of the map sheet.
* - mapSheetCodeSrc: Source code of the map sheet. - scaleRatio: Scale ratio of the map. -
* dataState: State/status of the map sheet data. - dataStateDttm: Timestamp of the data state. -
* useInference: Indicator or metadata for inference usage. - useInferenceDttm: Timestamp for
* inference-related use. - mapSheetPath: Path or location of the map sheet file. - refMapSheetNum:
* Reference to a related map sheet number. - createdUid: User ID of the record creator. -
* updatedUid: User ID of the last updater. - syncState and related fields: Fields to manage
* synchronization states and processes. - tifSizeBytes, tfwSizeBytes, totalSizeBytes: Fields to
* track file size details. - sync file name fields: Stores names of files relevant for
* synchronization and verification.
*
* <p>This entity is essential for tracking and managing map sheet revisions, status, and usage in a
* system leveraging 1:5k map data.
*/
@Getter
// entity의 접근제어를 위해 @setter를 사용 x
// @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
// 영상관리이력
@Table(name = "tb_map_sheet_mng_hst")
public class MapSheetMngHstEntity extends CommonDateEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "hst_uid")
private Long hstUid; // id
@Column(name = "mng_yyyy")
private Integer mngYyyy; // 년도
// JPA 연관관계: MapInkx5k 참조 (PK 기반) 소속도엽번호 1:5k
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "map_sheet_code", referencedColumnName = "fid")
private MapInkx5kEntity mapInkx5kByCode;
// TODO 1:5k 관련 정보 추후 제거 필요
@Column(name = "map_sheet_num")
private String mapSheetNum; // 도엽번호
@Column(name = "map_sheet_name")
private String mapSheetName;
// TODO END
// 도엽파일이 저장된 경로
@Column(name = "map_sheet_code_src")
private Integer mapSheetCodeSrc;
// 도엽비율?
@Column(name = "scale_ratio")
private Integer scaleRatio;
@Column(name = "data_state", length = 20)
private String dataState;
@Column(name = "data_state_dttm")
private ZonedDateTime dataStateDttm;
@Column(name = "use_inference")
private String useInference;
@Column(name = "use_inference_dttm")
private ZonedDateTime useInferenceDttm;
@Column(name = "map_sheet_path")
private String mapSheetPath;
@Column(name = "ref_map_sheet_num")
private String refMapSheetNum;
@Column(name = "created_uid")
private Long createdUid;
@Column(name = "updated_uid")
private Long updatedUid;
@Size(max = 20)
@Column(name = "sync_state", length = 20)
private String syncState;
@Size(max = 20)
@Column(name = "sync_check_state", length = 20)
private String syncCheckState;
@Column(name = "sync_strt_dttm")
private ZonedDateTime syncStrtDttm;
@Column(name = "sync_end_dttm")
private ZonedDateTime syncEndDttm;
@Column(name = "sync_check_strt_dttm")
private ZonedDateTime syncCheckStrtDttm;
@Column(name = "sync_check_end_dttm")
private ZonedDateTime syncCheckEndDttm;
@Column(name = "tif_size_bytes")
private Long tifSizeBytes;
@Column(name = "tfw_size_bytes")
private Long tfwSizeBytes;
@Column(name = "total_size_bytes")
private Long totalSizeBytes;
@Size(max = 100)
@Column(name = "sync_tif_file_name", length = 100)
private String syncTifFileName;
@Size(max = 100)
@Column(name = "sync_tfw_file_name", length = 100)
private String syncTfwFileName;
@Size(max = 100)
@Column(name = "sync_check_tif_file_name", length = 100)
private String syncCheckTifFileName;
@Size(max = 100)
@Column(name = "sync_check_tfw_file_name", length = 100)
private String syncCheckTfwFileName;
// 파일정보 업데이트
public void updateFileInfos(Long tifSizeBytes, Long tfwSizeBytes) {
tifSizeBytes = tifSizeBytes == null ? 0L : tifSizeBytes;
tfwSizeBytes = tfwSizeBytes == null ? 0L : tfwSizeBytes;
this.tifSizeBytes = tifSizeBytes;
this.tfwSizeBytes = tfwSizeBytes;
this.totalSizeBytes = tifSizeBytes + tfwSizeBytes;
}
}

View File

@@ -0,0 +1,34 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
@Getter
@Setter
@Entity
@Table(name = "tb_map_sheet_mng_year_yn")
public class MapSheetMngYearYnEntity {
@EmbeddedId private MapSheetMngYearYnEntityId id;
@NotNull
@Column(name = "yn", nullable = false, length = Integer.MAX_VALUE)
private String yn;
@NotNull
@ColumnDefault("now()")
@Column(name = "created_dttm", nullable = false)
private ZonedDateTime createdDttm;
@NotNull
@ColumnDefault("now()")
@Column(name = "updated_dttm", nullable = false)
private ZonedDateTime updatedDttm;
}

View File

@@ -0,0 +1,46 @@
package com.kamco.cd.kamcoback.postgres.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.io.Serializable;
import java.util.Objects;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.Hibernate;
@Getter
@Setter
@Embeddable
public class MapSheetMngYearYnEntityId implements Serializable {
private static final long serialVersionUID = 6282262062316057898L;
@Size(max = 20)
@NotNull
@Column(name = "map_sheet_num", nullable = false, length = 20)
private String mapSheetNum;
@NotNull
@Column(name = "mng_yyyy", nullable = false)
private Integer mngYyyy;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
return false;
}
MapSheetMngYearYnEntityId entity = (MapSheetMngYearYnEntityId) o;
return Objects.equals(this.mngYyyy, entity.mngYyyy)
&& Objects.equals(this.mapSheetNum, entity.mapSheetNum);
}
@Override
public int hashCode() {
return Objects.hash(mngYyyy, mapSheetNum);
}
}

View File

@@ -0,0 +1,7 @@
package com.kamco.cd.kamcoback.postgres.repository;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngFileEntity;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapSheetMngFileJobRepository
extends JpaRepository<MapSheetMngFileEntity, Long>, MapSheetMngFileJobRepositoryCustom {}

View File

@@ -0,0 +1,27 @@
package com.kamco.cd.kamcoback.postgres.repository;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngHstDto;
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
import java.util.List;
import org.springframework.data.domain.Page;
public interface MapSheetMngFileJobRepositoryCustom {
Page<MngDto> findMapSheetMngList(MapSheetMngDto.MngSearchReq searchReq);
void mngHstDataSyncStateUpdate(MapSheetMngDto.MngHstDto updateReq);
List<MngHstDto> findTargetMapSheetFileList(long targetNum, int pageSize);
Long findByMngYyyyTargetMapSheetNotYetCount(int mngYyyy);
public void mngDataState(int mngYyyy, String mngState);
public Integer findNotYetMapSheetMng();
public Long findByHstMapSheetBeforeYyyyListCount(int strtYyyy, int endYyyy, String mapSheetNum);
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus);
}

View File

@@ -0,0 +1,267 @@
package com.kamco.cd.kamcoback.postgres.repository;
import static com.kamco.cd.kamcoback.postgres.entity.QMapInkx5kEntity.mapInkx5kEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngEntity.mapSheetMngEntity;
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngHstEntity.mapSheetMngHstEntity;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngHstDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngSearchReq;
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class MapSheetMngFileJobRepositoryImpl implements MapSheetMngFileJobRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
@PersistenceContext private EntityManager em;
@Override
public Integer findNotYetMapSheetMng() {
Integer countQuery =
queryFactory
.select(mapSheetMngEntity.mngYyyy)
.from(mapSheetMngEntity)
.where(
mapSheetMngEntity
.mngState
.eq("NOTYET")
.or(mapSheetMngEntity.mngState.eq("PROCESSING")))
.limit(1)
.fetchOne();
return countQuery;
}
@Override
public Long findByMngYyyyTargetMapSheetNotYetCount(int mngYyyy) {
Long countQuery =
queryFactory
.select(mapSheetMngHstEntity.mngYyyy.count())
.from(mapSheetMngHstEntity)
.where(
mapSheetMngHstEntity
.mngYyyy
.eq(mngYyyy)
.and(mapSheetMngHstEntity.syncState.eq("NOTYET")))
.fetchOne();
return countQuery;
}
public void mngDataState(int mngYyyy, String mngState) {
long updateCount =
queryFactory
.update(mapSheetMngEntity)
.set(mapSheetMngEntity.mngState, mngState)
.set(mapSheetMngEntity.syncState, mngState)
.set(mapSheetMngEntity.syncCheckState, mngState)
.where(mapSheetMngEntity.mngYyyy.eq(mngYyyy))
.execute();
}
@Override
public Page<MapSheetMngDto.MngDto> findMapSheetMngList(MapSheetMngDto.MngSearchReq searchReq) {
Pageable pageable = searchReq.toPageable();
BooleanBuilder whereBuilder = new BooleanBuilder();
if (searchReq.getMngYyyy() != null) {
whereBuilder.and(mapSheetMngEntity.mngYyyy.eq(searchReq.getMngYyyy()));
}
List<MapSheetMngDto.MngDto> foundContent =
queryFactory
.select(
Projections.constructor(
MapSheetMngDto.MngDto.class,
Expressions.numberTemplate(
Integer.class,
"row_number() over(order by {0} desc)",
mapSheetMngEntity.createdDttm),
mapSheetMngEntity.mngYyyy,
mapSheetMngEntity.mngState,
mapSheetMngEntity.syncState,
mapSheetMngEntity.syncCheckState,
mapSheetMngHstEntity.count(),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncState.eq("DONE"))
.then(1L)
.otherwise(0L)
.sum()
.as("syncStateDoneCnt"),
new CaseBuilder()
.when(mapSheetMngHstEntity.syncCheckState.eq("DONE"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.dataState.eq("NOT"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.dataState.eq("TYPEERROR"))
.then(1L)
.otherwise(0L)
.sum(),
new CaseBuilder()
.when(mapSheetMngHstEntity.dataState.eq("SIZEERROR"))
.then(1L)
.otherwise(0L)
.sum(),
mapSheetMngHstEntity.syncStrtDttm.min(),
mapSheetMngHstEntity.syncCheckEndDttm.max()))
.from(mapSheetMngEntity)
.leftJoin(mapSheetMngHstEntity)
.on(mapSheetMngEntity.mngYyyy.eq(mapSheetMngHstEntity.mngYyyy))
.where(whereBuilder)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(mapSheetMngEntity.createdDttm.desc())
.groupBy(mapSheetMngEntity.mngYyyy)
.fetch();
Long countQuery =
queryFactory
.select(mapSheetMngEntity.mngYyyy.count())
.from(mapSheetMngEntity)
.where(whereBuilder)
.fetchOne();
return new PageImpl<>(foundContent, pageable, countQuery);
}
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, now)
.set(mapSheetMngHstEntity.syncState, updateReq.getSyncState())
.set(mapSheetMngHstEntity.syncEndDttm, now)
.set(mapSheetMngHstEntity.syncCheckState, "NOTYET")
.set(mapSheetMngHstEntity.syncCheckStrtDttm, now)
.set(mapSheetMngHstEntity.syncCheckEndDttm, now)
.set(mapSheetMngHstEntity.mapSheetPath, updateReq.getMapSheetPath())
.set(mapSheetMngHstEntity.syncTfwFileName, updateReq.getSyncTfwFileName())
.set(mapSheetMngHstEntity.syncTifFileName, updateReq.getSyncTifFileName())
.set(mapSheetMngHstEntity.useInference, updateReq.getUseInference())
.where(mapSheetMngHstEntity.hstUid.eq(updateReq.getHstUid()))
.execute();
} else {
long updateCount =
queryFactory
.update(mapSheetMngHstEntity)
.set(mapSheetMngHstEntity.dataState, updateReq.getDataState())
.set(mapSheetMngHstEntity.dataStateDttm, now)
.set(mapSheetMngHstEntity.syncState, updateReq.getSyncState())
.set(mapSheetMngHstEntity.syncStrtDttm, now)
.set(mapSheetMngHstEntity.syncEndDttm, now)
.set(mapSheetMngHstEntity.syncCheckState, "NOTYET")
.set(mapSheetMngHstEntity.syncCheckStrtDttm, now)
.set(mapSheetMngHstEntity.syncCheckEndDttm, now)
.set(mapSheetMngHstEntity.mapSheetPath, updateReq.getMapSheetPath())
.set(mapSheetMngHstEntity.syncTfwFileName, updateReq.getSyncTfwFileName())
.set(mapSheetMngHstEntity.syncTifFileName, updateReq.getSyncTifFileName())
.set(mapSheetMngHstEntity.useInference, updateReq.getUseInference())
.where(mapSheetMngHstEntity.hstUid.eq(updateReq.getHstUid()))
.execute();
}
}
@Override
public List<MngHstDto> findTargetMapSheetFileList(long targetNum, int pageSize) {
// Pageable pageable = searchReq.toPageable();
List<MngHstDto> foundContent =
queryFactory
.select(
Projections.constructor(
MngHstDto.class,
mapSheetMngHstEntity.hstUid,
mapSheetMngHstEntity.mngYyyy,
mapSheetMngHstEntity.mapSheetNum,
mapSheetMngHstEntity.refMapSheetNum,
mapSheetMngHstEntity.dataState,
mapSheetMngHstEntity.syncState,
mapSheetMngHstEntity.syncCheckState,
mapSheetMngHstEntity.syncStrtDttm,
mapSheetMngHstEntity.syncEndDttm,
mapSheetMngHstEntity.syncCheckStrtDttm,
mapSheetMngHstEntity.syncCheckEndDttm,
mapSheetMngHstEntity.mapSheetPath,
mapSheetMngHstEntity.syncCheckTfwFileName,
mapSheetMngHstEntity.syncCheckTifFileName,
mapSheetMngHstEntity.useInference,
mapSheetMngEntity.mngPath))
.from(mapSheetMngHstEntity)
.join(mapSheetMngEntity)
.on(mapSheetMngEntity.mngYyyy.eq(mapSheetMngHstEntity.mngYyyy))
.where(
mapSheetMngHstEntity.syncState.eq("NOTYET"),
mapSheetMngHstEntity.hstUid.mod(10L).eq(targetNum))
.limit(pageSize)
.orderBy(mapSheetMngHstEntity.hstUid.asc())
.fetch();
return foundContent;
}
@Override
public Long findByHstMapSheetBeforeYyyyListCount(int strtYyyy, int endYyyy, String mapSheetNum) {
Long countQuery =
queryFactory
.select(mapSheetMngHstEntity.mngYyyy.count())
.from(mapSheetMngHstEntity)
.where(
mapSheetMngHstEntity
.mngYyyy
.goe(strtYyyy)
.and(mapSheetMngHstEntity.mngYyyy.loe(endYyyy))
.and(mapSheetMngHstEntity.mapSheetNum.eq(mapSheetNum))
.and(mapSheetMngHstEntity.useInference.eq("USE"))
.and(
mapSheetMngHstEntity
.syncState
.eq("DONE")
.or(mapSheetMngHstEntity.syncCheckState.eq("DONE"))))
.fetchOne();
return countQuery;
}
@Override
public void updateException5kMapSheet(String mapSheetNum, CommonUseStatus commonUseStatus) {
long updateCount =
queryFactory
.update(mapInkx5kEntity)
.set(mapInkx5kEntity.useInference, commonUseStatus)
.set(mapInkx5kEntity.modifiedDate, ZonedDateTime.now())
.where(mapInkx5kEntity.mapidcdNo.eq(mapSheetNum))
.execute();
}
}

View File

@@ -0,0 +1,9 @@
package com.kamco.cd.kamcoback.postgres.repository;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngYearYnEntity;
import com.kamco.cd.kamcoback.postgres.entity.MapSheetMngYearYnEntityId;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MapSheetMngYearRepository
extends JpaRepository<MapSheetMngYearYnEntity, MapSheetMngYearYnEntityId>,
MapSheetMngYearRepositoryCustom {}

View File

@@ -0,0 +1,10 @@
package com.kamco.cd.kamcoback.postgres.repository;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngListCompareDto;
import java.util.List;
public interface MapSheetMngYearRepositoryCustom {
void saveFileInfo();
List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds);
}

View File

@@ -0,0 +1,101 @@
package com.kamco.cd.kamcoback.postgres.repository;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngListCompareDto;
import com.kamco.cd.kamcoback.postgres.entity.QMapSheetMngYearYnEntity;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class MapSheetMngYearRepositoryImpl implements MapSheetMngYearRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final EntityManager em;
/** 변화탐지 실행 가능 비교년도 저장 */
@Override
public void saveFileInfo() {
em.createNativeQuery("TRUNCATE TABLE tb_map_sheet_mng_year_yn").executeUpdate();
String sql =
"""
WITH bounds AS (
SELECT
map_sheet_num,
MIN(mng_yyyy::int) AS min_y,
MAX(mng_yyyy::int) AS max_y
FROM tb_map_sheet_mng_files
GROUP BY map_sheet_num
),
years AS (
SELECT
b.map_sheet_num,
gs.y AS mng_yyyy
FROM bounds b
CROSS JOIN LATERAL generate_series(b.min_y, b.max_y) AS gs(y)
),
exist AS (
SELECT DISTINCT
map_sheet_num,
mng_yyyy::int AS mng_yyyy
FROM tb_map_sheet_mng_files
),
src AS (
SELECT
y.map_sheet_num,
y.mng_yyyy,
CASE
WHEN e.map_sheet_num IS NULL THEN 'N'
ELSE 'Y'
END AS yn
FROM years y
LEFT JOIN exist e
ON e.map_sheet_num = y.map_sheet_num
AND e.mng_yyyy = y.mng_yyyy
)
INSERT INTO tb_map_sheet_mng_year_yn
(map_sheet_num, mng_yyyy, yn)
SELECT
map_sheet_num,
mng_yyyy,
yn
FROM src
ON CONFLICT (map_sheet_num, mng_yyyy)
DO UPDATE SET
yn = EXCLUDED.yn,
updated_dttm = now()
""";
em.createNativeQuery(sql).executeUpdate();
}
/**
* 변화탐지 실행 가능 비교년도 조회
*
* @param mngYyyy
* @param mapIds
* @return
*/
@Override
public List<MngListCompareDto> findByHstMapSheetCompareList(int mngYyyy, List<String> mapIds) {
QMapSheetMngYearYnEntity y = QMapSheetMngYearYnEntity.mapSheetMngYearYnEntity;
StringExpression mngYyyyStr = Expressions.stringTemplate("concat({0}, '')", mngYyyy);
return queryFactory
.select(
Projections.constructor(
MngListCompareDto.class, mngYyyyStr, y.id.mapSheetNum, y.id.mngYyyy.max()))
.from(y)
.where(y.id.mapSheetNum.in(mapIds), y.yn.eq("Y"), y.id.mngYyyy.loe(mngYyyy))
.groupBy(y.id.mapSheetNum)
.fetch();
}
}

View File

@@ -0,0 +1,393 @@
package com.kamco.cd.kamcoback.service;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import com.kamco.cd.kamcoback.dto.FileDto;
import com.kamco.cd.kamcoback.dto.FileDto.SrchFilesDepthDto;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.DmlReturn;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngFileAddReq;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto.MngHstDto;
import com.kamco.cd.kamcoback.enums.CommonUseStatus;
import com.kamco.cd.kamcoback.postgres.core.MapSheetMngFileJobCoreService;
import com.kamco.cd.kamcoback.utils.FIleChecker;
import com.kamco.cd.kamcoback.utils.FIleChecker.Basic;
import com.kamco.cd.kamcoback.dto.MapSheetMngDto;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
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.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MapSheetMngFileJobService {
private final MapSheetMngFileJobCoreService mapSheetMngFileJobCoreService;
@Value("${file.sync-root-dir}")
private String syncRootDir;
@Value("${file.sync-tmp-dir}")
private String syncTmpDir;
@Value("${file.sync-file-extention}")
private String syncFileExtention;
@Value("${file.sync-auto-exception-start-year}")
private int syncAutoExceptionStartYear;
@Value("${file.sync-auto-exception-before-year-cnt}")
private int syncAutoExceptionBeforeYearCnt;
public Integer checkMngFileSync() {
return mapSheetMngFileJobCoreService.findNotYetMapSheetMng();
}
@Transactional
public void checkMapSheetFileProcess(long targetNum, int mngSyncPageSize) {
List<MngHstDto> mapSheetFileNotYetList = findTargetMapSheetFileList(targetNum, mngSyncPageSize);
Long hstUid = 0L;
String syncState = "";
String syncCheckState = "";
String fileState = "";
String dataState = "";
int mngYyyy = 0;
SrchFilesDepthDto srchDto = new SrchFilesDepthDto();
List<Basic> basicList = new ArrayList<>();
if (mapSheetFileNotYetList.size() >= 1) {
mngYyyy = mapSheetFileNotYetList.get(0).getMngYyyy();
}
for (MngHstDto item : mapSheetFileNotYetList) {
// 5K도엽 자동추론제외
Long exceptCheckCnt =
this.mapSheetAutoExceptionUpdate(item.getMngYyyy(), item.getMapSheetNum());
// 도엽별 파일 체크 진행중으로 변경
item.setDataState("PROCESSING");
item.setUseInference("USE");
if (exceptCheckCnt == 0) {
item.setUseInference("EXCEPT");
}
mngHstDataSyncStateUpdate(item);
// 1. MngHstDto 객체의 필드 값에 접근
srchDto.setMaxDepth(10);
srchDto.setDirPath(item.getSyncMngPath());
srchDto.setExtension("tif,tfw");
srchDto.setFileNm(item.getMapSheetNum());
System.out.println(
"UID: "
+ hstUid
+ ", 상태: "
+ syncState
+ ", 관리경로: "
+ item.getSyncMngPath()
+ ", 파일명 "
+ item.getMapSheetNum()
+ " .tif,tfw");
// 도엽번호로 파일 찾기
basicList =
FIleChecker.getFilesFromAllDepth(
srchDto.getDirPath(),
srchDto.getFileNm(),
srchDto.getExtension(),
srchDto.getMaxDepth(),
srchDto.getSortType(),
0,
100);
int tfwCnt =
(int)
basicList.stream().filter(dto -> dto.getExtension().toString().equals("tfw")).count();
int tifCnt =
(int)
basicList.stream().filter(dto -> dto.getExtension().toString().equals("tif")).count();
syncState = "";
syncCheckState = "";
if (tfwCnt == 0 && tifCnt == 0) {
syncState = "NOFILE";
}
for (Basic item2 : basicList) {
MngFileAddReq addReq = new MngFileAddReq();
addReq.setMngYyyy(item.getMngYyyy());
addReq.setMapSheetNum(item.getMapSheetNum());
addReq.setRefMapSheetNum(item.getRefMapSheetNum());
addReq.setFilePath(item2.getParentPath());
addReq.setFileName(item2.getFileNm());
addReq.setFileExt(item2.getExtension());
addReq.setFileSize(item2.getFileSize());
addReq.setHstUid(item.getHstUid());
fileState = "DONE";
if ("tfw".equalsIgnoreCase(item2.getExtension())) {
if (tifCnt == 0) {
fileState = "NOTPAIR";
syncState = fileState;
} else if (tfwCnt > 1) {
fileState = "DUPLICATE";
syncState = fileState;
} else if (item2.getFileSize() == 0) {
fileState = "TYPEERROR";
syncState = fileState;
} else if (!FIleChecker.checkTfw(item2.getFullPath())) {
fileState = "TYPEERROR";
syncState = fileState;
}
item.setMapSheetPath(item2.getParentPath());
item.setSyncTfwFileName(item2.getFileNm());
} else if ("tif".equalsIgnoreCase(item2.getExtension())) {
if (tfwCnt == 0) {
fileState = "NOTPAIR";
syncState = fileState;
} else if (tifCnt > 1) {
fileState = "DUPLICATE";
syncState = fileState;
} else if (item2.getFileSize() == 0) {
fileState = "TYPEERROR";
syncState = fileState;
} else if (!FIleChecker.cmmndGdalInfo(item2.getFullPath())) {
fileState = "TYPEERROR";
syncState = fileState;
}
item.setMapSheetPath(item2.getParentPath());
item.setSyncTifFileName(item2.getFileNm());
}
addReq.setFileState(fileState);
DmlReturn DmlReturn = mngDataSave(addReq);
}
// 도엽별 파일 체크 완료로 변경
item.setDataState("DONE");
if (syncState.isEmpty()) {
syncState = "DONE";
}
item.setSyncState(syncState);
mngHstDataSyncStateUpdate(item);
}
// 사용할 수 있는 이전 년도 도엽 테이블 저장
mapSheetMngFileJobCoreService.saveSheetMngYear();
Long notyetCnt = this.mngDataStateDoneUpdate(mngYyyy);
}
public int checkIsNoFile(List<FileDto.Basic> basicList) {
if (basicList == null || basicList.size() == 0) {
return 0;
}
return basicList.size();
}
public Long mngDataStateDoneUpdate(int mngYyyy) {
Long notyetCnt = 0L;
if (mngYyyy > 0) {
notyetCnt = findByMngYyyyTargetMapSheetNotYetCount(mngYyyy);
if (notyetCnt == 0) {
mapSheetMngFileJobCoreService.mngDataState(mngYyyy, "DONE");
} else {
mapSheetMngFileJobCoreService.mngDataState(mngYyyy, "PROCESSING");
}
}
return notyetCnt;
}
public Long mapSheetAutoExceptionUpdate(int mngYyyy, String mapSheetNum) {
// 2025년 이전 파일싱크는 무조건 이전3년이 존재하지 않으므로 자동추론제외를 진행하지 않는다.(전년도 파일이 무조건 존재하는 것으로 리턴)
// if (syncAutoExceptionStartYear > mngYyyy) {
// return 1L;
// }
// int strtYyyy = mngYyyy - syncAutoExceptionBeforeYearCnt + 1;
int strtYyyy = 2020;
int endYyyy = mngYyyy;
// 본년도+이전년도가 3개년인 도엽 확인 -> 2020년도부터 현재까지
Long beforeCnt =
mapSheetMngFileJobCoreService.findByHstMapSheetBeforeYyyyListCount(
strtYyyy, endYyyy, mapSheetNum);
if (beforeCnt == 0) {
System.out.println("mapSheetAutoExceptionUpdate inference == 자동추론제외");
mapSheetMngFileJobCoreService.updateException5kMapSheet(
mapSheetNum, CommonUseStatus.AUTO_EXCEPT);
} else {
// 하나라도 있으면 USE
mapSheetMngFileJobCoreService.updateException5kMapSheet(mapSheetNum, CommonUseStatus.USE);
}
return beforeCnt;
}
public List<MngHstDto> findTargetMapSheetFileList(long targetNum, int pageSize) {
return mapSheetMngFileJobCoreService.findTargetMapSheetFileList(targetNum, pageSize);
}
public Long findByMngYyyyTargetMapSheetNotYetCount(int mngYyyy) {
return mapSheetMngFileJobCoreService.findByMngYyyyTargetMapSheetNotYetCount(mngYyyy);
}
public DmlReturn mngHstDataSyncStateUpdate(MngHstDto UpdateReq) {
return mapSheetMngFileJobCoreService.mngHstDataSyncStateUpdate(UpdateReq);
}
public DmlReturn mngDataSave(MngFileAddReq AddReq) {
return mapSheetMngFileJobCoreService.mngFileSave(AddReq);
}
public List<FileDto.Basic> getFilesDepthAll(SrchFilesDepthDto srchDto) {
Path startPath = Paths.get(srchDto.getDirPath());
int maxDepth = srchDto.getMaxDepth();
String dirPath = srchDto.getDirPath();
String targetFileNm = srchDto.getFileNm();
String extension = srchDto.getExtension();
String sortType = srchDto.getSortType();
int startPos = srchDto.getStartPos();
int endPos = srchDto.getEndPos();
int limit = endPos - startPos + 1;
Set<String> targetExtensions = createExtensionSet(extension);
List<FileDto.Basic> fileDtoList = new ArrayList<>();
SimpleDateFormat dttmFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
int fileTotCnt = 0;
long fileTotSize = 0;
try (Stream<Path> stream = Files.walk(startPath, maxDepth)) {
fileDtoList =
stream
.filter(Files::isRegularFile)
.filter(
p ->
extension == null
|| extension.equals("")
|| extension.equals("*")
|| targetExtensions.contains(extractExtension(p)))
.sorted(getFileComparator(sortType))
.filter(p -> p.getFileName().toString().contains(targetFileNm))
.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 FileDto.Basic(
fileNm, parentFolderNm, parentPath, fullPath, ext, fileSize, lastModified);
})
.collect(Collectors.toList());
// fileTotCnt = fileDtoList.size();
// fileTotSize = fileDtoList.stream().mapToLong(FileDto.Basic::getFileSize).sum();
} catch (IOException e) {
System.err.println("파일 I/O 오류 발생: " + e.getMessage());
}
return fileDtoList;
}
public 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 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 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;
}
}
}

View File

@@ -0,0 +1,43 @@
package com.kamco.cd.kamcoback.service;
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;
}
}

View File

@@ -0,0 +1,751 @@
package com.kamco.cd.kamcoback.utils;
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import com.kamco.cd.kamcoback.service.NameValidator;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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 java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
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<Basic> basicList = FIleChecker.getFilesFromAllDepth(dir, targetFileNm, extension);
return (int)
basicList.stream().filter(dto -> dto.getExtension().toString().equals(extension)).count();
}
public static Long getFileTotSize(List<Basic> files) {
Long fileTotSize = 0L;
if (files != null || files.size() > 0) {
fileTotSize = files.stream().mapToLong(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 deleteFile(String filePath) {
Path path = Paths.get(filePath);
try {
return Files.deleteIfExists(path);
} catch (IOException e) {
return false;
}
}
public static boolean validationMultipart(MultipartFile mfile) {
// 파일 유효성 검증
if (mfile == null || mfile.isEmpty() || mfile.getSize() == 0) {
return false;
}
return true;
}
public static void unzip(String fileName, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdirs(); // 대상 폴더가 없으면 생성
}
String zipFilePath = destDirectory + "/" + fileName;
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath))) {
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
File newFile = newFile(destDir, zipEntry);
if (zipEntry.isDirectory()) {
if (!newFile.isDirectory() && !newFile.mkdirs()) {
throw new IOException("디렉토리 생성 실패: " + newFile);
}
} else {
// 상위 디렉토리가 없는 경우 생성
File parent = newFile.getParentFile();
if (!parent.exists() && !parent.mkdirs()) {
throw new IOException("상위 디렉토리 생성 실패: " + parent);
}
// 실제 파일 쓰기
try (FileOutputStream fos = new FileOutputStream(newFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
File destFile = new File(destinationDir, zipEntry.getName());
String destDirPath = destinationDir.getCanonicalPath();
String destFilePath = destFile.getCanonicalPath();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("엔트리가 대상 디렉토리를 벗어남: " + zipEntry.getName());
}
return destFile;
}
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;
}
}
}

View File

@@ -0,0 +1,4 @@
server:
port: 9080

View File

@@ -0,0 +1,67 @@
server:
port: 9080
spring:
application:
name: imagery-make-dataset
profiles:
active: dev # 사용할 프로파일 지정 (ex. dev, prod, test)
datasource:
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
#url: jdbc:postgresql://localhost:5432/kamco_cds
username: kamco_cds
password: kamco_cds_Q!W@E#R$
hikari:
minimum-idle: 1
maximum-pool-size: 5
jpa:
hibernate:
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
properties:
hibernate:
jdbc:
batch_size: 50
default_batch_fetch_size: 100
logging:
level:
root: INFO
org.springframework.web: DEBUG
org.springframework.security: DEBUG
# 헬스체크 노이즈 핵심만 다운
org.springframework.security.web.FilterChainProxy: INFO
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
org.springframework.web.servlet.DispatcherServlet: INFO
# actuator
management:
health:
readinessstate:
enabled: true
livenessstate:
enabled: true
endpoint:
health:
probes:
enabled: true
show-details: always
endpoints:
jmx:
exposure:
exclude: "*"
web:
base-path: /monitor
exposure:
include:
- "health"
file:
#sync-root-dir: D:/kamco-nfs/images/
sync-root-dir: /kamco-nfs/images/
sync-tmp-dir: ${file.sync-root-dir}/tmp
sync-file-extention: tfw,tif
sync-auto-exception-start-year: 2025
sync-auto-exception-before-year-cnt: 3

View File

@@ -0,0 +1,67 @@
server:
port: 9080
spring:
application:
name: imagery-make-dataset
profiles:
active: local # 사용할 프로파일 지정 (ex. dev, prod, test)
datasource:
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
#url: jdbc:postgresql://localhost:5432/kamco_cds
username: kamco_cds
password: kamco_cds_Q!W@E#R$
hikari:
minimum-idle: 1
maximum-pool-size: 5
jpa:
hibernate:
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
properties:
hibernate:
jdbc:
batch_size: 50
default_batch_fetch_size: 100
logging:
level:
root: INFO
org.springframework.web: DEBUG
org.springframework.security: DEBUG
# 헬스체크 노이즈 핵심만 다운
org.springframework.security.web.FilterChainProxy: INFO
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
org.springframework.web.servlet.DispatcherServlet: INFO
# actuator
management:
health:
readinessstate:
enabled: true
livenessstate:
enabled: true
endpoint:
health:
probes:
enabled: true
show-details: always
endpoints:
jmx:
exposure:
exclude: "*"
web:
base-path: /monitor
exposure:
include:
- "health"
file:
#sync-root-dir: D:/kamco-nfs/images/
sync-root-dir: /kamco-nfs/images/
sync-tmp-dir: ${file.sync-root-dir}/tmp
sync-file-extention: tfw,tif
sync-auto-exception-start-year: 2025
sync-auto-exception-before-year-cnt: 3

View File

@@ -0,0 +1,67 @@
server:
port: 9080
spring:
application:
name: imagery-make-dataset
profiles:
active: prod # 사용할 프로파일 지정 (ex. dev, prod, test)
datasource:
url: jdbc:postgresql://192.168.2.127:15432/kamco_cds
#url: jdbc:postgresql://localhost:5432/kamco_cds
username: kamco_cds
password: kamco_cds_Q!W@E#R$
hikari:
minimum-idle: 1
maximum-pool-size: 5
jpa:
hibernate:
ddl-auto: update # 테이블이 없으면 생성, 있으면 업데이트
properties:
hibernate:
jdbc:
batch_size: 50
default_batch_fetch_size: 100
logging:
level:
root: INFO
org.springframework.web: DEBUG
org.springframework.security: DEBUG
# 헬스체크 노이즈 핵심만 다운
org.springframework.security.web.FilterChainProxy: INFO
org.springframework.security.web.authentication.AnonymousAuthenticationFilter: INFO
org.springframework.security.web.authentication.Http403ForbiddenEntryPoint: INFO
org.springframework.web.servlet.DispatcherServlet: INFO
# actuator
management:
health:
readinessstate:
enabled: true
livenessstate:
enabled: true
endpoint:
health:
probes:
enabled: true
show-details: always
endpoints:
jmx:
exposure:
exclude: "*"
web:
base-path: /monitor
exposure:
include:
- "health"
file:
#sync-root-dir: D:/kamco-nfs/images/
sync-root-dir: /kamco-nfs/images/
sync-tmp-dir: ${file.sync-root-dir}/tmp
sync-file-extention: tfw,tif
sync-auto-exception-start-year: 2025
sync-auto-exception-before-year-cnt: 3

View File

@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Chunk Upload Test</title>
</head>
<body>
<h2>대용량 파일 청크 업로드 테스트</h2>
* Chunk 테스트 사이즈 10M (10 * 1024 * 1024) - 성능에 따라 변경가능<br><br>
* 업로드 API선택</br></br>
<select name="apiUrl" id="apiUrl" style="width:600px;height:40px;">
<option value="/api/model/file-chunk-upload">모델파일Chunk업로드 ( /api/model/file-chunk-upload )</option>
<option value="/api/upload/file-chunk-upload">파일Chunk업로드(공통) ( /api/upload/file-chunk-upload )</option>
</select>
<br><br>
* 파일첨부<br><br>
<input type="file" id="chunkFile" style="height:40px;"><br><br>
<button onclick="startUpload()" style="height:40px;">업로드 시작</button>
<br><br><br><br>
* 업로드시 업로드 이력을 추적하기 위해 UUID생성해서 전달(파일병합시 사용)(script 예제참고)</br></br>
UUID : <input id="uuid" name="uuid" value="" style="width:300px;height:30px;" readonly><br><br>
* API 호출시 파일정보 추출해서 자동 할당해야 함.(script 예제참고)</br></br>
chunkIndex : <input style="height:30px;" id="chunkIndex" placeholder="chunkIndex" readonly><br><br>
chunkTotalIndex : <input style="height:30px;" id="chunkTotalIndex" placeholder="chunkTotalIndex" readonly ><br><br>
* API 호출시 파일정보 추출해서 자동 할당해야 함.(script 예제참고)</br></br>
fileSize : <input style="height:30px;" id="fileSize" placeholder="fileSize" readonly><br><br>
<!--
fileHash : <input id="fileHash" placeholder="fileHash"><br><br> -->
<br><br>
* 진행율(%)</br></br>
<div style="width:500px;height:30px;border:1px solid #cccccc;"><div id="prgssbar" style="width:100%;height:30px;background:#eeeeee;"></div></div>
<br><br>
* 결과메세지</br></br>
<div id="status" style="padding:20px;width:1200px;height:600px;border:1px solid #000000;"></div>
<script>
async function startUpload() {
const apiUrl = document.getElementById('apiUrl').value;
const file = document.getElementById('chunkFile').files[0];
const fileName = file.name;
//const datasetUid = Number(document.getElementById('datasetUid').value);
//const chunkIndex = document.getElementById('chunkIndex').value;
if (!file) return alert("파일을 선택하세요.");
const CHUNK_SIZE = 10 * 1024 * 1024; // 5MB
const fileSize = file.size;
var totalChunks = Math.ceil(fileSize / CHUNK_SIZE);
const chunkTotalIndex = totalChunks - 1;
const uuid = crypto.randomUUID(); // 고유 ID 생성
//var uuid = "";
document.getElementById('uuid').value = uuid;
document.getElementById('fileSize').value = file.size;
document.getElementById('chunkTotalIndex').value = chunkTotalIndex;
for (let i = 0; i < totalChunks; i++) {
//for (let i = 0; i < 1; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
document.getElementById('chunkIndex').value = i;
const formData = new FormData();
formData.append("uuid", uuid);
formData.append("fileSize", fileSize);
formData.append("fileName", fileName);
formData.append("chunkIndex", i);
formData.append("chunkTotalIndex", chunkTotalIndex);
formData.append("chunkFile", chunk);
try {
const response = await fetch(apiUrl, { method: 'POST', body: formData });
// 2. 응답 상태 확인 (200 OK 등)
if (!response.ok) {
throw new Error(`서버 에러: ${response.status}`);
}
// 3. 서버가 보낸 데이터 읽기 (JSON 형태라고 가정)
const result = await response.json();
document.getElementById('status').innerText = JSON.stringify(result, null, 2);
if( result.data.res != "success")
{
//오류 경고창 띄우는 것으로 처리하시면 됩니다.
break;
}
document.getElementById('prgssbar').style.width = result.data.uploadRate+"%";
} catch (error) {
console.error(`${i}번째 청크 업로드 실패:`, error);
break; // 오류 발생 시 중단
}
}
// 모든 청크 전송 후 최종 완료 요청
//var mergeResult = await completeUpload(uuid);
//document.getElementById('status').innerText = JSON.stringify(mergeResult, null, 2);
}
async function completeUpload(uuid) {
try {
const response = await fetch(`/api/upload/chunk-upload-complete/${uuid}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
});
if (!response.ok) {
throw new Error(`서ver 응답 에러: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error("완료 요청 중 오류 발생:", error);
}
}
</script>
</body>
</html>