refactor : biz 패키지 / 시스템 패키지 분리

pull/2/head
Kurt92 5 months ago
parent 10aa9a844d
commit 9865e6b31a

@ -2,7 +2,7 @@ package egovframework.config;
import egovframework.configProperties.InterceptorProperties;
import egovframework.interceptor.AuthInterceptor;
import go.kr.project.login.service.LoginService;
import go.kr.project.system.login.service.LoginService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ -47,7 +47,7 @@ public class SwaggerConfig {
public GroupedOpenApi loginApi() {
return GroupedOpenApi.builder()
.group("Login")
.packagesToScan("go.kr.project.login")
.packagesToScan("go.kr.project.system.login")
.pathsToMatch("/login/**")
.build();
}

@ -1,7 +1,7 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* packageName : go.kr.project.system.common.constant
* fileName : BatchConstants
* author :
* date : 2025-06-10

@ -5,7 +5,7 @@ import java.util.HashMap;
import java.util.Map;
/**
* packageName : go.kr.project.common.constant
* packageName : go.kr.project.system.common.constant
* fileName : FileContentTypeConstants
* author :
* date : 2025-05-17

@ -1,7 +1,7 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* packageName : go.kr.project.system.common.constant
* fileName : MessageConstants
* author :
* date : 2025-05-22

@ -1,7 +1,7 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* packageName : go.kr.project.system.common.constant
* fileName : SessionConstants
* author :
* date : 2025-05-22

@ -1,7 +1,7 @@
package egovframework.constant;
/**
* packageName : go.kr.project.common.constant
* packageName : go.kr.project.system.common.constant
* fileName : TilesConstants
* author :
* date : 2025-05-22

@ -8,10 +8,10 @@ import egovframework.filter.XssUtil;
import egovframework.util.ApiResponseEntity;
import egovframework.util.HttpServletUtil;
import egovframework.constant.SessionConstants;
import go.kr.project.login.mapper.LoginMapper;
import go.kr.project.login.model.SessionVO;
import go.kr.project.login.model.UserSessionVO;
import go.kr.project.login.service.LoginService;
import go.kr.project.system.login.mapper.LoginMapper;
import go.kr.project.system.login.model.SessionVO;
import go.kr.project.system.login.model.UserSessionVO;
import go.kr.project.system.login.service.LoginService;
import go.kr.project.system.menu.model.MenuVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@ -1,6 +1,6 @@
package egovframework.util;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@ -2,7 +2,7 @@ package egovframework.util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import egovframework.configProperties.FileUploadProperties;
import go.kr.project.common.model.FileVO;
import go.kr.project.system.common.model.FileVO;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

@ -1,8 +1,8 @@
package egovframework.util;
import egovframework.constant.SessionConstants;
import go.kr.project.login.model.LoginUserVO;
import go.kr.project.login.model.SessionVO;
import go.kr.project.system.login.model.LoginUserVO;
import go.kr.project.system.login.model.SessionVO;
import go.kr.project.system.group.model.GroupVO;
import go.kr.project.system.menu.model.MenuVO;
import go.kr.project.system.role.model.RoleVO;

@ -1,152 +0,0 @@
package go.kr.project.batch.config;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.job.SampleBatchJob;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.service.BatchJobService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* packageName : go.kr.project.batch.init
* fileName : BatchJobInitializer
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchJobInitializer {
private final BatchJobService batchJobService;
/**
*
* .
* .
*
* @param event
*/
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
log.info(BatchConstants.LOG_MSG_BATCH_INIT_SERVER_BOOT_DETECTED);
try {
// 서버 부팅 완료 후 5초 지연 - 이를 통해 서버가 완전히 안정화된 후 배치 작업을 초기화, 추후에 15초정도 늘릴예정
TimeUnit.SECONDS.sleep(5);
log.info(BatchConstants.LOG_MSG_BATCH_INIT_START);
// 샘플 배치 작업 등록 (초기 데이터가 없을 경우를 대비)
// 매 분마다 실행 (테스트용, 실제 운영에서는 적절한 주기로 변경)
//registerSampleBatchJob(BatchConstants.CRON_EVERY_MINUTE);
// 배치 작업 정보 테이블에서 작업 조회 및 스케줄링
initializeJobsFromDatabase();
log.info(BatchConstants.LOG_MSG_BATCH_INIT_COMPLETE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error(BatchConstants.LOG_MSG_BATCH_INIT_INTERRUPT_ERROR, e.getMessage(), e);
} catch (Exception e) {
log.error(String.format(BatchConstants.LOG_MSG_BATCH_INIT_ERROR, e.getMessage()), e);
}
}
/**
* .
* JOB_INFO_STATUS_DELETED .
*/
private void initializeJobsFromDatabase() {
try {
// 모든 배치 작업 정보 조회
BatchJobInfoVO paramVO = new BatchJobInfoVO();
List<BatchJobInfoVO> jobList = batchJobService.getBatchJobInfoList(paramVO);
if (jobList == null || jobList.isEmpty()) {
log.info(BatchConstants.LOG_MSG_BATCH_INIT_NO_JOBS);
return;
}
log.info(BatchConstants.LOG_MSG_BATCH_INIT_JOBS_COUNT, jobList.size());
// 각 작업을 스케줄러에 등록
for (BatchJobInfoVO job : jobList) {
try {
// JOB_INFO_STATUS_DELETED가 아닌 작업만 등록
if (BatchConstants.JOB_INFO_STATUS_DELETED.equals(job.getStatusCd())) {
log.info(BatchConstants.LOG_MSG_BATCH_INIT_SKIP_DELETED, job.getJobGroup(), job.getJobNm());
continue;
}
// 작업 클래스 로드
Class<?> jobClass = Class.forName(job.getJobClass());
// 스케줄러에 작업 등록
boolean result = batchJobService.scheduleJob(
(Class<? extends org.quartz.Job>) jobClass,
job.getJobNm(),
job.getJobGroup(),
job.getCronExpression(),
job.getJobDc()
);
// 일시중지일 경우 스케줄러 일시중지
if (BatchConstants.JOB_INFO_STATUS_PAUSED.equals(job.getStatusCd())) {
batchJobService.pauseJob(job.getJobNm(), job.getJobGroup());
}
if (result) {
log.info(BatchConstants.LOG_MSG_BATCH_INIT_SCHEDULE_SUCCESS, job.getJobGroup(), job.getJobNm());
} else {
log.warn(BatchConstants.LOG_MSG_BATCH_INIT_SCHEDULE_FAILED, job.getJobGroup(), job.getJobNm());
}
} catch (ClassNotFoundException e) {
log.error(BatchConstants.LOG_MSG_BATCH_INIT_CLASS_NOT_FOUND, job.getJobClass(), e);
} catch (Exception e) {
log.error(BatchConstants.LOG_MSG_BATCH_INIT_SCHEDULE_ERROR, job.getJobGroup(), job.getJobNm(), e.getMessage(), e);
}
}
} catch (Exception e) {
log.error(BatchConstants.LOG_MSG_BATCH_INIT_DB_ERROR, e.getMessage(), e);
}
}
/**
* .
* .
*/
private void registerSampleBatchJob(String cronExpression) {
String jobNm = BatchConstants.JOB_NAME_SAMPLE;
String jobGroup = BatchConstants.JOB_GROUP_SAMPLE;
try {
String description = BatchConstants.JOB_DESC_SAMPLE;
// 1. 배치 작업 정보를 데이터베이스에 등록
BatchJobInfoVO jobInfo = batchJobService.registerBatchJobInfo(
SampleBatchJob.class,
jobNm,
jobGroup,
cronExpression,
description
);
log.info(String.format(BatchConstants.LOG_MSG_BATCH_INIT_REGISTER_COMPLETE,
jobInfo != null ? jobInfo.getJobId() : "unknown"));
} catch (Exception e) {
log.error(String.format(BatchConstants.LOG_MSG_BATCH_INIT_REGISTER_ERROR, e.getMessage()), e);
}
}
}

@ -1,20 +0,0 @@
package go.kr.project.batch.config;
import org.springframework.context.annotation.Configuration;
/**
* packageName : go.kr.project.batch.config
* fileName : QuartzConfig
* author :
* date : 2025-06-10
* description : Quartz
* application.yml ,
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Configuration
public class QuartzConfig {
}

@ -1,307 +0,0 @@
package go.kr.project.batch.config;
import egovframework.constant.BatchConstants;
import egovframework.util.BatchSessionUtil;
import go.kr.project.batch.mapper.BatchJobMapper;
import go.kr.project.batch.model.BatchJobExecutionVO;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.util.ServerInfoUtil;
import go.kr.project.common.notification.service.NotificationService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.UUID;
/**
* packageName : go.kr.project.batch.listener
* fileName : BatchJobListener
* author :
* date : 2025-06-10
* description : JobListener
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@SuppressWarnings("deprecation")
public class QuartzJobListener implements JobListener {
private final BatchJobMapper batchJobMapper;
private final Scheduler scheduler;
private final NotificationService notificationService;
@Autowired
public QuartzJobListener(BatchJobMapper batchJobMapper, Scheduler scheduler, NotificationService notificationService) {
this.batchJobMapper = batchJobMapper;
this.scheduler = scheduler;
this.notificationService = notificationService;
// 스케줄러 인스턴스 ID 가져와서 설정
try {
String instanceId = scheduler.getSchedulerInstanceId();
String schedulerName = scheduler.getSchedulerName();
log.info("Quartz 스케줄러 초기화 - 이름: {}, 인스턴스 ID: {}", schedulerName, instanceId);
ServerInfoUtil.setQuartzInstanceId(instanceId);
} catch (SchedulerException e) {
log.error("Quartz 스케줄러 인스턴스 ID 가져오기 실패: {}", e.getMessage(), e);
}
}
@Override
public String getName() {
return "BatchJobListener";
}
/**
*
* TB_BATCH_JOB_EXECUTION .
*/
@Override
public void jobToBeExecuted(JobExecutionContext context) {
try {
JobKey jobKey = context.getJobDetail().getKey();
String jobNm = jobKey.getName();
String jobGroup = jobKey.getGroup();
log.info(String.format("배치 작업 실행 시작: %s.%s", jobGroup, jobNm));
// 실행 ID 생성
String executionId = UUID.randomUUID().toString();
// 상세 서버 정보 가져오기
String serverInfo = ServerInfoUtil.getDetailedServerInfo();
log.info("배치 작업 실행 서버 정보: {}", serverInfo);
// 배치 작업 실행 정보 생성
BatchJobExecutionVO execution = BatchJobExecutionVO.builder()
.executionId(executionId)
.jobNm(jobNm)
.jobGroup(jobGroup)
.startDttm(LocalDateTime.now())
.statusCd(BatchConstants.JOB_EXECUTION_STATUS_STARTED)
.serverInfo(serverInfo)
.build();
// 실행 정보를 데이터베이스에 저장
batchJobMapper.insertBatchJobExecution(execution);
// 배치 작업 시작 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_INFO,
String.format("배치 작업 실행 시작: %s.%s", jobGroup, jobNm));
// JobDataMap에 executionId 저장 (jobWasExecuted에서 사용)
context.getJobDetail().getJobDataMap().put(BatchConstants.JOB_DATA_EXECUTION_ID, executionId);
log.info(String.format("배치 작업 실행 정보 저장 완료: executionId=%s", executionId));
} catch (Exception e) {
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.trim().isEmpty()) {
errorMessage = e.getClass().getSimpleName() + " 발생";
}
log.error(String.format("배치 작업 로그 저장 중 오류 발생: %s", errorMessage), e);
}
}
/**
*
* TB_BATCH_JOB_EXECUTION .
*/
@Override
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
try {
JobKey jobKey = context.getJobDetail().getKey();
String jobNm = jobKey.getName();
String jobGroup = jobKey.getGroup();
// JobDataMap에서 executionId 가져오기
String executionId = (String) context.getJobDetail().getJobDataMap().get(BatchConstants.JOB_DATA_EXECUTION_ID);
if (executionId == null) {
log.warn(String.format("executionId를 찾을 수 없습니다: %s.%s", jobGroup, jobNm));
return;
}
// 실행 결과 정보 설정
String status;
String exitCd;
String exitMessage;
if (jobException != null) {
// 작업 실행 실패
status = BatchConstants.JOB_EXECUTION_STATUS_FAILED;
exitCd = BatchConstants.JOB_EXECUTION_EXIT_FAILED;
exitMessage = jobException.getMessage();
if (exitMessage == null || exitMessage.trim().isEmpty()) {
exitMessage = jobException.getClass().getSimpleName() + " 발생";
}
log.error(String.format("배치 작업 실행 실패: %s.%s - %s", jobGroup, jobNm, exitMessage));
// 배치 작업 실패 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_ERROR,
String.format("배치 작업 실행 실패: %s.%s - %s", jobGroup, jobNm, exitMessage));
} else {
// 작업 실행 성공 - JobDataMap에서 실제 배치 결과 종료 코드 확인
String batchResultExitCode = (String) context.getJobDetail().getJobDataMap().get("BATCH_RESULT_EXIT_CODE");
String batchResultMessage = (String) context.getJobDetail().getJobDataMap().get("BATCH_RESULT_MESSAGE");
if (batchResultExitCode != null) {
// 배치 작업에서 설정한 종료 코드를 기반으로 상태 결정
exitCd = batchResultExitCode;
if (BatchConstants.JOB_EXECUTION_EXIT_FAILED.equals(exitCd)) {
// 실패 종료 코드
status = BatchConstants.JOB_EXECUTION_STATUS_FAILED;
exitMessage = batchResultMessage != null ? batchResultMessage : "배치 작업이 실패했습니다.";
log.error(String.format("배치 작업 실행 실패: %s.%s - %s", jobGroup, jobNm, exitMessage));
// 배치 작업 실패 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_ERROR,
String.format("배치 작업 실행 실패: %s.%s - %s", jobGroup, jobNm, exitMessage));
} else if (BatchConstants.JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED.equals(exitCd)) {
status = BatchConstants.JOB_EXECUTION_STATUS_FAILED;
exitMessage = batchResultMessage != null ? batchResultMessage : "배치 작업이 부분적으로 완료되었습니다.";
log.error(String.format("배치 작업 부분 완료: %s.%s - %s", jobGroup, jobNm, exitMessage));
// 배치 작업 부분 완료 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_ERROR,
String.format("배치 작업 부분 완료: %s.%s - %s", jobGroup, jobNm, exitMessage));
} else {
// 완료 종료 코드
status = BatchConstants.JOB_EXECUTION_STATUS_COMPLETED;
exitMessage = batchResultMessage != null ? batchResultMessage : "배치 작업이 성공적으로 완료되었습니다.";
log.info(String.format("배치 작업 실행 완료: %s.%s - %s", jobGroup, jobNm, exitMessage));
// 배치 작업 성공 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_INFO,
String.format("배치 작업 실행 완료: %s.%s - %s", jobGroup, jobNm, exitMessage));
}
} else {
// 기본 완료 처리 (배치 결과 종료 코드가 설정되지 않은 경우)
status = BatchConstants.JOB_EXECUTION_STATUS_COMPLETED;
exitCd = BatchConstants.JOB_EXECUTION_EXIT_COMPLETED;
exitMessage = "배치 작업이 성공적으로 완료되었습니다.";
log.info(String.format("배치 작업 실행 완료: %s.%s", jobGroup, jobNm));
// 배치 작업 성공 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_INFO,
String.format("배치 작업 실행 완료: %s.%s", jobGroup, jobNm));
}
}
// 기존 실행 정보 조회하여 서버 정보 유지 또는 상세 서버 정보 사용
BatchJobExecutionVO existingExecution = batchJobMapper.selectBatchJobExecution(executionId);
String serverInfo = (existingExecution != null && existingExecution.getServerInfo() != null)
? existingExecution.getServerInfo()
: ServerInfoUtil.getDetailedServerInfo();
// 배치 작업 실행 정보 업데이트
BatchJobExecutionVO execution = BatchJobExecutionVO.builder()
.executionId(executionId)
.endDttm(LocalDateTime.now())
.statusCd(status)
.exitCd(exitCd)
.exitMessage(exitMessage)
.serverInfo(serverInfo)
.build();
batchJobMapper.updateBatchJobExecution(execution);
// 배치 작업 정보의 마지막 실행 ID 업데이트
try {
BatchJobInfoVO jobInfo = batchJobMapper.selectBatchJobInfoByNmAndGroup(jobNm, jobGroup);
if (jobInfo != null) {
batchJobMapper.updateBatchJobLastExecution(jobInfo.getJobId(), executionId);
}
} catch (Exception e) {
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.trim().isEmpty()) {
errorMessage = e.getClass().getSimpleName() + " 발생";
}
log.warn(String.format("배치 작업 정보 업데이트 중 오류 발생: %s", errorMessage));
// 업데이트 오류 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_WARN,
String.format("배치 작업 정보 업데이트 중 오류 발생: %s", errorMessage));
}
log.info(String.format("배치 작업 실행 결과 저장 완료: executionId=%s, status=%s", executionId, status));
// 배치 실행 완료 후 배치 종료코드가 COMPLETED가 아니면 알림 등록
if (!BatchConstants.JOB_EXECUTION_EXIT_COMPLETED.equals(status)) {
try {
// 배치 시스템 사용자 ID로 알림 등록
String batchUserId = BatchSessionUtil.getBatchUserId();
notificationService.registerBatchFailureNotification(
executionId, jobNm, jobGroup, status, exitMessage, batchUserId, null);
log.info("배치 실패 알림 등록 완료: executionId={}, status={}", executionId, status);
} catch (Exception notificationException) {
log.error("배치 실패 알림 등록 중 오류 발생: {}", notificationException.getMessage(), notificationException);
// 알림 등록 실패 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_ERROR,
String.format("배치 실패 알림 등록 중 오류 발생: %s", notificationException.getMessage()));
}
}
} catch (Exception e) {
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.trim().isEmpty()) {
errorMessage = e.getClass().getSimpleName() + " 발생";
}
log.error(String.format("배치 작업 로그 저장 중 오류 발생: %s", errorMessage), e);
}
}
/**
*
*/
@Override
public void jobExecutionVetoed(JobExecutionContext context) {
try {
JobKey jobKey = context.getJobDetail().getKey();
String jobNm = jobKey.getName();
String jobGroup = jobKey.getGroup();
log.warn(String.format("배치 작업 실행이 거부됨: %s.%s", jobGroup, jobNm));
// JobDataMap에서 executionId 가져오기
String executionId = (String) context.getJobDetail().getJobDataMap().get(BatchConstants.JOB_DATA_EXECUTION_ID);
if (executionId != null) {
// 배치 작업 실행 거부 로그 저장
batchJobMapper.insertBatchJobLog(executionId, BatchConstants.LOG_LEVEL_WARN,
String.format("배치 작업 실행이 거부됨: %s.%s", jobGroup, jobNm));
// 기존 실행 정보 조회하여 서버 정보 유지 또는 상세 서버 정보 사용
BatchJobExecutionVO existingExecution = batchJobMapper.selectBatchJobExecution(executionId);
String serverInfo = (existingExecution != null && existingExecution.getServerInfo() != null)
? existingExecution.getServerInfo()
: ServerInfoUtil.getDetailedServerInfo();
// 배치 작업 실행 정보 업데이트
BatchJobExecutionVO execution = BatchJobExecutionVO.builder()
.executionId(executionId)
.endDttm(LocalDateTime.now())
.statusCd(BatchConstants.JOB_EXECUTION_STATUS_VETOED)
.exitCd(BatchConstants.JOB_EXECUTION_EXIT_UNKNOWN)
.exitMessage("배치 작업 실행이 거부되었습니다.")
.serverInfo(serverInfo)
.build();
batchJobMapper.updateBatchJobExecution(execution);
} else {
log.warn(String.format("executionId를 찾을 수 없습니다: %s.%s", jobGroup, jobNm));
}
} catch (Exception e) {
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.trim().isEmpty()) {
errorMessage = e.getClass().getSimpleName() + " 발생";
}
log.error(String.format("배치 작업 로그 저장 중 오류 발생: %s", errorMessage), e);
}
}
}

@ -1,43 +0,0 @@
package go.kr.project.batch.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
/**
* packageName : go.kr.project.batch.config
* fileName : QuartzListenerConfig
* author :
* date : 2025-06-10
* description : Quartz JobListener
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class QuartzListenerConfig {
private final QuartzJobListener quartzBatchJobListener;
private final Scheduler scheduler;
/**
* JobListener .
* .
*/
@PostConstruct
public void addJobListener() {
try {
scheduler.getListenerManager().addJobListener(quartzBatchJobListener);
log.info("BatchJobListener가 성공적으로 등록되었습니다.");
} catch (Exception e) {
log.error("JobListener 등록 중 오류 발생: {}", e.getMessage(), e);
throw new RuntimeException("JobListener 등록 중 오류 발생", e);
}
}
}

@ -1,467 +0,0 @@
package go.kr.project.batch.controller;
import egovframework.constant.BatchConstants;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import go.kr.project.batch.model.BatchJobExecutionVO;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.model.BatchJobLogVO;
import go.kr.project.batch.service.BatchJobService;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.system.code.model.CodeDetailVO;
import io.swagger.v3.oas.annotations.Operation;
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 lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* packageName : go.kr.project.batch.controller
* fileName : BatchJobController
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Controller
@RequestMapping("/batch")
@RequiredArgsConstructor
@Tag(name = "배치 작업", description = "배치 작업 관련 API")
public class BatchJobController {
private final BatchJobService batchJobService;
private final CommonCodeService commonCodeService;
/**
* .
*
* @param model
* @return
*/
@Operation(summary = "배치 작업 목록 페이지", description = "배치 작업 목록 페이지를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 목록 페이지 조회 성공")
})
@GetMapping("/list.do")
public String batchJobList(Model model) {
List<CodeDetailVO> jobStatusCodeList = commonCodeService.selectCodeDetailListByGroupId("BATCH_JOB_STATUS");
model.addAttribute("jobStatusCodeList", jobStatusCodeList);
return "batch/list" + TilesConstants.BASE;
}
/**
* . (AJAX)
*
* @return
*/
@Operation(summary = "배치 작업 목록 조회 (AJAX)", description = "배치 작업 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 목록 조회 성공")
})
@PostMapping("/list.ajax")
@ResponseBody
public ResponseEntity<?> getBatchJobs(@ModelAttribute BatchJobInfoVO paramVO) {
// 배치 작업 목록 조회
List<BatchJobInfoVO> jobs = batchJobService.getBatchJobInfoList(paramVO);
paramVO.setTotalCount(jobs.size());
// 최근 48시간 내 실패한 작업 목록 조회
List<BatchJobInfoVO> recentlyFailedJobs = batchJobService.getRecentlyFailedJobs();
// 실패한 작업 정보 매핑 및 평균 소요시간 계산
for (BatchJobInfoVO job : jobs) {
// 최근 48시간 내 실패한 작업인지 확인
if (recentlyFailedJobs != null && !recentlyFailedJobs.isEmpty()) {
for (BatchJobInfoVO failedJob : recentlyFailedJobs) {
if (job.getJobNm().equals(failedJob.getJobNm()) &&
job.getJobGroup().equals(failedJob.getJobGroup())) {
job.setRecentlyFailed(true);
break;
}
}
}
// 평균 소요시간 계산
if (job.getJobNm() != null && job.getJobGroup() != null) {
String avgDuration = batchJobService.calculateAverageDuration(job.getJobNm(), job.getJobGroup());
job.setAvgDuration(avgDuration);
}
}
return ApiResponseUtil.successWithGrid(jobs, paramVO);
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
@Operation(summary = "배치 작업 즉시 실행", description = "배치 작업을 즉시 실행합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 실행 성공"),
@ApiResponse(responseCode = "400", description = "배치 작업 실행 실패")
})
@PostMapping("/trigger.ajax")
public ResponseEntity<?> triggerJob(
@RequestParam String jobId,
@RequestParam String jobNm,
@RequestParam String jobGroup) {
BatchJobInfoVO vo = batchJobService.getBatchJobInfo(jobId);
if (vo == null || vo.getStatusCd().equals(BatchConstants.JOB_INFO_STATUS_DELETED)) {
return ApiResponseUtil.error("배치 상태가 '삭제' 입니다.");
}
boolean result = batchJobService.triggerJob(jobNm, jobGroup);
if (result) {
return ApiResponseUtil.success("배치 작업이 성공적으로 실행되었습니다.");
} else {
return ApiResponseUtil.error("배치 작업 실행에 실패했습니다.");
}
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
@Operation(summary = "배치 작업 일시 중지", description = "배치 작업을 일시 중지합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 일시 중지 성공"),
@ApiResponse(responseCode = "400", description = "배치 작업 일시 중지 실패")
})
@PostMapping("/pause.ajax")
public ResponseEntity<?> pauseJob(
@RequestParam String jobId,
@RequestParam String jobNm,
@RequestParam String jobGroup) {
BatchJobInfoVO vo = batchJobService.getBatchJobInfo(jobId);
if (vo == null || !vo.getStatusCd().equals(BatchConstants.JOB_INFO_STATUS_ACTIVE)) {
return ApiResponseUtil.error("배치 상태가 '활성'이 아닙니다.");
}
// 스케줄러에서 작업 일시 중지
boolean result = batchJobService.pauseJob(jobNm, jobGroup);
if (result) {
// 데이터베이스에 상태 업데이트
boolean updateResult = batchJobService.updateJobStatus(jobNm, jobGroup, BatchConstants.JOB_INFO_STATUS_PAUSED);
if (updateResult) {
return ApiResponseUtil.success("배치 작업이 성공적으로 일시 중지되었습니다.");
} else {
log.warn("배치 작업은 일시 중지되었으나 상태 업데이트에 실패했습니다: {}.{}", jobGroup, jobNm);
return ApiResponseUtil.success("배치 작업이 일시 중지되었으나 상태 업데이트에 실패했습니다.");
}
} else {
return ApiResponseUtil.error("배치 작업 일시 중지에 실패했습니다.");
}
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
@Operation(summary = "배치 작업 재개", description = "배치 작업을 재개합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 재개 성공"),
@ApiResponse(responseCode = "400", description = "배치 작업 재개 실패")
})
@PostMapping("/resume.ajax")
public ResponseEntity<?> resumeJob(
@RequestParam String jobId,
@RequestParam String jobNm,
@RequestParam String jobGroup) {
BatchJobInfoVO vo = batchJobService.getBatchJobInfo(jobId);
if (vo == null || !vo.getStatusCd().equals(BatchConstants.JOB_INFO_STATUS_PAUSED)) {
return ApiResponseUtil.error("배치 상태가 '일시 중지'가 아닙니다.");
}
// 스케줄러에서 작업 재개
boolean result = batchJobService.resumeJob(jobNm, jobGroup);
if (result) {
// 데이터베이스에 상태 업데이트
boolean updateResult = batchJobService.updateJobStatus(jobNm, jobGroup, BatchConstants.JOB_INFO_STATUS_ACTIVE);
if (updateResult) {
return ApiResponseUtil.success("배치 작업이 성공적으로 재개되었습니다.");
} else {
log.warn("배치 작업은 재개되었으나 상태 업데이트에 실패했습니다: {}.{}", jobGroup, jobNm);
return ApiResponseUtil.success("배치 작업이 재개되었으나 상태 업데이트에 실패했습니다.");
}
} else {
return ApiResponseUtil.error("배치 작업 재개에 실패했습니다.");
}
}
/**
* ().
*
* @param jobNm
* @param jobGroup
* @return
*/
@Operation(summary = "배치 작업 삭제", description = "배치 작업을 완전히 중지(삭제)합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 삭제 성공"),
@ApiResponse(responseCode = "400", description = "배치 작업 삭제 실패")
})
@PostMapping("/delete.ajax")
public ResponseEntity<?> deleteJob(
@RequestParam String jobId,
@RequestParam String jobNm,
@RequestParam String jobGroup) {
BatchJobInfoVO vo = batchJobService.getBatchJobInfo(jobId);
if (vo == null || vo.getStatusCd().equals(BatchConstants.JOB_INFO_STATUS_DELETED)) {
return ApiResponseUtil.error("배치 상태가 이미 '삭제' 입니다.");
}
// 스케줄러에서 작업 삭제
boolean result = batchJobService.deleteJob(jobNm, jobGroup);
if (result) {
// 데이터베이스에 상태 업데이트 (삭제 상태로 변경)
boolean updateResult = batchJobService.updateJobStatus(jobNm, jobGroup, BatchConstants.JOB_INFO_STATUS_DELETED);
if (updateResult) {
return ApiResponseUtil.success("배치 작업이 성공적으로 삭제되었습니다.");
} else {
log.warn("배치 작업은 삭제되었으나 상태 업데이트에 실패했습니다: {}.{}", jobGroup, jobNm);
return ApiResponseUtil.success("배치 작업이 삭제되었으나 상태 업데이트에 실패했습니다.");
}
} else {
return ApiResponseUtil.error("배치 작업 삭제에 실패했습니다.");
}
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @param model
* @return
*/
@Operation(summary = "배치 작업 실행 결과 페이지", description = "배치 작업 실행 결과 페이지를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 실행 결과 페이지 조회 성공")
})
@GetMapping("/execution.do")
public String batchJobExecution(@RequestParam String jobNm, @RequestParam String jobGroup, Model model) {
model.addAttribute("jobNm", jobNm);
model.addAttribute("jobGroup", jobGroup);
List<CodeDetailVO> statusCodeList = commonCodeService.selectCodeDetailListByGroupId("BATCH_EXEC_STATUS");
model.addAttribute("statusCodeList", statusCodeList);
List<CodeDetailVO> exitCodeList = commonCodeService.selectCodeDetailListByGroupId("BATCH_EXIT_CODE");
model.addAttribute("exitCodeList", exitCodeList);
return "batch/execution" + TilesConstants.BASE;
}
/**
* .
*
* @param paramVO
* @return
*/
@Operation(summary = "배치 작업 실행 결과 목록 조회 (AJAX)", description = "배치 작업 실행 결과 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 실행 결과 목록 조회 성공")
})
@PostMapping("/execution.ajax")
public ResponseEntity<?> getBatchJobExecutions(@ModelAttribute BatchJobExecutionVO paramVO) {
// 총 게시물 수 조회
int totalCount = batchJobService.getBatchJobExecutionTotalCount(paramVO);
paramVO.setTotalCount(totalCount);
// 페이징 처리를 위한 설정
paramVO.setPagingYn("Y");
List<BatchJobExecutionVO> executions = batchJobService.getBatchJobExecutionList(paramVO);
return ApiResponseUtil.successWithGrid(executions, paramVO);
}
/**
* .
*
* @param executionId ID
* @param model
* @return
*/
@Operation(summary = "배치 작업 로그 페이지", description = "배치 작업 로그 페이지를 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 로그 페이지 조회 성공")
})
@GetMapping("/log.do")
public String batchJobLog(@RequestParam String executionId, Model model) {
model.addAttribute("executionId", executionId);
return "batch/log" + TilesConstants.BASE;
}
/**
* .
*
* @param paramVO BatchJobExecutionVO
* @return
*/
@Operation(summary = "배치 작업 실행 로그 목록 조회 (AJAX)", description = "배치 작업 실행 로그 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 실행 로그 목록 조회 성공")
})
@PostMapping("/log.ajax")
public ResponseEntity<?> getBatchJobLogs(@ModelAttribute BatchJobLogVO paramVO) {
List<BatchJobLogVO> logs = batchJobService.getBatchJobLogList(paramVO);
// 총 게시물 수 조회
paramVO.setTotalCount(logs.size());
return ApiResponseUtil.successWithGrid(logs, paramVO);
}
/**
*
*
* @param model
* @return
*/
@Operation(summary = "배치 작업 등록 폼", description = "배치 작업 등록 폼을 반환합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 등록 폼 반환 성공")
})
@PostMapping("/batchJobForm_layerPop.ajax")
public String getBatchJobForm(Model model) {
// go.kr.project.batch.job 패키지 내의 모든 Job 구현 클래스 스캔
List<Class<?>> jobClasses = scanJobClasses();
model.addAttribute("jobClasses", jobClasses);
return "batch/batchJobForm_layerPop";
}
/**
* go.kr.project.batch.job Job .
*
* @return Job
*/
private List<Class<?>> scanJobClasses() {
List<Class<?>> jobClasses = new ArrayList<>();
try {
// 클래스 스캐너 생성 (인터페이스, 추상 클래스 제외), false 로 바꾸면 인터페이스, 추상 클래스도 포함
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(true);
// Job 인터페이스를 구현한 클래스만 필터링
scanner.addIncludeFilter(new AssignableTypeFilter(Job.class));
// go.kr.project.batch.job 패키지 스캔
String packageName = "go.kr.project.batch.job";
Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(packageName);
// 스캔된 클래스 로드
for (BeanDefinition beanDefinition : beanDefinitions) {
String className = beanDefinition.getBeanClassName();
Class<?> clazz = Class.forName(className);
jobClasses.add(clazz);
log.info("스캔된 Job 클래스: {}", className);
}
} catch (Exception e) {
log.error("Job 클래스 스캔 중 오류 발생: {}", e.getMessage(), e);
}
return jobClasses;
}
/**
*
*
* @param paramVO
* @return
*/
@Operation(summary = "배치 작업 등록 (AJAX)", description = "배치 작업을 등록합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "배치 작업 등록 성공"),
@ApiResponse(responseCode = "400", description = "배치 작업 등록 실패")
})
@PostMapping("/register.ajax")
public ResponseEntity<?> registerBatchJob(@RequestBody BatchJobInfoVO paramVO) {
try {
// 필수 파라미터 검증
if (paramVO.getJobClass() == null || paramVO.getJobNm() == null || paramVO.getJobGroup() == null || paramVO.getCronExpression() == null) {
return ApiResponseUtil.error("필수 파라미터가 누락되었습니다.");
}
// 작업 클래스 로드
Class<?> clazz = Class.forName(paramVO.getJobClass());
if (!Job.class.isAssignableFrom(clazz)) {
return ApiResponseUtil.error("유효한 배치 작업 클래스가 아닙니다.");
}
BatchJobInfoVO batchJobInfoVO = batchJobService.getBatchJobInfoByNmAndGroup(paramVO.getJobNm(), paramVO.getJobGroup());
if ( batchJobInfoVO != null ) {
return ApiResponseUtil.error("이미 등록된 작업이름, 작업그룹 입니다.");
}
// 스케줄러에 작업 등록
boolean result = batchJobService.scheduleJob(
(Class<? extends org.quartz.Job>) clazz,
paramVO.getJobNm(),
paramVO.getJobGroup(),
paramVO.getCronExpression(),
paramVO.getJobDc()
);
if( !result ){
return ApiResponseUtil.error("배치 스케줄러 등록에 실패했습니다.");
}
// 배치 작업 등록
BatchJobInfoVO jobInfo = batchJobService.registerBatchJobInfo(
(Class<? extends Job>) clazz,
paramVO.getJobNm(),
paramVO.getJobGroup(),
paramVO.getCronExpression(),
paramVO.getJobDc()
);
if (jobInfo != null) {
return ApiResponseUtil.success(jobInfo, "배치 작업이 성공적으로 등록되었습니다.");
} else {
return ApiResponseUtil.error("배치 작업 등록에 실패했습니다.");
}
} catch (ClassNotFoundException e) {
return ApiResponseUtil.error("배치 작업 클래스를 찾을 수 없습니다: " + e.getMessage());
} catch (Exception e) {
return ApiResponseUtil.error("배치 작업 등록 중 오류가 발생했습니다: " + e.getMessage());
}
}
}

@ -1,288 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import egovframework.util.SessionUtil;
import go.kr.project.batch.model.BatchFileDataVO;
import go.kr.project.batch.model.BatchJobResult;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* packageName : go.kr.project.batch.job
* fileName : AbstractBatchJob
* author :
* date : 2025-07-15
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-07-15
*/
@Slf4j
public abstract class AbstractBatchJob implements Job {
// ===========================================================
// 상수 정의
// ===========================================================
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BatchConstants.DATE_FORMAT_DEFAULT);
// ===========================================================
// 메인 실행 메소드
// ===========================================================
/**
* .
*
* @param context
* @throws JobExecutionException
*/
@Override
public final void execute(JobExecutionContext context) throws JobExecutionException {
String jobName = context.getJobDetail().getKey().getName();
String jobGroup = context.getJobDetail().getKey().getGroup();
logJobStart(context, jobGroup, jobName, LocalDateTime.now().format(formatter));
try {
logBatchSessionInfo();
// 하위 클래스에서 구현한 비즈니스 로직 실행
BatchJobResult result = processBatchJob(context);
// 결과가 null이 아닌 경우에만 결과 처리 (복잡한 배치 작업용)
if (result != null) {
handleJobResult(context, result, jobGroup, jobName, LocalDateTime.now().format(formatter));
} else {
// 간단한 배치 작업의 경우 기본 완료 처리
handleJobComplete(context, jobGroup, jobName, LocalDateTime.now().format(formatter));
}
} catch (JobExecutionException e) {
throw e;
} catch (Exception e) {
handleJobError(context, jobGroup, jobName, e);
}
}
// ===========================================================
// 추상 메소드 - 하위 클래스에서 구현 필요
// ===========================================================
/**
* .
* .
*
* @param context
* @return ( null )
* @throws Exception
*/
protected abstract BatchJobResult processBatchJob(JobExecutionContext context) throws Exception;
// ===========================================================
// 배치 작업 로깅 메소드들
// ===========================================================
/**
* .
*/
private void logJobStart(JobExecutionContext context, String jobGroup, String jobName, String currentTime) {
String startMessage = String.format(BatchConstants.LOG_MSG_BATCH_JOB_START, jobGroup, jobName, currentTime);
BatchJobLogUtil.info(context, startMessage);
}
/**
* .
*/
private void logBatchSessionInfo() {
try {
// 배치 작업에서 세션 정보 접근 시도
String userId = SessionUtil.getUserId();
String userAccount = SessionUtil.getUserAccount();
boolean isLogin = SessionUtil.isLogin();
boolean isSystem = SessionUtil.isSystem();
if (userId != null && userAccount != null) {
// 웹 컨텍스트에서 실행된 경우
log.info("배치 작업 실행 - 사용자: {}, 계정: {}, 로그인: {}, 시스템권한: {}",
userId, userAccount, isLogin, isSystem);
} else {
// 배치 작업 컨텍스트에서 실행된 경우
String batchUserId = SessionUtil.getBatchUserId();
String batchUserAccount = SessionUtil.getBatchUserAccount();
boolean isBatchSystem = SessionUtil.isBatchSystem();
log.info("배치 작업 실행 (배치 컨텍스트) - 배치사용자: {}, 배치계정: {}, 시스템권한: {}",
batchUserId, batchUserAccount, isBatchSystem);
}
} catch (Exception e) {
log.warn("배치 작업 세션 정보 로깅 중 오류 발생: {}", e.getMessage());
}
}
// ===========================================================
// 배치 작업 결과 처리 메소드들
// ===========================================================
/**
* .
*/
private void handleJobResult(JobExecutionContext context, BatchJobResult result,
String jobGroup, String jobName, String currentTime) throws JobExecutionException {
String exitCd = result.getExitCd();
// JobExecutionContext에 배치 결과 종료 코드 저장 (QuartzJobListener에서 사용)
context.getJobDetail().getJobDataMap().put("BATCH_RESULT_EXIT_CODE", exitCd);
context.getJobDetail().getJobDataMap().put("BATCH_RESULT_MESSAGE", getBatchResultMessage(result));
if (BatchConstants.JOB_EXECUTION_EXIT_FAILED.equals(exitCd)) {
// 전체 실패 처리
handleJobFailed(context, result, jobGroup, jobName, currentTime);
} else if (BatchConstants.JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED.equals(exitCd)) {
// 부분 완료 처리
handlePartialComplete(context, result, jobGroup, jobName, currentTime);
} else {
// 정상 완료 처리
handleJobComplete(context, jobGroup, jobName, currentTime);
}
}
/**
* .
*/
private void handlePartialComplete(JobExecutionContext context, BatchJobResult result,
String jobGroup, String jobName, String currentTime) throws JobExecutionException {
String message = String.format(BatchConstants.LOG_MSG_BATCH_JOB_PARTIAL_COMPLETE,
jobGroup, jobName, result.getTotalItems(), result.getProcessedItems(), result.getSuccessItems(), result.getErrorItems(), currentTime);
BatchJobLogUtil.error(context, message);
}
/**
* .
*/
private void handleJobFailed(JobExecutionContext context, BatchJobResult result,
String jobGroup, String jobName, String currentTime) throws JobExecutionException {
String message = String.format(BatchConstants.LOG_MSG_BATCH_JOB_FAILED,
jobGroup, jobName, result.getTotalItems(), result.getProcessedItems(), result.getSuccessItems(), result.getErrorItems(), currentTime);
BatchJobLogUtil.error(context, message);
}
/**
* .
*/
private void handleJobComplete(JobExecutionContext context, String jobGroup, String jobName, String currentTime) {
String message = String.format(BatchConstants.LOG_MSG_BATCH_JOB_COMPLETE, jobGroup, jobName, currentTime);
BatchJobLogUtil.info(context, message);
}
/**
* .
*/
private void handleJobError(JobExecutionContext context, String jobGroup, String jobName, Exception e) throws JobExecutionException {
String errorMessage = String.format(BatchConstants.LOG_MSG_BATCH_JOB_ERROR, jobGroup, jobName, e.getMessage());
BatchJobLogUtil.error(context, errorMessage, e);
throw new JobExecutionException(errorMessage, e, false);
}
// ===========================================================
// 유틸리티 메소드들
// ===========================================================
/**
* .
* .
*/
protected String getBatchResultMessage(BatchJobResult result) {
if (BatchConstants.JOB_EXECUTION_EXIT_FAILED.equals(result.getExitCd())) {
return String.format("배치 작업 실패 - 처리: %d개, 성공: %d개, 실패: %d개",
result.getProcessedItems(), result.getSuccessItems(), result.getErrorItems());
} else if (BatchConstants.JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED.equals(result.getExitCd())) {
return String.format("배치 작업 부분 완료 - 처리: %d개, 성공: %d개, 실패: %d개",
result.getProcessedItems(), result.getSuccessItems(), result.getErrorItems());
} else {
return String.format("배치 작업 완료 - 처리: %d개, 성공: %d개, 실패: %d개",
result.getProcessedItems(), result.getSuccessItems(), result.getErrorItems());
}
}
/**
* .
* .
*/
protected void logBatchConfiguration(JobExecutionContext context, String... configMessages) {
for (String message : configMessages) {
BatchJobLogUtil.info(context, message);
}
}
/**
* .
* .
*
* @param context
* @param processedCount
* @param errorCount
* @param fileType (, : "파일", "ZIP 파일", "재처리 파일")
* @return
*/
protected String determineJobExitCode(JobExecutionContext context, int processedCount, int errorCount, String fileType) {
if (processedCount == errorCount && processedCount > 0) {
BatchJobLogUtil.info(context, String.format("전체 %s에서 에러가 발생하여 실패 종료 코드로 설정합니다. (총 %d개 %s 중 %d개 %s에서 에러 발생)",
fileType, processedCount, fileType, errorCount, fileType));
return BatchConstants.JOB_EXECUTION_EXIT_FAILED;
} else if (errorCount > 0) {
BatchJobLogUtil.info(context, String.format("일부 %s에서 에러가 발생하여 부분 완료 종료 코드로 설정합니다. (총 %d개 %s 중 %d개 %s에서 에러 발생)",
fileType, processedCount, fileType, errorCount, fileType));
return BatchConstants.JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED;
} else {
BatchJobLogUtil.info(context, String.format("모든 %s이 정상적으로 처리되었습니다. 배치 작업 정상 완료.", fileType));
return BatchConstants.JOB_EXECUTION_EXIT_COMPLETED;
}
}
// ===========================================================
// 공통 내부 클래스들
// ===========================================================
/**
*
*/
@lombok.Getter
protected static class FileProcessingResult {
private int processedCount = 0;
private int successCount = 0;
private int errorCount = 0;
public void incrementProcessed() { processedCount++; }
public void incrementSuccess() { successCount++; }
public void incrementError() { errorCount++; }
}
/**
*
*/
@lombok.Getter
@lombok.Setter
protected static class FileLineProcessingResult {
private final List<BatchFileDataVO> allData = new ArrayList<>();
private boolean hasError = false;
public void addSuccessData(BatchFileDataVO data) {
allData.add(data);
}
public void addErrorData(BatchFileDataVO data) {
allData.add(data);
}
public boolean hasError() {
return hasError;
}
}
}

