Files
invyone/backend-spring/src/main/java/com/erp/service/DataflowService.java
T

415 lines
20 KiB
Java

package com.erp.service;
import com.erp.common.BaseService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.regex.Pattern;
@Service
@Slf4j
public class DataflowService extends BaseService {
@Autowired
private ObjectMapper objectMapper;
/** 테이블명/컬럼명 안전성 검증 패턴 */
private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
// ═══════════════════════════════════════════════════════════════════
// table_relationships CRUD
// ═══════════════════════════════════════════════════════════════════
@Transactional
public Map<String, Object> insertTableRelationship(Map<String, Object> body,
String companyCode,
String userId) {
String relationshipName = str(body.get("relationshipName"));
String fromTableName = str(body.get("fromTableName"));
String fromColumnName = str(body.get("fromColumnName"));
String toTableName = str(body.get("toTableName"));
String toColumnName = str(body.get("toColumnName"));
String relationshipType = body.getOrDefault("relationshipType", "one-to-one").toString();
String connectionType = body.getOrDefault("connectionType", "simple-key").toString();
// diagram_id 결정: 전달된 값 or max+1
int diagramId;
Object diagramIdObj = body.get("diagramId");
if (diagramIdObj != null) {
diagramId = toInt(diagramIdObj);
} else {
Map<String, Object> maxParams = new HashMap<>();
maxParams.put("companyCode", companyCode);
Map<String, Object> maxRow = sqlSession.selectOne("dataflow.getMaxDiagramId", maxParams);
diagramId = toInt(maxRow != null ? maxRow.get("max_diagram_id") : null) + 1;
}
// 중복 확인
Map<String, Object> checkParams = new HashMap<>();
checkParams.put("diagramId", diagramId);
checkParams.put("fromTableName", fromTableName);
checkParams.put("fromColumnName", fromColumnName);
checkParams.put("toTableName", toTableName);
checkParams.put("toColumnName", toColumnName);
checkParams.put("companyCode", companyCode);
if (sqlSession.selectOne("dataflow.getExistingRelationshipInfo", checkParams) != null) {
throw new IllegalArgumentException(
"이미 존재하는 관계입니다: " + fromTableName + "." + fromColumnName
+ "" + toTableName + "." + toColumnName);
}
Map<String, Object> params = new HashMap<>();
params.put("diagramId", diagramId);
params.put("relationshipName", relationshipName);
params.put("fromTableName", fromTableName);
params.put("fromColumnName", fromColumnName);
params.put("toTableName", toTableName);
params.put("toColumnName", toColumnName);
params.put("relationshipType", relationshipType);
params.put("connectionType", connectionType);
params.put("companyCode", companyCode);
params.put("settings", toJsonString(body.get("settings")));
params.put("createdBy", userId);
sqlSession.insert("dataflow.insertTableRelationship", params);
Map<String, Object> idParams = new HashMap<>();
idParams.put("relationshipId", params.get("relationshipId"));
idParams.put("companyCode", companyCode);
return sqlSession.selectOne("dataflow.getTableRelationshipInfo", idParams);
}
public List<Map<String, Object>> getTableRelationshipList(String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
return sqlSession.selectList("dataflow.getTableRelationshipList", params);
}
public Map<String, Object> getTableRelationshipInfo(int relationshipId, String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("relationshipId", relationshipId);
params.put("companyCode", companyCode);
return sqlSession.selectOne("dataflow.getTableRelationshipInfo", params);
}
@Transactional
public Map<String, Object> updateTableRelationship(int relationshipId,
Map<String, Object> updateData,
String companyCode,
String userId) {
if (getTableRelationshipInfo(relationshipId, companyCode) == null) return null;
Map<String, Object> params = new HashMap<>();
params.put("relationshipId", relationshipId);
params.put("companyCode", companyCode);
params.put("updatedBy", userId);
copyIfPresent(updateData, params, "relationshipName");
copyIfPresent(updateData, params, "fromTableName");
copyIfPresent(updateData, params, "fromColumnName");
copyIfPresent(updateData, params, "toTableName");
copyIfPresent(updateData, params, "toColumnName");
copyIfPresent(updateData, params, "relationshipType");
copyIfPresent(updateData, params, "connectionType");
if (updateData.containsKey("settings")) {
params.put("settings", toJsonString(updateData.get("settings")));
}
sqlSession.update("dataflow.updateTableRelationship", params);
return getTableRelationshipInfo(relationshipId, companyCode);
}
@Transactional
public boolean deleteTableRelationship(int relationshipId, String companyCode) {
if (getTableRelationshipInfo(relationshipId, companyCode) == null) return false;
Map<String, Object> params = new HashMap<>();
params.put("relationshipId", relationshipId);
params.put("companyCode", companyCode);
sqlSession.update("dataflow.softDeleteTableRelationship", params);
return true;
}
// ═══════════════════════════════════════════════════════════════════
// data_relationship_bridge
// ═══════════════════════════════════════════════════════════════════
@Transactional
public Map<String, Object> insertDataLink(Map<String, Object> body,
String companyCode,
String userId) {
Map<String, Object> params = new HashMap<>();
params.put("relationshipId", toInt(body.get("relationshipId")));
params.put("fromTableName", body.get("fromTableName"));
params.put("fromColumnName", body.get("fromColumnName"));
params.put("toTableName", body.get("toTableName"));
params.put("toColumnName", body.get("toColumnName"));
params.put("connectionType", body.get("connectionType"));
params.put("companyCode", companyCode);
params.put("bridgeData", toJsonString(body.get("bridgeData")));
params.put("createdBy", userId);
sqlSession.insert("dataflow.insertDataLink", params);
// useGeneratedKeys 로 bridgeId 자동 채워짐
Map<String, Object> result = new LinkedHashMap<>(params);
result.put("bridge_id", params.get("bridgeId"));
return result;
}
public List<Map<String, Object>> getDataLinkList(int relationshipId,
String companyCode) {
Map<String, Object> params = new HashMap<>();
params.put("relationshipId", relationshipId);
params.put("companyCode", companyCode);
return sqlSession.selectList("dataflow.getDataLinkList", params);
}
@Transactional
public void deleteDataLink(int bridgeId, String companyCode, String deletedBy) {
Map<String, Object> params = new HashMap<>();
params.put("bridgeId", bridgeId);
params.put("companyCode", companyCode);
sqlSession.update("dataflow.softDeleteDataLink", params);
}
// ═══════════════════════════════════════════════════════════════════
// dynamic table data
// ═══════════════════════════════════════════════════════════════════
public Map<String, Object> getTableDataList(String tableName,
int page, int limit,
String search, String searchColumn,
String companyCode) {
// 안전성 검증 (SQL 인젝션 방지)
if (!SAFE_IDENTIFIER.matcher(tableName).matches()) {
throw new IllegalArgumentException("유효하지 않은 테이블명입니다.");
}
if (searchColumn != null && !searchColumn.isEmpty()
&& !SAFE_IDENTIFIER.matcher(searchColumn).matches()) {
throw new IllegalArgumentException("유효하지 않은 컬럼명입니다.");
}
// 테이블 존재 확인
Map<String, Object> checkParams = new HashMap<>();
checkParams.put("tableName", tableName.toLowerCase());
List<Map<String, Object>> tableInfo = sqlSession.selectList("dataflow.checkTableExists", checkParams);
if (tableInfo == null || tableInfo.isEmpty()) {
throw new IllegalArgumentException("테이블 '" + tableName + "'이 존재하지 않습니다.");
}
Map<String, Object> params = new HashMap<>();
params.put("tableName", tableName);
params.put("limit", limit);
params.put("offset", (page - 1) * limit);
if (search != null && !search.isEmpty()
&& searchColumn != null && !searchColumn.isEmpty()) {
params.put("search", search);
params.put("searchColumn", searchColumn);
}
Number totalNum = sqlSession.selectOne("dataflow.countTableData", params);
int total = totalNum != null ? totalNum.intValue() : 0;
List<Map<String, Object>> data = sqlSession.selectList("dataflow.getTableDataList", params);
int totalPages = (int) Math.ceil((double) total / limit);
Map<String, Object> pagination = new LinkedHashMap<>();
pagination.put("page", page);
pagination.put("limit", limit);
pagination.put("total", total);
pagination.put("totalPages", totalPages);
pagination.put("hasNext", page < totalPages);
pagination.put("hasPrev", page > 1);
Map<String, Object> result = new LinkedHashMap<>();
result.put("data", data);
result.put("pagination", pagination);
return result;
}
// ═══════════════════════════════════════════════════════════════════
// diagram operations
// ═══════════════════════════════════════════════════════════════════
public Map<String, Object> getDiagramList(String companyCode,
int page, int size,
String searchTerm) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
if (searchTerm != null && !searchTerm.isEmpty()) {
params.put("searchTerm", searchTerm);
}
List<Map<String, Object>> rows = sqlSession.selectList("dataflow.getRelationshipForDiagramList", params);
// diagram_id 기준으로 그룹화
Map<Integer, Map<String, Object>> diagramMap = new LinkedHashMap<>();
Map<Integer, Set<String>> tableSetMap = new HashMap<>();
for (Map<String, Object> rel : rows) {
int diagramId = toInt(rel.get("diagram_id"));
if (diagramId == 0) continue;
if (!diagramMap.containsKey(diagramId)) {
Map<String, Object> d = new LinkedHashMap<>();
d.put("diagramId", diagramId);
d.put("diagramName", rel.get("relationship_name"));
d.put("connectionType", rel.get("connection_type"));
d.put("relationshipType", rel.get("relationship_type"));
d.put("relationshipCount", 0);
d.put("createdAt", rel.get("created_date"));
d.put("createdBy", rel.get("created_by"));
d.put("updatedAt", rel.get("updated_date"));
d.put("updatedBy", rel.get("updated_by"));
diagramMap.put(diagramId, d);
tableSetMap.put(diagramId, new LinkedHashSet<>());
}
Map<String, Object> d = diagramMap.get(diagramId);
Set<String> tables = tableSetMap.get(diagramId);
d.put("relationshipCount", toInt(d.get("relationshipCount")) + 1);
if (rel.get("from_table_name") != null) tables.add(rel.get("from_table_name").toString());
if (rel.get("to_table_name") != null) tables.add(rel.get("to_table_name").toString());
}
// tableCount / tables 설정
diagramMap.forEach((id, d) -> {
Set<String> tables = tableSetMap.get(id);
d.put("tableCount", tables.size());
d.put("tables", new ArrayList<>(tables));
});
List<Map<String, Object>> allDiagrams = new ArrayList<>(diagramMap.values());
int total = allDiagrams.size();
int offset = (page - 1) * size;
int end = Math.min(offset + size, total);
List<Map<String, Object>> pageData = (offset < total)
? allDiagrams.subList(offset, end)
: new ArrayList<>();
Map<String, Object> result = new LinkedHashMap<>();
result.put("diagrams", pageData);
result.put("total", total);
result.put("page", page);
result.put("size", size);
result.put("totalPages", (int) Math.ceil((double) total / size));
return result;
}
public List<Map<String, Object>> getDiagramRelationshipList(String companyCode,
String diagramName) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("diagramName", diagramName);
return sqlSession.selectList("dataflow.getDiagramRelationshipListByName", params);
}
public List<Map<String, Object>> getDiagramRelationshipListByDiagramId(String companyCode,
int diagramId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("diagramId", diagramId);
return sqlSession.selectList("dataflow.getDiagramRelationshipListByDiagramId", params);
}
public List<Map<String, Object>> getDiagramRelationshipListByRelationshipId(String companyCode,
int relationshipId) {
Map<String, Object> params = new HashMap<>();
params.put("companyCode", companyCode);
params.put("relationshipId", relationshipId);
return sqlSession.selectList("dataflow.getDiagramRelationshipListByRelationshipId", params);
}
@Transactional
public String copyDiagram(String companyCode, String diagramName) {
// 원본 조회
Map<String, Object> queryParams = new HashMap<>();
queryParams.put("companyCode", companyCode);
queryParams.put("diagramName", diagramName);
List<Map<String, Object>> existing = sqlSession.selectList("dataflow.getDiagramRelationshipListByName", queryParams);
if (existing == null || existing.isEmpty()) {
throw new IllegalArgumentException("복사할 관계도를 찾을 수 없습니다.");
}
// 새 diagram_id 생성
Map<String, Object> maxParams = new HashMap<>();
maxParams.put("companyCode", companyCode);
Map<String, Object> maxRow = sqlSession.selectOne("dataflow.getMaxDiagramId", maxParams);
int newDiagramId = toInt(maxRow != null ? maxRow.get("max_diagram_id") : null) + 1;
String newDiagramName = diagramName + " Copy";
for (Map<String, Object> rel : existing) {
Map<String, Object> insertParams = new HashMap<>();
insertParams.put("diagramId", newDiagramId);
insertParams.put("relationshipName", newDiagramName);
insertParams.put("fromTableName", rel.get("from_table_name"));
insertParams.put("fromColumnName", rel.get("from_column_name"));
insertParams.put("toTableName", rel.get("to_table_name"));
insertParams.put("toColumnName", rel.get("to_column_name"));
insertParams.put("relationshipType", rel.get("relationship_type"));
insertParams.put("connectionType", rel.get("connection_type"));
insertParams.put("companyCode", companyCode);
insertParams.put("settings", toJsonString(rel.get("settings")));
insertParams.put("createdBy", "system");
sqlSession.insert("dataflow.insertTableRelationship", insertParams);
}
return newDiagramName;
}
@Transactional
public int deleteDiagram(String companyCode, String diagramName) {
Map<String, Object> queryParams = new HashMap<>();
queryParams.put("companyCode", companyCode);
queryParams.put("diagramName", diagramName);
List<Map<String, Object>> existing = sqlSession.selectList("dataflow.getDiagramRelationshipListByName", queryParams);
if (existing == null || existing.isEmpty()) return 0;
int diagramId = toInt(existing.get(0).get("diagram_id"));
Map<String, Object> deleteParams = new HashMap<>();
deleteParams.put("diagramId", diagramId);
deleteParams.put("companyCode", companyCode);
sqlSession.update("dataflow.softDeleteDiagramByDiagramId", deleteParams);
return existing.size();
}
// ═══════════════════════════════════════════════════════════════════
// private helpers
// ═══════════════════════════════════════════════════════════════════
private String toJsonString(Object obj) {
if (obj == null) return null;
if (obj instanceof String s) return s;
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.warn("JSON 직렬화 실패: {}", e.getMessage());
return null;
}
}
private void copyIfPresent(Map<String, Object> src, Map<String, Object> dest, String key) {
if (src.containsKey(key)) dest.put(key, src.get(key));
}
private int toInt(Object val) {
if (val == null) return 0;
try { return Integer.parseInt(val.toString()); } catch (NumberFormatException e) { return 0; }
}
private String str(Object val) {
return val == null ? null : val.toString();
}
}