API 상태 변화 감시 job
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
|
||||
import com.eactive.eai.data.entity.AbstractEntity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
|
||||
@Entity
|
||||
@Table(name = "API_STATUS")
|
||||
public class ApiStatus extends AbstractEntity<String> {
|
||||
|
||||
@Id
|
||||
@Column(name = "EAISVCNAME", length = 100)
|
||||
private String eaisvcname;
|
||||
|
||||
@Column(name = "STATUS_CODE", length = 1)
|
||||
private String statusCode;
|
||||
|
||||
@LastModifiedBy // ← EMSAuditorAware가 자동으로 채움
|
||||
@Column(name = "MOD_USERID", length = 50)
|
||||
private String modUserid = "SCHEDULER";
|
||||
|
||||
@LastModifiedDate // ← save() 시 자동으로 sysdate 채움
|
||||
@Column(name = "MOD_DATE")
|
||||
private LocalDateTime modDate;
|
||||
|
||||
@Override
|
||||
public @NonNull String getId() { return eaisvcname; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
|
||||
public interface ApiStatusEvent {
|
||||
String getEaisvcname();
|
||||
String getEvent();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import com.eactive.eai.data.jpa.BaseRepository;
|
||||
|
||||
public interface ApiStatusRepository extends BaseRepository<ApiStatus, String> {
|
||||
|
||||
/**
|
||||
* API 상태 판단. 정상(N), 점검(C), 지연(D), 장애(E)
|
||||
*
|
||||
* 1. 정상/지연/장애 -> 점검중 : CONTROL_START
|
||||
* 2. 점검 -> 점검X : CONTROL_END
|
||||
* 3. 정상/지연 -> 장애 : ERROR_START
|
||||
* 4. 장애 -> 장애X : ERROR_END
|
||||
* 5. 정상 -> 지연 : DELAY_START
|
||||
* 6. 지연 -> 지연X : DELAY_END
|
||||
* 7. 기타 : STAY
|
||||
*/
|
||||
@Query(nativeQuery = true, value =
|
||||
" SELECT EAISVCNAME, EVENT"
|
||||
+ " FROM ("
|
||||
+ " SELECT EAISVCNAME"
|
||||
+ " , CASE WHEN STATUS_CODE != 'C' AND CTRL_YN = 'Y' THEN 'CONTROL_START'"
|
||||
+ " WHEN STATUS_CODE = 'C' AND CTRL_YN = 'N' THEN 'CONTROL_END'"
|
||||
+ " WHEN STATUS_CODE IN ('N','D') AND ERR_YN = 'Y' THEN 'ERROR_START'"
|
||||
+ " WHEN STATUS_CODE = 'E' AND ERR_YN = 'N' THEN 'ERROR_END'"
|
||||
+ " WHEN STATUS_CODE = 'N' AND DELAY_YN = 'Y' THEN 'DELAY_START'"
|
||||
+ " WHEN STATUS_CODE = 'D' AND DELAY_YN = 'N' THEN 'DELAY_END'"
|
||||
+ " ELSE 'STAY' END AS EVENT"
|
||||
+ " FROM ("
|
||||
+ " SELECT A.EAISVCNAME"
|
||||
+ " , NVL(B.STATUS_CODE, '1') AS STATUS_CODE"
|
||||
+ " , NVL((SELECT 'Y' FROM TSEAITI01"
|
||||
+ " WHERE (TO_CHAR(SYSDATE,'HH24MI') BETWEEN SUBSTR(EAICTRLDSTICCTNT,16,4) AND SUBSTR(EAICTRLDSTICCTNT,21,4)"
|
||||
+ " OR SUBSTR(EAICTRLDSTICCTNT,16,9) = '0000|0000')"
|
||||
+ " AND A.EAISVCNAME = EAICTRLNAME),'N') AS CTRL_YN"
|
||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||
+ " WHEN (TOTAL - SUCCESS) * 100 / TOTAL > :errorRate THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL"
|
||||
+ " , SUM(SUCCESS_CNT) AS SUCCESS"
|
||||
+ " FROM API_STATS_MINUTE"
|
||||
+ " WHERE A.EAISVCNAME = API_NAME"
|
||||
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
||||
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:errorRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
|
||||
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS ERR_YN"
|
||||
+ " , (SELECT CASE WHEN TOTAL = 0 THEN 'X'"
|
||||
+ " WHEN RESP_SUM / TOTAL > :delayAvgRespTime THEN 'Y'"
|
||||
+ " ELSE 'N' END"
|
||||
+ " FROM (SELECT SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) AS TOTAL"
|
||||
+ " , SUM(SUCCESS_CNT) AS SUCCESS"
|
||||
+ " , SUM((SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) * AVG_RESP_TIME) AS RESP_SUM"
|
||||
+ " FROM API_STATS_MINUTE"
|
||||
+ " WHERE A.EAISVCNAME = API_NAME"
|
||||
+ " AND TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
|
||||
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:delayRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
|
||||
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI'))) AS DELAY_YN"
|
||||
+ " FROM TSEAIHE01 A LEFT OUTER JOIN API_STATUS B ON A.EAISVCNAME = B.EAISVCNAME"
|
||||
+ " )"
|
||||
+ " ) WHERE EVENT != 'STAY'")
|
||||
List<ApiStatusEvent> findApiStatusEvents(
|
||||
@Param("errorRate") int errorRate,
|
||||
@Param("errorRangeMinute") int errorRangeMinute,
|
||||
@Param("delayRangeMinute") int delayRangeMinute,
|
||||
@Param("delayAvgRespTime") int delayAvgRespTime
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.eactive.eai.rms.data.ext.djb.apistatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class ApiStatusService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatusService.class);
|
||||
|
||||
@Autowired
|
||||
private ApiStatusRepository apiStatusRepository;
|
||||
|
||||
|
||||
public void updateApiStatus(HashMap<String, String> param) {
|
||||
|
||||
List<ApiStatusEvent> list = apiStatusRepository.findApiStatusEvents(
|
||||
Integer.parseInt(param.get("errorRate")),
|
||||
Integer.parseInt(param.get("errorRangeMinute")),
|
||||
Integer.parseInt(param.get("delayRangeMinute")),
|
||||
Integer.parseInt(param.get("delayAvgRespTime"))
|
||||
);
|
||||
|
||||
for (ApiStatusEvent event : list) {
|
||||
String newStatusCode = resolveStatusCode(event.getEvent());
|
||||
if (newStatusCode == null) continue;
|
||||
|
||||
ApiStatus apiStatus = apiStatusRepository.findById(event.getEaisvcname())
|
||||
.orElse(new ApiStatus());
|
||||
|
||||
apiStatus.setEaisvcname(event.getEaisvcname());
|
||||
apiStatus.setStatusCode(newStatusCode);
|
||||
|
||||
apiStatusRepository.save(apiStatus); // PK 있으면 UPDATE, 없으면 INSERT
|
||||
log.debug("API 상태 변경: {} {} → {}", event.getEaisvcname(), event.getEvent(), newStatusCode);
|
||||
|
||||
//TODO: 내부직원 알림발송 & 제휴사 webhook 발송
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveStatusCode(String event) {
|
||||
switch (event) {
|
||||
case "CONTROL_START": return "C";
|
||||
case "CONTROL_END": return "N";
|
||||
case "ERROR_START": return "E";
|
||||
case "ERROR_END": return "N";
|
||||
case "DELAY_START": return "D";
|
||||
case "DELAY_END": return "N";
|
||||
default:
|
||||
log.warn("알 수 없는 이벤트: {}", event);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.eactive.eai.rms.ext.djb.job;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.quartz.DisallowConcurrentExecution;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import com.eactive.eai.rms.common.context.MonitoringContext;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
|
||||
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
|
||||
import com.eactive.eai.rms.common.util.CommonUtil;
|
||||
import com.eactive.eai.rms.data.ext.djb.apistatus.ApiStatusService;
|
||||
import com.eactive.eai.rms.onl.common.util.DateUtil;
|
||||
|
||||
/**
|
||||
* Job - Quartz Job
|
||||
* API_STATS_MINUTE 데이터를 1분마다 조회하여 Api 상태를 판단하여 API_STATUS 테이블을 insert/update 한다.
|
||||
* & api 상태가 변경된 경우, 알림 테이블에 저장한다
|
||||
*
|
||||
* <p>Cron Schedule 권장:</p>
|
||||
* <ul>
|
||||
* <li>기본: 0 * * * * ? (매분 1회 실행)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Job 파라미터 (JobDataMap):</p>
|
||||
* <ul>
|
||||
* <li>
|
||||
* <b>api.status.error.range_minute</b>: API 장애기준 판단 시간간격 (단위: 분, 기본값: 1)
|
||||
* <b>api.status.error.rate</b>: API 장애기준 오류건수 비율(%) (단위: 시간, 기본값: 100)
|
||||
* <b>api.status.delay.range_minute</b>: API 지연기준 판단 시간간격 (단위: 분, 기본값: 1)
|
||||
* <b>api.status.delay.avg_resp_time</b>: API 지연기준 평균응답시간 (단위: 밀리세컨드, 기본값: 10000)
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@DisallowConcurrentExecution
|
||||
public class ApiStatusUpdateJob implements Job {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ApiStatusUpdateJob.class);
|
||||
|
||||
/** Job 파라미터 키: API 장애기준 시간간격(분) */
|
||||
public static final String KEY_API_STATUS_ERROR_RANGE_MINUTE = "api.status.error.range_minute";
|
||||
|
||||
/** Job 파라미터 키: API 장애기준 오류건수 비율(%) */
|
||||
public static final String KEY_API_STATUS_ERROR_RATE = "api.status.error.rate";
|
||||
|
||||
/** Job 파라미터 키: API 지연기준 시간간격(분) */
|
||||
public static final String KEY_API_STATUS_DELAY_RANGE_MINUTE = "api.status.delay.range_minute";
|
||||
|
||||
/** Job 파라미터 키: API 지연기준 평균응답시간(ms) */
|
||||
public static final String KEY_API_STATUS_DELAY_AVG_RESP_TIME = "api.status.delay.avg_resp_time";
|
||||
|
||||
public static final String DEFAULT_ERROR_RANGE_MINUTE = "1";
|
||||
public static final String DEFAULT_ERROR_RATE = "100";
|
||||
public static final String DEFAULT_DELAY_RANGE_MINUTE = "1";
|
||||
public static final String DEFAULT_DELAY_AVG_RESP_TIME = "10000";
|
||||
|
||||
private transient MonitoringContext monitoringContext;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException {
|
||||
log.info("*** START ApiStatusUpdateJob run({})", CommonUtil.getToday("yyyy-MM-dd HH:mm"));
|
||||
|
||||
// Job 파라미터 로깅
|
||||
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
|
||||
logJobParameters(jobDataMap);
|
||||
|
||||
HashMap<String, String> param = this.checkParameters(jobDataMap);
|
||||
|
||||
|
||||
ApplicationContext appContext;
|
||||
try {
|
||||
appContext = (ApplicationContext) context.getScheduler().getContext().get("applicationContext");
|
||||
} catch (SchedulerException e) {
|
||||
log.error("applicationContext get module error", e);
|
||||
return;
|
||||
}
|
||||
|
||||
monitoringContext = (MonitoringContext) appContext.getBean("monitoringContext");
|
||||
ApiStatusService apiStatusUpdateService = appContext.getBean(ApiStatusService.class);
|
||||
|
||||
|
||||
DataSourceContextHolder.setDataSourceType(
|
||||
DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.APIGW));
|
||||
try {
|
||||
apiStatusUpdateService.updateApiStatus(param);
|
||||
} catch (Exception e) {
|
||||
log.error("ApiStatusUpdateJob execution failed", e);
|
||||
throw new JobExecutionException(e);
|
||||
} finally {
|
||||
DataSourceContextHolder.clearDataSourceType();
|
||||
}
|
||||
|
||||
log.info("*** END ApiStatusUpdateJob run({})", DateUtil.getDateTime("yyyy-MM-dd HH:mm"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Job 파라미터 로깅
|
||||
*/
|
||||
private void logJobParameters(JobDataMap jobDataMap) {
|
||||
if (jobDataMap == null || jobDataMap.isEmpty()) {
|
||||
log.debug("Job 파라미터 없음 (기본값 사용)");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("Job 파라미터: ");
|
||||
for (String key : jobDataMap.getKeys()) {
|
||||
sb.append(key).append("=").append(jobDataMap.getString(key)).append(", ");
|
||||
}
|
||||
log.info(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 집계 범위 시간 파라미터 파싱
|
||||
*
|
||||
* @param jobDataMap Job 파라미터 맵
|
||||
* @return 집계 범위 시간 (파싱 실패 시 기본값 반환)
|
||||
*/
|
||||
private HashMap<String,String> checkParameters(JobDataMap jobDataMap) {
|
||||
|
||||
HashMap<String, String> param = new HashMap<String, String>();
|
||||
|
||||
if (jobDataMap == null) {
|
||||
param.put("errorRangeMinute", DEFAULT_ERROR_RANGE_MINUTE); //1분동안
|
||||
param.put("errorRate", DEFAULT_ERROR_RATE); //에러가 100% 발생시 '장애'로 판단
|
||||
param.put("delayRangeMinute", DEFAULT_DELAY_RANGE_MINUTE); //1분동안
|
||||
param.put("delayAvgRespTime", DEFAULT_DELAY_AVG_RESP_TIME); //평균 응답속도가 10초 이상이면 '지연'으로 판단
|
||||
return param;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(jobDataMap.getString(KEY_API_STATUS_ERROR_RANGE_MINUTE))) {
|
||||
param.put("errorRangeMinute", DEFAULT_ERROR_RANGE_MINUTE);
|
||||
} else {
|
||||
param.put("errorRangeMinute", jobDataMap.getString(KEY_API_STATUS_ERROR_RANGE_MINUTE));
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(jobDataMap.getString(KEY_API_STATUS_ERROR_RATE))) {
|
||||
param.put("errorRate", DEFAULT_ERROR_RATE);
|
||||
} else {
|
||||
param.put("errorRate", jobDataMap.getString(KEY_API_STATUS_ERROR_RATE));
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(jobDataMap.getString(KEY_API_STATUS_DELAY_RANGE_MINUTE))) {
|
||||
param.put("delayRangeMinute", DEFAULT_DELAY_RANGE_MINUTE);
|
||||
} else {
|
||||
param.put("delayRangeMinute", jobDataMap.getString(KEY_API_STATUS_DELAY_RANGE_MINUTE));
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(jobDataMap.getString(KEY_API_STATUS_DELAY_AVG_RESP_TIME))) {
|
||||
param.put("delayAvgRespTime", DEFAULT_DELAY_AVG_RESP_TIME);
|
||||
} else {
|
||||
param.put("delayAvgRespTime", jobDataMap.getString(KEY_API_STATUS_DELAY_AVG_RESP_TIME));
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
/**
|
||||
* 개발 모드 여부 확인
|
||||
* @return eai.systemmode=D 이면 true
|
||||
*/
|
||||
private boolean isDevMode() {
|
||||
String systemMode = System.getProperty("eai.systemmode", "");
|
||||
return "D".equalsIgnoreCase(systemMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.eactive.eai.rms.ext.djb.statistics;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.eactive.eai.rms.common.vo.GridResponse;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDay;
|
||||
import com.eactive.eai.rms.data.entity.onl.kjb.statistics.ApiStatsDayService;
|
||||
import com.eactive.ext.kjb.statistics.mapping.ApiStatsUIMapper;
|
||||
import com.eactive.ext.kjb.statistics.service.ApiStatsExcelExportService;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsChartUI;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsSearch;
|
||||
import com.eactive.ext.kjb.statistics.ui.ApiStatsUI;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
public class ApiUseStatsController {
|
||||
|
||||
private final ApiStatsDayService service;
|
||||
private final ApiStatsUIMapper mapper;
|
||||
private final ApiStatsExcelExportService excelExportService;
|
||||
|
||||
@GetMapping(value = "/onl/djb/statistics/apiUseStatsMan.view")
|
||||
public String view() {
|
||||
return "/onl/djb/statistics/apiUseStatsMan";
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/djb/statistics/apiUseStatsMan.json", params = "cmd=LIST")
|
||||
public ResponseEntity<GridResponse<ApiStatsUI>> selectList(ApiStatsSearch search, Pageable pageable) {
|
||||
Pageable sortedPageable = PageRequest.of(
|
||||
pageable.getPageNumber(),
|
||||
pageable.getPageSize(),
|
||||
Sort.by(Sort.Order.desc("statTime")));
|
||||
|
||||
Page<ApiStatsDay> page = service.selectList(search, sortedPageable);
|
||||
Page<ApiStatsUI> uiPage = page.map(mapper::toVo);
|
||||
|
||||
return ResponseEntity.ok(new GridResponse<>(uiPage));
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/djb/statistics/apiStatsDayMan.json", params = "cmd=EXCEL_EXPORT")
|
||||
public void exportToExcel(ApiStatsSearch search, HttpServletResponse response) throws IOException {
|
||||
log.info("Excel export started - search: {}", search);
|
||||
|
||||
try {
|
||||
List<ApiStatsDay> dataList = service.selectListForExcel(search);
|
||||
log.info("Data retrieved - count: {}", dataList.size());
|
||||
|
||||
if (dataList.isEmpty()) {
|
||||
log.warn("No data found for export");
|
||||
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
|
||||
response.setContentType("application/json; charset=UTF-8");
|
||||
response.getWriter().write("{\"message\":\"조회된 데이터가 없습니다.\"}");
|
||||
response.getWriter().flush();
|
||||
return;
|
||||
}
|
||||
|
||||
List<ApiStatsUI> uiList = dataList.stream()
|
||||
.map(mapper::toVo)
|
||||
.collect(Collectors.toList());
|
||||
log.info("Data converted to UI list - count: {}", uiList.size());
|
||||
|
||||
String fileName = generateFileName(search);
|
||||
log.info("Generating Excel file: {}", fileName);
|
||||
|
||||
excelExportService.exportApiStats(uiList, fileName, response);
|
||||
log.info("Excel export completed successfully");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Excel export failed - error: {}", e.getMessage(), e);
|
||||
|
||||
if (!response.isCommitted()) {
|
||||
response.reset();
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
response.setContentType("application/json; charset=UTF-8");
|
||||
response.getWriter().write("{\"message\":\"Excel 파일 생성 중 오류가 발생했습니다: " + e.getMessage() + "\"}");
|
||||
response.getWriter().flush();
|
||||
} else {
|
||||
log.error("Cannot send error response - response already committed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String generateFileName(ApiStatsSearch search) {
|
||||
String timeRange = StringUtils.isNotBlank(search.getSearchStartDateTime())
|
||||
? search.getSearchStartDateTime()
|
||||
: LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
||||
return "API사용현황_" + timeRange + ".xlsx";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user