@ -1,87 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.model.BatchJobResult;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* packageName : go.kr.project.batch.job
* fileName : SampleBatchJob
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지 (클러스터링 환경에서 중복 실행 방지)
public class SampleBatchJob extends AbstractBatchJob {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BatchConstants.DATE_FORMAT_DEFAULT);
/**
* .
* .
*
* @param context
* @return ( null )
* @throws Exception
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
BatchJobLogUtil.info(context, "샘플 배치 작업 실행 중...");
// 현재 시간 로깅
BatchJobLogUtil.info(context, String.format("현재 시간: %s",
LocalDateTime.now().format(formatter)));
// 간단한 비즈니스 로직 시뮬레이션
int processedItems = 0;
for (int i = 0; i < 10; i++) {
BatchJobLogUtil.info(context, String.format("항목 %d 처리 중...", i + 1));
processedItems++;
//if( i == 6 ){
// throw new RuntimeException("강제 익셉션 테스트");
//}
// 각 항목 처리 시 짧은 지연 추가 (실제 작업 시뮬레이션)
try {
Thread.sleep(BatchConstants.TIME_ITEM_PROCESS_DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
}
BatchJobLogUtil.info(context, String.format("총 %d개 항목 처리 완료", processedItems));
// 작업 지연 시뮬레이션 (테스트를 위해 짧게 설정)
try {
// 테스트를 위해 3초로 줄임
//Thread.sleep(1000) = 1초
//Thread.sleep(60000) = 1분
//Thread.sleep(180000) = 3분
//Thread.sleep(300000) = 5분
Thread.sleep(BatchConstants.TIME_JOB_DELAY_TEST);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
// 간단한 배치 작업이므로 null 반환 (AbstractBatchJob에서 기본 완료 처리)
return null;
}
}

@ -1,87 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.model.BatchJobResult;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* packageName : go.kr.project.batch.job
* fileName : SampleBatchJob
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지 (클러스터링 환경에서 중복 실행 방지)
public class SampleBatchJob2 extends AbstractBatchJob {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BatchConstants.DATE_FORMAT_DEFAULT);
/**
* .
* .
*
* @param context
* @return ( null )
* @throws Exception
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
BatchJobLogUtil.info(context, "샘플 배치 작업 실행 중...");
// 현재 시간 로깅
BatchJobLogUtil.info(context, String.format("현재 시간: %s",
LocalDateTime.now().format(formatter)));
// 간단한 비즈니스 로직 시뮬레이션
int processedItems = 0;
for (int i = 0; i < 10; i++) {
BatchJobLogUtil.info(context, String.format("항목 %d 처리 중...", i + 1));
processedItems++;
if( i == 6 ){
throw new RuntimeException("강제 익셉션 테스트");
}
// 각 항목 처리 시 짧은 지연 추가 (실제 작업 시뮬레이션)
try {
Thread.sleep(BatchConstants.TIME_ITEM_PROCESS_DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
}
BatchJobLogUtil.info(context, String.format("총 %d개 항목 처리 완료", processedItems));
// 작업 지연 시뮬레이션 (테스트를 위해 짧게 설정)
try {
// 테스트를 위해 3초로 줄임
//Thread.sleep(1000) = 1초
//Thread.sleep(60000) = 1분
//Thread.sleep(180000) = 3분
//Thread.sleep(300000) = 5분
Thread.sleep(BatchConstants.TIME_JOB_DELAY_TEST);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
// 간단한 배치 작업이므로 null 반환 (AbstractBatchJob에서 기본 완료 처리)
return null;
}
}

@ -1,155 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.model.BatchJobResult;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* packageName : go.kr.project.batch.job
* fileName : SampleBatchJob
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지 (클러스터링 환경에서 중복 실행 방지)
public class SampleBatchJob3 extends AbstractBatchJob {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(BatchConstants.DATE_FORMAT_DEFAULT);
/**
* .
* .
*
* @param context
* @return
* @throws Exception
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
BatchJobLogUtil.info(context, "샘플 배치 작업 실행 중...");
// 현재 시간 로깅
BatchJobLogUtil.info(context, String.format("현재 시간: %s",
LocalDateTime.now().format(formatter)));
// 총 10,000개의 데이터를 처리하는 로직
final int TOTAL_ITEMS = 10000;
final int CHUNK_SIZE = 100;
int totalProcessedItems = 0;
int totalChunks = TOTAL_ITEMS / CHUNK_SIZE;
BatchJobLogUtil.info(context, String.format("총 %d개 데이터를 %d개씩 %d개 청크로 처리합니다.",
TOTAL_ITEMS, CHUNK_SIZE, totalChunks));
// 청크 단위로 처리
for (int chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
int startItem = chunkIndex * CHUNK_SIZE;
int endItem = startItem + CHUNK_SIZE - 1;
BatchJobLogUtil.info(context, String.format("청크 %d/%d 처리 시작 (항목 %d-%d)",
chunkIndex + 1, totalChunks, startItem + 1, endItem + 1));
try {
// 청크 내 아이템 처리
int chunkProcessedItems = processChunk(context, startItem, CHUNK_SIZE);
totalProcessedItems += chunkProcessedItems;
BatchJobLogUtil.info(context, String.format("청크 %d/%d 처리 완료: %d개 항목 처리됨",
chunkIndex + 1, totalChunks, chunkProcessedItems));
} catch (Exception e) {
// 청크 처리 중 오류 발생 시 해당 청크만 건너뛰고 다음 청크 처리
BatchJobLogUtil.error(context, String.format("청크 %d/%d 처리 중 오류 발생: %s - 이 청크를 건너뛰고 계속 진행합니다.",
chunkIndex + 1, totalChunks, e.getMessage()), e);
}
}
BatchJobLogUtil.info(context, String.format("총 %d개 항목 처리 완료", totalProcessedItems));
BatchJobLogUtil.info(context, String.format("총 %d개 청크 중 %d개 항목 처리 완료",
totalChunks, totalProcessedItems));
// 작업 지연 시뮬레이션 (테스트를 위해 짧게 설정)
try {
// 테스트를 위해 3초로 줄임
//Thread.sleep(1000) = 1초
//Thread.sleep(60000) = 1분
//Thread.sleep(180000) = 3분
//Thread.sleep(300000) = 5분
Thread.sleep(BatchConstants.TIME_JOB_DELAY_TEST);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
// 배치 작업 종료 코드 생성
String exitCd;
if (totalProcessedItems == 0) {
exitCd = BatchConstants.JOB_EXECUTION_EXIT_FAILED;
} else if (totalProcessedItems < TOTAL_ITEMS) {
exitCd = BatchConstants.JOB_EXECUTION_EXIT_PARTIALLY_COMPLETED;
BatchJobLogUtil.info(context, String.format("일부 항목만 처리되어 부분 완료 종료 코드로 설정합니다. (총 %d개 중 %d개 처리됨)",
TOTAL_ITEMS, totalProcessedItems));
} else {
exitCd = BatchConstants.JOB_EXECUTION_EXIT_COMPLETED;
}
return BatchJobResult.builder()
.exitCd(exitCd)
.totalItems(TOTAL_ITEMS)
.processedItems(totalProcessedItems)
.successItems(totalProcessedItems)
.errorItems(TOTAL_ITEMS - totalProcessedItems)
.build();
}
/**
* .
*
* @param context
* @param startIndex
* @param chunkSize
* @return
*/
private int processChunk(JobExecutionContext context, int startIndex, int chunkSize) {
int processedItems = 0;
for (int i = 0; i < chunkSize; i++) {
int currentItemIndex = startIndex + i;
// 특정 항목에서 강제로 예외 발생 (테스트용)
// 각 청크의 7번째 항목(인덱스 6)에서 예외 발생
if (i == 6 && startIndex % 300 == 0) {
throw new RuntimeException("청크 내 " + (i + 1) + "번째 항목(" + (currentItemIndex + 1) + "번 항목) 처리 중 강제 예외 발생");
}
// 항목 처리 로직
BatchJobLogUtil.info(context, String.format("항목 %d 처리 중...", currentItemIndex + 1));
processedItems++;
// 각 항목 처리 시 짧은 지연 추가 (실제 작업 시뮬레이션)
try {
Thread.sleep(BatchConstants.TIME_ITEM_PROCESS_DELAY);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
BatchJobLogUtil.error(context, "배치 작업이 중단되었습니다.", e);
throw new RuntimeException("배치 작업이 중단되었습니다.", e);
}
}
return processedItems;
}
}

@ -1,537 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import egovframework.util.SessionUtil;
import go.kr.project.batch.model.*;
import go.kr.project.batch.service.BatchFileDataService;
import go.kr.project.batch.service.BatchFileDataSuccessService;
import go.kr.project.batch.service.BatchFileRetryService;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* packageName : go.kr.project.batch.job
* fileName : ErrorFileRetryBatchJob
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지
public class SampleErrorFileRetryBatchJob extends AbstractBatchJob {
// ===========================================================
// 상수 정의
// ===========================================================
private static final DateTimeFormatter fileTimestampFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
// ===========================================================
// 의존성 주입
// ===========================================================
@Autowired
private BatchFileProcessingConfig fileConfig;
@Autowired
private BatchFileRetryConfig retryConfig;
@Autowired
private BatchFileDataService batchFileDataService;
@Autowired
private BatchFileDataSuccessService batchFileDataSuccessService;
@Autowired
private BatchFileRetryService batchFileRetryService;
// ===========================================================
// 메인 실행 메소드
// ===========================================================
/**
* .
*
* @param context
* @return
* @throws Exception
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
// 재처리 기능이 비활성화된 경우 종료
if (!retryConfig.isEnabled()) {
BatchJobLogUtil.info(context, "에러 파일 재처리 기능이 비활성화되어 있습니다.");
return BatchJobResult.builder()
.exitCd(BatchConstants.JOB_EXECUTION_EXIT_COMPLETED)
.totalItems(0)
.processedItems(0)
.successItems(0)
.errorItems(0)
.build();
}
return processRetryBatchJob(context);
}
// ===========================================================
// 메인 재처리 배치 처리 메소드들
// ===========================================================
/**
* .
*
* @param context
* @return
*/
private BatchJobResult processRetryBatchJob(JobExecutionContext context) {
BatchJobLogUtil.info(context, "에러 파일 재처리 배치 작업을 시작합니다.");
logBatchConfiguration(context,
String.format("에러 디렉토리: %s", fileConfig.getErrorDir()),
String.format("완료 디렉토리: %s", fileConfig.getCompleteDir()),
String.format("최대 재시도 횟수: %d", retryConfig.getMaxRetryCount()),
String.format("재시도 간격: %d시간", retryConfig.getRetryIntervalHours()));
// 1. DB에서 재처리 대상 파일 목록 조회
List<BatchFileRetryVO> retryTargetFiles = getRetryTargetFiles(context);
if (retryTargetFiles.isEmpty()) {
return createEmptyResult(context);
}
BatchJobLogUtil.info(context, String.format("재처리 대상 파일 개수: %d", retryTargetFiles.size()));
// 2. 각 재처리 대상 파일 처리
FileProcessingResult processingResult = processAllRetryFiles(context, retryTargetFiles);
String exitCode = determineJobExitCode(context, processingResult.getProcessedCount(), processingResult.getErrorCount(), "재처리 파일");
return BatchJobResult.builder()
.exitCd(exitCode)
.totalItems(retryTargetFiles.size())
.processedItems(processingResult.getProcessedCount())
.successItems(processingResult.getSuccessCount())
.errorItems(processingResult.getErrorCount())
.build();
}
/**
* .
*/
private BatchJobResult createEmptyResult(JobExecutionContext context) {
BatchJobLogUtil.info(context, "재처리 대상 파일이 없습니다.");
return BatchJobResult.builder()
.exitCd(BatchConstants.JOB_EXECUTION_EXIT_COMPLETED)
.totalItems(0)
.processedItems(0)
.successItems(0)
.errorItems(0)
.build();
}
// ===========================================================
// 재처리 대상 파일 조회 메소드들
// ===========================================================
/**
* DB .
*
* @param context
* @return
*/
private List<BatchFileRetryVO> getRetryTargetFiles(JobExecutionContext context) {
try {
BatchJobLogUtil.info(context, "재처리 대상 파일 목록을 DB에서 조회합니다.");
List<BatchFileRetryVO> retryTargetFiles = batchFileRetryService.selectRetryTargetFiles();
BatchJobLogUtil.info(context, String.format("DB에서 조회된 재처리 대상 파일 개수: %d", retryTargetFiles.size()));
return retryTargetFiles;
} catch (Exception e) {
BatchJobLogUtil.error(context, String.format("재처리 대상 파일 목록 조회 중 오류 발생: %s", e.getMessage()), e);
return new ArrayList<>();
}
}
// ===========================================================
// 재처리 파일 처리 메소드들
// ===========================================================
/**
* .
*/
private FileProcessingResult processAllRetryFiles(JobExecutionContext context, List<BatchFileRetryVO> retryTargetFiles) {
FileProcessingResult result = new FileProcessingResult();
for (BatchFileRetryVO retryFile : retryTargetFiles) {
try {
BatchJobLogUtil.info(context, String.format("➡️ 재처리 파일 처리 시작: %s (재시도 %d/%d회)",
retryFile.getOriginalFileNm(), retryFile.getRetryCnt(), retryFile.getMaxRetryCnt()));
// 재처리 상태를 PROCESSING으로 변경
batchFileRetryService.updateRetryStatus(retryFile.getRetryId(), BatchFileRetryVO.STATUS_PROCESSING);
boolean success = processRetryFile(context, retryFile);
result.incrementProcessed();
if (success) {
result.incrementSuccess();
// 재처리 성공 시 상태를 SUCCESS로 변경
retryFile.markAsSuccess();
batchFileRetryService.updateBatchFileRetry(retryFile);
BatchJobLogUtil.info(context, String.format("➡️ 재처리 파일 처리 성공: %s", retryFile.getOriginalFileNm()));
} else {
result.incrementError();
handleRetryFileError(context, retryFile);
}
} catch (Exception e) {
result.incrementProcessed();
result.incrementError();
BatchJobLogUtil.error(context, String.format("➡️ 재처리 파일 처리 중 예외 발생: %s - %s",
retryFile.getOriginalFileNm(), e.getMessage()), e);
try {
handleRetryFileError(context, retryFile);
} catch (Exception ex) {
BatchJobLogUtil.error(context, String.format("재처리 파일 에러 처리 중 예외 발생: %s", ex.getMessage()), ex);
}
}
}
return result;
}
/**
* .
*
* @param context
* @param retryFile
* @return
*/
private boolean processRetryFile(JobExecutionContext context, BatchFileRetryVO retryFile) {
try {
// 에러 디렉토리에서 파일 찾기
Path errorFilePath = findErrorFile(context, retryFile.getOriginalFileNm());
if (errorFilePath == null) {
BatchJobLogUtil.error(context, String.format("에러 디렉토리에서 파일을 찾을 수 없습니다: %s", retryFile.getOriginalFileNm()));
return false;
}
BatchJobLogUtil.info(context, String.format("➡️➡️ 에러 파일 읽기 시작: %s", errorFilePath.getFileName()));
// 파일 내용 읽기
List<String> lines = readFileLines(errorFilePath);
BatchJobLogUtil.info(context, String.format("➡️➡️ 파일 라인 수: %d", lines.size()));
// 파일 라인 처리
FileLineProcessingResult lineResult = processFileLines(context, retryFile.getOriginalFileNm(), lines);
// 처리된 데이터 저장
saveProcessedData(context, lineResult);
// 파일 이동 (성공 시 complete 디렉토리로, 실패 시 error 디렉토리에 유지)
moveRetryFile(context, errorFilePath, retryFile, !lineResult.hasError());
return !lineResult.hasError();
} catch (Exception e) {
BatchJobLogUtil.error(context, String.format("➡️➡️ 재처리 파일 처리 중 오류 발생: %s - %s",
retryFile.getOriginalFileNm(), e.getMessage()), e);
return false;
}
}
/**
* .
*/
private Path findErrorFile(JobExecutionContext context, String originalFileName) throws IOException {
Path errorDir = Paths.get(fileConfig.getErrorDir());
if (!Files.exists(errorDir)) {
BatchJobLogUtil.error(context, String.format("에러 디렉토리가 존재하지 않습니다: %s", errorDir));
return null;
}
// 에러 디렉토리와 하위 날짜 디렉토리에서 파일 검색
try (DirectoryStream<Path> stream = Files.newDirectoryStream(errorDir)) {
for (Path path : stream) {
if (Files.isDirectory(path)) {
// 날짜 하위 디렉토리에서 검색
Path foundFile = searchFileInDirectory(path, originalFileName);
if (foundFile != null) {
return foundFile;
}
} else if (Files.isRegularFile(path)) {
// 에러 디렉토리 직접 검색
String fileName = path.getFileName().toString();
if (fileName.startsWith(originalFileName)) {
return path;
}
}
}
}
return null;
}
/**
* .
*/
private Path searchFileInDirectory(Path directory, String originalFileName) throws IOException {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
for (Path file : stream) {
if (Files.isRegularFile(file)) {
String fileName = file.getFileName().toString();
if (fileName.startsWith(originalFileName)) {
return file;
}
}
}
}
return null;
}
/**
* .
*/
private List<String> readFileLines(Path filePath) throws IOException {
return Files.readAllLines(filePath, Charset.forName(fileConfig.getEncoding()));
}
/**
* . (SampleFileReadBatchJob )
*/
private FileLineProcessingResult processFileLines(JobExecutionContext context, String fileName, List<String> lines) {
FileLineProcessingResult result = new FileLineProcessingResult();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
int lineNumber = i + 1;
try {
BatchFileDataVO fileData = processLine(fileName, lineNumber, line);
result.addSuccessData(fileData);
} catch (Exception e) {
result.setHasError(true);
BatchFileDataVO errorData = createErrorData(fileName, lineNumber, line, e.getMessage());
result.addErrorData(errorData);
BatchJobLogUtil.error(context, String.format("➡️➡️ 라인 처리 오류 - 파일: %s, 라인: %d, 오류: %s",
fileName, lineNumber, e.getMessage()), e);
}
}
return result;
}
/**
* . (SampleFileReadBatchJob )
*/
private BatchFileDataVO processLine(String fileName, int lineNumber, String line) {
BatchFileDataVO fileData = new BatchFileDataVO();
fileData.setFileNm(fileName);
fileData.setLineNumber(lineNumber);
fileData.setRawData(line);
fileData.setProcessDttm(LocalDateTime.now());
fileData.setRgtr(SessionUtil.getBatchUserId());
// 라인이 비어있으면 에러 처리
if (line == null || line.trim().isEmpty()) {
throw new RuntimeException("⚠️ 빈 라인입니다.");
}
// 구분자로 컬럼 분리
String delimiter = fileConfig.getDelimiter();
String[] columns;
if ("|".equals(delimiter)) {
columns = line.split("\\|", -1);
} else if ("^".equals(delimiter)) {
columns = line.split("\\^", -1);
} else {
columns = line.split(delimiter, -1);
}
// 컬럼 수 검증 (최소 2개 이상)
if (columns.length < 2) {
throw new RuntimeException(String.format("⚠️ 컬럼 수가 부족합니다. 최소 2개 필요, 실제: %d개", columns.length));
}
// 각 컬럼에 데이터 설정
if (columns.length > 0) fileData.setColumn1(columns[0].trim());
if (columns.length > 1) fileData.setColumn2(columns[1].trim());
if (columns.length > 2) fileData.setColumn3(columns[2].trim());
if (columns.length > 3) fileData.setColumn4(columns[3].trim());
if (columns.length > 4) fileData.setColumn5(columns[4].trim());
// 비즈니스 로직 검증 (예: 첫 번째 컬럼이 숫자인지 확인)
try {
Integer.parseInt(fileData.getColumn1());
} catch (NumberFormatException e) {
throw new RuntimeException("⚠️ 첫 번째 컬럼은 숫자여야 합니다.");
}
fileData.setProcessStatus("SUCCESS");
return fileData;
}
/**
* .
*/
private BatchFileDataVO createErrorData(String fileName, int lineNumber, String line, String errorMessage) {
BatchFileDataVO errorData = new BatchFileDataVO();
errorData.setFileNm(fileName);
errorData.setLineNumber(lineNumber);
errorData.setRawData(line);
errorData.setProcessStatus("ERROR");
errorData.setErrorMessage(errorMessage);
errorData.setProcessDttm(LocalDateTime.now());
errorData.setRgtr(SessionUtil.getBatchUserId());
return errorData;
}
/**
* . (SampleFileReadBatchJob )
*/
private void saveProcessedData(JobExecutionContext context, FileLineProcessingResult result) {
List<BatchFileDataVO> allData = result.getAllData();
// 모든 데이터를 로그성 테이블에 저장
if (!allData.isEmpty()) {
int savedCount = batchFileDataService.insertBatchFileDataList(allData);
BatchJobLogUtil.info(context, String.format("➡️➡️ 로그 데이터 저장 완료: %d건", savedCount));
}
// 에러가 없는 경우에만 성공 데이터를 별도 저장
if (!result.hasError()) {
List<BatchFileDataSuccessVO> successDataList = convertToSuccessDataList(allData);
if (!successDataList.isEmpty()) {
int successSavedCount = batchFileDataSuccessService.insertBatchFileDataSuccessList(successDataList);
BatchJobLogUtil.info(context, String.format("➡️➡️ 성공 데이터 저장 완료: %d건", successSavedCount));
}
}
}
/**
* .
*/
private List<BatchFileDataSuccessVO> convertToSuccessDataList(List<BatchFileDataVO> fileDataList) {
List<BatchFileDataSuccessVO> successDataList = new ArrayList<>();
for (BatchFileDataVO fileData : fileDataList) {
// 성공한 데이터만 변환
if ("SUCCESS".equals(fileData.getProcessStatus())) {
BatchFileDataSuccessVO successData = new BatchFileDataSuccessVO();
successData.setFileNm(fileData.getFileNm());
successData.setLineNumber(fileData.getLineNumber());
successData.setColumn1(fileData.getColumn1());
successData.setColumn2(fileData.getColumn2());
successData.setColumn3(fileData.getColumn3());
successData.setColumn4(fileData.getColumn4());
successData.setColumn5(fileData.getColumn5());
successData.setRawData(fileData.getRawData());
successData.setProcessDttm(fileData.getProcessDttm());
successData.setRgtr(fileData.getRgtr());
successDataList.add(successData);
}
}
return successDataList;
}
/**
* .
*/
private void moveRetryFile(JobExecutionContext context, Path filePath, BatchFileRetryVO retryFile, boolean success) throws IOException {
String timestamp = LocalDateTime.now().format(fileTimestampFormatter);
String newFileName;
if (success) {
// 성공 시: 원본파일명.retry.success.타임스탬프
newFileName = retryFile.getOriginalFileNm() + ".retry.success." + timestamp;
} else {
// 실패 시: 원본파일명.retry.failed.재시도횟수.타임스탬프
newFileName = retryFile.getOriginalFileNm() + ".retry.failed." + (retryFile.getRetryCnt()+1) + "." + timestamp;
}
// DB에 재처리 파일명 업데이트
retryFile.setRetryFileNm(newFileName);
try {
batchFileRetryService.updateBatchFileRetry(retryFile);
BatchJobLogUtil.info(context, String.format("➡️➡️ DB 재처리 파일명 업데이트 완료: %s", newFileName));
} catch (Exception e) {
BatchJobLogUtil.error(context, String.format("DB 재처리 파일명 업데이트 중 오류 발생: %s", e.getMessage()), e);
}
// 대상 디렉토리 결정
String targetBaseDir = success ? fileConfig.getCompleteDir() : fileConfig.getErrorDir();
Path targetDir = Paths.get(targetBaseDir);
// 날짜별 하위 디렉토리 생성
if (fileConfig.isCreateDateSubdir()) {
String dateStr = LocalDateTime.now().format(dateFormatter);
targetDir = targetDir.resolve(dateStr);
}
// 디렉토리 생성
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
BatchJobLogUtil.info(context, String.format("➡️➡️ 디렉토리 생성: %s", targetDir));
}
// 파일 이동
Path targetFilePath = targetDir.resolve(newFileName);
Files.move(filePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
BatchJobLogUtil.info(context, String.format("➡️➡️ 재처리 파일 이동 완료: %s -> %s",
filePath, targetFilePath));
}
/**
* .
*/
private void handleRetryFileError(JobExecutionContext context, BatchFileRetryVO retryFile) {
try {
// 재시도 횟수 증가
retryFile.incrementRetryCnt();
if (retryFile.canRetry()) {
// 재시도 가능한 경우
retryFile.setRetryStatus(BatchFileRetryVO.STATUS_PENDING);
retryFile.setNextRetryDttm(LocalDateTime.now().plusHours(retryConfig.getRetryIntervalHours()));
BatchJobLogUtil.info(context, String.format("➡️ 재처리 파일 실패 - 다음 재시도 예정: %s (%d/%d회)",
retryFile.getOriginalFileNm(), retryFile.getRetryCnt(), retryFile.getMaxRetryCnt()));
} else {
// 최대 재시도 횟수 초과
retryFile.markAsExceeded();
BatchJobLogUtil.error(context, String.format("➡️ 재처리 파일 최대 재시도 횟수 초과: %s (%d/%d회)",
retryFile.getOriginalFileNm(), retryFile.getRetryCnt(), retryFile.getMaxRetryCnt()));
}
// DB 업데이트
batchFileRetryService.updateBatchFileRetry(retryFile);
} catch (Exception e) {
BatchJobLogUtil.error(context, String.format("재처리 파일 에러 처리 중 예외 발생: %s", e.getMessage()), e);
}
}
}

