This commit is contained in:
2025-02-12 18:32:21 +09:00
commit aff0f4eeda
767 changed files with 285356 additions and 0 deletions

View File

@@ -0,0 +1,356 @@
package com.caliverse.admin.history;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.dynamodb.domain.DocAttributeHandler;
import com.caliverse.admin.dynamodb.domain.doc.DynamoDBDocBase;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.text.SimpleDateFormat;
import java.util.*;
public class ChangeDetector {
private static final ObjectMapper objectMapper = new ObjectMapper()
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
.registerModule(new JavaTimeModule())
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 제외할 필드 목록
private static final Set<String> EXCLUDED_FIELDS = new HashSet<>(Arrays.asList(
"update_dt",
"update_by",
"created_dt",
"created_by"
));
@SuppressWarnings("unchecked")
public static List<FieldChange> detectInsertChanges(DynamoDBDocBase newDoc) {
Object attrib = DocAttributeHandler.getAttribValue(newDoc);
if (attrib == null) {
return new ArrayList<>();
}
return (attrib instanceof String)
? detectChangesFromJsonString((String) attrib)
: detectChangesFromObject(attrib);
}
private static List<FieldChange> detectChangesFromJsonString(String jsonString) {
try {
Map<String, Object> attribMap = objectMapper.readValue(jsonString,
new TypeReference<LinkedHashMap<String, Object>>() {});
return createFieldChanges(attribMap);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error processing JSON string changes", e);
}
}
private static List<FieldChange> detectChangesFromObject(Object attrib) {
Map<String, Object> attribMap = objectMapper.convertValue(attrib,
new TypeReference<LinkedHashMap<String, Object>>() {});
return createFieldChanges(attribMap);
}
// @SuppressWarnings("unchecked")
// public static List<FieldChange> detectInsertChanges(DynamoDBDocBase newDoc) {
// List<FieldChange> changes = new ArrayList<>();
// Object attrib = DocAttributeHandler.getAttribValue(newDoc);
//
// if (attrib == null) {
// return changes;
// }
//
// Map<String, Object> attribMap = objectMapper.convertValue(attrib, Map.class);
//
// for (Map.Entry<String, Object> entry : attribMap.entrySet()) {
// changes.add(new FieldChange(
// entry.getKey(),
// null,
// entry.getValue()
// ));
// }
//
// return changes;
// }
public static <T> List<FieldChange> detectInsertChanges(T newObj) {
List<FieldChange> changes = new ArrayList<>();
if (newObj == null) {
return changes;
}
Map<String, Object> newObjMap = objectMapper.convertValue(newObj, Map.class);
for (Map.Entry<String, Object> entry : newObjMap.entrySet()) {
String fieldName = entry.getKey();
Object newValue = entry.getValue();
if (isExcludedField(fieldName)) {
continue;
}
if (newValue instanceof String && isDateTimeString((String) newValue)) {
newValue = ((String) newValue).replace('T', ' ');
}
changes.add(new FieldChange(
fieldName,
null,
newValue
));
}
return changes;
}
@SuppressWarnings("unchecked")
public static List<FieldChange> detectChanges(DynamoDBDocBase beforeDoc, DynamoDBDocBase afterDoc) {
List<FieldChange> changes = new ArrayList<>();
if (beforeDoc == null || afterDoc == null) {
return changes;
}
Object beforeAttrib = DocAttributeHandler.getAttribValue(beforeDoc);
Object afterAttrib = DocAttributeHandler.getAttribValue(afterDoc);
if (beforeAttrib == null || afterAttrib == null) {
return changes;
}
try {
Map<String, Object> oldAttribMap = convertToMap(beforeAttrib);
Map<String, Object> newAttribMap = convertToMap(afterAttrib);
for (Map.Entry<String, Object> entry : newAttribMap.entrySet()) {
String fieldName = entry.getKey();
Object newValue = entry.getValue();
Object oldValue = oldAttribMap.get(fieldName);
if (!isEqual(oldValue, newValue)) {
changes.add(new FieldChange(fieldName, oldValue, newValue));
}
}
for (String fieldName : oldAttribMap.keySet()) {
if (!newAttribMap.containsKey(fieldName)) {
changes.add(new FieldChange(fieldName, oldAttribMap.get(fieldName), null));
}
}
return changes;
} catch (JsonProcessingException e) {
throw new RuntimeException("Error processing document changes", e);
}
}
// @SuppressWarnings("unchecked")
// public static List<FieldChange> detectChanges(DynamoDBDocBase beforeDoc, DynamoDBDocBase afterDoc) {
// List<FieldChange> changes = new ArrayList<>();
//
// if (beforeDoc == null || afterDoc == null) {
// return changes;
// }
//
// Object beforeAttrib = DocAttributeHandler.getAttribValue(beforeDoc);
// Object afterAttrib = DocAttributeHandler.getAttribValue(afterDoc);
//
// if (beforeAttrib == null || afterAttrib == null) {
// return changes;
// }
//
// Map<String, Object> oldAttribMap = objectMapper.convertValue(beforeAttrib, Map.class);
// Map<String, Object> newAttribMap = objectMapper.convertValue(afterAttrib, Map.class);
//
// for (Map.Entry<String, Object> entry : newAttribMap.entrySet()) {
// String fieldName = entry.getKey();
// Object newValue = entry.getValue();
// Object oldValue = oldAttribMap.get(fieldName);
//
// if (!isEqual(oldValue, newValue)) {
// changes.add(new FieldChange(fieldName, oldValue, newValue));
// }
// }
//
// for (String fieldName : oldAttribMap.keySet()) {
// if (!newAttribMap.containsKey(fieldName)) {
// changes.add(new FieldChange(fieldName, oldAttribMap.get(fieldName), null));
// }
// }
//
// return changes;
// }
public static <T> List<FieldChange> detectChanges(T beforeObj, T afterObj) {
List<FieldChange> changes = new ArrayList<>();
if (beforeObj == null || afterObj == null) {
return changes;
}
Map<String, Object> oldObjMap = objectMapper.convertValue(beforeObj, Map.class);
Map<String, Object> newObjMap = objectMapper.convertValue(afterObj, Map.class);
// 새로운 값의 모든 필드 확인
for (Map.Entry<String, Object> entry : newObjMap.entrySet()) {
String fieldName = entry.getKey();
if (isExcludedField(fieldName)) {
continue;
}
Object newValue = entry.getValue();
Object oldValue = oldObjMap.get(fieldName);
if (newValue instanceof String && isDateTimeString((String) newValue)) {
newValue = ((String) newValue).replace('T', ' ');
}
if (oldValue instanceof String && isDateTimeString((String) oldValue)) {
oldValue = ((String) oldValue).replace('T', ' ');
}
if (!isEqual(oldValue, newValue)) {
changes.add(new FieldChange(fieldName, oldValue, newValue));
}
}
// 삭제된 필드 확인
for (String fieldName : oldObjMap.keySet()) {
if (!newObjMap.containsKey(fieldName)) {
changes.add(new FieldChange(fieldName, oldObjMap.get(fieldName), null));
}
}
return changes;
}
@SuppressWarnings("unchecked")
public static List<FieldChange> detectDeleteChanges(DynamoDBDocBase deletedDoc) {
Object attrib = DocAttributeHandler.getAttribValue(deletedDoc);
if (attrib == null) {
return new ArrayList<>();
}
return (attrib instanceof String)
? detectDeleteChangesFromJsonString((String) attrib)
: detectDeleteChangesFromObject(attrib);
}
private static List<FieldChange> detectDeleteChangesFromJsonString(String jsonString) {
try {
Map<String, Object> attribMap = objectMapper.readValue(jsonString,
new TypeReference<LinkedHashMap<String, Object>>() {});
return createFieldChanges(attribMap);
} catch (JsonProcessingException e) {
throw new RuntimeException("Error processing JSON string changes for delete", e);
}
}
private static List<FieldChange> detectDeleteChangesFromObject(Object attrib) {
Map<String, Object> attribMap = objectMapper.convertValue(attrib,
new TypeReference<LinkedHashMap<String, Object>>() {});
return createFieldChanges(attribMap);
}
// @SuppressWarnings("unchecked")
// public static List<FieldChange> detectDeleteChanges(DynamoDBDocBase deletedDoc) {
// List<FieldChange> changes = new ArrayList<>();
// Object attrib = DocAttributeHandler.getAttribValue(deletedDoc);
//
// if (attrib == null) {
// return changes;
// }
//
// Map<String, Object> attribMap = objectMapper.convertValue(attrib, Map.class);
//
// for (Map.Entry<String, Object> entry : attribMap.entrySet()) {
// changes.add(new FieldChange(
// entry.getKey(),
// entry.getValue(),
// null
// ));
// }
//
// return changes;
// }
public static <T> List<FieldChange> detectDeleteChanges(T deletedObj) {
List<FieldChange> changes = new ArrayList<>();
if (deletedObj == null) {
return changes;
}
Map<String, Object> objMap = objectMapper.convertValue(deletedObj, Map.class);
for (Map.Entry<String, Object> entry : objMap.entrySet()) {
String fieldName = entry.getKey();
if (isExcludedField(fieldName)) {
continue;
}
Object oldValue = entry.getValue();
if (oldValue instanceof String && isDateTimeString((String) oldValue)) {
oldValue = ((String) oldValue).replace('T', ' ');
}
changes.add(new FieldChange(
fieldName,
oldValue,
null
));
}
return changes;
}
private static boolean isExcludedField(String fieldName) {
return EXCLUDED_FIELDS.contains(fieldName) ||
EXCLUDED_FIELDS.contains(toSnakeCase(fieldName)) ||
EXCLUDED_FIELDS.contains(toCamelCase(fieldName));
}
private static String toSnakeCase(String str) {
return str.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
private static String toCamelCase(String str) {
String[] words = str.split("[\\W_]+");
StringBuilder builder = new StringBuilder();
builder.append(words[0].toLowerCase());
for (int i = 1; i < words.length; i++) {
builder.append(words[i].substring(0, 1).toUpperCase());
builder.append(words[i].substring(1).toLowerCase());
}
return builder.toString();
}
private static boolean isDateTimeString(String value) {
return value != null && value.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}.*");
}
private static boolean isEqual(Object obj1, Object obj2) {
if (obj1 == null && obj2 == null) return true;
if (obj1 == null || obj2 == null) return false;
return obj1.equals(obj2);
}
private static List<FieldChange> createFieldChanges(Map<String, Object> attribMap) {
List<FieldChange> changes = new ArrayList<>();
for (Map.Entry<String, Object> entry : attribMap.entrySet()) {
changes.add(new FieldChange(
entry.getKey(),
null,
entry.getValue()
));
}
return changes;
}
private static Map<String, Object> convertToMap(Object attrib) throws JsonProcessingException {
if (attrib instanceof String) {
return objectMapper.readValue((String) attrib,
new TypeReference<LinkedHashMap<String, Object>>() {});
}
return objectMapper.convertValue(attrib,
new TypeReference<LinkedHashMap<String, Object>>() {});
}
}

