dev #187
@ -1,7 +1,6 @@
|
||||
package com.interplug.qcast.batch;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -10,9 +9,8 @@ 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;
|
||||
@ -26,6 +24,7 @@ public class JobLauncherController {
|
||||
private final Map<String, Job> jobs;
|
||||
private final JobLauncher jobLauncher;
|
||||
private final JobExplorer jobExplorer;
|
||||
private final JobOperator jobOperator;
|
||||
|
||||
@Value("${qsp.master-admin-user-batch-url}")
|
||||
private String qspInterfaceUrl;
|
||||
@ -170,7 +169,7 @@ public class JobLauncherController {
|
||||
* 공통 스케줄러 실행 메소드
|
||||
*/
|
||||
private String executeScheduledJob(String jobName) {
|
||||
// 1. 가장 먼저 스케줄러 설정 확인
|
||||
// 1. 스케줄러 설정 확인 (materialJob, commonCodeJob 예외)
|
||||
if (!"Y".equals(scheduler) && !"materialJob".equals(jobName) && !"commonCodeJob".equals(jobName)) {
|
||||
log.info("Scheduler disabled, skipping job {}", jobName);
|
||||
return "Scheduler disabled";
|
||||
@ -183,18 +182,19 @@ public class JobLauncherController {
|
||||
return "Job " + jobName + " not found";
|
||||
}
|
||||
|
||||
// 3. 다른 Job 실행 중인지 확인
|
||||
if (isAnyJobRunning()) {
|
||||
log.warn("Another job is running, skipping job {}", jobName);
|
||||
return "Another job is running";
|
||||
}
|
||||
|
||||
// 4. 같은 Job이 실행 중인지 확인
|
||||
if (runningJobs.contains(jobName) || isJobRunning(jobName)) {
|
||||
// 3. 같은 Job이 실행 중인지 확인
|
||||
if (isJobActuallyRunning(jobName)) {
|
||||
log.warn("Job {} is already running", 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;
|
||||
}
|
||||
|
||||
// 5. Job 실행
|
||||
try {
|
||||
runningJobs.add(jobName);
|
||||
@ -205,7 +205,7 @@ public class JobLauncherController {
|
||||
.toJobParameters();
|
||||
|
||||
JobExecution jobExecution = jobLauncher.run(job, jobParameters);
|
||||
log.info("Job {} started successfully", jobName);
|
||||
log.info("Job {} started successfully with execution ID: {}", jobName, jobExecution.getId());
|
||||
return "OK";
|
||||
|
||||
} catch (JobExecutionAlreadyRunningException e) {
|
||||
@ -219,6 +219,22 @@ public class JobLauncherController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 실행 중인 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job 실행 상태 확인
|
||||
*/
|
||||
@ -241,18 +257,103 @@ public class JobLauncherController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 중인 Job이 있는지 확인
|
||||
*/
|
||||
private boolean isAnyJobRunning() {
|
||||
String[] jobNames = {"storeAdditionalJob", "materialJob", "bomJob", "businessChargerJob",
|
||||
"adminUserJob", "priceJob", "commonCodeJob", "specialNoteDispItemAdditionalJob",
|
||||
"planConfirmJob", "estimateSyncJob"};
|
||||
|
||||
return Arrays.stream(jobNames)
|
||||
.anyMatch(this::isJobRunning);
|
||||
/**
|
||||
* 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<Long> 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<Long> 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<JobInstance> jobInstances = jobExplorer.findJobInstancesByJobName(jobName, 0, 1);
|
||||
|
||||
if (jobInstances.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JobInstance latestJobInstance = jobInstances.get(0);
|
||||
List<JobExecution> 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 목록 조회
|
||||
*/
|
||||
@ -311,4 +412,60 @@ public class JobLauncherController {
|
||||
|
||||
return jobStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 실행 중인 Job 상태 상세 확인
|
||||
*/
|
||||
@GetMapping("/batch/debug/running-jobs-detail")
|
||||
public Map<String, Object> getRunningJobsDetail() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
String[] jobNames = {"storeAdditionalJob", "materialJob", "bomJob", "businessChargerJob",
|
||||
"adminUserJob", "priceJob", "commonCodeJob", "specialNoteDispItemAdditionalJob",
|
||||
"planConfirmJob", "estimateSyncJob"};
|
||||
|
||||
Map<String, Object> jobDetails = new HashMap<>();
|
||||
List<String> actuallyRunningJobs = new ArrayList<>();
|
||||
|
||||
for (String jobName : jobNames) {
|
||||
Map<String, Object> jobDetail = new HashMap<>();
|
||||
|
||||
// 메모리 Set 확인
|
||||
boolean inMemorySet = runningJobs.contains(jobName);
|
||||
jobDetail.put("inMemorySet", inMemorySet);
|
||||
|
||||
// JobOperator 확인
|
||||
try {
|
||||
Set<Long> 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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user