@ -1,485 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import egovframework.util.SessionUtil;
import go.kr.project.batch.model.*;
import go.kr.project.batch.service.BatchFileDataService;
import go.kr.project.batch.service.BatchFileDataSuccessService;
import go.kr.project.batch.service.BatchFileRetryService;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* packageName : go.kr.project.batch.job
* fileName : SampleBatchJob
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지 (클러스터링 환경에서 중복 실행 방지)
public class SampleFileReadBatchJob extends AbstractBatchJob {
// ===========================================================
// 상수 정의
// ===========================================================
private static final DateTimeFormatter fileTimestampFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
// ===========================================================
// 의존성 주입
// ===========================================================
@Autowired
private BatchFileProcessingConfig fileConfig;
@Autowired
private BatchFileDataService batchFileDataService;
@Autowired
private BatchFileDataSuccessService batchFileDataSuccessService;
@Autowired
private BatchFileRetryService batchFileRetryService;
@Autowired
private BatchFileRetryConfig retryConfig;
// ===========================================================
// 메인 실행 메소드
// ===========================================================
// ===========================================================
// 메인 배치 처리 메소드들
// ===========================================================
/**
* .
*
* @param context
* @return
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
BatchJobLogUtil.info(context, "파일 처리 배치 작업을 시작합니다.");
logBatchConfiguration(context,
String.format("소스 디렉토리: %s", fileConfig.getSourceDir()),
String.format("완료 디렉토리: %s", fileConfig.getCompleteDir()),
String.format("에러 디렉토리: %s", fileConfig.getErrorDir()),
String.format("파일 구분자: %s", fileConfig.getDelimiter()));
List<Path> filesToProcess = getFilesToProcess(context);
if (filesToProcess.isEmpty()) {
return createEmptyResult(context);
}
BatchJobLogUtil.info(context, String.format("처리할 파일 개수: %d", filesToProcess.size()));
FileProcessingResult processingResult = processAllFiles(context, filesToProcess);
String exitCd = determineJobExitCode(context, processingResult.getProcessedCount(), processingResult.getErrorCount(), "파일");
return BatchJobResult.builder()
.exitCd(exitCd)
.totalItems(filesToProcess.size())
.processedItems(processingResult.getProcessedCount())
.successItems(processingResult.getSuccessCount())
.errorItems(processingResult.getErrorCount())
.build();
}
/**
* .
*/
private BatchJobResult createEmptyResult(JobExecutionContext context) {
BatchJobLogUtil.info(context, "처리할 파일이 없습니다.");
return BatchJobResult.builder()
.exitCd(BatchConstants.JOB_EXECUTION_EXIT_COMPLETED)
.totalItems(0)
.processedItems(0)
.successItems(0)
.errorItems(0)
.build();
}
// ===========================================================
// 파일 목록 조회 메소드들
// ===========================================================
/**
* .
*
* @param context
* @return
*/
private List<Path> getFilesToProcess(JobExecutionContext context) {
List<Path> files = new ArrayList<>();
try {
Path sourceDir = Paths.get(fileConfig.getSourceDir());
// 디렉토리가 존재하지 않으면 생성
if (!Files.exists(sourceDir)) {
Files.createDirectories(sourceDir);
BatchJobLogUtil.info(context, String.format("소스 디렉토리 생성: %s", sourceDir));
return files;
}
// 디렉토리 내의 모든 파일 조회 (하위 디렉토리 제외)
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourceDir, "*.{txt,csv,dat}")) {
for (Path file : stream) {
if (Files.isRegularFile(file)) {
files.add(file);
}
}
}
} catch (IOException e) {
BatchJobLogUtil.error(context, String.format("파일 목록 조회 중 오류 발생: %s", e.getMessage()), e);
}
return files;
}
// ===========================================================
// 파일 처리 메소드들
// ===========================================================
/**
* .
*/
private FileProcessingResult processAllFiles(JobExecutionContext context, List<Path> filesToProcess) {
FileProcessingResult result = new FileProcessingResult();
for (Path filePath : filesToProcess) {
try {
BatchJobLogUtil.info(context, String.format("➡️ 파일 처리 시작: %s", filePath.getFileName()));
boolean success = processFile(context, filePath);
result.incrementProcessed();
if (success) {
result.incrementSuccess();
BatchJobLogUtil.info(context, String.format("➡️ 파일 처리 성공: %s", filePath.getFileName()));
} else {
result.incrementError();
BatchJobLogUtil.error(context, String.format("➡️ 파일 처리 실패: %s", filePath.getFileName()));
}
} catch (Exception e) {
result.incrementProcessed();
result.incrementError();
BatchJobLogUtil.error(context, String.format("➡️ 파일 처리 중 예외 발생: %s - %s",
filePath.getFileName(), e.getMessage()), e);
}
}
return result;
}
/**
* .
*
* @param context
* @param filePath
* @return
*/
private boolean processFile(JobExecutionContext context, Path filePath) {
String fileName = filePath.getFileName().toString();
try {
BatchJobLogUtil.info(context, String.format("➡️➡️ 파일 읽기 시작: %s", fileName));
List<String> lines = readFileLines(filePath);
BatchJobLogUtil.info(context, String.format("➡️➡️ 파일 라인 수: %d", lines.size()));
FileLineProcessingResult lineResult = processFileLines(context, fileName, lines);
saveProcessedData(context, lineResult);
moveFile(context, filePath, lineResult.hasError());
return !lineResult.hasError();
} catch (Exception e) {
return handleFileProcessingError(context, filePath, fileName, e);
}
}
/**
* .
*/
private List<String> readFileLines(Path filePath) throws IOException {
return Files.readAllLines(filePath, Charset.forName(fileConfig.getEncoding()));
}
/**
* .
*/
private FileLineProcessingResult processFileLines(JobExecutionContext context, String fileName, List<String> lines) {
FileLineProcessingResult result = new FileLineProcessingResult();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
int lineNumber = i + 1;
try {
BatchFileDataVO fileData = processLine(fileName, lineNumber, line);
result.addSuccessData(fileData);
} catch (Exception e) {
result.setHasError(true);
BatchFileDataVO errorData = createErrorData(fileName, lineNumber, line, e.getMessage());
result.addErrorData(errorData);
BatchJobLogUtil.error(context, String.format("➡️➡️ 라인 처리 오류 - 파일: %s, 라인: %d, 오류: %s",
fileName, lineNumber, e.getMessage()), e);
}
}
return result;
}
/**
* .
*
* @param fileName
* @param lineNumber
* @param line
* @return
*/
private BatchFileDataVO processLine(String fileName, int lineNumber, String line) {
BatchFileDataVO fileData = new BatchFileDataVO();
fileData.setFileNm(fileName);
fileData.setLineNumber(lineNumber);
fileData.setRawData(line);
fileData.setProcessDttm(LocalDateTime.now());
fileData.setRgtr(SessionUtil.getBatchUserId());
// 라인이 비어있으면 에러 처리
if (line == null || line.trim().isEmpty()) {
throw new RuntimeException("⚠️ 빈 라인입니다.");
}
// 구분자로 컬럼 분리
String delimiter = fileConfig.getDelimiter();
String[] columns;
if ("|".equals(delimiter)) {
columns = line.split("\\|", -1);
} else if ("^".equals(delimiter)) {
columns = line.split("\\^", -1);
} else {
columns = line.split(delimiter, -1);
}
// 컬럼 수 검증 (최소 2개 이상)
if (columns.length < 2) {
throw new RuntimeException(String.format("⚠️ 컬럼 수가 부족합니다. 최소 2개 필요, 실제: %d개", columns.length));
}
// 각 컬럼에 데이터 설정
if (columns.length > 0) fileData.setColumn1(columns[0].trim());
if (columns.length > 1) fileData.setColumn2(columns[1].trim());
if (columns.length > 2) fileData.setColumn3(columns[2].trim());
if (columns.length > 3) fileData.setColumn4(columns[3].trim());
if (columns.length > 4) fileData.setColumn5(columns[4].trim());
// 비즈니스 로직 검증 (예: 첫 번째 컬럼이 숫자인지 확인)
try {
Integer.parseInt(fileData.getColumn1());
} catch (NumberFormatException e) {
throw new RuntimeException("⚠️ 첫 번째 컬럼은 숫자여야 합니다.");
}
fileData.setProcessStatus("SUCCESS");
return fileData;
}
/**
* .
*
* @param fileName
* @param lineNumber
* @param line
* @param errorMessage
* @return
*/
private BatchFileDataVO createErrorData(String fileName, int lineNumber, String line, String errorMessage) {
BatchFileDataVO errorData = new BatchFileDataVO();
errorData.setFileNm(fileName);
errorData.setLineNumber(lineNumber);
errorData.setRawData(line);
errorData.setProcessStatus("ERROR");
errorData.setErrorMessage(errorMessage);
errorData.setProcessDttm(LocalDateTime.now());
errorData.setRgtr(SessionUtil.getBatchUserId());
return errorData;
}
// ===========================================================
// 데이터 저장 메소드들
// ===========================================================
/**
* .
*/
private void saveProcessedData(JobExecutionContext context, FileLineProcessingResult result) {
List<BatchFileDataVO> allData = result.getAllData();
// 모든 데이터를 로그성 테이블에 저장
if (!allData.isEmpty()) {
int savedCount = batchFileDataService.insertBatchFileDataList(allData);
BatchJobLogUtil.info(context, String.format("➡️➡️ 로그 데이터 저장 완료: %d건", savedCount));
}
// 에러가 없는 경우에만 성공 데이터를 별도 저장
if (!result.hasError()) {
List<BatchFileDataSuccessVO> successDataList = convertToSuccessDataList(allData);
if (!successDataList.isEmpty()) {
int successSavedCount = batchFileDataSuccessService.insertBatchFileDataSuccessList(successDataList);
BatchJobLogUtil.info(context, String.format("➡️➡️ 성공 데이터 저장 완료: %d건", successSavedCount));
}
}
}
/**
* .
*
* @param fileDataList
* @return
*/
private List<BatchFileDataSuccessVO> convertToSuccessDataList(List<BatchFileDataVO> fileDataList) {
List<BatchFileDataSuccessVO> successDataList = new ArrayList<>();
for (BatchFileDataVO fileData : fileDataList) {
// 성공한 데이터만 변환
if ("SUCCESS".equals(fileData.getProcessStatus())) {
BatchFileDataSuccessVO successData = new BatchFileDataSuccessVO();
successData.setFileNm(fileData.getFileNm());
successData.setLineNumber(fileData.getLineNumber());
successData.setColumn1(fileData.getColumn1());
successData.setColumn2(fileData.getColumn2());
successData.setColumn3(fileData.getColumn3());
successData.setColumn4(fileData.getColumn4());
successData.setColumn5(fileData.getColumn5());
successData.setRawData(fileData.getRawData());
successData.setProcessDttm(fileData.getProcessDttm());
successData.setRgtr(fileData.getRgtr());
successDataList.add(successData);
}
}
return successDataList;
}
// ===========================================================
// 파일 이동 및 오류 처리 메소드들
// ===========================================================
/**
* .
*
* @param context
* @param filePath
* @param hasError
*/
private void moveFile(JobExecutionContext context, Path filePath, boolean hasError) throws IOException {
String fileName = filePath.getFileName().toString();
String timestamp = LocalDateTime.now().format(fileTimestampFormatter);
// 파일명 변경: 원본파일명.타임스탬프 형태로 변경
String newFileName = fileName + "." + timestamp;
// 대상 디렉토리 결정
String targetBaseDir = hasError ? fileConfig.getErrorDir() : fileConfig.getCompleteDir();
Path targetDir = Paths.get(targetBaseDir);
// 날짜별 하위 디렉토리 생성
if (fileConfig.isCreateDateSubdir()) {
String dateStr = LocalDateTime.now().format(dateFormatter);
targetDir = targetDir.resolve(dateStr);
}
// 디렉토리 생성
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir);
BatchJobLogUtil.info(context, String.format("➡️➡️ 디렉토리 생성: %s", targetDir));
}
// 파일 이동
Path targetFilePath = targetDir.resolve(newFileName);
Files.move(filePath, targetFilePath, StandardCopyOption.REPLACE_EXISTING);
BatchJobLogUtil.info(context, String.format("➡️➡️ 파일 이동 완료: %s -> %s",
filePath, targetFilePath));
// 에러 파일인 경우 재처리 이력 생성,
// 재처리 원본 파일명에 timestamp 추가이유는 동일파일명이 여러개 들어왔을때 read 되는 첫번째 파일만 처리되서
// 파일명이 동일할경우 정확한 파일의 처리를 위해
if (hasError && retryConfig.isEnabled()) {
createRetryRecord(context, newFileName);
}
}
/**
* .
*
* @param context
* @param fileName
*/
private void createRetryRecord(JobExecutionContext context, String fileName) {
try {
// 에러 유형 분류
String errorType = BatchFileRetryVO.ERROR_TYPE_DATA; // 기본값: 데이터 오류
String errorMessage = "파일 처리 중 데이터 오류 발생";
// 재처리 이력 생성
BatchFileRetryVO retryRecord = batchFileRetryService.createRetryRecord(
fileName,
errorType,
errorMessage,
retryConfig.getMaxRetryCount()
);
BatchJobLogUtil.info(context, String.format("➡️➡️ 재처리 이력 생성 완료: %s (재처리ID: %s)",
fileName, retryRecord.getRetryId()));
} catch (Exception ex) {
BatchJobLogUtil.error(context, String.format("재처리 이력 생성 중 오류 발생: %s - %s",
fileName, ex.getMessage()), ex);
}
}
/**
* .
*/
private boolean handleFileProcessingError(JobExecutionContext context, Path filePath, String fileName, Exception e) {
BatchJobLogUtil.error(context, String.format("➡️➡️ 파일 처리 중 오류 발생: %s - %s", fileName, e.getMessage()), e);
try {
moveFile(context, filePath, true);
} catch (Exception moveException) {
BatchJobLogUtil.error(context, String.format("➡️➡️ 파일 이동 중 오류 발생: %s", moveException.getMessage()), moveException);
}
return false;
}
}

@ -1,688 +0,0 @@
package go.kr.project.batch.job;
import egovframework.constant.BatchConstants;
import egovframework.util.ImageValidationUtil;
import egovframework.util.SessionUtil;
import go.kr.project.batch.model.BatchJobResult;
import go.kr.project.batch.model.ZipFileDetailLogVO;
import go.kr.project.batch.model.ZipFileProcessLogVO;
import go.kr.project.batch.service.ZipFileDetailLogService;
import go.kr.project.batch.service.ZipFileProcessLogService;
import go.kr.project.batch.util.BatchJobLogUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
* packageName : go.kr.project.batch.job
* fileName : ZipFileProcessBatchJob
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Slf4j
@Component
@DisallowConcurrentExecution // 동시 실행 방지
public class ZipFileProcessBatchJob extends AbstractBatchJob {
// ===========================================================
// 상수 정의
// ===========================================================
// ===========================================================
// 설정값 주입
// ===========================================================
@Value("${batch.zip.source-dir}")
private String sourceDirectory;
@Value("${batch.zip.extract-dir}")
private String extractDirectory;
@Value("${batch.zip.archive-dir}")
private String archiveDirectory;
@Value("${batch.zip.error-archive-dir}")
private String errorArchiveDirectory;
@Value("${batch.zip.error-extract-dir}")
private String errorExtractDirectory;
@Value("${batch.zip.create-date-subdir-pattern}")
private String createDateSubdirPattern;
// ===========================================================
// 의존성 주입
// ===========================================================
@Autowired
private ZipFileProcessLogService zipFileProcessLogService;
@Autowired
private ZipFileDetailLogService zipFileDetailLogService;
// ===========================================================
// 메인 실행 메소드
// ===========================================================
/**
* .
*
* @param context
* @return
* @throws Exception
*/
@Override
protected BatchJobResult processBatchJob(JobExecutionContext context) throws Exception {
return processZipFiles(context);
}
// ===========================================================
// ZIP 파일 처리 메소드들
// ===========================================================
/**
* ZIP .
*/
private BatchJobResult processZipFiles(JobExecutionContext context) {
BatchJobResult result = new BatchJobResult();
try {
// 디렉토리 생성
createDirectories();
// 처리할 ZIP 파일 목록 조회
List<Path> zipFiles = getZipFilesToProcess();
if (zipFiles.isEmpty()) {
BatchJobLogUtil.info(context, "처리할 파일이 없습니다.");
result.setExitCd(BatchConstants.JOB_EXECUTION_EXIT_COMPLETED);
return result;
}
BatchJobLogUtil.info(context, String.format("처리할 ZIP 파일 수: %d개", zipFiles.size()));
result.setTotalItems(zipFiles.size());
// 각 ZIP 파일 처리
for (Path zipFile : zipFiles) {
try {
boolean success = processZipFile(context, zipFile);
result.incrementProcessed();
if (success) {
result.incrementSuccess();
} else {
result.incrementError();
}
} catch (Exception e) {
log.error("ZIP 파일 처리 중 오류 발생: {}", zipFile, e);
result.incrementError();
}
}
// 배치 작업 종료 코드 결정 (공통 메소드 사용)
result.setExitCd(determineJobExitCode(context, result.getProcessedCount(), result.getErrorCount(), "ZIP 파일"));
} catch (Exception e) {
log.error("ZIP 파일 처리 배치 작업 중 오류 발생", e);
result.setExitCd(BatchConstants.JOB_EXECUTION_EXIT_FAILED);
result.setErrorMessage(e.getMessage());
}
return result;
}
/**
* .
*/
private void createDirectories() throws IOException {
Files.createDirectories(Paths.get(sourceDirectory));
Files.createDirectories(Paths.get(extractDirectory));
Files.createDirectories(Paths.get(archiveDirectory));
}
/**
* ZIP .
*/
private List<Path> getZipFilesToProcess() throws IOException {
List<Path> zipFiles = new ArrayList<>();
Path sourcePath = Paths.get(sourceDirectory);
if (!Files.exists(sourcePath)) {
return zipFiles;
}
try (DirectoryStream<Path> stream = Files.newDirectoryStream(sourcePath, "*.zip")) {
for (Path file : stream) {
if (Files.isRegularFile(file)) {
zipFiles.add(file);
}
}
}
return zipFiles;
}
/**
* ZIP .
*/
private boolean processZipFile(JobExecutionContext context, Path zipFile) {
String zipFileName = zipFile.getFileName().toString();
BatchJobLogUtil.info(context, String.format("ZIP 파일 처리 시작: %s", zipFileName));
// 처리 로그 생성 (검증 실패한 ZIP 파일도 로그를 남기기 위해 먼저 생성)
ZipFileProcessLogVO processLog = createProcessLog(zipFile);
String logId = zipFileProcessLogService.generateZipFileProcessLogId();
processLog.setLogId(logId);
zipFileProcessLogService.insertZipFileProcessLog(processLog);
// ZIP 파일 검증 (종합 검증)
if (!isValidZipFile(zipFile)) {
BatchJobLogUtil.error(context, String.format("ZIP 파일 검증 실패: %s", zipFileName));
try {
// 검증 실패한 ZIP 파일을 에러 아카이브 디렉토리로 이동
moveZipFileToError(zipFile);
BatchJobLogUtil.info(context, String.format("검증 실패한 ZIP 파일을 에러 디렉토리로 이동: %s", zipFileName));
} catch (IOException e) {
log.error("검증 실패한 ZIP 파일 이동 중 오류 발생: {}", zipFileName, e);
}
// 검증 실패한 ZIP 파일에 대한 ProcessLog 에러 처리
processLog.setProcessStatus("ERROR");
processLog.setErrorMessage("ZIP 파일 검증 실패 (파일 포맷이 올바르지 않거나 손상됨)");
processLog.setEndDttm(LocalDateTime.now());
zipFileProcessLogService.updateZipFileProcessLog(processLog);
return false;
}
Path extractPath = null;
try {
// ZIP 파일 압축 해제 및 파일 처리
ExtractResult extractResult = extractAndProcessZipFile(zipFile, logId);
List<ZipFileDetailLogVO> detailLogs = extractResult.getDetailLogs();
extractPath = extractResult.getExtractPath();
// 상세 로그 저장
if (!detailLogs.isEmpty()) {
zipFileDetailLogService.insertZipFileDetailLogList(detailLogs);
}
// 처리 결과 집계
updateProcessLogSummary(processLog, detailLogs);
// ZIP 파일 안에 파일이 없거나, 하나라도 에러가 있는 경우 전체 ZIP 파일을 에러로 처리
if (processLog.getTotalFileCnt() == 0 || processLog.getErrorFileCnt() > 0) {
// 압축 해제된 파일들을 error-extract-dir로 이동
moveExtractedFilesToErrorDir(zipFile, extractPath);
// ZIP 파일을 에러 아카이브 디렉토리로 이동
moveZipFileToError(zipFile);
// 처리 로그 에러 처리
processLog.setProcessStatus("ERROR");
String errorMessage;
if (processLog.getTotalFileCnt() == 0) {
errorMessage = "ZIP 파일 안에 압축된 파일이 없습니다";
} else {
errorMessage = String.format("ZIP 파일 내 파일에 문제가 있습니다 (총: %d개, 성공: %d개, 에러: %d개)",
processLog.getTotalFileCnt(), processLog.getSuccessFileCnt(), processLog.getErrorFileCnt());
}
processLog.setErrorMessage(errorMessage);
processLog.setEndDttm(LocalDateTime.now());
zipFileProcessLogService.updateZipFileProcessLog(processLog);
BatchJobLogUtil.error(context, String.format("ZIP 파일 처리 실패: %s - %s", zipFileName, errorMessage));
return false;
}
// 정상적인 ZIP 파일인 경우 (에러 파일이 0개)
BatchJobLogUtil.info(context, String.format("ZIP 파일 내 모든 파일이 정상적으로 처리됨: %s (총: %d개, 성공: %d개)",
zipFileName, processLog.getTotalFileCnt(), processLog.getSuccessFileCnt()));
// ZIP 파일을 아카이브 디렉토리로 이동
moveZipFileToArchive(zipFile);
// 처리 로그 완료 처리 (정상적인 ZIP 파일만 SUCCESS로 처리)
processLog.setProcessStatus("SUCCESS");
processLog.setEndDttm(LocalDateTime.now());
zipFileProcessLogService.updateZipFileProcessLog(processLog);
BatchJobLogUtil.info(context, String.format("ZIP 파일 처리 완료: %s (총: %d, 성공: %d, 실패: %d)",
zipFileName, processLog.getTotalFileCnt(), processLog.getSuccessFileCnt(), processLog.getErrorFileCnt()));
return true;
} catch (Exception e) {
log.error("ZIP 파일 처리 중 오류 발생: {}", zipFileName, e);
try {
// 압축 해제된 파일들을 error-extract-dir로 이동 (extractPath가 있는 경우)
if (extractPath != null) {
moveExtractedFilesToErrorDir(zipFile, extractPath);
}
// ZIP 파일을 에러 아카이브 디렉토리로 이동
moveZipFileToError(zipFile);
} catch (IOException moveError) {
log.error("ZIP 파일 에러 디렉토리 이동 실패: {}", zipFileName, moveError);
}
// 처리 로그 오류 처리
processLog.setProcessStatus("ERROR");
processLog.setErrorMessage(e.getMessage());
processLog.setEndDttm(LocalDateTime.now());
zipFileProcessLogService.updateZipFileProcessLog(processLog);
BatchJobLogUtil.error(context, String.format("ZIP 파일 처리 실패: %s", zipFileName), e);
return false;
}
}
/**
* .
*/
private ZipFileProcessLogVO createProcessLog(Path zipFile) {
ZipFileProcessLogVO processLog = new ZipFileProcessLogVO();
processLog.setZipFileNm(zipFile.getFileName().toString());
processLog.setZipFilePath(zipFile.toString());
processLog.setExtractPath(extractDirectory);
processLog.setArchivePath(archiveDirectory);
processLog.setProcessStatus("PROCESSING");
processLog.setStartDttm(LocalDateTime.now());
String batchUserId = SessionUtil.getBatchUserId();
processLog.setRgtr(batchUserId);
processLog.setMdfr(batchUserId);
// 초기값 설정
processLog.setTotalFileCnt(0);
processLog.setSuccessFileCnt(0);
processLog.setErrorFileCnt(0);
processLog.setImageFileCnt(0);
processLog.setNonImageFileCnt(0);
processLog.setCorruptedFileCnt(0);
return processLog;
}
/**
* ZIP .
*/
private ExtractResult extractAndProcessZipFile(Path zipFile, String logId) throws IOException {
List<ZipFileDetailLogVO> detailLogs = new ArrayList<>();
Path extractPath = Paths.get(extractDirectory);
// 날짜별 하위 디렉토리 생성 (고정값으로 항상 생성)
DateTimeFormatter patternFormatter;
patternFormatter = DateTimeFormatter.ofPattern(createDateSubdirPattern);
String dateStr = LocalDateTime.now().format(patternFormatter);
extractPath = extractPath.resolve(dateStr);
// ZIP 파일명으로 하위 디렉토리 생성 (고정값으로 항상 생성)
String zipFileName = zipFile.getFileName().toString();
// 확장자 제거 (.zip)
String zipNameWithoutExt = zipFileName.substring(0, zipFileName.lastIndexOf('.'));
extractPath = extractPath.resolve(zipNameWithoutExt);
// 추출 디렉토리 생성
if (!Files.exists(extractPath)) {
Files.createDirectories(extractPath);
log.info("압축 해제 디렉토리 생성: {}", extractPath);
}
// ZIP 파일 읽기 가능 여부 검증
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFile))) {
ZipEntry entry;
int fileCount = 0;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
fileCount++;
ZipFileDetailLogVO detailLog = processZipEntry(zis, entry, extractPath, logId);
detailLogs.add(detailLog);
}
zis.closeEntry();
}
// ZIP 파일 안에 압축된 파일 개수가 0인 경우 에러 처리
if (fileCount == 0) {
throw new IOException("ZIP 파일 안에 압축된 파일이 없습니다.");
}
} catch (Exception e) {
// ZIP 파일을 읽을 수 없는 경우 에러 처리
log.error("ZIP 파일 읽기 실패: {}", zipFile.getFileName(), e);
throw new IOException("ZIP 파일을 읽을 수 없습니다: " + e.getMessage(), e);
}
return new ExtractResult(detailLogs, extractPath);
}
/**
* ZIP ( ) .
*/
private ZipFileDetailLogVO processZipEntry(ZipInputStream zis, ZipEntry entry, Path extractPath, String logId) {
ZipFileDetailLogVO detailLog = new ZipFileDetailLogVO();
detailLog.setLogId(logId);
detailLog.setFileNm(entry.getName());
detailLog.setFileSize(entry.getSize());
detailLog.setProcessDttm(LocalDateTime.now());
detailLog.setRgtr(SessionUtil.getBatchUserId());
try {
// 파일을 추출 디렉토리에 저장
Path targetFile = extractPath.resolve(entry.getName());
Files.createDirectories(targetFile.getParent());
Files.copy(zis, targetFile, StandardCopyOption.REPLACE_EXISTING);
detailLog.setFilePath(targetFile.toString());
detailLog.setFileSize(Files.size(targetFile));
// 파일 타입 확인
String mimeType = Files.probeContentType(targetFile);
detailLog.setFileType(mimeType != null ? mimeType : "unknown");
// 이미지 파일 검증
boolean isImage = ImageValidationUtil.isImageFile(targetFile);
detailLog.setImageYn(isImage ? "Y" : "N");
if (isImage) {
// 이미지 파일인 경우 손상 여부 확인
boolean isCorrupted = ImageValidationUtil.isImageCorrupted(targetFile);
detailLog.setCorruptedYn(isCorrupted ? "Y" : "N");
if (isCorrupted) {
detailLog.setProcessStatus("ERROR");
detailLog.setErrorMessage("손상된 이미지 파일");
log.warn("손상된 이미지 파일 발견: {}", entry.getName());
} else {
detailLog.setProcessStatus("SUCCESS");
log.debug("정상 이미지 파일 처리: {}", entry.getName());
}
} else {
// 비이미지 파일은 에러로 처리
detailLog.setProcessStatus("ERROR");
detailLog.setErrorMessage("이미지 파일이 아닙니다");
detailLog.setCorruptedYn("N");
log.warn("비이미지 파일 발견: {}", entry.getName());
}
} catch (IOException e) {
log.error("ZIP 엔트리 처리 중 오류 발생: {}", entry.getName(), e);
detailLog.setProcessStatus("ERROR");
detailLog.setErrorMessage("파일 추출 실패: " + e.getMessage());
detailLog.setImageYn("N");
detailLog.setCorruptedYn("N");
}
return detailLog;
}
/**
* .
*/
private void updateProcessLogSummary(ZipFileProcessLogVO processLog, List<ZipFileDetailLogVO> detailLogs) {
int totalFiles = detailLogs.size();
int successFiles = 0;
int errorFiles = 0;
int imageFiles = 0;
int nonImageFiles = 0;
int corruptedFiles = 0;
for (ZipFileDetailLogVO detailLog : detailLogs) {
if ("SUCCESS".equals(detailLog.getProcessStatus())) {
successFiles++;
} else {
errorFiles++;
}
if ("Y".equals(detailLog.getImageYn())) {
imageFiles++;
if ("Y".equals(detailLog.getCorruptedYn())) {
corruptedFiles++;
}
} else {
nonImageFiles++;
}
}
processLog.setTotalFileCnt(totalFiles);
processLog.setSuccessFileCnt(successFiles);
processLog.setErrorFileCnt(errorFiles);
processLog.setImageFileCnt(imageFiles);
processLog.setNonImageFileCnt(nonImageFiles);
processLog.setCorruptedFileCnt(corruptedFiles);
}
/**
* ZIP .
*/
private void moveZipFileToArchive(Path zipFile) throws IOException {
Path archivePath = Paths.get(archiveDirectory);
// 날짜별 하위 디렉토리 생성 (고정값으로 항상 생성)
DateTimeFormatter patternFormatter;
patternFormatter = DateTimeFormatter.ofPattern(createDateSubdirPattern);
String dateStr = LocalDateTime.now().format(patternFormatter);
archivePath = archivePath.resolve(dateStr);
// 아카이브 디렉토리 생성
if (!Files.exists(archivePath)) {
Files.createDirectories(archivePath);
log.info("아카이브 디렉토리 생성: {}", archivePath);
}
Path targetPath = archivePath.resolve(zipFile.getFileName());
// 동일한 파일명이 있는 경우 타임스탬프 추가
if (Files.exists(targetPath)) {
String fileName = zipFile.getFileName().toString();
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
String extension = fileName.substring(fileName.lastIndexOf('.'));
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
targetPath = archivePath.resolve(baseName + "_" + timestamp + extension);
}
Files.move(zipFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
log.info("ZIP 파일 아카이브 완료: {} -> {}", zipFile, targetPath);
}
/**
* ZIP .
*/
private void moveZipFileToError(Path zipFile) throws IOException {
Path errorPath = Paths.get(errorArchiveDirectory);
// 날짜별 하위 디렉토리 생성 (고정값으로 항상 생성)
DateTimeFormatter patternFormatter;
patternFormatter = DateTimeFormatter.ofPattern(createDateSubdirPattern);
String dateStr = LocalDateTime.now().format(patternFormatter);
errorPath = errorPath.resolve(dateStr);
// 에러 디렉토리 생성
if (!Files.exists(errorPath)) {
Files.createDirectories(errorPath);
log.info("에러 디렉토리 생성: {}", errorPath);
}
Path targetPath = errorPath.resolve(zipFile.getFileName());
// 동일한 파일명이 있는 경우 타임스탬프 추가
if (Files.exists(targetPath)) {
String fileName = zipFile.getFileName().toString();
String baseName = fileName.substring(0, fileName.lastIndexOf('.'));
String extension = fileName.substring(fileName.lastIndexOf('.'));
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
targetPath = errorPath.resolve(baseName + "_" + timestamp + extension);
}
Files.move(zipFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
log.info("ZIP 파일 에러 아카이브 디렉토리 이동 완료: {} -> {}", zipFile, targetPath);
}
/**
* ZIP error-extract-dir .
*/
private void moveExtractedFilesToErrorDir(Path zipFile, Path extractPath) {
try {
if (!Files.exists(extractPath)) {
log.debug("압축 해제 경로가 존재하지 않음: {}", extractPath);
return;
}
// error-extract-dir 경로 생성
Path errorExtractPath = Paths.get(errorExtractDirectory);
// 날짜별 하위 디렉토리 생성 (고정값으로 항상 생성)
DateTimeFormatter patternFormatter;
patternFormatter = DateTimeFormatter.ofPattern(createDateSubdirPattern);
String dateStr = LocalDateTime.now().format(patternFormatter);
errorExtractPath = errorExtractPath.resolve(dateStr);
// 에러 압축 해제 상위 디렉토리 생성
if (!Files.exists(errorExtractPath)) {
Files.createDirectories(errorExtractPath);
log.info("에러 압축 해제 상위 디렉토리 생성: {}", errorExtractPath);
}
// ZIP 파일명으로 하위 디렉토리 경로 생성
String zipFileName = zipFile.getFileName().toString();
String zipNameWithoutExt = zipFileName.substring(0, zipFileName.lastIndexOf('.'));
Path targetExtractPath = errorExtractPath.resolve(zipNameWithoutExt);
// 동일한 디렉토리명이 있는 경우 타임스탬프 추가
if (Files.exists(targetExtractPath)) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
targetExtractPath = errorExtractPath.resolve(zipNameWithoutExt + "_" + timestamp);
}
// 압축 해제 디렉토리 전체를 error-extract-dir로 이동
Files.move(extractPath, targetExtractPath, StandardCopyOption.REPLACE_EXISTING);
log.info("압축 해제 디렉토리를 에러 디렉토리로 이동 완료: {} -> {}", extractPath, targetExtractPath);
} catch (IOException e) {
log.error("압축 해제 디렉토리를 에러 디렉토리로 이동 중 오류 발생: {}", extractPath, e);
}
}
// ===========================================================
// ZIP 파일 검증 메소드들
// ===========================================================
/**
* ZIP ( : 50 4B 03 04)
*/
private boolean isValidZipBySignature(Path zipFile) {
try (FileInputStream fis = new FileInputStream(zipFile.toFile())) {
byte[] signature = new byte[4];
if (fis.read(signature) != 4) {
log.error("ZIP 파일 시그니처 읽기 실패 (파일 크기 부족): {}", zipFile.getFileName());
return false;
}
// ZIP 매직 넘버: 50 4B 03 04
boolean isValidSignature = signature[0] == 0x50 && signature[1] == 0x4B &&
signature[2] == 0x03 && signature[3] == 0x04;
if (!isValidSignature) {
log.error("ZIP 파일 시그니처 검증 실패: {}", zipFile.getFileName());
}
return isValidSignature;
} catch (IOException e) {
log.error("ZIP 파일 시그니처 검증 중 오류 발생: {}", zipFile.getFileName(), e);
return false;
}
}
/**
* ZipFile ZIP
*/
private boolean isValidZipByZipFile(Path zipFile) {
try (ZipFile zipFileObj = new ZipFile(zipFile.toFile())) {
// ZipFile 객체가 성공적으로 생성되면 유효한 ZIP 파일
return true;
} catch (Exception e) {
log.error("ZipFile 클래스 검증 실패: {}", zipFile.getFileName(), e);
return false;
}
}
/**
* ZIP ( )
* 1. ( )
* 2. ZipFile
* 3. ZipInputStream
*/
private boolean isValidZipFile(Path zipFile) {
log.debug("ZIP 파일 종합 검증 시작: {}", zipFile.getFileName());
// 1. 파일 시그니처 검증 (매직 넘버 체크)
if (!isValidZipBySignature(zipFile)) {
log.error("ZIP 파일 시그니처 검증 실패: {}", zipFile.getFileName());
return false;
}
// 2. ZipFile 클래스를 사용한 검증
if (!isValidZipByZipFile(zipFile)) {
log.error("ZipFile 클래스 검증 실패: {}", zipFile.getFileName());
return false;
}
// 3. ZipInputStream을 사용한 검증
try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFile))) {
// 첫 번째 엔트리를 읽어보려고 시도
ZipEntry firstEntry = zis.getNextEntry();
// 첫 번째 엔트리가 존재하면 정상적인 ZIP 파일로 판단
// null이면 빈 ZIP 파일이지만 포맷은 정상
log.debug("ZipInputStream 검증 성공: {}", zipFile.getFileName());
return true;
} catch (Exception e) {
// ZIP 파일 포맷이 아니거나 손상된 경우
log.error("ZipInputStream 검증 실패: {}", zipFile.getFileName(), e);
return false;
}
}
// ===========================================================
// 내부 클래스들
// ===========================================================
/**
* ZIP
*/
@lombok.Getter
private static class ExtractResult {
private final List<ZipFileDetailLogVO> detailLogs;
private final Path extractPath;
public ExtractResult(List<ZipFileDetailLogVO> detailLogs, Path extractPath) {
this.detailLogs = detailLogs;
this.extractPath = extractPath;
}
}
}