View File

@@ -0,0 +1,27 @@
package com.caliverse.admin.history.domain;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.domain.entity.HISTORYTYPE;
import com.caliverse.admin.dynamodb.domain.doc.DynamoDBDocBase;
import com.caliverse.admin.global.common.utils.DateUtils;
import com.caliverse.admin.history.entity.DBType;
import com.caliverse.admin.history.entity.EDBOperationType;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Getter
@Setter
@Document(collection = "historyLog")
public class DynamodbHistoryLogInfo extends HistoryLogInfoBase {
private DynamoDBDocBase data;
public DynamodbHistoryLogInfo(EDBOperationType operationType, HISTORYTYPE historytype, String tableName, String message, String tranId, List<FieldChange> changed, String userId, String userIP, DynamoDBDocBase data) {
super(DBType.DYNAMODB, DateUtils.nowDateTime(), operationType, historytype, tableName, message, tranId,changed, userId, userIP);
this.data = data;
}
}

View File

@@ -0,0 +1,49 @@
package com.caliverse.admin.history.domain;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.domain.entity.HISTORYTYPE;
import com.caliverse.admin.history.entity.DBType;
import com.caliverse.admin.history.entity.EDBOperationType;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class HistoryLogInfoBase implements historyLog {
private DBType dbType;
private String timestamp;
private EDBOperationType operationType;
private HISTORYTYPE historyType;
private String tableName;
private String message;
private String TranId;
private List<FieldChange> changed;
private String userId;
private String userIP;
public HistoryLogInfoBase(DBType dbType,
String timestamp,
EDBOperationType operationType,
HISTORYTYPE historyType,
String tableName,
String message,
String TranId,
List<FieldChange> changed,
String userId,
String userIP
) {
this.dbType = dbType;
this.timestamp = timestamp;
this.operationType = operationType;
this.historyType = historyType;
this.tableName = tableName;
this.message = message;
this.TranId = TranId;
this.changed = changed;
this.userId = userId;
this.userIP = userIP;
}
}

