아이템 백과사전 export 처리
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
package com.caliverse.admin.domain.api;
|
||||
|
||||
import com.caliverse.admin.domain.request.LogGenericRequest;
|
||||
import com.caliverse.admin.domain.response.DictionaryResponse;
|
||||
import com.caliverse.admin.domain.service.MetaDataService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -26,4 +28,10 @@ public class DictionaryController {
|
||||
@RequestParam Map<String, String> requestParams){
|
||||
return ResponseEntity.ok().body( metaDataService.getItemDictList(requestParams));
|
||||
}
|
||||
|
||||
@GetMapping("/item/excel-export")
|
||||
public void itemExcelExport(HttpServletResponse response,
|
||||
@RequestParam Map<String, String> requestParams){
|
||||
metaDataService.itemExcelExport(response, requestParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -701,9 +701,34 @@ public class ExcelService {
|
||||
}
|
||||
|
||||
private String translateFieldName(String fieldName) {
|
||||
if (fieldName == null || fieldName.trim().isEmpty()) {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
Map<String, String> translations = getFieldTranslations();
|
||||
|
||||
return translations.getOrDefault(fieldName.toLowerCase(), fieldName);
|
||||
// 띄어쓰기로 구분된 경우 각각 번역 후 합치기
|
||||
if (fieldName.contains(" ")) {
|
||||
String[] parts = fieldName.split("\\s+"); // 하나 이상의 공백으로 분리
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i > 0) {
|
||||
result.append(" ");
|
||||
}
|
||||
|
||||
String part = parts[i].trim();
|
||||
if (!part.isEmpty()) {
|
||||
String translated = translations.getOrDefault(part.toLowerCase(), part);
|
||||
result.append(translated);
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// 띄어쓰기가 없는 경우 기존 로직
|
||||
return translations.getOrDefault(fieldName, fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -741,6 +766,42 @@ public class ExcelService {
|
||||
translations.put("amount", "금액");
|
||||
translations.put("quantity", "수량");
|
||||
translations.put("count", "개수");
|
||||
translations.put("itemId", "아이템 ID");
|
||||
translations.put("itemName", "아이템명");
|
||||
translations.put("sellType", "판매시 재화");
|
||||
translations.put("sellPrice", "판매시 재화량");
|
||||
translations.put("buyType", "구매시 재화");
|
||||
translations.put("buyPrice", "구매시 재화량");
|
||||
translations.put("buyDiscountRate", "구매시 할인율");
|
||||
translations.put("brand", "개수");
|
||||
translations.put("typeLarge", "개수");
|
||||
translations.put("typeSmall", "개수");
|
||||
translations.put("attib", "속성");
|
||||
translations.put("attribRandomGroup", "랜덤 그룹");
|
||||
translations.put("buff", "버프");
|
||||
translations.put("defaultAttrib", "기본 속성");
|
||||
translations.put("itemSet", "아이템 세트");
|
||||
translations.put("rarity", "희귀도");
|
||||
translations.put("etc", "기타");
|
||||
translations.put("dressSlotType", "착용 부위");
|
||||
translations.put("gachaGroupId", "랜덤박스 그룹 ID");
|
||||
translations.put("linkedLand", "연결된 랜드");
|
||||
translations.put("productLink", "제품 URL");
|
||||
translations.put("propSmallType", "제작 아이템 그룹");
|
||||
translations.put("ugqAction", "UGQ 사용 가능 여부");
|
||||
translations.put("expire", "만료");
|
||||
translations.put("expireEndDt", "만료 종료 시간");
|
||||
translations.put("expireStartDt", "만료 시작 시간");
|
||||
translations.put("expireTimeSec", "만료 시간 연장 여부");
|
||||
translations.put("expireType", "아이템 만료 타입");
|
||||
translations.put("trade", "거래");
|
||||
translations.put("cartBuy", "상점에서 구매 가능 여부");
|
||||
translations.put("systemTradable", "상점에서 판매 가능 여부");
|
||||
translations.put("throwable", "버리기 가능 여부");
|
||||
translations.put("userTradable", "유저 간 거래 가능 여부");
|
||||
translations.put("country", "Count");
|
||||
translations.put("maxCount", "최대 보유 가능 수량");
|
||||
translations.put("stackMaxCount", "최대 스택 가능 수량");
|
||||
|
||||
return translations;
|
||||
}
|
||||
@@ -954,6 +1015,9 @@ public class ExcelService {
|
||||
.stream()
|
||||
.map(list -> list.get(0))
|
||||
.collect(Collectors.toList());
|
||||
}else {
|
||||
// 일반 객체의 경우 다양성을 위한 스마트 샘플링
|
||||
sampleData = createSmartSampleForGeneralObjects(sampleData);
|
||||
}
|
||||
|
||||
return generateHeadersFromSampleObjects(sampleData);
|
||||
@@ -964,38 +1028,186 @@ public class ExcelService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 최적화된 객체 플래튼화 (캐시 활용)
|
||||
* 일반 객체를 위한 스마트 샘플링
|
||||
* - 객체의 다양성을 최대화하여 모든 가능한 필드를 찾아내기 위함
|
||||
*/
|
||||
private Map<String, Object> flattenObjectToMapOptimized(Object item) {
|
||||
Map<String, Object> flatMap = new LinkedHashMap<>();
|
||||
private List<?> createSmartSampleForGeneralObjects(List<?> data) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (item == null) return flatMap;
|
||||
// 데이터가 50개 미만이면 모두 사용
|
||||
if (data.size() <= 50) {
|
||||
return data.stream().filter(Objects::nonNull).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Class<?> clazz = item.getClass();
|
||||
List<Field> allFields = getCachedFields(clazz);
|
||||
Set<Object> uniqueSamples = new LinkedHashSet<>();
|
||||
int step = Math.max(1, data.size() / 20); // 최대 20개 샘플 추출
|
||||
|
||||
for (Field field : allFields) {
|
||||
if (shouldSkipField(field)) continue;
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
Object fieldValue = field.get(item);
|
||||
String fieldName = field.getName();
|
||||
|
||||
if ("message".equals(fieldName)) continue;
|
||||
|
||||
if (fieldValue instanceof Map) {
|
||||
Map<String, Object> nestedMap = (Map<String, Object>) fieldValue;
|
||||
flattenMapOptimized(nestedMap, fieldName, flatMap);
|
||||
} else if (fieldValue != null) {
|
||||
flatMap.put(fieldName, fieldValue);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 접근할 수 없는 필드는 무시
|
||||
// 균등하게 분포된 샘플 선택
|
||||
for (int i = 0; i < data.size() && uniqueSamples.size() < 20; i += step) {
|
||||
Object item = data.get(i);
|
||||
if (item != null) {
|
||||
uniqueSamples.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return flatMap;
|
||||
// 첫 번째와 마지막 요소는 반드시 포함 (경계값 처리)
|
||||
if (!data.isEmpty() && data.get(0) != null) {
|
||||
uniqueSamples.add(data.get(0));
|
||||
}
|
||||
if (data.size() > 1 && data.get(data.size() - 1) != null) {
|
||||
uniqueSamples.add(data.get(data.size() - 1));
|
||||
}
|
||||
|
||||
// 중간 지점들도 추가 (복잡한 객체 구조 발견을 위해)
|
||||
int quarterPoint = data.size() / 4;
|
||||
int halfPoint = data.size() / 2;
|
||||
int threeQuarterPoint = (data.size() * 3) / 4;
|
||||
|
||||
if (quarterPoint < data.size() && data.get(quarterPoint) != null) {
|
||||
uniqueSamples.add(data.get(quarterPoint));
|
||||
}
|
||||
if (halfPoint < data.size() && data.get(halfPoint) != null) {
|
||||
uniqueSamples.add(data.get(halfPoint));
|
||||
}
|
||||
if (threeQuarterPoint < data.size() && data.get(threeQuarterPoint) != null) {
|
||||
uniqueSamples.add(data.get(threeQuarterPoint));
|
||||
}
|
||||
|
||||
return new ArrayList<>(uniqueSamples);
|
||||
}
|
||||
|
||||
/**
|
||||
* 최적화된 객체 플래튼화 (캐시 활용)
|
||||
*/
|
||||
private Map<String, Object> flattenObjectToMapOptimized(Object item) {
|
||||
if (item == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
Class<?> clazz = item.getClass();
|
||||
List<Field> fields = getCachedFields(clazz);
|
||||
|
||||
for (Field field : fields) {
|
||||
if (shouldSkipField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(item);
|
||||
String fieldName = field.getName();
|
||||
|
||||
if (value == null) {
|
||||
result.put(fieldName, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map 타입 처리
|
||||
if (value instanceof Map) {
|
||||
Map<String, Object> mapValue = (Map<String, Object>) value;
|
||||
flattenMapOptimized(mapValue, fieldName, result);
|
||||
}
|
||||
// Collection 타입 처리
|
||||
else if (value instanceof Collection) {
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
handleCollectionFieldOptimized(collection, fieldName, result);
|
||||
}
|
||||
// 배열 타입 처리
|
||||
else if (value.getClass().isArray()) {
|
||||
handleArrayFieldOptimized(value, fieldName, result);
|
||||
}
|
||||
// 중첩 객체 처리
|
||||
else if (isComplexObject(value)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMapOptimized(value);
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
// 기본 타입 및 단순 객체
|
||||
else {
|
||||
result.put(fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("Error accessing field during optimized object flattening", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void handleCollectionFieldOptimized(Collection<?> collection, String fieldName, Map<String, Object> result) {
|
||||
if (collection.isEmpty()) {
|
||||
result.put(fieldName, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
int maxItems = 50; // 성능을 위해 50개로 제한
|
||||
|
||||
for (Object item : collection) {
|
||||
if (index >= maxItems) {
|
||||
result.put(fieldName + "[...]", String.format("더 많은 항목이 있음 (총 %d개 중 %d개만 표시)",
|
||||
collection.size(), maxItems));
|
||||
break;
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
result.put(fieldName + "[" + index + "]", null);
|
||||
} else if (item instanceof Map) {
|
||||
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||
flattenMapOptimized(mapItem, fieldName + "[" + index + "]", result);
|
||||
} else if (isComplexObject(item)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMapOptimized(item);
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "[" + index + "]." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
result.put(fieldName + "[" + index + "]", item);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 최적화된 배열 필드 처리
|
||||
*/
|
||||
private void handleArrayFieldOptimized(Object arrayValue, String fieldName, Map<String, Object> result) {
|
||||
int length = java.lang.reflect.Array.getLength(arrayValue);
|
||||
|
||||
if (length == 0) {
|
||||
result.put(fieldName, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxItems = 50; // 성능을 위해 50개로 제한
|
||||
|
||||
for (int i = 0; i < Math.min(length, maxItems); i++) {
|
||||
Object item = java.lang.reflect.Array.get(arrayValue, i);
|
||||
|
||||
if (item == null) {
|
||||
result.put(fieldName + "[" + i + "]", null);
|
||||
} else if (item instanceof Map) {
|
||||
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||
flattenMapOptimized(mapItem, fieldName + "[" + i + "]", result);
|
||||
} else if (isComplexObject(item)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMapOptimized(item);
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "[" + i + "]." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
result.put(fieldName + "[" + i + "]", item);
|
||||
}
|
||||
}
|
||||
|
||||
if (length > maxItems) {
|
||||
result.put(fieldName + "[...]", String.format("더 많은 항목이 있음 (총 %d개 중 %d개만 표시)",
|
||||
length, maxItems));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1168,34 +1380,79 @@ public class ExcelService {
|
||||
|
||||
/**
|
||||
* Object 리스트의 모든 객체를 검사하여 헤더 생성 (Map 필드는 자동으로 펼치기)
|
||||
* @param data 데이터 리스트
|
||||
* @return 모든 필드를 포함한 헤더 Map
|
||||
*/
|
||||
private Map<String, String> generateHeadersFromSampleObjects(List<?> data) {
|
||||
LinkedHashMap<String, String> headers = new LinkedHashMap<>();
|
||||
Set<String> allFieldKeys = new LinkedHashSet<>();
|
||||
|
||||
if (data == null || data.isEmpty()) {
|
||||
return headers;
|
||||
private Map<String, String> generateHeadersFromSampleObjects(List<?> sampleData) {
|
||||
if (sampleData == null || sampleData.isEmpty()) {
|
||||
log.warn("Sample data is null or empty for header generation");
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
for (Object item : data) {
|
||||
if (item != null) {
|
||||
Map<String, Object> flatMap = flattenObjectToMapOptimized(item);
|
||||
allFieldKeys.addAll(flatMap.keySet());
|
||||
Set<String> allKeys = new LinkedHashSet<>();
|
||||
|
||||
try {
|
||||
// 각 샘플 객체를 펼쳐서 모든 가능한 키 수집
|
||||
for (Object item : sampleData) {
|
||||
if (item != null) {
|
||||
Map<String, Object> flattened = flattenObjectToMapOptimized(item);
|
||||
|
||||
// 제외할 헤더들 필터링
|
||||
Set<String> filteredKeys = flattened.keySet().stream()
|
||||
.filter(key -> !EXCLUDED_HEADERS.contains(key))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
allKeys.addAll(filteredKeys);
|
||||
}
|
||||
}
|
||||
|
||||
// 키를 읽기 쉬운 헤더명으로 변환
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
|
||||
// 키를 논리적 순서로 정렬 (기본 필드 -> 중첩 객체 필드 -> 배열/컬렉션)
|
||||
List<String> sortedKeys = allKeys.stream()
|
||||
.sorted(this::compareHeaderKeys)
|
||||
.toList();
|
||||
|
||||
for (String key : sortedKeys) {
|
||||
String headerName = convertFlatKeyToHeader(key);
|
||||
headers.put(key, headerName);
|
||||
}
|
||||
|
||||
log.info("Generated {} headers from {} sample objects", headers.size(), sampleData.size());
|
||||
return headers;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error generating headers from sample objects", e);
|
||||
return generateFallbackHeaders(sampleData);
|
||||
}
|
||||
|
||||
for (String key : allFieldKeys) {
|
||||
if (EXCLUDED_HEADERS.contains(key)) continue;
|
||||
|
||||
String headerName = convertFlatKeyToHeader(key);
|
||||
headers.put(key, headerName);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
private int compareHeaderKeys(String key1, String key2) {
|
||||
// 기본 필드 우선 (점이나 대괄호가 없는 것)
|
||||
boolean isSimple1 = !key1.contains(".") && !key1.contains("[");
|
||||
boolean isSimple2 = !key2.contains(".") && !key2.contains("[");
|
||||
|
||||
if (isSimple1 && !isSimple2) return -1;
|
||||
if (!isSimple1 && isSimple2) return 1;
|
||||
|
||||
// 중첩 레벨 비교 (점의 개수)
|
||||
int dotCount1 = (int) key1.chars().filter(ch -> ch == '.').count();
|
||||
int dotCount2 = (int) key2.chars().filter(ch -> ch == '.').count();
|
||||
|
||||
if (dotCount1 != dotCount2) {
|
||||
return Integer.compare(dotCount1, dotCount2);
|
||||
}
|
||||
|
||||
// 배열 인덱스는 마지막에 (대괄호 포함)
|
||||
boolean hasArray1 = key1.contains("[");
|
||||
boolean hasArray2 = key2.contains("[");
|
||||
|
||||
if (!hasArray1 && hasArray2) return -1;
|
||||
if (hasArray1 && !hasArray2) return 1;
|
||||
|
||||
// 마지막으로 알파벳 순서
|
||||
return key1.compareTo(key2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 펼쳐진 키를 읽기 쉬운 헤더명으로 변환
|
||||
@@ -1208,58 +1465,185 @@ public class ExcelService {
|
||||
String[] parts = key.split("\\.");
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i > 0) result.append(" ");
|
||||
try {
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
if (i > 0) result.append(" ");
|
||||
|
||||
String part = parts[i].replaceAll("\\[\\d+\\]", "");
|
||||
String translatedPart = translateFieldName(part);
|
||||
result.append(translatedPart);
|
||||
String part = parts[i].replaceAll("\\[\\d+\\]", "");
|
||||
String translatedPart = translateFieldName(part);
|
||||
result.append(translatedPart);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}catch (Exception e) {
|
||||
log.error("Error converting key {} to header name", key, e);
|
||||
return key;
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 객체를 펼친 Map으로 변환
|
||||
*/
|
||||
private Map<String, Object> flattenObjectToMap(Object item) {
|
||||
Map<String, Object> flatMap = new LinkedHashMap<>();
|
||||
if (item == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
if (item == null) return flatMap;
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
Class<?> clazz = item.getClass();
|
||||
List<Field> allFields = getAllFields(clazz);
|
||||
try {
|
||||
Class<?> clazz = item.getClass();
|
||||
List<Field> fields = getCachedFields(clazz);
|
||||
|
||||
for (Field field : allFields) {
|
||||
if (java.lang.reflect.Modifier.isStatic(field.getModifiers()) ||
|
||||
java.lang.reflect.Modifier.isFinal(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
Object fieldValue = field.get(item);
|
||||
String fieldName = field.getName();
|
||||
|
||||
// message 필드는 제외 (이미 header, body로 분리됨)
|
||||
if ("message".equals(fieldName)) {
|
||||
for (Field field : fields) {
|
||||
if (shouldSkipField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof Map) {
|
||||
// Map 필드를 펼치기
|
||||
Map<String, Object> nestedMap = (Map<String, Object>) fieldValue;
|
||||
flattenMap(nestedMap, fieldName, flatMap);
|
||||
} else if (fieldValue != null) {
|
||||
// 일반 필드는 그대로 추가
|
||||
flatMap.put(fieldName, fieldValue);
|
||||
field.setAccessible(true);
|
||||
Object value = field.get(item);
|
||||
String fieldName = field.getName();
|
||||
|
||||
if (value == null) {
|
||||
result.put(fieldName, null);
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 접근할 수 없는 필드는 무시
|
||||
|
||||
// Map 타입 처리
|
||||
if (value instanceof Map) {
|
||||
Map<String, Object> mapValue = (Map<String, Object>) value;
|
||||
flattenMapOptimized(mapValue, fieldName, result);
|
||||
}
|
||||
// Collection 타입 처리
|
||||
else if (value instanceof Collection) {
|
||||
Collection<?> collection = (Collection<?>) value;
|
||||
handleCollectionField(collection, fieldName, result);
|
||||
}
|
||||
// 배열 타입 처리
|
||||
else if (value.getClass().isArray()) {
|
||||
handleArrayField(value, fieldName, result);
|
||||
}
|
||||
// 중첩 객체 처리 (기본 타입이 아닌 경우)
|
||||
else if (isComplexObject(value)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMap(value);
|
||||
// 중첩 객체의 필드들을 상위 필드명과 함께 펼치기
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
// 기본 타입 및 단순 객체
|
||||
else {
|
||||
result.put(fieldName, value);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("Error accessing field during object flattening", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void handleCollectionField(Collection<?> collection, String fieldName, Map<String, Object> result) {
|
||||
if (collection.isEmpty()) {
|
||||
result.put(fieldName, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
for (Object item : collection) {
|
||||
if (item == null) {
|
||||
result.put(fieldName + "[" + index + "]", null);
|
||||
} else if (item instanceof Map) {
|
||||
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||
flattenMapOptimized(mapItem, fieldName + "[" + index + "]", result);
|
||||
} else if (isComplexObject(item)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMap(item);
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "[" + index + "]." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
result.put(fieldName + "[" + index + "]", item);
|
||||
}
|
||||
index++;
|
||||
|
||||
// 너무 많은 아이템이 있으면 제한 (성능상 이유)
|
||||
if (index >= 100) {
|
||||
result.put(fieldName + "[...]", "더 많은 항목이 있음 (생략됨)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArrayField(Object arrayValue, String fieldName, Map<String, Object> result) {
|
||||
int length = java.lang.reflect.Array.getLength(arrayValue);
|
||||
|
||||
if (length == 0) {
|
||||
result.put(fieldName, "[]");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Math.min(length, 100); i++) { // 최대 100개까지만 처리
|
||||
Object item = java.lang.reflect.Array.get(arrayValue, i);
|
||||
|
||||
if (item == null) {
|
||||
result.put(fieldName + "[" + i + "]", null);
|
||||
} else if (item instanceof Map) {
|
||||
Map<String, Object> mapItem = (Map<String, Object>) item;
|
||||
flattenMapOptimized(mapItem, fieldName + "[" + i + "]", result);
|
||||
} else if (isComplexObject(item)) {
|
||||
Map<String, Object> nestedMap = flattenObjectToMap(item);
|
||||
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
|
||||
result.put(fieldName + "[" + i + "]." + entry.getKey(), entry.getValue());
|
||||
}
|
||||
} else {
|
||||
result.put(fieldName + "[" + i + "]", item);
|
||||
}
|
||||
}
|
||||
|
||||
return flatMap;
|
||||
if (length > 100) {
|
||||
result.put(fieldName + "[...]", "더 많은 항목이 있음 (생략됨)");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isComplexObject(Object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?> clazz = value.getClass();
|
||||
|
||||
// 기본 타입들
|
||||
if (clazz.isPrimitive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 래퍼 타입들과 자주 사용되는 타입들
|
||||
if (clazz == String.class ||
|
||||
clazz == Boolean.class ||
|
||||
clazz == Integer.class ||
|
||||
clazz == Long.class ||
|
||||
clazz == Double.class ||
|
||||
clazz == Float.class ||
|
||||
clazz == Short.class ||
|
||||
clazz == Byte.class ||
|
||||
clazz == Character.class ||
|
||||
java.util.Date.class.isAssignableFrom(clazz) ||
|
||||
java.time.temporal.Temporal.class.isAssignableFrom(clazz)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enum 타입
|
||||
if (clazz.isEnum()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// java.* 패키지의 클래스들은 대부분 기본 타입으로 취급
|
||||
if (clazz.getName().startsWith("java.")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,22 +2,29 @@ package com.caliverse.admin.domain.service;
|
||||
|
||||
import com.caliverse.admin.domain.datacomponent.MetaDataHandler;
|
||||
import com.caliverse.admin.domain.entity.*;
|
||||
import com.caliverse.admin.domain.entity.excel.ExcelBusinessLog;
|
||||
import com.caliverse.admin.domain.entity.log.GenericLog;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBattleConfigData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaBrandData;
|
||||
import com.caliverse.admin.domain.entity.metadata.MetaItemData;
|
||||
import com.caliverse.admin.domain.request.LogGenericRequest;
|
||||
import com.caliverse.admin.domain.response.BattleEventResponse;
|
||||
import com.caliverse.admin.domain.response.DictionaryResponse;
|
||||
import com.caliverse.admin.global.common.annotation.RequestLog;
|
||||
import com.caliverse.admin.global.common.code.CommonCode;
|
||||
import com.caliverse.admin.global.common.code.ErrorCode;
|
||||
import com.caliverse.admin.global.common.exception.RestApiException;
|
||||
import com.caliverse.admin.global.component.tracker.ExcelProgressTracker;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.mongodb.UncategorizedMongoDbException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -27,6 +34,8 @@ public class MetaDataService {
|
||||
|
||||
@Autowired
|
||||
private MetaDataHandler metaDataHandler;
|
||||
private final ExcelService excelService;
|
||||
private final ExcelProgressTracker progressTracker;
|
||||
|
||||
public DictionaryResponse getBrandList(){
|
||||
|
||||
@@ -85,7 +94,6 @@ public class MetaDataService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 페이징 처리
|
||||
int currentPageNo = 1;
|
||||
int currentPageSize = 10; // 기본값
|
||||
@@ -145,6 +153,46 @@ public class MetaDataService {
|
||||
.build();
|
||||
}
|
||||
|
||||
public void itemExcelExport(HttpServletResponse response, @RequestParam Map<String, String> requestParam){
|
||||
String searchType = requestParam.get("search_type");
|
||||
String searchData = requestParam.get("search_data").trim();
|
||||
String largeType = requestParam.get("large_type");
|
||||
String smallType = requestParam.get("small_type");
|
||||
String brand = requestParam.get("brand");
|
||||
String gender = requestParam.get("gender");
|
||||
String taskId = requestParam.get("task_id");
|
||||
String lang = requestParam.get("lang");
|
||||
|
||||
if(taskId == null || taskId.isEmpty() || taskId.equals("undefined")){
|
||||
log.error("itemExcelExport Excel Export taskId is null or empty");
|
||||
throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_EXCEL_DOWN.toString());
|
||||
}
|
||||
|
||||
LANGUAGETYPE languageType = LANGUAGETYPE.valueOf(lang.toUpperCase());
|
||||
|
||||
progressTracker.updateProgress(taskId, 5, 100, "엑셀 생성 준비 중...");
|
||||
|
||||
List<MetaItemData> items = metaDataHandler.getMetaItemListData();
|
||||
|
||||
List<ItemDict> itemDictList = createItemDictList(items, languageType, searchType, searchData, largeType, smallType, brand, gender);
|
||||
progressTracker.updateProgress(taskId, 30, 100, "데이터 생성완료");
|
||||
|
||||
try{
|
||||
excelService.generateExcelToResponse(
|
||||
response,
|
||||
itemDictList,
|
||||
"Item Dictionary Data",
|
||||
"sheet1",
|
||||
taskId
|
||||
);
|
||||
|
||||
}catch (Exception e){
|
||||
log.error("Excel Export Create Error", e);
|
||||
throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_EXCEL_DOWN.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<ItemDict> createItemDictList(List<MetaItemData> items, LANGUAGETYPE languageType,
|
||||
String searchType, String searchData, String largeType,
|
||||
String smallType, String brand, String gender) {
|
||||
|
||||
Reference in New Issue
Block a user