@ -1,78 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.BatchFileDataVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : BatchFileDataMapper
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Mapper
public interface BatchFileDataMapper {
/**
* .
*
* @param batchFileData
* @return
*/
int insertBatchFileData(BatchFileDataVO batchFileData);
/**
* .
*
* @param batchFileData
* @return
*/
int updateBatchFileData(BatchFileDataVO batchFileData);
/**
* .
*
* @param fileDataId ID
* @return
*/
int deleteBatchFileData(@Param("fileDataId") String fileDataId);
/**
* .
*
* @param fileDataId ID
* @return
*/
BatchFileDataVO selectBatchFileData(@Param("fileDataId") String fileDataId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileDataTotalCount(BatchFileDataVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileDataVO> selectBatchFileDataList(BatchFileDataVO paramVO);
/**
* .
*
* @param fileNm
* @return
*/
List<BatchFileDataVO> selectBatchFileDataByFileNm(@Param("fileNm") String fileNm);
}

@ -1,78 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.BatchFileDataSuccessVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : BatchFileDataSuccessMapper
* author :
* date : 2025-01-14
* description : ( )
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Mapper
public interface BatchFileDataSuccessMapper {
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
int insertBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess);
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
int updateBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess);
/**
* .
*
* @param successDataId ID
* @return
*/
int deleteBatchFileDataSuccess(@Param("successDataId") String successDataId);
/**
* .
*
* @param successDataId ID
* @return
*/
BatchFileDataSuccessVO selectBatchFileDataSuccess(@Param("successDataId") String successDataId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileDataSuccessTotalCount(BatchFileDataSuccessVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileDataSuccessVO> selectBatchFileDataSuccessList(BatchFileDataSuccessVO paramVO);
/**
* .
*
* @param fileNm
* @return
*/
List<BatchFileDataSuccessVO> selectBatchFileDataSuccessByFileNm(@Param("fileNm") String fileNm);
}

@ -1,103 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.BatchFileRetryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : BatchFileRetryMapper
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Mapper
public interface BatchFileRetryMapper {
/**
* .
*
* @param batchFileRetry
* @return
*/
int insertBatchFileRetry(BatchFileRetryVO batchFileRetry);
/**
* .
*
* @param batchFileRetry
* @return
*/
int updateBatchFileRetry(BatchFileRetryVO batchFileRetry);
/**
* .
*
* @param retryId ID
* @return
*/
int deleteBatchFileRetry(@Param("retryId") String retryId);
/**
* .
*
* @param retryId ID
* @return
*/
BatchFileRetryVO selectBatchFileRetry(@Param("retryId") String retryId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileRetryListTotalCount(BatchFileRetryVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileRetryVO> selectBatchFileRetryList(BatchFileRetryVO paramVO);
/**
* .
*
* @param originalFileNm
* @return
*/
List<BatchFileRetryVO> selectBatchFileRetryByOriginalFileNm(@Param("originalFileNm") String originalFileNm);
/**
* .
* ( PENDING )
*
* @return
*/
List<BatchFileRetryVO> selectRetryTargetFiles();
/**
* .
*
* @param retryId ID
* @param retryStatus
* @return
*/
int updateRetryStatus(@Param("retryId") String retryId, @Param("retryStatus") String retryStatus);
/**
* .
*
* @param retryId ID
* @return
*/
int incrementRetryCnt(@Param("retryId") String retryId);
}

@ -1,165 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.BatchJobExecutionVO;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.model.BatchJobLogVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : BatchJobMapper
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Mapper
public interface BatchJobMapper {
/**
* .
*
* @param jobInfo
* @return
*/
int insertBatchJobInfo(BatchJobInfoVO jobInfo);
/**
* .
*
* @param jobInfo
* @return
*/
int updateBatchJobInfo(BatchJobInfoVO jobInfo);
/**
* .
*
* @param jobId ID
* @return
*/
int deleteBatchJobInfo(String jobId);
/**
* .
*
* @param jobId ID
* @return
*/
BatchJobInfoVO selectBatchJobInfo(String jobId);
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
BatchJobInfoVO selectBatchJobInfoByNmAndGroup(String jobNm, String jobGroup);
/**
* .
*
* @return
*/
List<BatchJobInfoVO> selectBatchJobInfoList(BatchJobInfoVO paramVO);
/**
* .
*
* @param execution
* @return
*/
int insertBatchJobExecution(BatchJobExecutionVO execution);
/**
* .
*
* @param execution
* @return
*/
int updateBatchJobExecution(BatchJobExecutionVO execution);
/**
* .
*
* @param executionId ID
* @return
*/
BatchJobExecutionVO selectBatchJobExecution(String executionId);
/**
* .
*
* @param paramVO BatchJobInfoVO
* @return
*/
int selectBatchJobExecutionTotalCount(BatchJobExecutionVO paramVO);
/**
* .
*
* @param paramVO VO
* @return
*/
List<BatchJobExecutionVO> selectBatchJobExecutionList(BatchJobExecutionVO paramVO);
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
BatchJobExecutionVO selectLastBatchJobExecution(String jobNm, String jobGroup);
/**
* .
*
* @param jobId ID
* @param statusCd
* @return
*/
int updateBatchJobStatus(String jobId, String statusCd);
/**
* ID .
*
* @param jobId ID
* @param executionId ID
* @return
*/
int updateBatchJobLastExecution(String jobId, String executionId);
/**
* .
*
* @param executionId ID
* @param logLevel (INFO, WARN, ERROR)
* @param logMessage
* @return
*/
int insertBatchJobLog(String executionId, String logLevel, String logMessage);
/**
* .
*
* @param paramVO BatchJobLogVO
* @return
*/
List<BatchJobLogVO> selectBatchJobLogList(BatchJobLogVO paramVO);
/**
* 48 .
*
* @return 48 ( )
*/
List<BatchJobInfoVO> selectRecentlyFailedJobs();
}

@ -1,93 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.ZipFileDetailLogVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : ZipFileDetailLogMapper
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Mapper
public interface ZipFileDetailLogMapper {
/**
* ZIP ID .
*
* @return ID
*/
String generateZipFileDetailLogId();
/**
* ZIP .
*
* @param zipFileDetailLog ZIP
* @return
*/
int insertZipFileDetailLog(ZipFileDetailLogVO zipFileDetailLog);
/**
* ZIP .
*
* @param zipFileDetailLogList ZIP
* @return
*/
int insertZipFileDetailLogList(List<ZipFileDetailLogVO> zipFileDetailLogList);
/**
* ZIP .
*
* @param detailLogId ID
* @return
*/
int deleteZipFileDetailLog(@Param("detailLogId") String detailLogId);
/**
* ID ZIP .
*
* @param logId ID
* @return
*/
int deleteZipFileDetailLogByLogId(@Param("logId") String logId);
/**
* ZIP .
*
* @param detailLogId ID
* @return ZIP
*/
ZipFileDetailLogVO selectZipFileDetailLog(@Param("detailLogId") String detailLogId);
/**
* ID ZIP .
*
* @param logId ID
* @return ZIP
*/
List<ZipFileDetailLogVO> selectZipFileDetailLogListByLogId(@Param("logId") String logId);
/**
* ZIP .
*
* @param paramVO
* @return
*/
int selectZipFileDetailLogListTotalCount(ZipFileDetailLogVO paramVO);
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
List<ZipFileDetailLogVO> selectZipFileDetailLogList(ZipFileDetailLogVO paramVO);
}

@ -1,85 +0,0 @@
package go.kr.project.batch.mapper;
import go.kr.project.batch.model.ZipFileProcessLogVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* packageNm : go.kr.project.batch.mapper
* fileNm : ZipFileProcessLogMapper
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Mapper
public interface ZipFileProcessLogMapper {
/**
* ZIP ID .
*
* @return ID
*/
String generateZipFileProcessLogId();
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
int insertZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog);
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
int updateZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog);
/**
* ZIP .
*
* @param logId ID
* @return
*/
int deleteZipFileProcessLog(@Param("logId") String logId);
/**
* ZIP .
*
* @param logId ID
* @return ZIP
*/
ZipFileProcessLogVO selectZipFileProcessLog(@Param("logId") String logId);
/**
* ZIP .
*
* @param paramVO
* @return
*/
int selectZipFileProcessLogListTotalCount(ZipFileProcessLogVO paramVO);
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
List<ZipFileProcessLogVO> selectZipFileProcessLogList(ZipFileProcessLogVO paramVO);
/**
* ZIP .
*
* @param zipFileNm ZIP
* @return ZIP
*/
ZipFileProcessLogVO selectProcessingZipFileLog(@Param("zipFileNm") String zipFileNm);
}

@ -1,44 +0,0 @@
package go.kr.project.batch.model;
import go.kr.project.common.model.PagingVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchFileDataSuccessVO
* author :
* date : 2025-01-14
* description : VO ( )
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BatchFileDataSuccessVO extends PagingVO {
private String successDataId; // 성공데이터ID
private String fileNm; // 파일명
private Integer lineNumber; // 라인번호
private String column1; // 컬럼1
private String column2; // 컬럼2
private String column3; // 컬럼3
private String column4; // 컬럼4
private String column5; // 컬럼5
private String rawData; // 원본데이터
private LocalDateTime processDttm; // 처리일시
private LocalDateTime regDttm; // 등록일시 (REG_DTTM)
private String rgtr; // 등록자 (RGTR)
private LocalDateTime mdfcnDttm; // 수정일시 (MDFCN_DTTM)
private String mdfr; // 수정자 (MDFR)
// 검색 조건용 필드
private String searchFileNm; // 파일명 검색
private String searchStartDate; // 처리일시 시작일
private String searchEndDate; // 처리일시 종료일
private String searchColumn1; // 컬럼1 검색
}

@ -1,46 +0,0 @@
package go.kr.project.batch.model;
import go.kr.project.common.model.PagingVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchFileDataVO
* author :
* date : 2025-01-14
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BatchFileDataVO extends PagingVO {
private String fileDataId; // 파일데이터ID
private String fileNm; // 파일명
private Integer lineNumber; // 라인번호
private String column1; // 컬럼1
private String column2; // 컬럼2
private String column3; // 컬럼3
private String column4; // 컬럼4
private String column5; // 컬럼5
private String rawData; // 원본데이터
private String processStatus; // 처리상태(SUCCESS/ERROR)
private String errorMessage; // 에러메시지
private LocalDateTime processDttm; // 처리일시
private LocalDateTime regDttm; // 등록일시 (REG_DTTM)
private String rgtr; // 등록자 (RGTR)
private LocalDateTime mdfcnDttm; // 수정일시 (MDFCN_DTTM)
private String mdfr; // 수정자 (MDFR)
// 검색 조건용 필드
private String searchFileNm; // 파일명 검색
private String searchProcessStatus; // 처리상태 검색
private String searchStartDate; // 처리일시 시작일
private String searchEndDate; // 처리일시 종료일
}

@ -1,29 +0,0 @@
package go.kr.project.batch.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchFileProcessingConfig
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Data
@Component
@ConfigurationProperties(prefix = "batch.file.processing")
public class BatchFileProcessingConfig {
private String sourceDir; // 읽을 대상 파일이 있는 디렉토리
private String completeDir; // 파일 처리 완료 후 이동될 디렉토리
private String errorDir; // 파일 처리 에러 시 이동될 디렉토리
private String delimiter; // 파일 컬럼 구분자
private String encoding; // 파일 인코딩
private boolean createDateSubdir; // yyyymmdd 하위 디렉토리 생성 여부
}

@ -1,26 +0,0 @@
package go.kr.project.batch.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchFileRetryConfig
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Data
@Component
@ConfigurationProperties(prefix = "batch.file.retry")
public class BatchFileRetryConfig {
private int maxRetryCount = 3; // 최대 재시도 횟수
private int retryIntervalHours = 1; // 재시도 간격 (시간)
private boolean enabled = true; // 에러 파일 재처리 기능 활성화 여부
}

@ -1,92 +0,0 @@
package go.kr.project.batch.model;
import go.kr.project.common.model.PagingVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* packageNm : go.kr.project.batch.model
* fileNm : BatchFileRetryVO
* author :
* date : 2025-01-14
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BatchFileRetryVO extends PagingVO {
private String retryId; // 재처리ID
private String originalFileNm; // 원본파일명
private String retryFileNm; // 재처리파일명
private int retryCnt; // 재시도횟수
private int maxRetryCnt; // 최대재시도횟수
private String retryStatus; // 재처리상태(PENDING/PROCESSING/SUCCESS/FAILED/EXCEEDED)
private String errorType; // 에러유형(FORMAT_ERROR/DATA_ERROR/SYSTEM_ERROR)
private String errorMessage; // 에러메시지
private LocalDateTime retryDttm; // 재처리일시
private LocalDateTime nextRetryDttm; // 다음재처리예정일시
private LocalDateTime completedDttm; // 완료일시
private LocalDateTime regDttm; // 등록일시 (REG_DTTM)
private String rgtr; // 등록자 (RGTR)
private LocalDateTime mdfcnDttm; // 수정일시 (MDFCN_DTTM)
private String mdfr; // 수정자 (MDFR)
// 재처리 상태 상수
public static final String STATUS_PENDING = "PENDING";
public static final String STATUS_PROCESSING = "PROCESSING";
public static final String STATUS_SUCCESS = "SUCCESS";
public static final String STATUS_FAILED = "FAILED";
public static final String STATUS_EXCEEDED = "EXCEEDED";
// 에러 유형 상수
public static final String ERROR_TYPE_FORMAT = "FORMAT_ERROR";
public static final String ERROR_TYPE_DATA = "DATA_ERROR";
public static final String ERROR_TYPE_SYSTEM = "SYSTEM_ERROR";
/**
* .
*
* @return
*/
public boolean canRetry() {
return retryCnt < maxRetryCnt &&
(STATUS_PENDING.equals(retryStatus) || STATUS_FAILED.equals(retryStatus));
}
/**
* .
*/
public void incrementRetryCnt() {
this.retryCnt++;
}
/**
* .
*/
public void markAsSuccess() {
this.retryStatus = STATUS_SUCCESS;
this.completedDttm = LocalDateTime.now();
}
/**
* .
*/
public void markAsFailed(String errorMessage) {
this.retryStatus = STATUS_FAILED;
this.errorMessage = errorMessage;
}
/**
* .
*/
public void markAsExceeded() {
this.retryStatus = STATUS_EXCEEDED;
this.completedDttm = LocalDateTime.now();
}
}

@ -1,81 +0,0 @@
package go.kr.project.batch.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* packageNm : go.kr.project.batch.model
* fileNm : BatchJobExecutionVO
* author :
* date : 2025-06-10
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BatchJobExecutionVO extends PagingVO {
/** 실행 ID (UUID) */
private String executionId;
/** 작업 이름 */
private String jobNm;
/** 작업 그룹 */
private String jobGroup;
/** 시작 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime startDttm;
/** 종료 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime endDttm;
/** 상태 (STARTED, COMPLETED, FAILED) */
private String statusCd;
/** 상태 이름 (코드 테이블에서 조회) */
private String statusNm;
/** 종료 코드 */
private String exitCd;
/** 종료 코드 이름 (코드 테이블에서 조회) */
private String exitCdNm;
/** 종료 메시지 */
private String exitMessage;
/** 서버 정보 */
private String serverInfo;
/** 생성 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDttm;
// 검색조건
private String searchStatusCd;
private String searchExitCd;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
private LocalDate searchStartDt;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul")
private LocalDate searchEndDt;
}

@ -1,74 +0,0 @@
package go.kr.project.batch.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageNm : go.kr.project.batch.model
* fileNm : BatchJobInfoVO
* author :
* date : 2025-06-10
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BatchJobInfoVO extends PagingVO {
/** 작업 ID (UUID) */
private String jobId;
/** 작업 이름 */
private String jobNm;
/** 작업 그룹 */
private String jobGroup;
/** 작업 클래스 */
private String jobClass;
/** Cron 표현식 */
private String cronExpression;
/** 작업 설명 */
private String jobDc;
/** 상태 (ACTIVE, PAUSED, DELETED) */
private String statusCd;
/** 상태 이름 (코드 테이블에서 조회) */
private String statusNm;
/** 마지막 실행 ID */
private String lastExecutionId;
/** 생성 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime regDttm;
/** 수정 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime mdfcnDttm;
//검색조건
private String searchStatusCd;
/** 최근 48시간 내 실패 여부 */
private boolean recentlyFailed;
/** 최근 5일 평균 소요시간 (xx시간 xx분 xx초 형식) */
private String avgDuration;
}

@ -1,51 +0,0 @@
package go.kr.project.batch.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchJobLogVO
* author :
* date : 2025-06-10
* description : VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@EqualsAndHashCode(callSuper=true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class BatchJobLogVO extends PagingVO {
/** 로그 ID */
private String logId;
/** 실행 ID (UUID) */
private String executionId;
/** 로그 시간 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime logDttm;
/** 로그 레벨 (INFO, WARN, ERROR) */
private String logLevel;
/** 로그 메시지 */
private String logMessage;
/** 생성 시간 */
private LocalDateTime regDttm;
//검색조건
private String searchLogLevel;
}

@ -1,102 +0,0 @@
package go.kr.project.batch.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* packageName : go.kr.project.batch.model
* fileName : BatchJobResult
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BatchJobResult {
/**
* (QuartzJobListener )
*/
private String statusCd;
/**
* ( )
*/
private String exitCd;
/**
*
*/
private int totalItems;
/**
*
*/
private int processedItems;
/**
* ( )
*/
private String errorMessage;
/**
*
*/
private int successItems;
/**
*
*/
private int errorItems;
/**
* .
*/
public void incrementProcessed() {
this.processedItems++;
}
/**
* .
*/
public void incrementSuccess() {
this.successItems++;
}
/**
* .
*/
public void incrementError() {
this.errorItems++;
}
/**
* .
*/
public int getProcessedCount() {
return this.processedItems;
}
/**
* .
*/
public int getSuccessCount() {
return this.successItems;
}
/**
* .
*/
public int getErrorCount() {
return this.errorItems;
}
}

@ -1,98 +0,0 @@
package go.kr.project.batch.model;
import go.kr.project.common.model.PagingVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* packageNm : go.kr.project.batch.model
* fileNm : ZipFileDetailLogVO
* author :
* date : 2025-01-27
* description : ZIP VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ZipFileDetailLogVO extends PagingVO {
/**
* ID
*/
private String detailLogId;
/**
* ID
*/
private String logId;
/**
*
*/
private String fileNm;
/**
*
*/
private String filePath;
/**
* (bytes)
*/
private Long fileSize;
/**
*
*/
private String fileType;
/**
* (Y/N)
*/
private String imageYn;
/**
* (Y/N)
*/
private String corruptedYn;
/**
* (SUCCESS/ERROR)
*/
private String processStatus;
/**
*
*/
private String errorMessage;
/**
*
*/
private LocalDateTime processDttm;
/**
*
*/
private LocalDateTime regDttm; // 등록일시 (REG_DTTM)
/**
* ID
*/
private String rgtr; // 등록자 (RGTR)
/**
*
*/
private LocalDateTime mdfcnDttm; // 수정일시 (MDFCN_DTTM)
/**
* ID
*/
private String mdfr; // 수정자 (MDFR)
}

@ -1,118 +0,0 @@
package go.kr.project.batch.model;
import go.kr.project.common.model.PagingVO;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* packageNm : go.kr.project.batch.model
* fileNm : ZipFileProcessLogVO
* author :
* date : 2025-01-27
* description : ZIP VO
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ZipFileProcessLogVO extends PagingVO {
/**
* ID
*/
private String logId;
/**
* ZIP
*/
private String zipFileNm;
/**
* ZIP
*/
private String zipFilePath;
/**
*
*/
private String extractPath;
/**
*
*/
private String archivePath;
/**
*
*/
private Integer totalFileCnt;
/**
*
*/
private Integer successFileCnt;
/**
*
*/
private Integer errorFileCnt;
/**
*
*/
private Integer imageFileCnt;
/**
*
*/
private Integer nonImageFileCnt;
/**
*
*/
private Integer corruptedFileCnt;
/**
* (PROCESSING/SUCCESS/ERROR)
*/
private String processStatus;
/**
*
*/
private String errorMessage;
/**
*
*/
private LocalDateTime startDttm;
/**
*
*/
private LocalDateTime endDttm;
/**
*
*/
private LocalDateTime regDttm; // 등록일시 (REG_DTTM)
/**
* ID
*/
private String rgtr; // 등록자 (RGTR)
/**
*
*/
private LocalDateTime mdfcnDttm; // 수정일시 (MDFCN_DTTM)
/**
* ID
*/
private String mdfr; // 수정자 (MDFR)
}

@ -1,83 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.BatchFileDataVO;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : BatchFileDataService
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
public interface BatchFileDataService {
/**
* .
*
* @param batchFileData
* @return
*/
int insertBatchFileData(BatchFileDataVO batchFileData);
/**
* .
*
* @param batchFileData
* @return
*/
int updateBatchFileData(BatchFileDataVO batchFileData);
/**
* .
*
* @param fileDataId ID
* @return
*/
int deleteBatchFileData(String fileDataId);
/**
* .
*
* @param fileDataId ID
* @return
*/
BatchFileDataVO selectBatchFileData(String fileDataId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileDataTotalCount(BatchFileDataVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileDataVO> selectBatchFileDataList(BatchFileDataVO paramVO);
/**
* .
*
* @param fileNm
* @return
*/
List<BatchFileDataVO> selectBatchFileDataByFileNm(String fileNm);
/**
* .
*
* @param batchFileDataList
* @return
*/
int insertBatchFileDataList(List<BatchFileDataVO> batchFileDataList);
}

@ -1,83 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.BatchFileDataSuccessVO;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : BatchFileDataSuccessService
* author :
* date : 2025-01-14
* description : ( )
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
public interface BatchFileDataSuccessService {
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
int insertBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess);
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
int updateBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess);
/**
* .
*
* @param successDataId ID
* @return
*/
int deleteBatchFileDataSuccess(String successDataId);
/**
* .
*
* @param successDataId ID
* @return
*/
BatchFileDataSuccessVO selectBatchFileDataSuccess(String successDataId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileDataSuccessTotalCount(BatchFileDataSuccessVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileDataSuccessVO> selectBatchFileDataSuccessList(BatchFileDataSuccessVO paramVO);
/**
* .
*
* @param fileNm
* @return
*/
List<BatchFileDataSuccessVO> selectBatchFileDataSuccessByFileNm(String fileNm);
/**
* .
*
* @param batchFileDataSuccessList
* @return
*/
int insertBatchFileDataSuccessList(List<BatchFileDataSuccessVO> batchFileDataSuccessList);
}

@ -1,110 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.BatchFileRetryVO;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : BatchFileRetryService
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
public interface BatchFileRetryService {
/**
* .
*
* @param batchFileRetry
* @return
*/
int insertBatchFileRetry(BatchFileRetryVO batchFileRetry);
/**
* .
*
* @param batchFileRetry
* @return
*/
int updateBatchFileRetry(BatchFileRetryVO batchFileRetry);
/**
* .
*
* @param retryId ID
* @return
*/
int deleteBatchFileRetry(String retryId);
/**
* .
*
* @param retryId ID
* @return
*/
BatchFileRetryVO selectBatchFileRetry(String retryId);
/**
* .
*
* @param paramVO
* @return
*/
int selectBatchFileRetryListTotalCount(BatchFileRetryVO paramVO);
/**
* .
*
* @param paramVO
* @return
*/
List<BatchFileRetryVO> selectBatchFileRetryList(BatchFileRetryVO paramVO);
/**
* .
*
* @param originalFileNm
* @return
*/
List<BatchFileRetryVO> selectBatchFileRetryByOriginalFileNm(String originalFileNm);
/**
* .
*
* @return
*/
List<BatchFileRetryVO> selectRetryTargetFiles();
/**
* .
*
* @param retryId ID
* @param retryStatus
* @return
*/
int updateRetryStatus(String retryId, String retryStatus);
/**
* .
*
* @param retryId ID
* @return
*/
int incrementRetryCnt(String retryId);
/**
* .
*
* @param originalFileNm
* @param errorType
* @param errorMessage
* @param maxRetryCnt
* @return
*/
BatchFileRetryVO createRetryRecord(String originalFileNm, String errorType, String errorMessage, int maxRetryCnt);
}

@ -1,158 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.BatchJobExecutionVO;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.model.BatchJobLogVO;
import org.quartz.Job;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : BatchJobService
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
public interface BatchJobService {
/**
* . (quartz scheduler )
*
* @param jobClass
* @param jobNm
* @param jobGroup
* @param cronExpression Cron
* @param description
* @return
*/
boolean scheduleJob(Class<? extends Job> jobClass, String jobNm, String jobGroup,
String cronExpression, String description);
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
boolean triggerJob(String jobNm, String jobGroup);
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
boolean pauseJob(String jobNm, String jobGroup);
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
boolean resumeJob(String jobNm, String jobGroup);
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
boolean deleteJob(String jobNm, String jobGroup);
/**
* .
*
* @return
*/
BatchJobInfoVO getBatchJobInfo(String jobId);
/**
* .
*
* @param paramVO VO
* @return
*/
List<BatchJobInfoVO> getBatchJobInfoList(BatchJobInfoVO paramVO);
/**
* .
*
* @param paramVO VO
* @return
*/
int getBatchJobExecutionTotalCount(BatchJobExecutionVO paramVO);
/**
* .
*
* @param paramVO VO
* @return
*/
List<BatchJobExecutionVO> getBatchJobExecutionList(BatchJobExecutionVO paramVO);
/**
* .
*
* @param jobClass
* @param jobNm
* @param jobGroup
* @param cronExpression Cron
* @param description
* @return
*/
BatchJobInfoVO registerBatchJobInfo(Class<? extends Job> jobClass, String jobNm, String jobGroup,
String cronExpression, String description);
/**
* .
*
* @param paramVO BatchJobExecutionVO
* @return
*/
List<BatchJobLogVO> getBatchJobLogList(BatchJobLogVO paramVO);
/**
* .
*
* @param jobNm
* @param jobGroup
* @param status (ACTIVE, PAUSED, DELETED)
* @return
*/
boolean updateJobStatus(String jobNm, String jobGroup, String status);
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
BatchJobInfoVO getBatchJobInfoByNmAndGroup(String jobNm, String jobGroup);
/**
* 48 .
*
* @return 48 ( )
*/
List<BatchJobInfoVO> getRecentlyFailedJobs();
/**
* 5 .
*
* @param jobNm
* @param jobGroup
* @return (xx xx xx )
*/
String calculateAverageDuration(String jobNm, String jobGroup);
}

@ -1,90 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.ZipFileDetailLogVO;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : ZipFileDetailLogService
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
public interface ZipFileDetailLogService {
/**
* ZIP ID .
*
* @return ID
*/
String generateZipFileDetailLogId();
/**
* ZIP .
*
* @param zipFileDetailLog ZIP
* @return
*/
int insertZipFileDetailLog(ZipFileDetailLogVO zipFileDetailLog);
/**
* ZIP .
*
* @param zipFileDetailLogList ZIP
* @return
*/
int insertZipFileDetailLogList(List<ZipFileDetailLogVO> zipFileDetailLogList);
/**
* ZIP .
*
* @param detailLogId ID
* @return
*/
int deleteZipFileDetailLog(String detailLogId);
/**
* ID ZIP .
*
* @param logId ID
* @return
*/
int deleteZipFileDetailLogByLogId(String logId);
/**
* ZIP .
*
* @param detailLogId ID
* @return ZIP
*/
ZipFileDetailLogVO selectZipFileDetailLog(String detailLogId);
/**
* ID ZIP .
*
* @param logId ID
* @return ZIP
*/
List<ZipFileDetailLogVO> selectZipFileDetailLogListByLogId(String logId);
/**
* ZIP .
*
* @param paramVO
* @return
*/
int selectZipFileDetailLogListTotalCount(ZipFileDetailLogVO paramVO);
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
List<ZipFileDetailLogVO> selectZipFileDetailLogList(ZipFileDetailLogVO paramVO);
}

@ -1,82 +0,0 @@
package go.kr.project.batch.service;
import go.kr.project.batch.model.ZipFileProcessLogVO;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service
* fileNm : ZipFileProcessLogService
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
public interface ZipFileProcessLogService {
/**
* ZIP ID .
*
* @return ID
*/
String generateZipFileProcessLogId();
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
int insertZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog);
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
int updateZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog);
/**
* ZIP .
*
* @param logId ID
* @return
*/
int deleteZipFileProcessLog(String logId);
/**
* ZIP .
*
* @param logId ID
* @return ZIP
*/
ZipFileProcessLogVO selectZipFileProcessLog(String logId);
/**
* ZIP .
*
* @param paramVO
* @return
*/
int selectZipFileProcessLogListTotalCount(ZipFileProcessLogVO paramVO);
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
List<ZipFileProcessLogVO> selectZipFileProcessLogList(ZipFileProcessLogVO paramVO);
/**
* ZIP .
*
* @param zipFileNm ZIP
* @return ZIP
*/
ZipFileProcessLogVO selectProcessingZipFileLog(String zipFileNm);
}

@ -1,136 +0,0 @@
package go.kr.project.batch.service.impl;
import go.kr.project.batch.mapper.BatchFileDataMapper;
import go.kr.project.batch.model.BatchFileDataVO;
import go.kr.project.batch.service.BatchFileDataService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : BatchFileDataServiceImpl
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BatchFileDataServiceImpl extends EgovAbstractServiceImpl implements BatchFileDataService {
private final BatchFileDataMapper batchFileDataMapper;
/**
* .
*
* @param batchFileData
* @return
*/
@Override
@Transactional
public int insertBatchFileData(BatchFileDataVO batchFileData) {
log.debug("[배치파일데이터등록] 파일명: {}, 라인번호: {}", batchFileData.getFileNm(), batchFileData.getLineNumber());
return batchFileDataMapper.insertBatchFileData(batchFileData);
}
/**
* .
*
* @param batchFileData
* @return
*/
@Override
@Transactional
public int updateBatchFileData(BatchFileDataVO batchFileData) {
log.debug("[배치파일데이터수정] 파일데이터ID: {}", batchFileData.getFileDataId());
return batchFileDataMapper.updateBatchFileData(batchFileData);
}
/**
* .
*
* @param fileDataId ID
* @return
*/
@Override
@Transactional
public int deleteBatchFileData(String fileDataId) {
log.debug("[배치파일데이터삭제] 파일데이터ID: {}", fileDataId);
return batchFileDataMapper.deleteBatchFileData(fileDataId);
}
/**
* .
*
* @param fileDataId ID
* @return
*/
@Override
public BatchFileDataVO selectBatchFileData(String fileDataId) {
log.debug("[배치파일데이터조회] 파일데이터ID: {}", fileDataId);
return batchFileDataMapper.selectBatchFileData(fileDataId);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public int selectBatchFileDataTotalCount(BatchFileDataVO paramVO) {
log.debug("[배치파일데이터총개수조회] 검색파일명: {}, 검색상태: {}", paramVO.getSearchFileNm(), paramVO.getSearchProcessStatus());
return batchFileDataMapper.selectBatchFileDataTotalCount(paramVO);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public List<BatchFileDataVO> selectBatchFileDataList(BatchFileDataVO paramVO) {
log.debug("[배치파일데이터목록조회] 검색파일명: {}, 검색상태: {}", paramVO.getSearchFileNm(), paramVO.getSearchProcessStatus());
return batchFileDataMapper.selectBatchFileDataList(paramVO);
}
/**
* .
*
* @param fileNm
* @return
*/
@Override
public List<BatchFileDataVO> selectBatchFileDataByFileNm(String fileNm) {
log.debug("[파일명별배치파일데이터조회] 파일명: {}", fileNm);
return batchFileDataMapper.selectBatchFileDataByFileNm(fileNm);
}
/**
* .
*
* @param batchFileDataList
* @return
*/
@Override
@Transactional
public int insertBatchFileDataList(List<BatchFileDataVO> batchFileDataList) {
log.debug("[배치파일데이터일괄등록] 등록건수: {}", batchFileDataList.size());
int insertCount = 0;
for (BatchFileDataVO batchFileData : batchFileDataList) {
insertCount += batchFileDataMapper.insertBatchFileData(batchFileData);
}
log.debug("[배치파일데이터일괄등록완료] 실제등록건수: {}", insertCount);
return insertCount;
}
}

@ -1,136 +0,0 @@
package go.kr.project.batch.service.impl;
import go.kr.project.batch.mapper.BatchFileDataSuccessMapper;
import go.kr.project.batch.model.BatchFileDataSuccessVO;
import go.kr.project.batch.service.BatchFileDataSuccessService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : BatchFileDataSuccessServiceImpl
* author :
* date : 2025-01-14
* description : ( )
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BatchFileDataSuccessServiceImpl extends EgovAbstractServiceImpl implements BatchFileDataSuccessService {
private final BatchFileDataSuccessMapper batchFileDataSuccessMapper;
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
@Override
@Transactional
public int insertBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess) {
log.debug("[배치파일성공데이터등록] 파일명: {}, 라인번호: {}", batchFileDataSuccess.getFileNm(), batchFileDataSuccess.getLineNumber());
return batchFileDataSuccessMapper.insertBatchFileDataSuccess(batchFileDataSuccess);
}
/**
* .
*
* @param batchFileDataSuccess
* @return
*/
@Override
@Transactional
public int updateBatchFileDataSuccess(BatchFileDataSuccessVO batchFileDataSuccess) {
log.debug("[배치파일성공데이터수정] 성공데이터ID: {}", batchFileDataSuccess.getSuccessDataId());
return batchFileDataSuccessMapper.updateBatchFileDataSuccess(batchFileDataSuccess);
}
/**
* .
*
* @param successDataId ID
* @return
*/
@Override
@Transactional
public int deleteBatchFileDataSuccess(String successDataId) {
log.debug("[배치파일성공데이터삭제] 성공데이터ID: {}", successDataId);
return batchFileDataSuccessMapper.deleteBatchFileDataSuccess(successDataId);
}
/**
* .
*
* @param successDataId ID
* @return
*/
@Override
public BatchFileDataSuccessVO selectBatchFileDataSuccess(String successDataId) {
log.debug("[배치파일성공데이터조회] 성공데이터ID: {}", successDataId);
return batchFileDataSuccessMapper.selectBatchFileDataSuccess(successDataId);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public int selectBatchFileDataSuccessTotalCount(BatchFileDataSuccessVO paramVO) {
log.debug("[배치파일성공데이터총개수조회] 검색파일명: {}, 검색컬럼1: {}", paramVO.getSearchFileNm(), paramVO.getSearchColumn1());
return batchFileDataSuccessMapper.selectBatchFileDataSuccessTotalCount(paramVO);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public List<BatchFileDataSuccessVO> selectBatchFileDataSuccessList(BatchFileDataSuccessVO paramVO) {
log.debug("[배치파일성공데이터목록조회] 검색파일명: {}, 검색컬럼1: {}", paramVO.getSearchFileNm(), paramVO.getSearchColumn1());
return batchFileDataSuccessMapper.selectBatchFileDataSuccessList(paramVO);
}
/**
* .
*
* @param fileNm
* @return
*/
@Override
public List<BatchFileDataSuccessVO> selectBatchFileDataSuccessByFileNm(String fileNm) {
log.debug("[파일명별배치파일성공데이터조회] 파일명: {}", fileNm);
return batchFileDataSuccessMapper.selectBatchFileDataSuccessByFileNm(fileNm);
}
/**
* .
*
* @param batchFileDataSuccessList
* @return
*/
@Override
@Transactional
public int insertBatchFileDataSuccessList(List<BatchFileDataSuccessVO> batchFileDataSuccessList) {
log.debug("[배치파일성공데이터일괄등록] 등록건수: {}", batchFileDataSuccessList.size());
int insertCount = 0;
for (BatchFileDataSuccessVO batchFileDataSuccess : batchFileDataSuccessList) {
insertCount += batchFileDataSuccessMapper.insertBatchFileDataSuccess(batchFileDataSuccess);
}
log.debug("[배치파일성공데이터일괄등록완료] 실제등록건수: {}", insertCount);
return insertCount;
}
}

@ -1,208 +0,0 @@
package go.kr.project.batch.service.impl;
import egovframework.util.SessionUtil;
import go.kr.project.batch.mapper.BatchFileRetryMapper;
import go.kr.project.batch.model.BatchFileRetryVO;
import go.kr.project.batch.service.BatchFileRetryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : BatchFileRetryServiceImpl
* author :
* date : 2025-01-14
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-14
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BatchFileRetryServiceImpl extends EgovAbstractServiceImpl implements BatchFileRetryService {
private final BatchFileRetryMapper batchFileRetryMapper;
private static final DateTimeFormatter fileTimestampFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
/**
* .
*
* @param batchFileRetry
* @return
*/
@Override
public int insertBatchFileRetry(BatchFileRetryVO batchFileRetry) {
log.debug("재처리 이력 등록 - 원본파일명: {}", batchFileRetry.getOriginalFileNm());
// 기본값 설정
if (batchFileRetry.getRgtr() == null) {
batchFileRetry.setRgtr(SessionUtil.getBatchUserId());
}
if (batchFileRetry.getRetryStatus() == null) {
batchFileRetry.setRetryStatus(BatchFileRetryVO.STATUS_PENDING);
}
if (batchFileRetry.getRetryCnt() == 0) {
batchFileRetry.setRetryCnt(0); // 최초등록은 0으로 셋팅
}
if (batchFileRetry.getMaxRetryCnt() == 0) {
batchFileRetry.setMaxRetryCnt(3);
}
return batchFileRetryMapper.insertBatchFileRetry(batchFileRetry);
}
/**
* .
*
* @param batchFileRetry
* @return
*/
@Override
public int updateBatchFileRetry(BatchFileRetryVO batchFileRetry) {
log.debug("재처리 이력 수정 - 재처리ID: {}", batchFileRetry.getRetryId());
if (batchFileRetry.getMdfr() == null) {
batchFileRetry.setMdfr(SessionUtil.getBatchUserId());
}
return batchFileRetryMapper.updateBatchFileRetry(batchFileRetry);
}
/**
* .
*
* @param retryId ID
* @return
*/
@Override
public int deleteBatchFileRetry(String retryId) {
log.debug("재처리 이력 삭제 - 재처리ID: {}", retryId);
return batchFileRetryMapper.deleteBatchFileRetry(retryId);
}
/**
* .
*
* @param retryId ID
* @return
*/
@Override
public BatchFileRetryVO selectBatchFileRetry(String retryId) {
log.debug("재처리 이력 조회 - 재처리ID: {}", retryId);
return batchFileRetryMapper.selectBatchFileRetry(retryId);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public int selectBatchFileRetryListTotalCount(BatchFileRetryVO paramVO) {
log.debug("재처리 이력 목록 총 개수 조회");
return batchFileRetryMapper.selectBatchFileRetryListTotalCount(paramVO);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public List<BatchFileRetryVO> selectBatchFileRetryList(BatchFileRetryVO paramVO) {
log.debug("재처리 이력 목록 조회");
return batchFileRetryMapper.selectBatchFileRetryList(paramVO);
}
/**
* .
*
* @param originalFileNm
* @return
*/
@Override
public List<BatchFileRetryVO> selectBatchFileRetryByOriginalFileNm(String originalFileNm) {
log.debug("원본 파일명으로 재처리 이력 조회 - 파일명: {}", originalFileNm);
return batchFileRetryMapper.selectBatchFileRetryByOriginalFileNm(originalFileNm);
}
/**
* .
*
* @return
*/
@Override
public List<BatchFileRetryVO> selectRetryTargetFiles() {
log.debug("재처리 대상 파일 목록 조회");
return batchFileRetryMapper.selectRetryTargetFiles();
}
/**
* .
*
* @param retryId ID
* @param retryStatus
* @return
*/
@Override
public int updateRetryStatus(String retryId, String retryStatus) {
log.debug("재처리 상태 업데이트 - 재처리ID: {}, 상태: {}", retryId, retryStatus);
return batchFileRetryMapper.updateRetryStatus(retryId, retryStatus);
}
/**
* .
*
* @param retryId ID
* @return
*/
@Override
public int incrementRetryCnt(String retryId) {
log.debug("재처리 횟수 증가 - 재처리ID: {}", retryId);
return batchFileRetryMapper.incrementRetryCnt(retryId);
}
/**
* .
*
* @param originalFileNm
* @param errorType
* @param errorMessage
* @param maxRetryCnt
* @return
*/
@Override
public BatchFileRetryVO createRetryRecord(String originalFileNm, String errorType, String errorMessage, int maxRetryCnt) {
log.debug("재처리 이력 생성 - 원본파일명: {}, 에러유형: {}", originalFileNm, errorType);
// 재처리 이력 생성
BatchFileRetryVO retryRecord = new BatchFileRetryVO();
retryRecord.setOriginalFileNm(originalFileNm);
retryRecord.setRetryFileNm(originalFileNm); // 초기에는 원본파일명으로 설정, 실제 재처리 시 업데이트됨
retryRecord.setRetryCnt(0);
retryRecord.setMaxRetryCnt(maxRetryCnt);
retryRecord.setRetryStatus(BatchFileRetryVO.STATUS_PENDING);
retryRecord.setErrorType(errorType);
retryRecord.setErrorMessage(errorMessage);
retryRecord.setRetryDttm(LocalDateTime.now());
retryRecord.setRgtr(SessionUtil.getBatchUserId());
// 다음 재처리 예정일시 설정 (1시간 후)
retryRecord.setNextRetryDttm(LocalDateTime.now().plusHours(1));
// DB에 저장
insertBatchFileRetry(retryRecord);
return retryRecord;
}
}

@ -1,429 +0,0 @@
package go.kr.project.batch.service.impl;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.mapper.BatchJobMapper;
import go.kr.project.batch.model.BatchJobExecutionVO;
import go.kr.project.batch.model.BatchJobInfoVO;
import go.kr.project.batch.model.BatchJobLogVO;
import go.kr.project.batch.service.BatchJobService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.quartz.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : BatchJobServiceImpl
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BatchJobServiceImpl extends EgovAbstractServiceImpl implements BatchJobService {
private final Scheduler scheduler;
private final BatchJobMapper batchJobMapper;
/**
* . (quartz scheduler )
*
* @param jobClass
* @param jobNm
* @param jobGroup
* @param cronExpression Cron
* @param description
* @return
*/
@Override
public boolean scheduleJob(Class<? extends Job> jobClass, String jobNm, String jobGroup,
String cronExpression, String description) {
try {
// JobDetail 생성
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(jobNm, jobGroup)
.withDescription(description)
.storeDurably()
.build();
// Trigger 생성 (Cron 표현식 사용)
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(jobNm + BatchConstants.JOB_TRIGGER_SUFFIX, jobGroup)
.withDescription(description)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.forJob(jobDetail)
.build();
// 스케줄러에 작업 등록
if (scheduler.checkExists(jobDetail.getKey())) {
// 이미 존재하는 작업이면 업데이트
scheduler.rescheduleJob(trigger.getKey(), trigger);
log.info(String.format("배치 작업 업데이트 완료: %s.%s", jobGroup, jobNm));
} else {
// 새 작업 등록
scheduler.scheduleJob(jobDetail, trigger);
log.info(String.format("배치 작업 등록 완료: %s.%s", jobGroup, jobNm));
}
return true;
} catch (SchedulerException e) {
log.error(String.format("배치 작업 스케줄링 중 오류 발생: %s", e.getMessage()), e);
return false;
}
}
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
@Override
public boolean triggerJob(String jobNm, String jobGroup) {
try {
JobKey jobKey = new JobKey(jobNm, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.triggerJob(jobKey);
log.info(String.format("배치 작업 즉시 실행: %s.%s", jobGroup, jobNm));
return true;
}
return false;
} catch (SchedulerException e) {
log.error(String.format("배치 작업 즉시 실행 중 오류 발생: %s", e.getMessage()), e);
return false;
}
}
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
@Override
public boolean pauseJob(String jobNm, String jobGroup) {
try {
JobKey jobKey = new JobKey(jobNm, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey);
log.info(String.format("배치 작업 일시 중지: %s.%s", jobGroup, jobNm));
return true;
}
return false;
} catch (SchedulerException e) {
log.error(String.format("배치 작업 일시 중지 중 오류 발생: %s", e.getMessage()), e);
return false;
}
}
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
@Override
public boolean resumeJob(String jobNm, String jobGroup) {
try {
JobKey jobKey = new JobKey(jobNm, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.resumeJob(jobKey);
log.info(String.format("배치 작업 재개: %s.%s", jobGroup, jobNm));
return true;
}
return false;
} catch (SchedulerException e) {
log.error(String.format("배치 작업 재개 중 오류 발생: %s", e.getMessage()), e);
return false;
}
}
/**
* . (quartz scheduler )
*
* @param jobNm
* @param jobGroup
* @return
*/
@Override
public boolean deleteJob(String jobNm, String jobGroup) {
try {
JobKey jobKey = new JobKey(jobNm, jobGroup);
if (scheduler.checkExists(jobKey)) {
scheduler.deleteJob(jobKey);
log.info(String.format("배치 작업 삭제: %s.%s", jobGroup, jobNm));
return true;
}
return false;
} catch (SchedulerException e) {
log.error(String.format("배치 작업 삭제 중 오류 발생: %s", e.getMessage()), e);
return false;
}
}
/**
* .
*
* @return
*/
@Override
public BatchJobInfoVO getBatchJobInfo(String jobId) {
return batchJobMapper.selectBatchJobInfo(jobId);
}
/**
* .
*
* @return
*/
@Override
public List<BatchJobInfoVO> getBatchJobInfoList(BatchJobInfoVO paramVO) {
return batchJobMapper.selectBatchJobInfoList(paramVO);
}
/**
* .
*
* @param paramVO
* @return
*/
@Override
public int getBatchJobExecutionTotalCount(BatchJobExecutionVO paramVO) {
return batchJobMapper.selectBatchJobExecutionTotalCount(paramVO);
}
/**
* .
*
* @param paramVO VO
* @return
*/
@Override
public List<BatchJobExecutionVO> getBatchJobExecutionList(BatchJobExecutionVO paramVO) {
return batchJobMapper.selectBatchJobExecutionList(paramVO);
}
/**
* .
*
* @param jobClass
* @param jobNm
* @param jobGroup
* @param cronExpression Cron
* @param JobDc
* @return
*/
@Override
public BatchJobInfoVO registerBatchJobInfo(Class<? extends Job> jobClass, String jobNm, String jobGroup,
String cronExpression, String JobDc) {
// 이미 등록된 작업인지 확인
BatchJobInfoVO existingJob = batchJobMapper.selectBatchJobInfoByNmAndGroup(jobNm, jobGroup);
if (existingJob != null) {
// 이미 존재하는 작업이면 업데이트
existingJob.setCronExpression(cronExpression);
existingJob.setJobDc(JobDc);
existingJob.setStatusCd(BatchConstants.JOB_INFO_STATUS_ACTIVE);
batchJobMapper.updateBatchJobInfo(existingJob);
return existingJob;
} else {
// 새 작업 등록
BatchJobInfoVO jobInfo = BatchJobInfoVO.builder()
.jobId(UUID.randomUUID().toString())
.jobNm(jobNm)
.jobGroup(jobGroup)
.jobClass(jobClass.getName())
.cronExpression(cronExpression)
.jobDc(JobDc)
.statusCd(BatchConstants.JOB_INFO_STATUS_ACTIVE)
.build();
batchJobMapper.insertBatchJobInfo(jobInfo);
return jobInfo;
}
}
/**
* .
*
* @param paramVO BatchJobExecutionVO
* @return
*/
@Override
public List<BatchJobLogVO> getBatchJobLogList(BatchJobLogVO paramVO) {
return batchJobMapper.selectBatchJobLogList(paramVO);
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @param status (ACTIVE, PAUSED, DELETED)
* @return
*/
@Override
@Transactional
public boolean updateJobStatus(String jobNm, String jobGroup, String status) {
try {
// 작업 정보 조회
BatchJobInfoVO jobInfo = getBatchJobInfoByNmAndGroup(jobNm, jobGroup);
if (jobInfo == null) {
log.warn("배치 작업 정보를 찾을 수 없습니다: {}.{}", jobGroup, jobNm);
return false;
}
// 스케줄러에서 작업 상태 변경
JobKey jobKey = new JobKey(jobNm, jobGroup);
if (scheduler.checkExists(jobKey)) {
if (BatchConstants.JOB_INFO_STATUS_PAUSED.equals(status)) {
// 일시 중지
scheduler.pauseJob(jobKey);
log.info("배치 작업을 일시 중지했습니다: {}.{}", jobGroup, jobNm);
} else if (BatchConstants.JOB_INFO_STATUS_ACTIVE.equals(status)) {
// 활성화 (재개)
scheduler.resumeJob(jobKey);
log.info("배치 작업을 재개했습니다: {}.{}", jobGroup, jobNm);
} else if (BatchConstants.JOB_INFO_STATUS_DELETED.equals(status)) {
// 삭제
scheduler.deleteJob(jobKey);
log.info("배치 작업을 삭제했습니다: {}.{}", jobGroup, jobNm);
}
}
// 데이터베이스에서 작업 상태 업데이트
int result = batchJobMapper.updateBatchJobStatus(jobInfo.getJobId(), status);
if (result > 0) {
log.info("배치 작업 상태를 업데이트했습니다: {}.{} -> {}", jobGroup, jobNm, status);
return true;
} else {
log.warn("배치 작업 상태 업데이트에 실패했습니다: {}.{}", jobGroup, jobNm);
return false;
}
} catch (Exception e) {
log.error("배치 작업 상태 업데이트 중 오류 발생: {}", e.getMessage(), e);
return false;
}
}
/**
* .
*
* @param jobNm
* @param jobGroup
* @return
*/
@Override
public BatchJobInfoVO getBatchJobInfoByNmAndGroup(String jobNm, String jobGroup) {
return batchJobMapper.selectBatchJobInfoByNmAndGroup(jobNm, jobGroup);
}
/**
* 48 .
*
* @return 48 ( )
*/
@Override
public List<BatchJobInfoVO> getRecentlyFailedJobs() {
return batchJobMapper.selectRecentlyFailedJobs();
}
/**
* 5 .
*
* @param jobNm
* @param jobGroup
* @return (xx xx xx )
*/
@Override
public String calculateAverageDuration(String jobNm, String jobGroup) {
try {
// 최근 5일간의 데이터를 조회하기 위한 파라미터 설정
BatchJobExecutionVO paramVO = new BatchJobExecutionVO();
paramVO.setJobNm(jobNm);
paramVO.setJobGroup(jobGroup);
// 현재 날짜로부터 5일 전 날짜 계산
java.time.LocalDate endDate = java.time.LocalDate.now();
java.time.LocalDate startDate = endDate.minusDays(5);
paramVO.setSearchStartDt(startDate);
paramVO.setSearchEndDt(endDate);
// 상태가 COMPLETED인 실행 결과만 조회
paramVO.setSearchStatusCd(BatchConstants.JOB_EXECUTION_STATUS_COMPLETED);
// 실행 결과 조회
List<BatchJobExecutionVO> executions = batchJobMapper.selectBatchJobExecutionList(paramVO);
// 실행 결과가 없으면 '-' 반환
if (executions == null || executions.isEmpty()) {
return "-";
}
long totalDurationMillis = 0;
int validExecutionCount = 0;
// 각 실행 결과의 소요시간 계산
for (BatchJobExecutionVO execution : executions) {
if (execution.getStartDttm() != null && execution.getEndDttm() != null) {
java.time.Duration duration = java.time.Duration.between(execution.getStartDttm(), execution.getEndDttm());
long durationMillis = duration.toMillis();
if (durationMillis > 0) {
totalDurationMillis += durationMillis;
validExecutionCount++;
}
}
}
// 유효한 실행 결과가 없으면 '-' 반환
if (validExecutionCount == 0) {
return "-";
}
// 평균 소요시간 계산 (밀리초, 소수점 반올림)
double avgDurationMillis = (double) totalDurationMillis / validExecutionCount;
long avgDurationMillisRounded = Math.round(avgDurationMillis);
// 시간, 분, 초로 변환 (초 단위 반올림)
long totalSeconds = Math.round(avgDurationMillisRounded / 1000.0);
long hours = totalSeconds / 3600;
long minutes = (totalSeconds % 3600) / 60;
long seconds = totalSeconds % 60;
// xx시간 xx분 xx초 형식으로 반환
StringBuilder result = new StringBuilder();
if (hours > 0) {
result.append(hours).append("시간 ");
}
if (minutes > 0 || hours > 0) {
result.append(minutes).append("분 ");
}
result.append(seconds).append("초");
return result.toString();
} catch (Exception e) {
log.error("평균 소요시간 계산 중 오류 발생: {}", e.getMessage(), e);
return "-";
}
}
}

@ -1,184 +0,0 @@
package go.kr.project.batch.service.impl;
import go.kr.project.batch.mapper.ZipFileDetailLogMapper;
import go.kr.project.batch.model.ZipFileDetailLogVO;
import go.kr.project.batch.service.ZipFileDetailLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : ZipFileDetailLogServiceImpl
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ZipFileDetailLogServiceImpl extends EgovAbstractServiceImpl implements ZipFileDetailLogService {
private final ZipFileDetailLogMapper zipFileDetailLogMapper;
/**
* ZIP ID .
*
* @return ID
*/
@Override
public String generateZipFileDetailLogId() {
try {
return zipFileDetailLogMapper.generateZipFileDetailLogId();
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 ID 생성 중 오류 발생", e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 ID 생성 실패", e);
}
}
/**
* ZIP .
*
* @param zipFileDetailLog ZIP
* @return
*/
@Override
@Transactional
public int insertZipFileDetailLog(ZipFileDetailLogVO zipFileDetailLog) {
try {
return zipFileDetailLogMapper.insertZipFileDetailLog(zipFileDetailLog);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 등록 중 오류 발생: {}", zipFileDetailLog, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 등록 실패", e);
}
}
/**
* ZIP .
*
* @param zipFileDetailLogList ZIP
* @return
*/
@Override
@Transactional
public int insertZipFileDetailLogList(List<ZipFileDetailLogVO> zipFileDetailLogList) {
try {
if (zipFileDetailLogList == null || zipFileDetailLogList.isEmpty()) {
log.warn("등록할 ZIP 파일 처리 상세 로그 목록이 비어있습니다.");
return 0;
}
return zipFileDetailLogMapper.insertZipFileDetailLogList(zipFileDetailLogList);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 목록 일괄 등록 중 오류 발생: size={}",
zipFileDetailLogList != null ? zipFileDetailLogList.size() : 0, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 목록 일괄 등록 실패", e);
}
}
/**
* ZIP .
*
* @param detailLogId ID
* @return
*/
@Override
@Transactional
public int deleteZipFileDetailLog(String detailLogId) {
try {
return zipFileDetailLogMapper.deleteZipFileDetailLog(detailLogId);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 삭제 중 오류 발생: detailLogId={}", detailLogId, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 삭제 실패", e);
}
}
/**
* ID ZIP .
*
* @param logId ID
* @return
*/
@Override
@Transactional
public int deleteZipFileDetailLogByLogId(String logId) {
try {
return zipFileDetailLogMapper.deleteZipFileDetailLogByLogId(logId);
} catch (Exception e) {
log.error("로그 ID로 ZIP 파일 처리 상세 로그 삭제 중 오류 발생: logId={}", logId, e);
throw new RuntimeException("로그 ID로 ZIP 파일 처리 상세 로그 삭제 실패", e);
}
}
/**
* ZIP .
*
* @param detailLogId ID
* @return ZIP
*/
@Override
public ZipFileDetailLogVO selectZipFileDetailLog(String detailLogId) {
try {
return zipFileDetailLogMapper.selectZipFileDetailLog(detailLogId);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 조회 중 오류 발생: detailLogId={}", detailLogId, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 조회 실패", e);
}
}
/**
* ID ZIP .
*
* @param logId ID
* @return ZIP
*/
@Override
public List<ZipFileDetailLogVO> selectZipFileDetailLogListByLogId(String logId) {
try {
return zipFileDetailLogMapper.selectZipFileDetailLogListByLogId(logId);
} catch (Exception e) {
log.error("로그 ID로 ZIP 파일 처리 상세 로그 목록 조회 중 오류 발생: logId={}", logId, e);
throw new RuntimeException("로그 ID로 ZIP 파일 처리 상세 로그 목록 조회 실패", e);
}
}
/**
* ZIP .
*
* @param paramVO
* @return
*/
@Override
public int selectZipFileDetailLogListTotalCount(ZipFileDetailLogVO paramVO) {
try {
return zipFileDetailLogMapper.selectZipFileDetailLogListTotalCount(paramVO);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 목록 총 개수 조회 중 오류 발생: {}", paramVO, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 목록 총 개수 조회 실패", e);
}
}
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
@Override
public List<ZipFileDetailLogVO> selectZipFileDetailLogList(ZipFileDetailLogVO paramVO) {
try {
return zipFileDetailLogMapper.selectZipFileDetailLogList(paramVO);
} catch (Exception e) {
log.error("ZIP 파일 처리 상세 로그 목록 조회 중 오류 발생: {}", paramVO, e);
throw new RuntimeException("ZIP 파일 처리 상세 로그 목록 조회 실패", e);
}
}
}

@ -1,162 +0,0 @@
package go.kr.project.batch.service.impl;
import go.kr.project.batch.mapper.ZipFileProcessLogMapper;
import go.kr.project.batch.model.ZipFileProcessLogVO;
import go.kr.project.batch.service.ZipFileProcessLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageNm : go.kr.project.batch.service.impl
* fileNm : ZipFileProcessLogServiceImpl
* author :
* date : 2025-01-27
* description : ZIP
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-01-27
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ZipFileProcessLogServiceImpl extends EgovAbstractServiceImpl implements ZipFileProcessLogService {
private final ZipFileProcessLogMapper zipFileProcessLogMapper;
/**
* ZIP ID .
*
* @return ID
*/
@Override
public String generateZipFileProcessLogId() {
try {
return zipFileProcessLogMapper.generateZipFileProcessLogId();
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 ID 생성 중 오류 발생", e);
throw new RuntimeException("ZIP 파일 처리 로그 ID 생성 실패", e);
}
}
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
@Override
@Transactional
public int insertZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog) {
try {
return zipFileProcessLogMapper.insertZipFileProcessLog(zipFileProcessLog);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 등록 중 오류 발생: {}", zipFileProcessLog, e);
throw new RuntimeException("ZIP 파일 처리 로그 등록 실패", e);
}
}
/**
* ZIP .
*
* @param zipFileProcessLog ZIP
* @return
*/
@Override
@Transactional
public int updateZipFileProcessLog(ZipFileProcessLogVO zipFileProcessLog) {
try {
return zipFileProcessLogMapper.updateZipFileProcessLog(zipFileProcessLog);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 수정 중 오류 발생: {}", zipFileProcessLog, e);
throw new RuntimeException("ZIP 파일 처리 로그 수정 실패", e);
}
}
/**
* ZIP .
*
* @param logId ID
* @return
*/
@Override
@Transactional
public int deleteZipFileProcessLog(String logId) {
try {
return zipFileProcessLogMapper.deleteZipFileProcessLog(logId);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 삭제 중 오류 발생: logId={}", logId, e);
throw new RuntimeException("ZIP 파일 처리 로그 삭제 실패", e);
}
}
/**
* ZIP .
*
* @param logId ID
* @return ZIP
*/
@Override
public ZipFileProcessLogVO selectZipFileProcessLog(String logId) {
try {
return zipFileProcessLogMapper.selectZipFileProcessLog(logId);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 조회 중 오류 발생: logId={}", logId, e);
throw new RuntimeException("ZIP 파일 처리 로그 조회 실패", e);
}
}
/**
* ZIP .
*
* @param paramVO
* @return
*/
@Override
public int selectZipFileProcessLogListTotalCount(ZipFileProcessLogVO paramVO) {
try {
return zipFileProcessLogMapper.selectZipFileProcessLogListTotalCount(paramVO);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 목록 총 개수 조회 중 오류 발생: {}", paramVO, e);
throw new RuntimeException("ZIP 파일 처리 로그 목록 총 개수 조회 실패", e);
}
}
/**
* ZIP .
*
* @param paramVO
* @return ZIP
*/
@Override
public List<ZipFileProcessLogVO> selectZipFileProcessLogList(ZipFileProcessLogVO paramVO) {
try {
return zipFileProcessLogMapper.selectZipFileProcessLogList(paramVO);
} catch (Exception e) {
log.error("ZIP 파일 처리 로그 목록 조회 중 오류 발생: {}", paramVO, e);
throw new RuntimeException("ZIP 파일 처리 로그 목록 조회 실패", e);
}
}
/**
* ZIP .
*
* @param zipFileNm ZIP
* @return ZIP
*/
@Override
public ZipFileProcessLogVO selectProcessingZipFileLog(String zipFileNm) {
try {
return zipFileProcessLogMapper.selectProcessingZipFileLog(zipFileNm);
} catch (Exception e) {
log.error("처리 중인 ZIP 파일 로그 조회 중 오류 발생: zipFileNm={}", zipFileNm, e);
throw new RuntimeException("처리 중인 ZIP 파일 로그 조회 실패", e);
}
}
}

@ -1,117 +0,0 @@
package go.kr.project.batch.util;
import egovframework.constant.BatchConstants;
import go.kr.project.batch.mapper.BatchJobMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
/**
* packageName : go.kr.project.batch.util
* fileName : BatchJobLogUtil
* author :
* date : 2025-06-10
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-10
*/
@Slf4j
@Component
public class BatchJobLogUtil {
private static BatchJobMapper batchJobMapper;
/**
* BatchJobMapper .
*
* @param mapper
*/
public BatchJobLogUtil(BatchJobMapper mapper) {
BatchJobLogUtil.batchJobMapper = mapper;
}
/**
* INFO .
*
* @param context
* @param message
*/
public static void info(JobExecutionContext context, String message) {
saveLog(context, BatchConstants.LOG_LEVEL_INFO, message);
log.info(message);
}
/**
* WARN .
*
* @param context
* @param message
*/
public static void warn(JobExecutionContext context, String message) {
saveLog(context, BatchConstants.LOG_LEVEL_WARN, message);
log.warn(message);
}
/**
* ERROR .
*
* @param context
* @param message
*/
public static void error(JobExecutionContext context, String message) {
saveLog(context, BatchConstants.LOG_LEVEL_ERROR, message);
log.error(message);
}
/**
* ERROR . ( )
*
* @param context
* @param message
* @param e
*/
public static void error(JobExecutionContext context, String message, Exception e) {
String errorMessage = message;
if (e != null) {
String exceptionMessage = e.getMessage();
if (exceptionMessage != null && !exceptionMessage.trim().isEmpty()) {
errorMessage = message + " - " + exceptionMessage;
} else {
errorMessage = message + " - " + e.getClass().getSimpleName();
}
}
saveLog(context, BatchConstants.LOG_LEVEL_ERROR, errorMessage);
if (e != null) {
log.error(message, e);
} else {
log.error(message);
}
}
/**
* .
*
* @param context
* @param logLevel
* @param message
*/
private static void saveLog(JobExecutionContext context, String logLevel, String message) {
try {
if (batchJobMapper != null) {
String executionId = (String) context.getJobDetail().getJobDataMap().get(BatchConstants.JOB_DATA_EXECUTION_ID);
if (executionId != null) {
batchJobMapper.insertBatchJobLog(executionId, logLevel, message);
} else {
log.warn("executionId를 찾을 수 없어 로그를 저장할 수 없습니다.");
}
} else {
log.warn("BatchJobMapper가 초기화되지 않아 로그를 저장할 수 없습니다.");
}
} catch (Exception e) {
log.error(String.format("배치 작업 로그 저장 중 오류 발생: %s", e.getMessage()), e);
}
}
}

@ -1,149 +0,0 @@
package go.kr.project.batch.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* packageName : go.kr.project.batch.util
* fileName : ServerInfoUtil
* author :
* date : 2025-06-19
* description :
* ===========================================================
* DATE AUTHOR NOTE
* -----------------------------------------------------------
* 2025-06-19
* 2025-06-20
*/
@Slf4j
@Component
public class ServerInfoUtil {
private static Environment environment;
@Autowired
public ServerInfoUtil(Environment environment) {
ServerInfoUtil.environment = environment;
}
/**
* .
*
* @return
*/
public static String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.error("서버 호스트명을 가져오는 중 오류 발생: {}", e.getMessage(), e);
return "unknown-host";
}
}
/**
* IP .
*
* @return IP
*/
public static String getIpAddress() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("서버 IP 주소를 가져오는 중 오류 발생: {}", e.getMessage(), e);
return "0.0.0.0";
}
}
/**
* .
*
* @return
*/
public static String getApplicationName() {
if (environment != null) {
return environment.getProperty("spring.application.name", "unknown-app");
}
return "unknown-app";
}
/**
* .
*
* @return
*/
public static String getApplicationPort() {
if (environment != null) {
return environment.getProperty("server.port", "0");
}
return "0";
}
/**
* JVM ID(PID) .
*
* @return JVM ID
*/
public static String getProcessId() {
// JVM 프로세스 ID 가져오기
String jvmName = ManagementFactory.getRuntimeMXBean().getName();
// 형식: PID@hostname
return jvmName.split("@")[0];
}
// Quartz 스케줄러 인스턴스 ID 저장 변수
private static String quartzInstanceId = null;
/**
* Quartz ID .
*
* @param instanceId ID
*/
public static void setQuartzInstanceId(String instanceId) {
quartzInstanceId = instanceId;
log.info("Quartz 스케줄러 인스턴스 ID 설정: {}", instanceId);
}
/**
* Quartz ID .
*
* @return ID
*/
public static String getQuartzInstanceId() {
return quartzInstanceId != null ? quartzInstanceId : "unknown-instance";
}
/**
* ( + IP) .
*
* @return ( + IP)
*/
public static String getServerInfo() {
return getHostName() + " (" + getIpAddress() + ")";
}
/**
* .
* , IP, , , JVM ID, Quartz ID .
*
* @return
*/
public static String getDetailedServerInfo() {
StringBuilder sb = new StringBuilder();
sb.append(getHostName()).append(" (").append(getIpAddress()).append(")");
sb.append(" | App: ").append(getApplicationName());
sb.append(" | Port: ").append(getApplicationPort());
sb.append(" | PID: ").append(getProcessId());
if (quartzInstanceId != null) {
sb.append(" | Quartz: ").append(quartzInstanceId);
}
return sb.toString();
}
}

