Merge pull request 'kurt/kurt' (#2) from kurt/kurt into dev
Reviewed-on: http://211.119.124.110:3000/cjm/clean-parking/pulls/2pull/4/head
commit
e454d7c2bb
@ -1 +1 @@
|
||||
rootProject.name = 'xit-framework'
|
||||
rootProject.name = 'clean-parking'
|
||||
@ -0,0 +1,74 @@
|
||||
//package egovframework.config.JPAConf;
|
||||
//
|
||||
//import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
//import org.springframework.beans.factory.ObjectProvider;
|
||||
//import org.springframework.beans.factory.annotation.Qualifier;
|
||||
//import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
//import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
//import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.context.annotation.Primary;
|
||||
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
//import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
//import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
//import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
//import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
|
||||
//import org.springframework.transaction.PlatformTransactionManager;
|
||||
//
|
||||
//import javax.persistence.EntityManagerFactory;
|
||||
//import javax.sql.DataSource;
|
||||
//import java.util.HashMap;
|
||||
//
|
||||
//@Configuration
|
||||
//@EnableJpaRepositories(
|
||||
// basePackages = "go.kr.project.domain.repo.cp", // Clean Parking용 레포지토리 경로
|
||||
// entityManagerFactoryRef = "cpEntityManagerFactory",
|
||||
// transactionManagerRef = "cpTransactionManager"
|
||||
//)
|
||||
//public class CpDbConf {
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// @ConfigurationProperties("spring.datasource.cp")
|
||||
// public DataSourceProperties cpDataSourceProperties() {
|
||||
// return new DataSourceProperties();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// public DataSource cpDataSource() {
|
||||
// return cpDataSourceProperties().initializeDataSourceBuilder().build();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// public LocalContainerEntityManagerFactoryBean cpEntityManagerFactory(@Qualifier("cpEntityManagerFactoryBuilder") EntityManagerFactoryBuilder builder) {
|
||||
// return builder
|
||||
// .dataSource(cpDataSource())
|
||||
// .packages("go.kr.project.domain.entity")
|
||||
// .persistenceUnit("cp")
|
||||
// .build();
|
||||
// }
|
||||
// @Bean(name = "cpEntityManagerFactoryBuilder")
|
||||
// public EntityManagerFactoryBuilder entityManagerFactoryBuilder(
|
||||
// JpaVendorAdapter jpaVendorAdapter,
|
||||
// ObjectProvider<PersistenceUnitManager> persistenceUnitManager
|
||||
// ) {
|
||||
// return new EntityManagerFactoryBuilder(jpaVendorAdapter, new HashMap<>(), persistenceUnitManager.getIfAvailable());
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @Bean(name = "cpJpaTransactionManager")
|
||||
// @Primary
|
||||
// public PlatformTransactionManager cpTransactionManager(
|
||||
// @Qualifier("cpEntityManagerFactory") EntityManagerFactory cpEntityManagerFactory) {
|
||||
// return new JpaTransactionManager(cpEntityManagerFactory);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// public JPAQueryFactory cpQueryFactory(@Qualifier("cpEntityManagerFactory") EntityManagerFactory emf) {
|
||||
// return new JPAQueryFactory(emf.createEntityManager());
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,67 @@
|
||||
//package egovframework.config.JPAConf;
|
||||
//
|
||||
//import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
//import org.springframework.beans.factory.ObjectProvider;
|
||||
//import org.springframework.beans.factory.annotation.Qualifier;
|
||||
//import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
|
||||
//import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
//import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
//import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
//import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
//import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
//import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
|
||||
//import org.springframework.transaction.PlatformTransactionManager;
|
||||
//
|
||||
//import javax.persistence.EntityManagerFactory;
|
||||
//import javax.sql.DataSource;
|
||||
//import java.util.HashMap;
|
||||
//
|
||||
//@Configuration
|
||||
//@EnableJpaRepositories(
|
||||
// basePackages = "go.kr.project.domain.repo.ep", // Electric Parking용 레포지토리 경로
|
||||
// entityManagerFactoryRef = "epEntityManagerFactory",
|
||||
// transactionManagerRef = "epTransactionManager"
|
||||
//)
|
||||
//public class EpDbConf {
|
||||
//
|
||||
// @Bean
|
||||
// @ConfigurationProperties("spring.datasource.ep")
|
||||
// public DataSourceProperties epDataSourceProperties() {
|
||||
// return new DataSourceProperties();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public DataSource epDataSource() {
|
||||
// return epDataSourceProperties().initializeDataSourceBuilder().build();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public LocalContainerEntityManagerFactoryBean epEntityManagerFactory(@Qualifier("epEntityManagerFactoryBuilder") EntityManagerFactoryBuilder builder) {
|
||||
// return builder
|
||||
// .dataSource(epDataSource())
|
||||
// .packages("go.kr.project.domain.entity")
|
||||
// .persistenceUnit("ep")
|
||||
// .build();
|
||||
// }
|
||||
//
|
||||
// @Bean(name = "epEntityManagerFactoryBuilder")
|
||||
// public EntityManagerFactoryBuilder entityManagerFactoryBuilder(
|
||||
// JpaVendorAdapter jpaVendorAdapter,
|
||||
// ObjectProvider<PersistenceUnitManager> persistenceUnitManager
|
||||
// ) {
|
||||
// return new EntityManagerFactoryBuilder(jpaVendorAdapter, new HashMap<>(), persistenceUnitManager.getIfAvailable());
|
||||
// }
|
||||
//
|
||||
// @Bean(name = "epJpaTransactionManager")
|
||||
// public PlatformTransactionManager epTransactionManager(@Qualifier("epEntityManagerFactory") EntityManagerFactory epEntityManagerFactory) {
|
||||
// return new JpaTransactionManager(epEntityManagerFactory);
|
||||
// }
|
||||
// @Bean
|
||||
// public JPAQueryFactory epQueryFactory(@Qualifier("epEntityManagerFactory") EntityManagerFactory emf) {
|
||||
// return new JPAQueryFactory(emf.createEntityManager());
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@ -0,0 +1,31 @@
|
||||
package egovframework.config.JPAConf;
|
||||
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
|
||||
@Configuration
|
||||
public class JPAConfig {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Bean
|
||||
public JPAQueryFactory jpaQueryFactory() {
|
||||
return new JPAQueryFactory(em);
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public JpaVendorAdapter jpaVendorAdapter() {
|
||||
// HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
|
||||
// adapter.setShowSql(false);
|
||||
// adapter.setGenerateDdl(false);
|
||||
// adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
|
||||
// return adapter;
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
//package egovframework.config.myBatisConf;
|
||||
//
|
||||
//import org.apache.ibatis.session.SqlSessionFactory;
|
||||
//import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
//import org.mybatis.spring.SqlSessionTemplate;
|
||||
//import org.springframework.beans.factory.annotation.Qualifier;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.context.annotation.Primary;
|
||||
//import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
//
|
||||
//import javax.sql.DataSource;
|
||||
//
|
||||
//@Configuration
|
||||
//public class CpMyBatisConf {
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// public SqlSessionFactory cpSqlSessionFactory(@Qualifier("cpDataSource") DataSource cpDataSource) throws Exception {
|
||||
// SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
|
||||
// factoryBean.setDataSource(cpDataSource);
|
||||
// return factoryBean.getObject();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// @Primary
|
||||
// public SqlSessionTemplate cpSqlSessionTemplate(@Qualifier("cpSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
|
||||
// return new SqlSessionTemplate(sqlSessionFactory);
|
||||
// }
|
||||
//
|
||||
// @Bean(name = "cpMyBatisTransactionManager")
|
||||
// @Primary
|
||||
// public DataSourceTransactionManager cpMyBatisTransactionManager(@Qualifier("cpDataSource") DataSource cpDataSource) {
|
||||
// return new DataSourceTransactionManager(cpDataSource);
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,33 @@
|
||||
//package egovframework.config.myBatisConf;
|
||||
//
|
||||
//import org.apache.ibatis.session.SqlSessionFactory;
|
||||
//import org.mybatis.spring.SqlSessionFactoryBean;
|
||||
//import org.mybatis.spring.SqlSessionTemplate;
|
||||
//import org.springframework.beans.factory.annotation.Qualifier;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.context.annotation.Primary;
|
||||
//import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
//
|
||||
//import javax.sql.DataSource;
|
||||
//
|
||||
//@Configuration
|
||||
//public class EpMyBatisConf {
|
||||
//
|
||||
// @Bean
|
||||
// public SqlSessionFactory epSqlSessionFactory(@Qualifier("epDataSource") DataSource epDataSource) throws Exception {
|
||||
// SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
|
||||
// factoryBean.setDataSource(epDataSource);
|
||||
// return factoryBean.getObject();
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public SqlSessionTemplate epSqlSessionTemplate(@Qualifier("epSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
|
||||
// return new SqlSessionTemplate(sqlSessionFactory);
|
||||
// }
|
||||
//
|
||||
// @Bean(name = "epMyBatisTransactionManager")
|
||||
// public DataSourceTransactionManager epMyBatisTransactionManager(@Qualifier("epDataSource") DataSource epDataSource) {
|
||||
// return new DataSourceTransactionManager(epDataSource);
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,17 @@
|
||||
package egovframework.dataSourece;
|
||||
|
||||
public class DataSourceContextHolder {
|
||||
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
|
||||
|
||||
public static void set(String key) {
|
||||
contextHolder.set(key);
|
||||
}
|
||||
|
||||
public static String get() {
|
||||
return contextHolder.get();
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
contextHolder.remove();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
//package egovframework.dataSourece;
|
||||
//
|
||||
//import org.springframework.aop.Advisor;
|
||||
//import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
|
||||
//import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
//import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
//import org.springframework.beans.factory.annotation.Qualifier;
|
||||
//import org.springframework.context.annotation.Bean;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.transaction.PlatformTransactionManager;
|
||||
//import org.springframework.transaction.TransactionDefinition;
|
||||
//import org.springframework.transaction.interceptor.*;
|
||||
//
|
||||
//import javax.transaction.Transactional;
|
||||
//import java.util.Collections;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.Map;
|
||||
//
|
||||
//@Configuration
|
||||
//public class DynamicRoutingTransactionConfig {
|
||||
//
|
||||
// @Bean
|
||||
// public TransactionInterceptor dynamicTransactionInterceptor(DynamicTransactionManager dynamicTxManager) {
|
||||
// NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
|
||||
// RuleBasedTransactionAttribute txAttr = new RuleBasedTransactionAttribute();
|
||||
// txAttr.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
||||
// txAttr.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
|
||||
//
|
||||
// Map<String, TransactionAttribute> txMap = new HashMap<>();
|
||||
// txMap.put("*", txAttr);
|
||||
// source.setNameMap(txMap);
|
||||
//
|
||||
// return new TransactionInterceptor(dynamicTxManager, source);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public Advisor dynamicTxAdvisor(TransactionInterceptor dynamicTransactionInterceptor) {
|
||||
// // @DynamicTransactional만 매칭되게 설정
|
||||
// AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(null, DynamicTransactional.class);
|
||||
// return new DefaultPointcutAdvisor(pointcut, dynamicTransactionInterceptor);
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public DynamicTransactionManager dynamicTxManager(
|
||||
// @Qualifier("cpJpaTransactionManager") PlatformTransactionManager cp,
|
||||
// @Qualifier("epJpaTransactionManager") PlatformTransactionManager ep) {
|
||||
//
|
||||
// Map<String, PlatformTransactionManager> map = new HashMap<>();
|
||||
// map.put("cp", cp);
|
||||
// map.put("ep", ep);
|
||||
// return new DynamicTransactionManager(map, cp); // default는 cp
|
||||
// }
|
||||
//
|
||||
// @Bean
|
||||
// public BeanNameAutoProxyCreator txAutoProxyCreator() {
|
||||
// BeanNameAutoProxyCreator proxyCreator = new BeanNameAutoProxyCreator();
|
||||
// proxyCreator.setBeanNames("*Service", "*ServiceImpl");
|
||||
// proxyCreator.setInterceptorNames("dynamicTransactionInterceptor");
|
||||
// return proxyCreator;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
@ -0,0 +1,43 @@
|
||||
//package egovframework.dataSourece;
|
||||
//
|
||||
//import org.springframework.transaction.PlatformTransactionManager;
|
||||
//import org.springframework.transaction.TransactionDefinition;
|
||||
//import org.springframework.transaction.TransactionStatus;
|
||||
//import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
//
|
||||
//import java.util.Map;
|
||||
//
|
||||
//public class DynamicTransactionManager implements PlatformTransactionManager {
|
||||
//
|
||||
// private final Map<String, PlatformTransactionManager> transactionManagerMap;
|
||||
// private final PlatformTransactionManager defaultTransactionManager;
|
||||
//
|
||||
// public DynamicTransactionManager(Map<String, PlatformTransactionManager> transactionManagerMap,
|
||||
// PlatformTransactionManager defaultTransactionManager) {
|
||||
// this.transactionManagerMap = transactionManagerMap;
|
||||
// this.defaultTransactionManager = defaultTransactionManager;
|
||||
// }
|
||||
//
|
||||
// private PlatformTransactionManager resolveTransactionManager() {
|
||||
// String key = DataSourceContextHolder.get();
|
||||
// if (key == null) {
|
||||
// return defaultTransactionManager;
|
||||
// }
|
||||
// return transactionManagerMap.getOrDefault(key, defaultTransactionManager);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public TransactionStatus getTransaction(TransactionDefinition definition) {
|
||||
// return resolveTransactionManager().getTransaction(definition);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void commit(TransactionStatus status) {
|
||||
// resolveTransactionManager().commit(status);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void rollback(TransactionStatus status) {
|
||||
// resolveTransactionManager().rollback(status);
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,13 @@
|
||||
//package egovframework.dataSourece;
|
||||
//
|
||||
//import javax.transaction.Transactional;
|
||||
//import java.lang.annotation.ElementType;
|
||||
//import java.lang.annotation.Retention;
|
||||
//import java.lang.annotation.RetentionPolicy;
|
||||
//import java.lang.annotation.Target;
|
||||
//
|
||||
//@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
//@Retention(RetentionPolicy.RUNTIME)
|
||||
//@Transactional // 기존 스프링 트랜잭션 어노테이션 상속
|
||||
//public @interface DynamicTransactional {
|
||||
//}
|
||||
@ -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.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,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,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,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,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
|
||||
@ -0,0 +1,57 @@
|
||||
package go.kr.project.biz.minwon.init.controller;
|
||||
|
||||
import egovframework.constant.TilesConstants;
|
||||
import egovframework.util.ApiResponseUtil;
|
||||
import go.kr.project.biz.minwon.init.model.MinwonInitDto;
|
||||
import go.kr.project.biz.minwon.init.service.MinwonInitService;
|
||||
import go.kr.project.template.noticeSample.model.NoticeSampleSearchVO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class MinwonInitController {
|
||||
|
||||
private final MinwonInitService minwonInitService;
|
||||
|
||||
/**
|
||||
* 민원접수 초기자료 목록 페이지
|
||||
* @return 뷰 경로
|
||||
*/
|
||||
@GetMapping("/minwon/init/init.do")
|
||||
@Operation(summary = "민원접수 초기자료", description = "민원접수 초기자료 목록 페이지를 조회합니다.")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "조회 성공")
|
||||
})
|
||||
public String minwonInitView() {
|
||||
|
||||
return "biz/minwon/init/init" + TilesConstants.BASE;
|
||||
}
|
||||
|
||||
@PostMapping("/minwon/init/list.ajax")
|
||||
public ResponseEntity<?> getMinwonInitListAjax(@ModelAttribute MinwonInitDto.Request.SearchMinwonInitList dto) {
|
||||
|
||||
// 총 게시물 수 조회
|
||||
int totalCount = 0;
|
||||
dto.setTotalCount(totalCount);
|
||||
|
||||
// 페이징 처리를 위한 설정
|
||||
dto.setPagingYn("Y");
|
||||
|
||||
// 리스트 조회
|
||||
List<MinwonInitDto.Response.InitAnswers> result = minwonInitService.findInitAnswers(dto);
|
||||
|
||||
return ApiResponseUtil.successWithGrid(result, dto);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
package go.kr.project.biz.minwon.init.model;
|
||||
|
||||
import go.kr.project.system.common.model.DefaultVO;
|
||||
import go.kr.project.system.common.model.PagingVO;
|
||||
import lombok.*;
|
||||
|
||||
public class MinwonInitDto {
|
||||
|
||||
|
||||
public static class Request {
|
||||
@Getter
|
||||
@Setter
|
||||
public static class SearchMinwonInitList extends PagingVO {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class Response {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class InitAnswers {
|
||||
|
||||
private String mmIngb; //등록구분
|
||||
private String asBbsNo; //목록번호
|
||||
private String mmSgnm; //신고자
|
||||
private String mmSgtel; //담당자
|
||||
private String asTel; //전화번호
|
||||
private String asJsdate; //접수일자
|
||||
private String asLimitDt; //처리기한
|
||||
private String mmDate; //위반일자
|
||||
private String mmImagegb; //첨부
|
||||
private int mmImagecnt; //사진
|
||||
private String mmSgcont; //위반내용
|
||||
private String asJsno; //접수번호
|
||||
private String mmCarno; //차량번호
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package go.kr.project.biz.minwon.init.repository;
|
||||
|
||||
import com.querydsl.core.types.Projections;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import go.kr.project.biz.minwon.init.model.MinwonInitDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static go.kr.project.domain.entity.QCpAnswer.cpAnswer;
|
||||
import static go.kr.project.domain.entity.QCpMain.cpMain;
|
||||
|
||||
@Repository
|
||||
@RequiredArgsConstructor
|
||||
public class MinwonInitQueryDslRepository {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
|
||||
public List<MinwonInitDto.Response.InitAnswers> findInitAnswers(MinwonInitDto.Request.SearchMinwonInitList dto) {
|
||||
|
||||
List<MinwonInitDto.Response.InitAnswers> result = queryFactory
|
||||
.select(
|
||||
Projections.fields(
|
||||
MinwonInitDto.Response.InitAnswers.class,
|
||||
cpMain.mmDlgb,
|
||||
cpAnswer.asBbsNo,
|
||||
cpAnswer.asUser,
|
||||
cpMain.mmSgtel,
|
||||
cpAnswer.asTel,
|
||||
cpAnswer.asJsdate,
|
||||
cpAnswer.asLimitDt,
|
||||
cpMain.mmDate,
|
||||
cpMain.mmImagegb,
|
||||
cpMain.mmImagecnt,
|
||||
cpMain.mmSgcont,
|
||||
cpAnswer.asJsno,
|
||||
cpMain.mmCarno
|
||||
)
|
||||
)
|
||||
.from(cpMain)
|
||||
.innerJoin(cpAnswer).on(cpMain.mmCode.eq(cpAnswer.asMmcode))
|
||||
.where(
|
||||
cpMain.mmState.eq("01"),
|
||||
cpMain.mmDlgb.eq("2")
|
||||
)
|
||||
.fetch();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
package go.kr.project.biz.minwon.init.service;
|
||||
|
||||
import go.kr.project.biz.minwon.init.model.MinwonInitDto;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MinwonInitService {
|
||||
|
||||
|
||||
List<MinwonInitDto.Response.InitAnswers> findInitAnswers(MinwonInitDto.Request.SearchMinwonInitList dto);
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package go.kr.project.biz.minwon.init.service.impl;
|
||||
|
||||
import go.kr.project.biz.minwon.init.model.MinwonInitDto;
|
||||
import go.kr.project.biz.minwon.init.repository.MinwonInitQueryDslRepository;
|
||||
import go.kr.project.biz.minwon.init.service.MinwonInitService;
|
||||
import go.kr.project.domain.repo.cp.CpAnswerRepository;
|
||||
import go.kr.project.domain.repo.cp.CpMainRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MinwonInitServiceImpl implements MinwonInitService {
|
||||
|
||||
private final CpMainRepository cpMainRepository;
|
||||
private final CpAnswerRepository cpAnswerRepository;
|
||||
private final MinwonInitQueryDslRepository minwonInitQueryDslRepository;
|
||||
|
||||
@Override
|
||||
public List<MinwonInitDto.Response.InitAnswers> findInitAnswers(MinwonInitDto.Request.SearchMinwonInitList dto) {
|
||||
|
||||
return minwonInitQueryDslRepository.findInitAnswers(dto);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package go.kr.project.biz.totalInfo.controller;
|
||||
|
||||
import egovframework.constant.MessageConstants;
|
||||
import egovframework.constant.TilesConstants;
|
||||
import egovframework.util.ApiResponseUtil;
|
||||
import go.kr.project.biz.minwon.init.model.MinwonInitDto;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class TotalInfoController {
|
||||
|
||||
@GetMapping("/total/info.do")
|
||||
public String totalInfoViewPopup(){
|
||||
|
||||
return "biz/totalInfo/totalInfo_popup" + TilesConstants.POPUP;
|
||||
}
|
||||
|
||||
@PostMapping("/total/info/info.ajax")
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> getMinwonInitListAjax() {
|
||||
|
||||
|
||||
|
||||
return ApiResponseUtil.success("");
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package go.kr.project.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Entity
|
||||
@Table(name = "cp_answer")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CpAnswer {
|
||||
|
||||
@Id
|
||||
@Column(name = "AS_MMCODE", length = 16)
|
||||
private String asMmcode;
|
||||
|
||||
@Column(name = "AS_SGGCODE", length = 5)
|
||||
private String asSggcode;
|
||||
|
||||
@Column(name = "AS_INGB", length = 3)
|
||||
private String asIngb;
|
||||
|
||||
@Column(name = "AS_JSDATE", length = 8)
|
||||
private String asJsdate;
|
||||
|
||||
@Column(name = "AS_JSNO", length = 30)
|
||||
private String asJsno;
|
||||
|
||||
@Column(name = "AS_JSNO_M", length = 30)
|
||||
private String asJsnoM;
|
||||
|
||||
@Column(name = "AS_BBS_NO", length = 15)
|
||||
private String asBbsNo;
|
||||
|
||||
@Column(name = "AS_LIMIT_DT", length = 16)
|
||||
private String asLimitDt;
|
||||
|
||||
@Column(name = "AS_USER", length = 50)
|
||||
private String asUser;
|
||||
|
||||
@Column(name = "AS_TEL", length = 20)
|
||||
private String asTel;
|
||||
|
||||
@Column(name = "AS_CELL", length = 20)
|
||||
private String asCell;
|
||||
|
||||
@Column(name = "AS_EMAIL", length = 50)
|
||||
private String asEmail;
|
||||
|
||||
@Column(name = "AS_STATE", length = 1)
|
||||
private String asState;
|
||||
|
||||
@Column(name = "AS_POST_CD", length = 1)
|
||||
private String asPostCd;
|
||||
|
||||
@Column(name = "AS_POST_DT", length = 14)
|
||||
private String asPostDt;
|
||||
|
||||
@Column(name = "AS_STATE_DT", length = 14)
|
||||
private String asStateDt;
|
||||
|
||||
@Column(name = "AS_TEXT", length = 1000)
|
||||
private String asText;
|
||||
|
||||
@Column(name = "AS_REUSER")
|
||||
private Integer asReuser;
|
||||
|
||||
@Column(name = "AS_INLINE", length = 1)
|
||||
private String asInline;
|
||||
|
||||
@Column(name = "AS_SYS_GUBUN_C", length = 8)
|
||||
private String asSysGubunC;
|
||||
|
||||
@Column(name = "AS_PETI_ANC_CODE_V", length = 7)
|
||||
private String asPetiAncCodeV;
|
||||
|
||||
@Column(name = "AS_PETI_NO_C", length = 30)
|
||||
private String asPetiNoC;
|
||||
|
||||
|
||||
public void changeState(String newState) {
|
||||
this.asState = newState;
|
||||
}
|
||||
|
||||
public void changePostDtNow() {
|
||||
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
this.asPostDt = LocalDateTime.now().format(dtf);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package go.kr.project.domain.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "cp_bdong", indexes = {
|
||||
@Index(name = "CP_BDONG_IDX1", columnList = "BD_CODE, BD_DONGNAME")
|
||||
})
|
||||
@Getter
|
||||
public class CpBdong {
|
||||
|
||||
@Id
|
||||
@Column(name = "BD_CODE", length = 10, nullable = false)
|
||||
private String bdCode;
|
||||
|
||||
@Column(name = "BD_SGGNAME", length = 40)
|
||||
private String bdSggName;
|
||||
|
||||
@Column(name = "BD_DONGNAME", length = 40)
|
||||
private String bdDongName;
|
||||
|
||||
@Column(name = "BD_ENABLE", length = 1)
|
||||
private String bdEnable;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package go.kr.project.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "cp_cancel", indexes = {
|
||||
@Index(name = "CP_CANCEL_IDX1", columnList = "CC_MMCODE"),
|
||||
@Index(name = "CP_CANCEL_IDX2", columnList = "CC_SGGCODE, CC_DATE")
|
||||
})
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CpCancel {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "CC_CODE")
|
||||
private Long ccCode;
|
||||
|
||||
@Column(name = "CC_SGGCODE", length = 5)
|
||||
private String ccSggcode;
|
||||
|
||||
@Column(name = "CC_MMCODE", length = 16)
|
||||
private String ccMmcode;
|
||||
|
||||
@Column(name = "CC_DATE", length = 8)
|
||||
private String ccDate;
|
||||
|
||||
@Column(name = "CC_CAUSE", length = 3)
|
||||
private String ccCause;
|
||||
|
||||
@Column(name = "CC_ETC", length = 1000)
|
||||
private String ccEtc;
|
||||
|
||||
@Column(name = "CC_INDT", length = 14)
|
||||
private String ccIndt;
|
||||
|
||||
@Column(name = "CC_INUSER")
|
||||
private Integer ccInuser;
|
||||
}
|
||||
@ -0,0 +1,162 @@
|
||||
package go.kr.project.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "cp_main")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CpMain {
|
||||
|
||||
@Id
|
||||
@Column(name = "MM_CODE", length = 16)
|
||||
private String mmCode;
|
||||
|
||||
@Column(name = "MM_SGGCODE", length = 5)
|
||||
private String mmSggcode;
|
||||
|
||||
@Column(name = "MM_DLGB", length = 1)
|
||||
private String mmDlgb;
|
||||
|
||||
@Column(name = "MM_INGB", length = 2)
|
||||
private String mmIngb;
|
||||
|
||||
@Column(name = "MM_DATE", length = 8)
|
||||
private String mmDate;
|
||||
|
||||
@Column(name = "MM_TIME", length = 4)
|
||||
private String mmTime;
|
||||
|
||||
@Column(name = "MM_LAWGB", length = 2)
|
||||
private String mmLawgb;
|
||||
|
||||
@Column(name = "MM_SGNM", length = 500)
|
||||
private String mmSgnm;
|
||||
|
||||
@Column(name = "MM_SGTEL", length = 100)
|
||||
private String mmSgtel;
|
||||
|
||||
@Column(name = "MM_SGCONT", length = 1000)
|
||||
private String mmSgcont;
|
||||
|
||||
@Column(name = "MM_SGPOS", length = 200)
|
||||
private String mmSgpos;
|
||||
|
||||
@Column(name = "MM_BDCODE", length = 10)
|
||||
private String mmBdcode;
|
||||
|
||||
@Column(name = "MM_GPS_X", length = 20)
|
||||
private String mmGpsX;
|
||||
|
||||
@Column(name = "MM_GPS_Y", length = 20)
|
||||
private String mmGpsY;
|
||||
|
||||
@Column(name = "MM_TRAC", length = 1)
|
||||
private String mmTrac;
|
||||
|
||||
@Column(name = "MM_SNO", length = 20)
|
||||
private String mmSno;
|
||||
|
||||
@Column(name = "MM_IMAGECNT")
|
||||
private Integer mmImagecnt;
|
||||
|
||||
@Column(name = "MM_IMAGEGB", length = 1)
|
||||
private String mmImagegb;
|
||||
|
||||
@Column(name = "MM_CARNO", length = 20)
|
||||
private String mmCarno;
|
||||
|
||||
@Column(name = "MM_VHMNO", length = 25)
|
||||
private String mmVhmno;
|
||||
|
||||
@Column(name = "MM_CARGB", length = 1)
|
||||
private String mmCargb;
|
||||
|
||||
@Column(name = "MM_CARKIND", length = 1)
|
||||
private String mmCarkind;
|
||||
|
||||
@Column(name = "MM_OMCODE", length = 13)
|
||||
private String mmOmcode;
|
||||
|
||||
@Column(name = "MM_SDATE", length = 8)
|
||||
private String mmSdate;
|
||||
|
||||
@Column(name = "MM_EDATE", length = 8)
|
||||
private String mmEdate;
|
||||
|
||||
@Column(name = "MM_KEUM1")
|
||||
private Integer mmKeum1;
|
||||
|
||||
@Column(name = "MM_KEUM2")
|
||||
private Integer mmKeum2;
|
||||
|
||||
@Column(name = "MM_SUKEUM")
|
||||
private Integer mmSukeum;
|
||||
|
||||
@Column(name = "MM_MINUS_KEUM")
|
||||
private Integer mmMinusKeum;
|
||||
|
||||
@Column(name = "MM_ADD_KEUM")
|
||||
private Integer mmAddKeum;
|
||||
|
||||
@Column(name = "MM_RECALL", length = 1)
|
||||
private String mmRecall;
|
||||
|
||||
@Column(name = "MM_INUSER")
|
||||
private Integer mmInuser;
|
||||
|
||||
@Column(name = "MM_INDT", length = 14)
|
||||
private String mmIndt;
|
||||
|
||||
@Column(name = "MM_STATE", length = 2)
|
||||
private String mmState;
|
||||
|
||||
@Column(name = "MM_STATE_DT", length = 14)
|
||||
private String mmStateDt;
|
||||
|
||||
@Column(name = "MM_CARCHECK", length = 1)
|
||||
private String mmCarcheck;
|
||||
|
||||
@Column(name = "MM_PRECODE", length = 13)
|
||||
private String mmPrecode;
|
||||
|
||||
@Column(name = "MM_ETC", length = 1000)
|
||||
private String mmEtc;
|
||||
|
||||
@Column(name = "MM_VIDEOFILENM", length = 30)
|
||||
private String mmVideofilenm;
|
||||
|
||||
@Column(name = "MM_SAFEZONE", length = 1)
|
||||
private String mmSafezone;
|
||||
|
||||
@Column(name = "MM_VIORCNT", length = 4)
|
||||
private String mmViorcnt;
|
||||
|
||||
@Column(name = "MM_TIME2", length = 4)
|
||||
private String mmTime2;
|
||||
|
||||
@Column(name = "MM_CARNAME", length = 60)
|
||||
private String mmCarname;
|
||||
|
||||
@Column(name = "MM_CARCOLOR", length = 20)
|
||||
private String mmCarcolor;
|
||||
|
||||
@Column(name = "MM_CARFUEL", length = 10)
|
||||
private String mmCarfuel;
|
||||
|
||||
@Column(name = "MM_TRANSMIT_SGG", length = 50)
|
||||
private String mmTransmitSgg;
|
||||
|
||||
@Column(name = "MM_TRANSMIT_TEAM", length = 50)
|
||||
private String mmTransmitTeam;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package go.kr.project.domain.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "cp_main_etc1")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class CpMainEtc1 {
|
||||
|
||||
@Id
|
||||
@Column(name = "MM_CODE", length = 16)
|
||||
private String mmCode;
|
||||
|
||||
@Column(name = "MM_JSDATE", length = 8)
|
||||
private String mmJsdate;
|
||||
|
||||
@Column(name = "MM_KEY", length = 30)
|
||||
private String mmKey;
|
||||
|
||||
@Column(name = "MM_TEXT", length = 4000)
|
||||
private String mmText;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue