diff --git a/src/main/java/com/interplug/qcast/batch/JobLauncherController.java b/src/main/java/com/interplug/qcast/batch/JobLauncherController.java index a7958ba6..438bb19e 100644 --- a/src/main/java/com/interplug/qcast/batch/JobLauncherController.java +++ b/src/main/java/com/interplug/qcast/batch/JobLauncherController.java @@ -1,6 +1,5 @@ package com.interplug.qcast.batch; -import java.time.Duration; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -9,8 +8,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.*; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.launch.JobOperator; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRestartException; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.GetMapping; @@ -21,10 +21,10 @@ import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @Slf4j public class JobLauncherController { - private final Map jobs; + private final Map jobs; // 여러 Job을 주입받도록 변경 + private final JobLauncher jobLauncher; private final JobExplorer jobExplorer; - private final JobOperator jobOperator; @Value("${qsp.master-admin-user-batch-url}") private String qspInterfaceUrl; @@ -34,205 +34,207 @@ public class JobLauncherController { // 현재 실행 중인 Job 추적을 위한 Set private final Set runningJobs = ConcurrentHashMap.newKeySet(); - /** * 특정 Job을 매핑으로 실행하는 메소드 + * + * @param jobName + * @return + * @throws JobInstanceAlreadyCompleteException + * @throws JobExecutionAlreadyRunningException + * @throws JobParametersInvalidException + * @throws JobRestartException */ - @GetMapping("/batch/job/{jobName}") - public Map launchJob(@PathVariable String jobName) { + @GetMapping("/batch/job/{jobName}") // Path Variable로 jobName을 받음 + public Map launchJob(@PathVariable String jobName) + throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, + JobParametersInvalidException, JobRestartException { + + Job job = jobs.get(jobName); - Map resultMap = new HashMap<>(); - + Map resultMap = new HashMap(); if (job == null) { + // return "Job " + jobName + " not found"; resultMap.put("code", "FAILED"); - resultMap.put("message", "Job " + jobName + " not found"); + resultMap.put("message", "Job" + jobName + " not found"); return resultMap; } // 실행 중인 Job 확인 if (runningJobs.contains(jobName) || isJobRunning(jobName)) { - resultMap.put("code", "ALREADY_RUNNING"); - resultMap.put("message", "Job " + jobName + " is already running"); + log.warn("Job {} is already running, skipping execution", jobName); + resultMap.put("code", "FAILED"); + resultMap.put("message", "Job "+ jobName +" is already running, skipping execution"); return resultMap; } - try { - runningJobs.add(jobName); - JobParameters jobParameters = new JobParametersBuilder() - .addString("jobName", jobName) - .addDate("time", new Date()) - .toJobParameters(); + JobParameters jobParameters = new JobParametersBuilder().addString("jobName", jobName) + .addDate("time", new Date()).toJobParameters(); - JobExecution jobExecution = jobLauncher.run(job, jobParameters); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); - BatchStatus status = jobExecution.getStatus(); - ExitStatus exitStatus = jobExecution.getExitStatus(); - resultMap.put("code", status.toString()); - resultMap.put("message", exitStatus.getExitDescription()); - } catch (JobExecutionAlreadyRunningException e) { - resultMap.put("code", "ALREADY_RUNNING"); - resultMap.put("message", "Job " + jobName + " is already running"); - } catch (Exception e) { - resultMap.put("code", "FAILED"); - resultMap.put("message", "Error: " + e.getMessage()); - } finally { - runningJobs.remove(jobName); - } + BatchStatus status = jobExecution.getStatus(); + ExitStatus exitStatus = jobExecution.getExitStatus(); + resultMap.put("code", status.toString()); + resultMap.put("message", exitStatus.getExitDescription()); + + + // return "Job " + jobName + " started"; return resultMap; } /** * Q.CAST 판매점 / 사용자 / 즐겨찾기 / 노출 아이템 동기화 배치 + * + */ - @Scheduled(cron = "0 30 23 * * *") - public String storeAdditionalJob() { + // @Scheduled(cron = "*/5 * * * * *") + @Scheduled(cron = "0 50 23 * * *") + public String storeAdditionalJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("storeAdditionalJob"); + } /** * 아이템 동기화 배치 + * + */ @Scheduled(cron = "0 30 02 * * *") - public String materialJob() { + public String materialJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("materialJob"); + } /** * BOM 아이템 동기화 배치 + * + */ @Scheduled(cron = "0 40 02 * * *") - public String bomJob() { + public String bomJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("bomJob"); + } /** * 영업사원 동기화 배치 + * + */ @Scheduled(cron = "0 40 03 * * *") - public String businessChargerJob() { + public String businessChargerJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("businessChargerJob"); + } /** * 관리자 유저 동기화 배치 + * + */ - @Scheduled(cron = "0 0 01 * * *") - public String adminUserJob() { + @Scheduled(cron = "0 30 01 * * *") + public String adminUserJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("adminUserJob"); + } /** * 가격 동기화 배치 + * + */ - @Scheduled(cron = "0 30 0 * * *") // 시간 조정: 00:30 - public String priceJob() { + @Scheduled(cron = "0 20 00 * * *") + public String priceJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("priceJob"); + } /** * 공통코드 M_COMM_H, M_COMM_L 동기화 배치 + * + */ @Scheduled(cron = "0 10 03 * * *") - public String commonCodeJob() { + public String commonCodeJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("commonCodeJob"); + } /** * Q.CAST 견적특이사항 / 아이템 표시, 미표시 동기화 배치 + * + */ - @Scheduled(cron = "0 45 23 * * *") // 시간 조정: 23:45 - public String specialNoteDispItemAdditionalInfoJob() { + @Scheduled(cron = "0 30 23 * * *") + public String specialNoteDispItemAdditionalInfoJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { return executeScheduledJob("specialNoteDispItemAdditionalJob"); + } /** * Plan Confrim 동기화 배치 + * + */ @Scheduled(cron = "0 05 04 * * *") - public String planConfirmJob() { + public String planConfirmJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("planConfirmJob"); + } /** * 견적서 전송 동기화 배치 + * + */ @Scheduled(cron = "0 20 04 * * *") - public String estimateSyncJob() { + public String estimateSyncJob() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + return executeScheduledJob("estimateSyncJob"); + } /** * 공통 스케줄러 실행 메소드 */ - private String executeScheduledJob(String jobName) { - // 1. 스케줄러 설정 확인 (materialJob, commonCodeJob 예외) - if (!"Y".equals(scheduler) && !"materialJob".equals(jobName) && !"commonCodeJob".equals(jobName)) { - log.info("Scheduler disabled, skipping job {}", jobName); - return "Scheduler disabled"; - } - - // 2. Job 존재 확인 + private String executeScheduledJob(String jobName) throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { Job job = jobs.get(jobName); if (job == null) { log.error("Job {} not found", jobName); return "Job " + jobName + " not found"; } - // 3. 같은 Job이 실행 중인지 확인 - if (isJobActuallyRunning(jobName)) { - log.warn("Job {} is already running", jobName); + if (!"Y".equals(scheduler) + && !"materialJob".equals(jobName) + && !"commonCodeJob".equals(jobName)) { + log.info("Scheduler disabled, skipping job {}", jobName); + return "Scheduler disabled"; + } + + // 실행 중인 Job 확인 + if (runningJobs.contains(jobName) || isJobRunning(jobName)) { + log.warn("Job {} is already running, skipping execution", jobName); return "Job already running"; } - // 4. 다른 Job이 실행 중인지 확인 - String runningJobName = findCurrentlyRunningJob(); - if (runningJobName != null) { - log.warn("Another job '{}' is running, skipping job {}", runningJobName, jobName); - return "Another job is running: " + runningJobName; - } + JobParameters jobParameters = + new JobParametersBuilder().addDate("time", new Date()).toJobParameters(); - // 5. Job 실행 - try { - runningJobs.add(jobName); - log.info("Starting job {}", jobName); + jobLauncher.run(job, jobParameters); - JobParameters jobParameters = new JobParametersBuilder() - .addDate("time", new Date()) - .toJobParameters(); - - JobExecution jobExecution = jobLauncher.run(job, jobParameters); - log.info("Job {} started successfully with execution ID: {}", jobName, jobExecution.getId()); - return "OK"; - - } catch (JobExecutionAlreadyRunningException e) { - log.warn("Job {} is already running: {}", jobName, e.getMessage()); - return "Job already running"; - } catch (Exception e) { - log.error("Error executing job {}: {}", jobName, e.getMessage()); - return "Error: " + e.getMessage(); - } finally { - runningJobs.remove(jobName); - } - } - - /** - * 현재 실행 중인 Job 찾기 - */ - private String findCurrentlyRunningJob() { - String[] jobNames = {"storeAdditionalJob", "materialJob", "bomJob", "businessChargerJob", - "adminUserJob", "priceJob", "commonCodeJob", "specialNoteDispItemAdditionalJob", - "planConfirmJob", "estimateSyncJob"}; - - for (String jobName : jobNames) { - if (isJobActuallyRunning(jobName)) { - return jobName; - } - } - return null; + return jobName+ " executed successfully"; } /** @@ -256,216 +258,4 @@ public class JobLauncherController { return false; } } - - - /** - * Job이 실제로 실행 중인지 확인 - */ - private boolean isJobActuallyRunning(String jobName) { - try { - // 1. 메모리 Set에서 확인 - if (runningJobs.contains(jobName)) { - log.debug("Job {} found in runningJobs set", jobName); - - // 2. JobOperator로 실제 실행 상태 확인 - try { - Set runningExecutions = jobOperator.getRunningExecutions(jobName); - if (!runningExecutions.isEmpty()) { - log.debug("Job {} has {} running executions", jobName, runningExecutions.size()); - return true; - } else { - log.warn("Job {} in runningJobs set but no actual running executions found", jobName); - runningJobs.remove(jobName); // 메모리 Set 정리 - return false; - } - } catch (Exception e) { - log.warn("Error checking JobOperator for {}: {}", jobName, e.getMessage()); - return false; - } - } - - // 3. JobOperator로 직접 확인 - try { - Set runningExecutions = jobOperator.getRunningExecutions(jobName); - if (!runningExecutions.isEmpty()) { - log.info("Job {} has running executions but not in runningJobs set", jobName); - runningJobs.add(jobName); // 메모리 Set 동기화 - return true; - } - } catch (Exception e) { - log.warn("Error checking JobOperator for {}: {}", jobName, e.getMessage()); - } - - // 4. JobExplorer로 DB 상태 확인 (보조적) - return isJobRunningInDatabase(jobName); - - } catch (Exception e) { - log.error("Error checking job running status for {}: {}", jobName, e.getMessage()); - return false; - } - } - - /** - * DB에서 Job 실행 상태 확인 (시간 기반 필터링) - */ - private boolean isJobRunningInDatabase(String jobName) { - try { - List jobInstances = jobExplorer.findJobInstancesByJobName(jobName, 0, 1); - - if (jobInstances.isEmpty()) { - return false; - } - - JobInstance latestJobInstance = jobInstances.get(0); - List jobExecutions = jobExplorer.getJobExecutions(latestJobInstance); - - if (jobExecutions.isEmpty()) { - return false; - } - - JobExecution latestExecution = jobExecutions.get(0); - BatchStatus status = latestExecution.getStatus(); - - if (status == BatchStatus.STARTED || status == BatchStatus.STARTING) { - if (latestExecution.getStartTime() != null) { - long minutesAgo = java.time.Duration.between( - latestExecution.getStartTime(), - java.time.LocalDateTime.now() - ).toMinutes(); - - // 30분 이상 된 실행은 중단된 것으로 간주 - if (minutesAgo > 30) { - log.warn("Job {} has stale execution in DB (started {} minutes ago)", jobName, minutesAgo); - return false; - } - - log.debug("Job {} appears to be running in DB (started {} minutes ago)", jobName, minutesAgo); - return true; - } - } - - return false; - - } catch (Exception e) { - log.error("Error checking job running status in DB for {}: {}", jobName, e.getMessage()); - return false; - } - } - - - - /** - * 현재 실행 중인 Job 목록 조회 - */ - @GetMapping("/batch/running-jobs") - public Map getRunningJobs() { - Map result = new HashMap<>(); - result.put("runningJobs", new ArrayList<>(runningJobs)); - - Map jobStatuses = new HashMap<>(); - String[] jobNames = {"storeAdditionalJob", "materialJob", "bomJob", "businessChargerJob", - "adminUserJob", "priceJob", "commonCodeJob", "specialNoteDispItemAdditionalJob", - "planConfirmJob", "estimateSyncJob"}; - - for (String jobName : jobNames) { - jobStatuses.put(jobName, getJobStatus(jobName)); - } - - result.put("jobStatuses", jobStatuses); - return result; - } - - /** - * 특정 Job의 상태 정보 조회 - */ - private Map getJobStatus(String jobName) { - Map jobStatus = new HashMap<>(); - - try { - List jobInstances = jobExplorer.findJobInstancesByJobName(jobName, 0, 1); - if (jobInstances.isEmpty()) { - jobStatus.put("status", "NEVER_EXECUTED"); - return jobStatus; - } - - JobInstance latestJobInstance = jobInstances.get(0); - List jobExecutions = jobExplorer.getJobExecutions(latestJobInstance); - - if (!jobExecutions.isEmpty()) { - JobExecution latestExecution = jobExecutions.get(0); - jobStatus.put("status", latestExecution.getStatus().toString()); - jobStatus.put("startTime", latestExecution.getStartTime()); - jobStatus.put("endTime", latestExecution.getEndTime()); - - // Duration 계산 (LocalDateTime용) - if (latestExecution.getEndTime() != null && latestExecution.getStartTime() != null) { - Duration duration = Duration.between(latestExecution.getStartTime(), latestExecution.getEndTime()); - jobStatus.put("durationSeconds", duration.getSeconds()); - jobStatus.put("durationMillis", duration.toMillis()); - } - } - - } catch (Exception e) { - jobStatus.put("status", "ERROR"); - jobStatus.put("error", e.getMessage()); - } - - return jobStatus; - } - - /** - * 실행 중인 Job 상태 상세 확인 - */ - @GetMapping("/batch/debug/running-jobs-detail") - public Map getRunningJobsDetail() { - Map result = new HashMap<>(); - String[] jobNames = {"storeAdditionalJob", "materialJob", "bomJob", "businessChargerJob", - "adminUserJob", "priceJob", "commonCodeJob", "specialNoteDispItemAdditionalJob", - "planConfirmJob", "estimateSyncJob"}; - - Map jobDetails = new HashMap<>(); - List actuallyRunningJobs = new ArrayList<>(); - - for (String jobName : jobNames) { - Map jobDetail = new HashMap<>(); - - // 메모리 Set 확인 - boolean inMemorySet = runningJobs.contains(jobName); - jobDetail.put("inMemorySet", inMemorySet); - - // JobOperator 확인 - try { - Set runningExecutions = jobOperator.getRunningExecutions(jobName); - boolean hasRunningExecutions = !runningExecutions.isEmpty(); - - jobDetail.put("runningExecutions", runningExecutions); - jobDetail.put("hasRunningExecutions", hasRunningExecutions); - } catch (Exception e) { - jobDetail.put("operatorError", e.getMessage()); - jobDetail.put("hasRunningExecutions", false); - } - - // DB 상태 확인 - boolean runningInDB = isJobRunningInDatabase(jobName); - jobDetail.put("runningInDB", runningInDB); - - // 실제 실행 여부 - boolean actuallyRunning = isJobActuallyRunning(jobName); - jobDetail.put("actuallyRunning", actuallyRunning); - - if (actuallyRunning) { - actuallyRunningJobs.add(jobName); - } - - jobDetails.put(jobName, jobDetail); - } - - result.put("jobDetails", jobDetails); - result.put("actuallyRunningJobs", actuallyRunningJobs); - result.put("isAnyJobRunning", !actuallyRunningJobs.isEmpty()); - result.put("schedulerEnabled", scheduler); - - return result; - } - -} \ No newline at end of file +}