View File

@@ -0,0 +1,27 @@
package com.caliverse.admin.history.domain;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.domain.entity.HISTORYTYPE;
import com.caliverse.admin.global.common.utils.DateUtils;
import com.caliverse.admin.history.entity.DBType;
import com.caliverse.admin.history.entity.EDBOperationType;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
@Getter
@Setter
@Document(collection = "historyLog")
public class MysqlHistoryLogInfo extends HistoryLogInfoBase {
private Object data;
public MysqlHistoryLogInfo(EDBOperationType operationType, HISTORYTYPE historytype, String tableName, String message, String tranId, List<FieldChange> changed, String userId, String userIP, Object data) {
super(DBType.MYSQL, DateUtils.nowDateTime(), operationType, historytype, tableName, message, tranId, changed, userId, userIP);
this.data = data;
}
}

View File

@@ -0,0 +1,5 @@
package com.caliverse.admin.history.domain;
public interface historyLog {
}

View File

@@ -0,0 +1,18 @@
package com.caliverse.admin.history.entity;
public enum DBType {
DYNAMODB,
MYSQL,
MONGODB
;
public static DBType getHistoryType(String type) {
for (DBType historyType : DBType.values()) {
if (historyType.name().equals(type)) {
return historyType;
}
}
return null;
}
}

View File

@@ -0,0 +1,13 @@
package com.caliverse.admin.history.entity;
public enum EDBOperationType {
INSERT("등록"),
UPDATE("수정"),
DELETE("삭제");
private String operationType;
EDBOperationType(String type) {
this.operationType = type;
}
}

View File

@@ -0,0 +1,7 @@
package com.caliverse.admin.history.repository;
import com.caliverse.admin.history.domain.DynamodbHistoryLogInfo;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface DynamodbHistoryLogRepository extends MongoRepository<DynamodbHistoryLogInfo, String> {
}

View File

@@ -0,0 +1,6 @@
package com.caliverse.admin.history.repository;
public interface MongoAdminRepository {
}

View File

@@ -0,0 +1,7 @@
package com.caliverse.admin.history.repository;
import com.caliverse.admin.history.domain.MysqlHistoryLogInfo;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface MysqlHistoryLogRepository extends MongoRepository<MysqlHistoryLogInfo, String> {
}

View File

@@ -0,0 +1,109 @@
package com.caliverse.admin.history.service;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.domain.entity.HISTORYTYPE;
import com.caliverse.admin.dynamodb.domain.doc.DynamoDBDocBase;
import com.caliverse.admin.global.component.transaction.TransactionIdManager;
import com.caliverse.admin.history.ChangeDetector;
import com.caliverse.admin.history.domain.DynamodbHistoryLogInfo;
import com.caliverse.admin.history.entity.EDBOperationType;
import com.caliverse.admin.history.repository.DynamodbHistoryLogRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class DynamodbHistoryLogService {
@Value("${amazon.dynamodb.metaTable}")
private String tableName;
private final TransactionIdManager transactionIdManager;
private final DynamodbHistoryLogRepository dynamodbHistoryLogRepository;
public DynamodbHistoryLogInfo insertHistoryLog(HISTORYTYPE historyType,
String message,
DynamoDBDocBase metadata,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectInsertChanges(metadata);
DynamodbHistoryLogInfo historyLog = new DynamodbHistoryLogInfo(
EDBOperationType.INSERT,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
metadata
);
return dynamodbHistoryLogRepository.save(historyLog);
}
public DynamodbHistoryLogInfo updateHistoryLog(HISTORYTYPE historyType,
String message,
DynamoDBDocBase beforeMetadata,
DynamoDBDocBase afterMetadata,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectChanges(
beforeMetadata,
afterMetadata
);
DynamodbHistoryLogInfo historyLog = new DynamodbHistoryLogInfo(
EDBOperationType.UPDATE,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
afterMetadata
);
return dynamodbHistoryLogRepository.save(historyLog);
}
public DynamodbHistoryLogInfo deleteHistoryLog(HISTORYTYPE historyType,
String message,
DynamoDBDocBase metadata,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectDeleteChanges(metadata);
DynamodbHistoryLogInfo historyLog = new DynamodbHistoryLogInfo(
EDBOperationType.DELETE,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
metadata
);
return dynamodbHistoryLogRepository.save(historyLog);
}
public List<DynamodbHistoryLogInfo> getAllHistoryLogs() {
return dynamodbHistoryLogRepository.findAll();
}
public Optional<DynamodbHistoryLogInfo> getHistoryLogById(String id) {
return dynamodbHistoryLogRepository.findById(id);
}
}