@ -1,12 +1,12 @@
package go.kr.project.bbs.config.controller;
package go.kr.project.biz.bbs.config.controller;
import egovframework.configProperties.FileUploadProperties;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import go.kr.project.bbs.config.service.BbsConfigService;
import go.kr.project.bbs.model.BbsConfigSearchVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.biz.bbs.config.service.BbsConfigService;
import go.kr.project.biz.bbs.model.BbsConfigSearchVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import go.kr.project.system.common.service.CommonCodeService;
import go.kr.project.system.code.model.CodeDetailVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
/**
* packageName : go.kr.project.bbs.config.controller
* packageName : go.kr.project.biz.bbs.config.controller
* fileName : BbsConfigController
* author :
* date : 2025-05-24

@ -1,12 +1,12 @@
package go.kr.project.bbs.config.service;
package go.kr.project.biz.bbs.config.service;
import go.kr.project.bbs.model.BbsConfigSearchVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsConfigSearchVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import java.util.List;
/**
* packageName : go.kr.project.bbs.config.service
* packageName : go.kr.project.biz.bbs.config.service
* fileName : BbsConfigService
* author :
* date : 2025-05-24

@ -1,12 +1,12 @@
package go.kr.project.bbs.config.service.impl;
package go.kr.project.biz.bbs.config.service.impl;
import egovframework.exception.MessageException;
import egovframework.util.SessionUtil;
import go.kr.project.bbs.config.service.BbsConfigService;
import go.kr.project.bbs.mapper.BbsConfigMapper;
import go.kr.project.bbs.mapper.BbsPostMapper;
import go.kr.project.bbs.model.BbsConfigSearchVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.config.service.BbsConfigService;
import go.kr.project.biz.bbs.mapper.BbsConfigMapper;
import go.kr.project.biz.bbs.mapper.BbsPostMapper;
import go.kr.project.biz.bbs.model.BbsConfigSearchVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
@ -16,7 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* packageName : go.kr.project.bbs.config.service.impl
* packageName : go.kr.project.biz.bbs.config.service.impl
* fileName : BbsConfigServiceImpl
* author :
* date : 2025-05-24

@ -1,13 +1,13 @@
package go.kr.project.bbs.mapper;
package go.kr.project.biz.bbs.mapper;
import go.kr.project.bbs.model.BbsConfigSearchVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsConfigSearchVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.bbs.mapper
* packageName : go.kr.project.biz.bbs.mapper
* fileName : BbsConfigMapper
* author :
* date : 2025-05-24

@ -1,10 +1,10 @@
package go.kr.project.bbs.mapper;
package go.kr.project.biz.bbs.mapper;
import go.kr.project.bbs.model.BbsPostAnswerVO;
import go.kr.project.biz.bbs.model.BbsPostAnswerVO;
import org.apache.ibatis.annotations.Mapper;
/**
* packageName : go.kr.project.bbs.mapper
* packageName : go.kr.project.biz.bbs.mapper
* fileName : BbsPostAnswerMapper
* author :
* date : 2025-05-24

@ -1,15 +1,15 @@
package go.kr.project.bbs.mapper;
package go.kr.project.biz.bbs.mapper;
import go.kr.project.bbs.model.BbsCommentVO;
import go.kr.project.bbs.model.BbsFileVO;
import go.kr.project.bbs.model.BbsPostSearchVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.model.BbsCommentVO;
import go.kr.project.biz.bbs.model.BbsFileVO;
import go.kr.project.biz.bbs.model.BbsPostSearchVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.bbs.mapper
* packageName : go.kr.project.biz.bbs.mapper
* fileName : BbsConfigMapper
* author :
* date : 2025-05-24

@ -1,7 +1,7 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.DefaultVO;
import go.kr.project.system.common.model.DefaultVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
@ -11,7 +11,7 @@ import java.time.LocalDateTime;
import java.util.List;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsCommentVO
* author :
* date : 2025-05-24

@ -1,10 +1,10 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsConfigVO
* author :
* date : 2025-05-24

@ -1,4 +1,4 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
@ -6,7 +6,7 @@ import lombok.*;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsConfigVO
* author :
* date : 2025-05-24

@ -1,14 +1,14 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.DefaultVO;
import go.kr.project.system.common.model.DefaultVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsFileVO
* author :
* date : 2025-05-24

@ -1,14 +1,14 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.DefaultVO;
import go.kr.project.system.common.model.DefaultVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsPostAnswerVO
* author :
* date : 2025-05-24

@ -1,14 +1,14 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsPostVO
* author :
* date : 2025-05-24

@ -1,4 +1,4 @@
package go.kr.project.bbs.model;
package go.kr.project.biz.bbs.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
@ -8,7 +8,7 @@ import java.time.LocalDateTime;
import java.util.List;
/**
* packageName : go.kr.project.bbs.model
* packageName : go.kr.project.biz.bbs.model
* fileName : BbsPostVO
* author :
* date : 2025-05-24

@ -1,14 +1,14 @@
package go.kr.project.bbs.post.controller;
package go.kr.project.biz.bbs.post.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.bbs.model.BbsCommentVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.bbs.model.BbsPostSearchVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.bbs.post.service.BbsPostService;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.biz.bbs.model.BbsCommentVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsPostSearchVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.post.service.BbsPostService;
import go.kr.project.system.common.service.CommonCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* packageName : go.kr.project.bbs.post.controller
* packageName : go.kr.project.biz.bbs.post.controller
* fileName : BbsPostController
* author :
* date : 2025-05-24

@ -1,13 +1,13 @@
package go.kr.project.bbs.post.controller;
package go.kr.project.biz.bbs.post.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.bbs.model.BbsCommentVO;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.bbs.model.BbsPostSearchVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.bbs.post.service.BbsPostService;
import go.kr.project.biz.bbs.model.BbsCommentVO;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsPostSearchVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.post.service.BbsPostService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -24,7 +24,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* packageName : go.kr.project.bbs.post.controller
* packageName : go.kr.project.biz.bbs.post.controller
* fileName : UserBbsController
* author :
* date : 2025-05-24

@ -1,6 +1,6 @@
package go.kr.project.bbs.post.service;
package go.kr.project.biz.bbs.post.service;
import go.kr.project.bbs.model.*;
import go.kr.project.biz.bbs.model.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
@ -8,7 +8,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* packageName : go.kr.project.bbs.post.service
* packageName : go.kr.project.biz.bbs.post.service
* fileName : BbsPostService
* author :
* date : 2025-05-24

@ -1,14 +1,14 @@
package go.kr.project.bbs.post.service.impl;
package go.kr.project.biz.bbs.post.service.impl;
import egovframework.constant.FileContentTypeConstants;
import egovframework.util.FileUtil;
import go.kr.project.bbs.config.service.BbsConfigService;
import go.kr.project.bbs.mapper.BbsPostMapper;
import go.kr.project.bbs.model.*;
import go.kr.project.bbs.post.service.BbsPostService;
import go.kr.project.common.model.FileVO;
import go.kr.project.common.model.HtmlEditorFileVO;
import go.kr.project.common.service.HtmlEditorService;
import go.kr.project.biz.bbs.config.service.BbsConfigService;
import go.kr.project.biz.bbs.mapper.BbsPostMapper;
import go.kr.project.biz.bbs.model.*;
import go.kr.project.biz.bbs.post.service.BbsPostService;
import go.kr.project.system.common.model.FileVO;
import go.kr.project.system.common.model.HtmlEditorFileVO;
import go.kr.project.system.common.service.HtmlEditorService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
@ -23,7 +23,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* packageName : go.kr.project.bbs.post.service.impl
* packageName : go.kr.project.biz.bbs.post.service.impl
* fileName : BbsPostServiceImpl
* author :
* date : 2025-05-24

@ -1,17 +1,17 @@
package go.kr.project.bbs.qna.controller;
package go.kr.project.biz.bbs.qna.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.bbs.config.service.BbsConfigService;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.bbs.model.BbsPostAnswerVO;
import go.kr.project.bbs.model.BbsPostSearchVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.bbs.post.service.BbsPostService;
import go.kr.project.bbs.qna.service.BbsPostAnswerService;
import go.kr.project.common.notification.service.NotificationService;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.biz.bbs.config.service.BbsConfigService;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsPostAnswerVO;
import go.kr.project.biz.bbs.model.BbsPostSearchVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.post.service.BbsPostService;
import go.kr.project.biz.bbs.qna.service.BbsPostAnswerService;
import go.kr.project.system.common.notification.service.NotificationService;
import go.kr.project.system.common.service.CommonCodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -29,7 +29,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* packageName : go.kr.project.bbs.qna.controller
* packageName : go.kr.project.biz.bbs.qna.controller
* fileName : AdminQnaController
* author :
* date : 2025-05-24

@ -1,15 +1,15 @@
package go.kr.project.bbs.qna.controller;
package go.kr.project.biz.bbs.qna.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.bbs.config.service.BbsConfigService;
import go.kr.project.bbs.model.BbsConfigVO;
import go.kr.project.bbs.model.BbsPostSearchVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.bbs.post.service.BbsPostService;
import go.kr.project.bbs.qna.service.BbsPostAnswerService;
import go.kr.project.common.notification.service.NotificationService;
import go.kr.project.biz.bbs.config.service.BbsConfigService;
import go.kr.project.biz.bbs.model.BbsConfigVO;
import go.kr.project.biz.bbs.model.BbsPostSearchVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.post.service.BbsPostService;
import go.kr.project.biz.bbs.qna.service.BbsPostAnswerService;
import go.kr.project.system.common.notification.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* packageName : go.kr.project.bbs.qna.controller
* packageName : go.kr.project.biz.bbs.qna.controller
* fileName : UserQnaController
* author :
* date : 2025-05-24

@ -1,9 +1,9 @@
package go.kr.project.bbs.qna.service;
package go.kr.project.biz.bbs.qna.service;
import go.kr.project.bbs.model.BbsPostAnswerVO;
import go.kr.project.biz.bbs.model.BbsPostAnswerVO;
/**
* packageName : go.kr.project.bbs.qna.service
* packageName : go.kr.project.biz.bbs.qna.service
* fileName : BbsPostAnswerService
* author :
* date : 2025-05-24

@ -1,10 +1,10 @@
package go.kr.project.bbs.qna.service.impl;
package go.kr.project.biz.bbs.qna.service.impl;
import go.kr.project.bbs.mapper.BbsPostAnswerMapper;
import go.kr.project.bbs.mapper.BbsPostMapper;
import go.kr.project.bbs.model.BbsPostAnswerVO;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.bbs.qna.service.BbsPostAnswerService;
import go.kr.project.biz.bbs.mapper.BbsPostAnswerMapper;
import go.kr.project.biz.bbs.mapper.BbsPostMapper;
import go.kr.project.biz.bbs.model.BbsPostAnswerVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.qna.service.BbsPostAnswerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
@ -12,7 +12,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* packageName : go.kr.project.bbs.qna.service.impl
* packageName : go.kr.project.biz.bbs.qna.service.impl
* fileName : BbsPostAnswerServiceImpl
* author :
* date : 2025-05-24

@ -1,4 +1,4 @@
package go.kr.project.main.controller;
package go.kr.project.biz.main.controller;
import egovframework.constant.TilesConstants;
import io.swagger.v3.oas.annotations.Operation;
@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpServletRequest;
/**
* packageName : go.kr.project.login.controller
* packageName : go.kr.project.system.login.controller
* fileName : LoginController
* author :
* date : 25. 5. 8.

@ -1,6 +1,6 @@
package go.kr.project.system.auth.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import java.util.List;

@ -1,7 +1,7 @@
package go.kr.project.system.auth.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

@ -1,7 +1,7 @@
package go.kr.project.system.auth.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

@ -1,6 +1,6 @@
package go.kr.project.system.auth.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;

@ -1,6 +1,6 @@
package go.kr.project.system.code.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import java.util.List;

@ -1,6 +1,6 @@
package go.kr.project.system.code.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
import java.util.List;

@ -1,6 +1,6 @@
package go.kr.project.system.code.model;
import go.kr.project.common.model.PagingVO;
import go.kr.project.system.common.model.PagingVO;
import lombok.*;
/**

@ -1,7 +1,7 @@
package go.kr.project.common.controller;
package go.kr.project.system.common.controller;
import egovframework.util.ApiResponseUtil;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.system.common.service.CommonCodeService;
import go.kr.project.system.code.model.CodeDetailVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* packageName : go.kr.project.common.controller
* packageName : go.kr.project.system.common.controller
* fileName : CommonCodeController
* author :
* date : 2025-05-10

@ -1,9 +1,9 @@
package go.kr.project.common.controller;
package go.kr.project.system.common.controller;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.common.notification.model.NotificationVO;
import go.kr.project.common.service.CommonHeaderService;
import go.kr.project.system.common.notification.model.NotificationVO;
import go.kr.project.system.common.service.CommonHeaderService;
import go.kr.project.template.calendarSample.model.CalendarScheduleVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;

@ -1,4 +1,4 @@
package go.kr.project.common.controller;
package go.kr.project.system.common.controller;
import egovframework.configProperties.LoginProperties;
import io.swagger.v3.oas.annotations.Operation;

@ -1,9 +1,9 @@
package go.kr.project.common.controller;
package go.kr.project.system.common.controller;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.common.model.HtmlEditorFileVO;
import go.kr.project.common.service.HtmlEditorService;
import go.kr.project.system.common.model.HtmlEditorFileVO;
import go.kr.project.system.common.service.HtmlEditorService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -24,7 +24,7 @@ import java.util.HashMap;
import java.util.Objects;
/**
* packageName : go.kr.project.common.controller
* packageName : go.kr.project.system.common.controller
* fileName : HtmlEditorController
* author :
* date : 2025-05-23

@ -1,4 +1,4 @@
package go.kr.project.common.controller;
package go.kr.project.system.common.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* packageName : go.kr.project.common.controller
* packageName : go.kr.project.system.common.controller
* fileName : SearchAddressController
* author :
* date : 25. 5. 13.

@ -1,4 +1,4 @@
package go.kr.project.common.error;
package go.kr.project.system.common.error;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -8,7 +8,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* packageName : go.kr.project.common.error
* packageName : go.kr.project.system.common.error
* fileName : ErrorController
* author :
* date : 25. 6. 5.

@ -1,4 +1,4 @@
package go.kr.project.common.mapper;
package go.kr.project.system.common.mapper;
import go.kr.project.system.code.model.CodeDetailVO;
import org.apache.ibatis.annotations.Mapper;
@ -6,7 +6,7 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.mapper
* packageName : go.kr.project.system.common.mapper
* fileName : CommonCodeMapper
* author :
* date : 2025-05-10

@ -1,13 +1,13 @@
package go.kr.project.common.mapper;
package go.kr.project.system.common.mapper;
import go.kr.project.bbs.model.BbsPostVO;
import go.kr.project.biz.bbs.model.BbsPostVO;
import go.kr.project.template.calendarSample.model.CalendarScheduleVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.mapper
* packageName : go.kr.project.system.common.mapper
* fileName : CommonCodeMapper
* author :
* date : 2025-05-10

@ -1,12 +1,12 @@
package go.kr.project.common.mapper;
package go.kr.project.system.common.mapper;
import go.kr.project.common.model.HtmlEditorFileVO;
import go.kr.project.system.common.model.HtmlEditorFileVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.mapper
* packageName : go.kr.project.system.common.mapper
* fileName : HtmlEditorMapper
* author :
* date : 2025-05-23

@ -1,11 +1,11 @@
package go.kr.project.common.model;
package go.kr.project.system.common.model;
import lombok.Data;
import java.io.Serializable;
/**
* packageName : go.kr.project.common
* packageName : go.kr.project.system.common
* fileName : DefaultVO
* author :
* date : 25. 5. 8.

@ -1,4 +1,4 @@
package go.kr.project.common.model;
package go.kr.project.system.common.model;
import lombok.Data;

@ -1,4 +1,4 @@
package go.kr.project.common.model;
package go.kr.project.system.common.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
@ -7,7 +7,7 @@ import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
* packageName : go.kr.project.common.model
* packageName : go.kr.project.system.common.model
* fileName : HtmlEditorFileVO
* author :
* date : 2025-05-23

@ -1,10 +1,10 @@
package go.kr.project.common.model;
package go.kr.project.system.common.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* packageName : go.kr.project.common
* packageName : go.kr.project.system.common
* fileName : PagingVO
* author :
* date : 25. 5. 8.

@ -1,4 +1,4 @@
package go.kr.project.common.notification.config;
package go.kr.project.system.common.notification.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
import java.util.List;
/**
* packageName : go.kr.project.common.notification.config
* packageName : go.kr.project.system.common.notification.config
* fileName : NotificationProperties
* author :
* date : 2023-06-10

@ -1,9 +1,9 @@
package go.kr.project.common.notification.controller;
package go.kr.project.system.common.notification.controller;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.common.notification.model.NotificationVO;
import go.kr.project.common.notification.service.NotificationService;
import go.kr.project.system.common.notification.model.NotificationVO;
import go.kr.project.system.common.notification.service.NotificationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
@ -20,7 +20,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* packageName : go.kr.project.common.notification.controller
* packageName : go.kr.project.system.common.notification.controller
* fileName : NotificationController
* author :
* date : 2023-06-10

@ -1,12 +1,12 @@
package go.kr.project.common.notification.mapper;
package go.kr.project.system.common.notification.mapper;
import go.kr.project.common.notification.model.NotificationVO;
import go.kr.project.system.common.notification.model.NotificationVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.notification.mapper
* packageName : go.kr.project.system.common.notification.mapper
* fileName : NotificationMapper
* author :
* date : 2023-06-10

@ -1,12 +1,12 @@
package go.kr.project.common.notification.mapper;
package go.kr.project.system.common.notification.mapper;
import go.kr.project.common.notification.model.NotificationTargetVO;
import go.kr.project.system.common.notification.model.NotificationTargetVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* packageName : go.kr.project.common.notification.mapper
* packageName : go.kr.project.system.common.notification.mapper
* fileName : NotificationTargetMapper
* author :
* date : 2023-06-10

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save