엑셀 csv 형식으로 변경
전투시스템 종료시간 추가 메모리 버퍼 정리
This commit is contained in:
@@ -38,6 +38,11 @@ public class BattleController {
|
||||
public ResponseEntity<BattleEventResponse> getBattleRewardList(){
|
||||
return ResponseEntity.ok().body( battleEventService.getBattleRewardList());
|
||||
}
|
||||
|
||||
@GetMapping("/game-mode/list")
|
||||
public ResponseEntity<BattleEventResponse> getGameModeList(){
|
||||
return ResponseEntity.ok().body( battleEventService.getGameModeList());
|
||||
}
|
||||
|
||||
@PostMapping("/event")
|
||||
public ResponseEntity<BattleEventResponse> postBattleEvent(
|
||||
|
||||
@@ -31,6 +31,8 @@ public class BattleEvent {
|
||||
// 시작 일자
|
||||
@JsonProperty("event_start_dt")
|
||||
private LocalDateTime eventStartDt;
|
||||
@JsonProperty("event_end_time")
|
||||
private LocalDateTime eventEndTime;
|
||||
// 종료 일자
|
||||
@JsonProperty("event_end_dt")
|
||||
private LocalDateTime eventEndDt;
|
||||
@@ -51,6 +53,8 @@ public class BattleEvent {
|
||||
private Integer rewardGroupId;
|
||||
@JsonProperty("instance_id")
|
||||
private Integer instanceId;
|
||||
@JsonProperty("game_mode_id")
|
||||
private Integer gameModeId;
|
||||
|
||||
private boolean deleted;
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ public class BattleEventRequest {
|
||||
// 시작 일자
|
||||
@JsonProperty("event_start_dt")
|
||||
private LocalDateTime eventStartDt;
|
||||
@JsonProperty("event_end_time")
|
||||
private LocalDateTime eventEndTime;
|
||||
// 종료 일자
|
||||
@JsonProperty("event_end_dt")
|
||||
private LocalDateTime eventEndDt;
|
||||
@@ -51,6 +53,8 @@ public class BattleEventRequest {
|
||||
private Integer rewardGroupId;
|
||||
@JsonProperty("instance_id")
|
||||
private Integer instanceId;
|
||||
@JsonProperty("game_mode_id")
|
||||
private Integer gameModeId;
|
||||
|
||||
@JsonProperty("create_by")
|
||||
private Long createBy;
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.caliverse.admin.domain.entity.BattleEvent;
|
||||
import com.caliverse.admin.domain.entity.LandAuction;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBattleConfigData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBattleRewardData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaGameModeData;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -41,6 +42,9 @@ public class BattleEventResponse {
|
||||
@JsonProperty("battle_reward_list")
|
||||
private List<MetaBattleRewardData> battleRewardList;
|
||||
|
||||
@JsonProperty("game_mode_list")
|
||||
private List<MetaGameModeData> gameModeList;
|
||||
|
||||
private String message;
|
||||
|
||||
private int total;
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.caliverse.admin.domain.entity.BattleEvent;
|
||||
import com.caliverse.admin.domain.entity.HISTORYTYPEDETAIL;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBattleConfigData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBattleRewardData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaGameFFAConfigData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaGameModeData;
|
||||
import com.caliverse.admin.domain.request.BattleEventRequest;
|
||||
import com.caliverse.admin.domain.response.BattleEventResponse;
|
||||
import com.caliverse.admin.dynamodb.service.DynamodbBattleEventService;
|
||||
@@ -23,10 +25,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -75,6 +74,21 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
//전투시스템 게임모드 데이터
|
||||
public BattleEventResponse getGameModeList(){
|
||||
|
||||
List<MetaGameModeData> list = metaDataHandler.getMetaGameModeListData();
|
||||
|
||||
return BattleEventResponse.builder()
|
||||
.status(CommonCode.SUCCESS.getHttpStatus())
|
||||
.result(CommonCode.SUCCESS.getResult())
|
||||
.resultData(BattleEventResponse.ResultData.builder()
|
||||
.gameModeList(list)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 전투시스템 이벤트 조회
|
||||
public BattleEventResponse getBattleEventList(@RequestParam Map<String, String> requestParam){
|
||||
|
||||
@@ -112,6 +126,7 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
// 전투시스템 이벤트 저장
|
||||
@Transactional(transactionManager = "transactionManager")
|
||||
public BattleEventResponse postBattleEvent(BattleEventRequest battleEventRequest){
|
||||
if(battleEventRequest.getRepeatType().equals(BattleEvent.BATTLE_REPEAT_TYPE.NONE)){
|
||||
@@ -130,11 +145,8 @@ public class BattleEventService {
|
||||
}
|
||||
battleEventRequest.setInstanceId(CommonConstants.BATTLE_INSTANCE_ID); //고정값으로 넣고 추후 맵정보가 늘어나면 선택하는 걸로
|
||||
|
||||
if(battleEventRequest.getRoundTime().equals(0)
|
||||
|| battleEventRequest.getHotTime().equals(0)
|
||||
|| battleEventRequest.getConfigId().equals(0)
|
||||
if(battleEventRequest.getGameModeId().equals(0)
|
||||
|| battleEventRequest.getRoundCount().equals(0)
|
||||
|| battleEventRequest.getRewardGroupId().equals(0)
|
||||
){
|
||||
return BattleEventResponse.builder()
|
||||
.status(CommonCode.ERROR.getHttpStatus())
|
||||
@@ -142,10 +154,15 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
int operation_time = calcEndTime(battleEventRequest);
|
||||
// int operation_time = ffACalcEndTime(battleEventRequest);
|
||||
LocalTime startTime = battleEventRequest.getEventStartDt().toLocalTime();
|
||||
LocalTime endTime = battleEventRequest.getEventEndTime().toLocalTime();
|
||||
|
||||
long duration = Duration.between(startTime, endTime).getSeconds();
|
||||
|
||||
int operation_time = (int) duration;
|
||||
battleEventRequest.setEventOperationTime(operation_time);
|
||||
|
||||
// int is_time = battleMapper.chkTimeOver(battleEventRequest);
|
||||
List<BattleEvent> existingList = battleMapper.getCheckBattleEventList(battleEventRequest);
|
||||
boolean isTime = isTimeOverlapping(existingList, battleEventRequest);
|
||||
if(isTime){
|
||||
@@ -155,7 +172,7 @@ public class BattleEventService {
|
||||
.result(ErrorCode.ERROR_BATTLE_EVENT_TIME_OVER.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
// dynamoDB 기준으로 id를 관리하려고했으나 종료된 데이터를 주기적으로 지워주다보니 id가 꼬일수 있어서 운영DB기준으로 변경
|
||||
// int next_event_id = dynamodbBattleEventService.getEventId() + 1;
|
||||
// battleEventRequest.setEventId(next_event_id);
|
||||
|
||||
@@ -188,6 +205,7 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
// 전투시스템 이벤트 수정
|
||||
@Transactional(transactionManager = "transactionManager")
|
||||
public BattleEventResponse updateBattleEvent(Long id, BattleEventRequest battleEventRequest) {
|
||||
battleEventRequest.setId(id);
|
||||
@@ -212,7 +230,7 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
int operation_time = calcEndTime(battleEventRequest);
|
||||
int operation_time = ffACalcEndTime(battleEventRequest);
|
||||
battleEventRequest.setEventOperationTime(operation_time);
|
||||
|
||||
// 일자만 필요해서 UTC시간으로 변경되다보니 한국시간(+9)을 더해서 마지막시간으로 설정
|
||||
@@ -250,6 +268,7 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
// 전투시스템 이벤트 중단
|
||||
@Transactional(transactionManager = "transactionManager")
|
||||
public BattleEventResponse updateStopBattleEvent(Long id){
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
@@ -291,6 +310,7 @@ public class BattleEventService {
|
||||
.build();
|
||||
}
|
||||
|
||||
// 전투시스템 이벤트 삭제(사용안함)
|
||||
@Transactional(transactionManager = "transactionManager")
|
||||
public BattleEventResponse deleteBattleEvent(BattleEventRequest battleEventRequest){
|
||||
Map<String,Object> map = new HashMap<>();
|
||||
@@ -352,14 +372,25 @@ public class BattleEventService {
|
||||
}
|
||||
|
||||
// 이벤트 동작 시간 계산
|
||||
private int calcEndTime(BattleEventRequest battleEventRequest){
|
||||
MetaBattleConfigData config = metaDataHandler.getMetaBattleConfigsListData().stream()
|
||||
.filter(data -> data.getId().equals(battleEventRequest.getConfigId()))
|
||||
private int ffACalcEndTime(BattleEventRequest battleEventRequest){
|
||||
MetaGameModeData gameModeData = metaDataHandler.getMetaGameModeListData().stream()
|
||||
.filter(data -> data.getId().equals(battleEventRequest.getGameModeId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(gameModeData == null) return 0;
|
||||
|
||||
MetaGameFFAConfigData config = metaDataHandler.getMetaGameFFAConfigListData().stream()
|
||||
.filter(data -> data.getId().equals(gameModeData.getModeConfigId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// MetaBattleConfigData config = metaDataHandler.getMetaBattleConfigsListData().stream()
|
||||
// .filter(data -> data.getId().equals(battleEventRequest.getConfigId()))
|
||||
// .findFirst()
|
||||
// .orElse(null);
|
||||
if(config == null) return 0;
|
||||
|
||||
int round_time = battleEventRequest.getRoundTime();
|
||||
int round_time = config.getRoundTime();
|
||||
int round_count = battleEventRequest.getRoundCount();
|
||||
int round_wait_time = config.getNextRoundWaitTime();
|
||||
int result_wait_time = config.getResultUIWaitTime();
|
||||
@@ -380,12 +411,10 @@ public class BattleEventService {
|
||||
BattleEvent.BATTLE_REPEAT_TYPE newRepeatType = battleEventRequest.getRepeatType();
|
||||
|
||||
return existingList.stream().anyMatch(existingEvent -> {
|
||||
// 자기 자신은 제외 (수정 시)
|
||||
if (battleEventRequest.getId() != null && battleEventRequest.getId().equals(existingEvent.getId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 기존 이벤트 정보
|
||||
LocalDateTime existingStartDt = existingEvent.getEventStartDt();
|
||||
LocalDateTime existingEndDt = existingEvent.getEventEndDt();
|
||||
LocalDate existingStartDate = existingStartDt.toLocalDate();
|
||||
@@ -395,60 +424,44 @@ public class BattleEventService {
|
||||
BattleEvent.BATTLE_REPEAT_TYPE existingRepeatType = existingEvent.getRepeatType();
|
||||
|
||||
// 1. 두 이벤트가 모두 NONE 타입인 경우
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
// 같은 날짜인지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
if (newStartDate.equals(existingStartDate)) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. NONE 타입과 DAY 타입 간의 중복 체크
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
// NONE 이벤트의 날짜가 DAY 이벤트의 기간 내에 있는지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
if (isDateInRange(newStartDate, existingStartDate, existingEndDate)) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. DAY 타입과 NONE 타입 간의 중복 체크
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
// NONE 이벤트의 날짜가 DAY 이벤트의 기간 내에 있는지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
if (isDateInRange(existingStartDate, newStartDate, newEndDate)) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 두 이벤트가 모두 DAY 타입인 경우
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
// 날짜 범위가 겹치는지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
if (datesOverlap(newStartDate, newEndDate, existingStartDate, existingEndDate)) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5. NONE 타입과 요일 타입 간의 중복 체크
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE &&
|
||||
isWeekdayType(existingRepeatType)) {
|
||||
// NONE 이벤트의 날짜가 요일 이벤트의 기간 내에 있는지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE && isWeekdayType(existingRepeatType)) {
|
||||
if (isDateInRange(newStartDate, existingStartDate, existingEndDate)) {
|
||||
// NONE 이벤트의 요일이 요일 이벤트의 요일과 일치하는지 확인
|
||||
DayOfWeek noneDayOfWeek = newStartDate.getDayOfWeek();
|
||||
DayOfWeek weekdayType = getDayOfWeekFromRepeatType(existingRepeatType);
|
||||
|
||||
if (noneDayOfWeek == weekdayType) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
}
|
||||
@@ -456,16 +469,12 @@ public class BattleEventService {
|
||||
}
|
||||
|
||||
// 6. 요일 타입과 NONE 타입 간의 중복 체크
|
||||
if (isWeekdayType(newRepeatType) &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
// NONE 이벤트의 날짜가 요일 이벤트의 기간 내에 있는지 확인
|
||||
if (isWeekdayType(newRepeatType) && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.NONE) {
|
||||
if (isDateInRange(existingStartDate, newStartDate, newEndDate)) {
|
||||
// NONE 이벤트의 요일이 요일 이벤트의 요일과 일치하는지 확인
|
||||
DayOfWeek noneDayOfWeek = existingStartDate.getDayOfWeek();
|
||||
DayOfWeek weekdayType = getDayOfWeekFromRepeatType(newRepeatType);
|
||||
|
||||
if (noneDayOfWeek == weekdayType) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
}
|
||||
@@ -473,13 +482,9 @@ public class BattleEventService {
|
||||
}
|
||||
|
||||
// 7. DAY 타입과 요일 타입 간의 중복 체크
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY &&
|
||||
isWeekdayType(existingRepeatType)) {
|
||||
// 날짜 범위가 겹치는지 확인
|
||||
if (newRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY && isWeekdayType(existingRepeatType)) {
|
||||
if (datesOverlap(newStartDate, newEndDate, existingStartDate, existingEndDate)) {
|
||||
// 날짜 범위 내에 해당 요일이 적어도 하나 있는지 확인
|
||||
if (hasOverlappingWeekday(newStartDate, newEndDate, existingStartDate, existingEndDate, getDayOfWeekFromRepeatType(existingRepeatType))) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
}
|
||||
@@ -487,13 +492,9 @@ public class BattleEventService {
|
||||
}
|
||||
|
||||
// 8. 요일 타입과 DAY 타입 간의 중복 체크
|
||||
if (isWeekdayType(newRepeatType) &&
|
||||
existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
// 날짜 범위가 겹치는지 확인
|
||||
if (isWeekdayType(newRepeatType) && existingRepeatType == BattleEvent.BATTLE_REPEAT_TYPE.DAY) {
|
||||
if (datesOverlap(newStartDate, newEndDate, existingStartDate, existingEndDate)) {
|
||||
// 날짜 범위 내에 해당 요일이 적어도 하나 있는지 확인
|
||||
if (hasOverlappingWeekday(newStartDate, newEndDate, existingStartDate, existingEndDate, getDayOfWeekFromRepeatType(newRepeatType))) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
}
|
||||
@@ -502,11 +503,8 @@ public class BattleEventService {
|
||||
|
||||
// 9. 두 이벤트가 모두 요일 타입인 경우
|
||||
if (isWeekdayType(newRepeatType) && isWeekdayType(existingRepeatType)) {
|
||||
// 같은 요일인지 확인
|
||||
if (newRepeatType == existingRepeatType) {
|
||||
// 날짜 범위가 겹치는지 확인
|
||||
if (datesOverlap(newStartDate, newEndDate, existingStartDate, existingEndDate)) {
|
||||
// 시간이 겹치는지 확인
|
||||
return !existingStartTime.isAfter(newEndTime) && !newStartTime.isAfter(existingEndTime);
|
||||
}
|
||||
}
|
||||
@@ -558,18 +556,18 @@ public class BattleEventService {
|
||||
LocalDate overlapEnd = end1.isBefore(end2) ? end1 : end2;
|
||||
|
||||
if (overlapStart.isAfter(overlapEnd)) {
|
||||
return false; // 겹치는 날짜 범위 없음
|
||||
return false;
|
||||
}
|
||||
|
||||
// 겹치는 날짜 범위 내에 해당 요일이 있는지 확인
|
||||
LocalDate current = overlapStart;
|
||||
while (!current.isAfter(overlapEnd)) {
|
||||
if (current.getDayOfWeek() == dayOfWeek) {
|
||||
return true; // 해당 요일 발견
|
||||
return true;
|
||||
}
|
||||
current = current.plusDays(1);
|
||||
}
|
||||
|
||||
return false; // 해당 요일 없음
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,10 @@ import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -26,10 +28,16 @@ import java.util.zip.ZipOutputStream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExcelService {
|
||||
private final ExcelProgressTracker progressTracker;
|
||||
private final DiagnosisService diagnosisService;
|
||||
|
||||
// CSV 구분자
|
||||
private static final String CSV_DELIMITER = ",";
|
||||
private static final String CSV_QUOTE = "\"";
|
||||
private static final String CSV_LINE_SEPARATOR = "\n";
|
||||
|
||||
private static final Set<String> EXCLUDED_HEADERS = Set.of(
|
||||
"header.TranId",
|
||||
"header.Actor.AccountId",
|
||||
@@ -42,11 +50,6 @@ public class ExcelService {
|
||||
"body.Infos[0].Domain"
|
||||
);
|
||||
|
||||
public ExcelService(ExcelProgressTracker progressTracker, DiagnosisService diagnosisService) {
|
||||
this.progressTracker = progressTracker;
|
||||
this.diagnosisService = diagnosisService;
|
||||
}
|
||||
|
||||
// 멀티 스레드 처리를 위한 스레드 풀
|
||||
private final ExecutorService executorService = Executors.newFixedThreadPool(
|
||||
Runtime.getRuntime().availableProcessors()
|
||||
@@ -73,21 +76,290 @@ public class ExcelService {
|
||||
int dataSize = data.size();
|
||||
log.info("Processing {} records for Excel download", dataSize);
|
||||
|
||||
if (dataSize <= 10000) {
|
||||
// 1만건 이하: 단일 엑셀 파일 (XSSFWorkbook 사용)
|
||||
if (dataSize <= 50000) {
|
||||
// 5만건 이하: 단일 CSV 파일
|
||||
log.info("Creating single Excel file");
|
||||
generateStreamingExcel(response, data, fileName, sheetName, taskId);
|
||||
} else if (dataSize <= 200000) {
|
||||
// 1만건 초과 ~ 20만건: 1만건씩 분할하여 ZIP 파일
|
||||
// generateStreamingExcel(response, data, fileName, sheetName, taskId);
|
||||
generateStreamingCsv(response, data, fileName, taskId);
|
||||
} else if (dataSize <= 500000) {
|
||||
// 5만건 초과 ~ 50만건: 5만건씩 분할하여 ZIP 파일
|
||||
log.info("Creating multiple Excel files (split by 10,000 records)");
|
||||
generateMultipleExcelFilesAsZipParallel(response, data, fileName, sheetName, 10000, taskId);
|
||||
// generateMultipleExcelFilesAsZipParallel(response, data, fileName, sheetName, 10000, taskId);
|
||||
generateMultipleCsvFilesAsZip(response, data, fileName, 50000, taskId);
|
||||
} else {
|
||||
// 20만건 초과: 에러
|
||||
// 50만건 초과: 에러
|
||||
throw new IllegalArgumentException(
|
||||
String.format("데이터가 너무 많습니다. 최대 200,000건까지만 지원합니다. (현재: %,d건)", dataSize));
|
||||
String.format("데이터가 너무 많습니다. 최대 500,000건까지만 지원합니다. (현재: %,d건)", dataSize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메인 CSV 생성 메서드
|
||||
* @param response HttpServletResponse 객체
|
||||
* @param data 변환할 데이터 객체 리스트
|
||||
* @param fileName 다운로드될 파일명
|
||||
* @param taskId 진행률 추적 ID
|
||||
*/
|
||||
public void generateCsvToResponse(HttpServletResponse response, List<?> data,
|
||||
String fileName, String taskId) throws IOException {
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
throw new IllegalArgumentException("데이터가 비어있습니다.");
|
||||
}
|
||||
|
||||
int dataSize = data.size();
|
||||
log.info("Processing {} records for CSV download", dataSize);
|
||||
|
||||
if (dataSize <= 50000) {
|
||||
// 5만건 이하: 단일 CSV 파일
|
||||
log.info("Creating single CSV file");
|
||||
generateStreamingCsv(response, data, fileName, taskId);
|
||||
} else if (dataSize <= 500000) {
|
||||
// 5만건 초과 ~ 50만건: 5만건씩 분할하여 ZIP 파일
|
||||
log.info("Creating multiple CSV files (split by 50,000 records)");
|
||||
generateMultipleCsvFilesAsZip(response, data, fileName, 50000, taskId);
|
||||
} else {
|
||||
// 50만건 초과: 에러
|
||||
throw new IllegalArgumentException(
|
||||
String.format("데이터가 너무 많습니다. 최대 500,000건까지만 지원합니다. (현재: %,d건)", dataSize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 스트리밍 방식으로 CSV 생성 (메모리 효율적)
|
||||
*/
|
||||
private void generateStreamingCsv(HttpServletResponse response, List<?> data,
|
||||
String fileName, String taskId) throws IOException {
|
||||
|
||||
setupCsvResponseHeaders(response, fileName);
|
||||
progressTracker.updateProgress(taskId, 35, 100, "헤더 정보 생성중...");
|
||||
|
||||
// 헤더 생성
|
||||
Map<String, String> headers = generateOptimizedHeaders(data);
|
||||
String[] fieldNames = headers.keySet().toArray(new String[0]);
|
||||
|
||||
progressTracker.updateProgress(taskId, 40, 100, "CSV 파일 생성 시작...");
|
||||
|
||||
try (OutputStreamWriter writer = new OutputStreamWriter(
|
||||
response.getOutputStream(), StandardCharsets.UTF_8);
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(writer, 8192)) {
|
||||
|
||||
// UTF-8 BOM 추가 (Excel에서 한글 깨짐 방지)
|
||||
bufferedWriter.write('\ufeff');
|
||||
|
||||
// 헤더 행 작성
|
||||
writeCsvHeader(bufferedWriter, headers.values());
|
||||
progressTracker.updateProgress(taskId, 42, 100, "헤더 작성 완료. 데이터 처리 시작...");
|
||||
|
||||
// 배치 처리로 데이터 작성
|
||||
int batchSize = 10000;
|
||||
int totalDataSize = data.size();
|
||||
int processedDataSize = 0;
|
||||
|
||||
for (int i = 0; i < data.size(); i += batchSize) {
|
||||
int endIdx = Math.min(i + batchSize, data.size());
|
||||
List<?> batch = data.subList(i, endIdx);
|
||||
|
||||
try {
|
||||
// 배치를 미리 변환
|
||||
List<Map<String, Object>> flattenedBatch = preProcessBatch(batch);
|
||||
|
||||
// CSV 행 작성
|
||||
for (Map<String, Object> flatMap : flattenedBatch) {
|
||||
if (flatMap != null) {
|
||||
writeCsvRow(bufferedWriter, flatMap, fieldNames);
|
||||
}
|
||||
}
|
||||
|
||||
processedDataSize += batch.size();
|
||||
|
||||
// 진행률 업데이트
|
||||
int progress = 42 + (int) ((processedDataSize * 53.0) / totalDataSize);
|
||||
progressTracker.updateProgress(taskId, progress, 100,
|
||||
String.format("데이터 처리 중... (%,d/%,d)", processedDataSize, totalDataSize));
|
||||
|
||||
// 주기적으로 flush
|
||||
if (processedDataSize % 50000 == 0) {
|
||||
bufferedWriter.flush();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Batch processing failed at index {}: {}", i, e.getMessage(), e);
|
||||
processedDataSize += batch.size(); // 오류 발생한 배치도 처리된 것으로 간주
|
||||
}
|
||||
}
|
||||
|
||||
bufferedWriter.flush();
|
||||
progressTracker.updateProgress(taskId, 100, 100,
|
||||
String.format("CSV 다운로드 완료! (총 %,d건)", totalDataSize));
|
||||
|
||||
log.info("CSV generation completed successfully for taskId: {} ({} rows)",
|
||||
taskId, totalDataSize);
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("CSV generation failed for taskId: {}", taskId, e);
|
||||
progressTracker.updateProgress(taskId, -1, 100, "CSV 생성 실패: " + e.getMessage());
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error during CSV generation for taskId: {}", taskId, e);
|
||||
progressTracker.updateProgress(taskId, -1, 100, "예상치 못한 오류 발생: " + e.getMessage());
|
||||
throw new IOException("CSV 생성 중 예상치 못한 오류가 발생했습니다: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 병렬 처리로 다중 CSV 파일 ZIP 생성
|
||||
*/
|
||||
private void generateMultipleCsvFilesAsZip(HttpServletResponse response, List<?> data,
|
||||
String fileName, int maxRowsPerFile, String taskId) throws IOException {
|
||||
|
||||
setupZipResponseHeaders(response, fileName + ".zip");
|
||||
progressTracker.updateProgress(taskId, 35, 100, "헤더 정보 생성중...");
|
||||
|
||||
Map<String, String> headers = generateOptimizedHeaders(data);
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
|
||||
int totalFiles = (int) Math.ceil((double) data.size() / maxRowsPerFile);
|
||||
progressTracker.updateProgress(taskId, 40, 100,
|
||||
String.format("ZIP 파일 생성 준비 중... (총 %d개 파일)", totalFiles));
|
||||
|
||||
AtomicInteger completedFiles = new AtomicInteger(0);
|
||||
|
||||
// 청크를 병렬로 처리
|
||||
List<CompletableFuture<CsvFileData>> futures = new ArrayList<>();
|
||||
|
||||
for (int fileIndex = 0; fileIndex < totalFiles; fileIndex++) {
|
||||
int startIndex = fileIndex * maxRowsPerFile;
|
||||
int endIndex = Math.min(startIndex + maxRowsPerFile, data.size());
|
||||
List<?> chunk = data.subList(startIndex, endIndex);
|
||||
|
||||
final int currentFileIndex = fileIndex;
|
||||
CompletableFuture<CsvFileData> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String csvFileName = String.format("%s_part%d.csv",
|
||||
fileName, currentFileIndex + 1);
|
||||
|
||||
byte[] csvData = createSingleCsvFileBytes(chunk, headers);
|
||||
|
||||
int completed = completedFiles.incrementAndGet();
|
||||
progressTracker.updateProgress(taskId,
|
||||
40 + (int) ((completed * 50.0) / totalFiles), 100,
|
||||
String.format("CSV 파일 완료... (%d/%d)", completed, totalFiles));
|
||||
|
||||
return new CsvFileData(csvFileName, csvData, currentFileIndex);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, executorService);
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 완료된 순서대로 ZIP에 추가
|
||||
for (int i = 0; i < futures.size(); i++) {
|
||||
try {
|
||||
CsvFileData fileData = futures.get(i).get();
|
||||
|
||||
ZipEntry zipEntry = new ZipEntry(fileData.fileName);
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
zipOut.write(fileData.data);
|
||||
zipOut.closeEntry();
|
||||
|
||||
int zipProgress = 90 + (int) (((i + 1) * 10.0) / totalFiles);
|
||||
progressTracker.updateProgress(taskId, zipProgress, 100,
|
||||
String.format("ZIP 압축 완료... (%d/%d)", i + 1, totalFiles));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing file {}: {}", i, e.getMessage(), e);
|
||||
throw new IOException("CSV file creation failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 인덱스 파일 생성
|
||||
createIndexFile(zipOut, fileName, totalFiles, data.size(), maxRowsPerFile);
|
||||
|
||||
zipOut.finish();
|
||||
progressTracker.updateProgress(taskId, 100, 100, "ZIP 파일 생성 완료!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSV 헤더 작성
|
||||
*/
|
||||
private void writeCsvHeader(BufferedWriter writer, Collection<String> headers) throws IOException {
|
||||
boolean first = true;
|
||||
for (String header : headers) {
|
||||
if (!first) {
|
||||
writer.write(CSV_DELIMITER);
|
||||
}
|
||||
writer.write(escapeCsvValue(header));
|
||||
first = false;
|
||||
}
|
||||
writer.write(CSV_LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSV 데이터 행 작성
|
||||
*/
|
||||
private void writeCsvRow(BufferedWriter writer, Map<String, Object> flatMap, String[] fieldNames) throws IOException {
|
||||
boolean first = true;
|
||||
for (String fieldName : fieldNames) {
|
||||
if (!first) {
|
||||
writer.write(CSV_DELIMITER);
|
||||
}
|
||||
Object value = flatMap.get(fieldName);
|
||||
writer.write(escapeCsvValue(value != null ? value.toString() : ""));
|
||||
first = false;
|
||||
}
|
||||
writer.write(CSV_LINE_SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSV 값 이스케이프 처리
|
||||
*/
|
||||
private String escapeCsvValue(String value) {
|
||||
if (value == null || value.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 쉼표, 개행, 큰따옴표가 포함된 경우 큰따옴표로 감싸기
|
||||
if (value.contains(CSV_DELIMITER) || value.contains(CSV_LINE_SEPARATOR) || value.contains(CSV_QUOTE)) {
|
||||
// 큰따옴표는 두 개로 이스케이프
|
||||
value = value.replace(CSV_QUOTE, CSV_QUOTE + CSV_QUOTE);
|
||||
return CSV_QUOTE + value + CSV_QUOTE;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 CSV 파일 바이트 생성
|
||||
*/
|
||||
private byte[] createSingleCsvFileBytes(List<?> data, Map<String, String> headers) throws IOException {
|
||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
|
||||
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
|
||||
|
||||
// UTF-8 BOM 추가
|
||||
bufferedWriter.write('\ufeff');
|
||||
|
||||
// 헤더 작성
|
||||
writeCsvHeader(bufferedWriter, headers.values());
|
||||
|
||||
// 데이터 작성
|
||||
String[] fieldNames = headers.keySet().toArray(new String[0]);
|
||||
List<Map<String, Object>> flattenedData = preProcessBatch(data);
|
||||
|
||||
for (Map<String, Object> flatMap : flattenedData) {
|
||||
writeCsvRow(bufferedWriter, flatMap, fieldNames);
|
||||
}
|
||||
|
||||
bufferedWriter.flush();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Object 리스트로부터 엑셀 파일을 HttpServletResponse로 직접 출력
|
||||
* @param response HttpServletResponse 객체
|
||||
@@ -142,7 +414,6 @@ public class ExcelService {
|
||||
String[] fieldNames = headers.keySet().toArray(new String[0]);
|
||||
progressTracker.updateProgress(taskId, 37, 100, "헤더 정보 완료...");
|
||||
|
||||
// SXSSFWorkbook으로 스트리밍 처리
|
||||
try {
|
||||
workbook = new XSSFWorkbook();
|
||||
progressTracker.updateProgress(taskId, 38, 100, "workbook 생성 완료...");
|
||||
@@ -485,14 +756,40 @@ public class ExcelService {
|
||||
response.setHeader("Cache-Control", "max-age=0");
|
||||
}
|
||||
|
||||
/**
|
||||
* CSV 응답 헤더 설정
|
||||
*/
|
||||
private void setupCsvResponseHeaders(HttpServletResponse response, String fileName) throws IOException {
|
||||
try {
|
||||
String cleanFileName = fileName + ".csv";
|
||||
String encodedFileName = URLEncoder.encode(fileName + ".csv", StandardCharsets.UTF_8)
|
||||
.replaceAll("\\+", "%20");
|
||||
|
||||
response.setContentType("text/csv; charset=UTF-8");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setHeader("Content-Disposition",
|
||||
String.format("attachment; filename=\"%s\"; filename*=UTF-8''%s",
|
||||
cleanFileName, encodedFileName));
|
||||
response.setHeader("Cache-Control", "max-age=0");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires", "0");
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to setup CSV response headers for fileName: {}", fileName, e);
|
||||
throw new IOException("응답 헤더 설정 실패: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ZIP 파일 응답 헤더 설정
|
||||
*/
|
||||
private void setupZipResponseHeaders(HttpServletResponse response, String fileName) throws IOException {
|
||||
String cleanFileName = fileName.endsWith(".zip") ? fileName : fileName + ".zip";
|
||||
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
|
||||
response.setHeader("Content-Disposition",
|
||||
String.format("attachment; filename=\"%s\"; filename*=UTF-8''%s",
|
||||
cleanFileName, encodedFileName));
|
||||
response.setHeader("Cache-Control", "max-age=0");
|
||||
}
|
||||
|
||||
@@ -999,7 +1296,7 @@ public class ExcelService {
|
||||
int totalRecords, int maxRowsPerFile) throws IOException {
|
||||
|
||||
StringBuilder indexContent = new StringBuilder();
|
||||
indexContent.append("=== 엑셀 파일 다운로드 정보 ===\n\n");
|
||||
indexContent.append("=== CSV 파일 다운로드 정보 ===\n\n");
|
||||
indexContent.append("파일명: ").append(baseFileName).append("\n");
|
||||
indexContent.append("총 데이터 건수: ").append(String.format("%,d", totalRecords)).append("건\n");
|
||||
indexContent.append("분할된 파일 수: ").append(totalFiles).append("개\n");
|
||||
@@ -1017,13 +1314,26 @@ public class ExcelService {
|
||||
}
|
||||
|
||||
indexContent.append("=== 사용 안내 ===\n");
|
||||
indexContent.append("• 각 엑셀 파일을 개별적으로 열어서 사용하세요.\n");
|
||||
indexContent.append("• 각 CSV 파일을 개별적으로 열어서 사용하세요.\n");
|
||||
indexContent.append("• 파일명의 숫자는 데이터 순서를 나타냅니다.\n");
|
||||
indexContent.append("• 모든 파일의 헤더 구조는 동일합니다.\n");
|
||||
indexContent.append("• Excel에서 열 때는 '데이터 > 텍스트 나누기'로 열어주세요.\n");
|
||||
|
||||
ZipEntry indexEntry = new ZipEntry("📋 파일정보_및_사용안내.txt");
|
||||
zipOut.putNextEntry(indexEntry);
|
||||
zipOut.write(indexContent.toString().getBytes(StandardCharsets.UTF_8));
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
|
||||
private static class CsvFileData {
|
||||
final String fileName;
|
||||
final byte[] data;
|
||||
final int fileIndex;
|
||||
|
||||
CsvFileData(String fileName, byte[] data, int fileIndex) {
|
||||
this.fileName = fileName;
|
||||
this.data = data;
|
||||
this.fileIndex = fileIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ public class S3Service {
|
||||
@Value("${amazon.aws.region}")
|
||||
private String region;
|
||||
|
||||
@Value("${amazon.s3.cloud-front}")
|
||||
private String cloudFrontUrl;
|
||||
|
||||
@Value("${spring.profiles.active}")
|
||||
private String activeProfile;
|
||||
|
||||
public Optional<GetObjectAttributesResponse> getObjectMetadata(String key) {
|
||||
try {
|
||||
GetObjectAttributesRequest request = GetObjectAttributesRequest.builder()
|
||||
@@ -47,10 +53,10 @@ public class S3Service {
|
||||
}
|
||||
|
||||
public String uploadFile(File file, String directoryName, String contentType) throws IOException, S3Exception {
|
||||
String fileName = UUID.randomUUID().toString() + "-" + file.getName();
|
||||
String fileName = UUID.randomUUID() + "-" + file.getName();
|
||||
|
||||
// S3 객체 키 (경로 + 파일명)
|
||||
String objectKey = directoryName + "/" + fileName;
|
||||
String objectKey = String.format("%s/%s/%s", activeProfile, directoryName, fileName);
|
||||
|
||||
try {
|
||||
|
||||
@@ -65,11 +71,8 @@ public class S3Service {
|
||||
putObjectRequest,
|
||||
RequestBody.fromBytes(Files.readAllBytes(file.toPath()))
|
||||
);
|
||||
//https://metaverse-myhomeugc-test.s3.us-west-2.amazonaws.com/0002896883264fc9af5be62134939ce4/552ec8a4302348edb3fbf1496811d75f.ugcinfo
|
||||
return "https://" + bucketName + ".s3." +
|
||||
s3Client.serviceClientConfiguration().region().toString() +
|
||||
".amazonaws.com/" + objectKey;
|
||||
// return "s3://" + bucketName + objectKey;
|
||||
|
||||
return cloudFrontUrl + objectKey;
|
||||
}catch (S3Exception | IOException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -34,9 +34,13 @@ public class BattleEventAttrib extends DynamoDBAttribBase {
|
||||
@JsonProperty("start_min")
|
||||
private Integer startMin;
|
||||
|
||||
@JsonProperty("open_duration_minutes")
|
||||
private Integer openDurationMinutes;
|
||||
|
||||
@JsonProperty("end_date")
|
||||
private String endDate;
|
||||
|
||||
//추후 사용안함
|
||||
@JsonProperty("instance_id")
|
||||
private Integer instanceId;
|
||||
|
||||
@@ -46,16 +50,22 @@ public class BattleEventAttrib extends DynamoDBAttribBase {
|
||||
@JsonProperty("day_of_week_type")
|
||||
private HashSet<EDayOfWeekType> dayOfWeekType;
|
||||
|
||||
//추후 사용안함
|
||||
@JsonProperty("ffa_config_data_id")
|
||||
private Integer configDataId;
|
||||
|
||||
//추후 사용안함
|
||||
@JsonProperty("ffa_reward_group_id")
|
||||
private Integer rewardGroupId;
|
||||
|
||||
@JsonProperty("ffa_hot_time")
|
||||
private Integer hotTime;
|
||||
|
||||
//추후 사용안함
|
||||
@JsonProperty("round_count")
|
||||
private Integer roundCount;
|
||||
|
||||
@JsonProperty("game_mode_id")
|
||||
private Integer gameModeId;
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import software.amazon.awssdk.enhanced.dynamodb.Key;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
@@ -103,6 +104,8 @@ public class BattleEventRepositoryImpl extends BaseDynamoDBRepository<BattleEven
|
||||
attrib.setHotTime(battleEventRequest.getHotTime());
|
||||
attrib.setRoundCount(battleEventRequest.getRoundCount());
|
||||
attrib.setActive(true);
|
||||
attrib.setGameModeId(battleEventRequest.getGameModeId());
|
||||
attrib.setOpenDurationMinutes((int) Duration.ofSeconds(battleEventRequest.getEventOperationTime()).toMinutes());
|
||||
|
||||
BattleEventDoc doc = new BattleEventDoc();
|
||||
doc.setPK(DynamoDBConstants.PK_KEY_BATTLE_EVENT);
|
||||
@@ -153,6 +156,8 @@ public class BattleEventRepositoryImpl extends BaseDynamoDBRepository<BattleEven
|
||||
attrib.setHotTime(battleEventRequest.getHotTime());
|
||||
attrib.setRoundCount(battleEventRequest.getRoundCount());
|
||||
attrib.setActive(true);
|
||||
attrib.setGameModeId(battleEventRequest.getGameModeId());
|
||||
attrib.setOpenDurationMinutes((int) Duration.ofSeconds(battleEventRequest.getEventOperationTime()).toMinutes());
|
||||
|
||||
afterDoc.setAttribValue(objectMapper.writeValueAsString(attrib));
|
||||
afterDoc.setUpdatedDateTime(CommonUtils.convertUTCDate(nowDate));
|
||||
|
||||
@@ -109,7 +109,7 @@ public class DynamicScheduler {
|
||||
if(!baseDate.isBefore(end_dt.toLocalDate())){
|
||||
change_status = BattleEvent.BATTLE_STATUS.END;
|
||||
}else{
|
||||
change_status = BattleEvent.BATTLE_STATUS.WAIT;
|
||||
change_status = status.equals(BattleEvent.BATTLE_STATUS.STOP) ? BattleEvent.BATTLE_STATUS.STOP : BattleEvent.BATTLE_STATUS.WAIT;
|
||||
}
|
||||
log.info("battle event_id: {}, start_dt: {}, end_dt: {}, todayStart: {}, todayEnd: {} STATUS CHANGE {}", event.getId(), start_dt, end_dt, todayStart, todayEnd, change_status);
|
||||
map.put("status", change_status);
|
||||
|
||||
@@ -2,16 +2,19 @@ package com.caliverse.admin.scheduler;
|
||||
|
||||
import com.caliverse.admin.domain.entity.InGame;
|
||||
import com.caliverse.admin.domain.entity.Mail;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.scheduling.config.ScheduledTask;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ScheduleService {
|
||||
|
||||
private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
|
||||
@@ -19,6 +22,9 @@ public class ScheduleService {
|
||||
private final Map<Long, Mail> mailTask = new ConcurrentHashMap<>();
|
||||
private final Map<Long, InGame> noticeTask = new ConcurrentHashMap<>();
|
||||
|
||||
private static final int MAX_MAIL_TASKS = 500;
|
||||
private static final int MAX_NOTICE_TASKS = 500;
|
||||
|
||||
public boolean isTaskExist(String key){
|
||||
return scheduledTasks.containsKey(key);
|
||||
}
|
||||
@@ -35,22 +41,60 @@ public class ScheduleService {
|
||||
}
|
||||
}
|
||||
|
||||
// public boolean isTaskExist(String type, Long id) {
|
||||
// switch (type) {
|
||||
// case "mail" -> {
|
||||
// return mailTask.containsKey(id);
|
||||
// }
|
||||
// case "notice" -> {
|
||||
// return noticeTask.containsKey(id);
|
||||
// }
|
||||
// default -> {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void createTask(Mail mail) {
|
||||
// mailTask.put(mail.getId(), mail);
|
||||
// }
|
||||
public void cleanupCompletedTasks() {
|
||||
try {
|
||||
Iterator<Map.Entry<String, ScheduledFuture<?>>> iterator = scheduledTasks.entrySet().iterator();
|
||||
int removedCount = 0;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, ScheduledFuture<?>> entry = iterator.next();
|
||||
ScheduledFuture<?> future = entry.getValue();
|
||||
|
||||
if (future.isDone() || future.isCancelled()) {
|
||||
iterator.remove();
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedCount > 0) {
|
||||
log.info("Cleaned up {} completed scheduled tasks. Current task count: {}",
|
||||
removedCount, scheduledTasks.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error during cleanup of completed tasks", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupMailTasks() {
|
||||
if (mailTask.size() > MAX_MAIL_TASKS) {
|
||||
int toRemove = mailTask.size() - MAX_MAIL_TASKS;
|
||||
Iterator<Long> iterator = mailTask.keySet().iterator();
|
||||
int removed = 0;
|
||||
|
||||
while (iterator.hasNext() && removed < toRemove) {
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
removed++;
|
||||
}
|
||||
|
||||
// log.info("Cleaned up {} mail tasks due to size limit", removed);
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanupNoticeTasks() {
|
||||
if (noticeTask.size() > MAX_NOTICE_TASKS) {
|
||||
int toRemove = noticeTask.size() - MAX_NOTICE_TASKS;
|
||||
Iterator<Long> iterator = noticeTask.keySet().iterator();
|
||||
int removed = 0;
|
||||
|
||||
while (iterator.hasNext() && removed < toRemove) {
|
||||
iterator.next();
|
||||
iterator.remove();
|
||||
removed++;
|
||||
}
|
||||
|
||||
// log.info("Cleaned up {} notice tasks due to size limit", removed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ public enum SchedulerType {
|
||||
BATTLE_EVENT,
|
||||
WEB3,
|
||||
LAND_OWNER_CHANGES,
|
||||
DATA_INITIALIZE
|
||||
DATA_INITIALIZE,
|
||||
CLEAN_UP
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ public class ScheduleRunnerPolling {
|
||||
schedulerManager.executeScheduler(SchedulerType.DATA_INITIALIZE);
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = 300000)
|
||||
public void cleanupJob(){
|
||||
schedulerManager.executeScheduler(SchedulerType.CLEAN_UP);
|
||||
}
|
||||
|
||||
/*
|
||||
매일 UTC 기준 00시 50분 00초에 실행, (한국 시간 9시 50분) 30분에 돌릴경우 데이터가 다 넘어 오지 않는 경우 있어서 수정처리
|
||||
이게 가장 먼저 실행 되어야 한다.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.caliverse.admin.scheduler.polling.service;
|
||||
|
||||
import com.caliverse.admin.scheduler.CommonScheduler;
|
||||
import com.caliverse.admin.scheduler.ScheduleService;
|
||||
import com.caliverse.admin.scheduler.entity.SchedulerType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class cleanupScheduler extends CommonScheduler {
|
||||
|
||||
private final ScheduleService scheduleService;
|
||||
|
||||
public cleanupScheduler(ScheduleService scheduleService) {
|
||||
this.scheduleService = scheduleService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeInternal() {
|
||||
scheduleService.cleanupCompletedTasks();
|
||||
scheduleService.cleanupMailTasks();
|
||||
scheduleService.cleanupNoticeTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerType getSchedulerType() {
|
||||
return SchedulerType.CLEAN_UP;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user