View File

@@ -0,0 +1,108 @@
package com.caliverse.admin.history.service;
import com.caliverse.admin.domain.adminlog.FieldChange;
import com.caliverse.admin.domain.entity.HISTORYTYPE;
import com.caliverse.admin.global.component.transaction.TransactionIdManager;
import com.caliverse.admin.history.ChangeDetector;
import com.caliverse.admin.history.domain.MysqlHistoryLogInfo;
import com.caliverse.admin.history.entity.EDBOperationType;
import com.caliverse.admin.history.repository.MysqlHistoryLogRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class MysqlHistoryLogService {
private final TransactionIdManager transactionIdManager;
private final MysqlHistoryLogRepository mysqlHistoryLogRepository;
public <T> void insertHistoryLog(HISTORYTYPE historyType,
String tableName,
String message,
T data,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectInsertChanges(data);
MysqlHistoryLogInfo historyLog = new MysqlHistoryLogInfo(
EDBOperationType.INSERT,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
data
);
mysqlHistoryLogRepository.save(historyLog);
}
public <T> void updateHistoryLog(HISTORYTYPE historyType,
String tableName,
String message,
T beforeData,
T afterData,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectChanges(
beforeData,
afterData
);
MysqlHistoryLogInfo historyLog = new MysqlHistoryLogInfo(
EDBOperationType.UPDATE,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
afterData
);
mysqlHistoryLogRepository.save(historyLog);
}
public <T> void deleteHistoryLog(HISTORYTYPE historyType,
String tableName,
String message,
T data,
String userId,
String userIP
){
List<FieldChange> changes = ChangeDetector.detectDeleteChanges(data);
MysqlHistoryLogInfo historyLog = new MysqlHistoryLogInfo(
EDBOperationType.DELETE,
historyType,
tableName,
message,
transactionIdManager.getCurrentTransactionId(),
changes,
userId,
userIP,
data
);
mysqlHistoryLogRepository.save(historyLog);
}
public List<MysqlHistoryLogInfo> getAllHistoryLogs() {
return mysqlHistoryLogRepository.findAll();
}
public Optional<MysqlHistoryLogInfo> getHistoryLogById(String id) {
return mysqlHistoryLogRepository.findById(id);
}
}