[agent-pipeline] pipe-20260327053504-cc40 round-2
This commit is contained in:
@@ -0,0 +1,552 @@
|
||||
package com.erp.service;
|
||||
|
||||
import com.erp.mapper.DdlMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DdlService {
|
||||
|
||||
private final DdlMapper ddlMapper;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
private static final Set<String> SYSTEM_TABLES = Set.of(
|
||||
"user_info", "company_mng", "menu_info", "auth_group",
|
||||
"table_labels", "column_labels", "screen_definitions", "screen_layouts",
|
||||
"common_code", "multi_lang_key_master", "multi_lang_text",
|
||||
"button_action_standards", "ddl_execution_log"
|
||||
);
|
||||
|
||||
private static final Set<String> RESERVED_WORDS = Set.of(
|
||||
"user", "order", "group", "table", "column", "index", "select", "insert",
|
||||
"update", "delete", "from", "where", "join", "on", "as", "and", "or", "not",
|
||||
"null", "true", "false", "create", "alter", "drop", "primary", "key",
|
||||
"foreign", "references", "constraint", "default", "unique", "check",
|
||||
"view", "procedure", "function"
|
||||
);
|
||||
|
||||
private static final Set<String> RESERVED_COLUMNS = Set.of(
|
||||
"id", "created_date", "updated_date", "company_code"
|
||||
);
|
||||
|
||||
public DdlService(DdlMapper ddlMapper, JdbcTemplate jdbcTemplate,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
this.ddlMapper = ddlMapper;
|
||||
this.jdbcTemplate = jdbcTemplate;
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// CREATE TABLE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public Map<String, Object> createTable(String tableName, List<Map<String, Object>> columns,
|
||||
String companyCode, String userId, String description) {
|
||||
// 1. 검증
|
||||
Map<String, Object> validation = validateTableCreation(tableName, columns);
|
||||
if (!(Boolean) validation.get("isValid")) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> errors = (List<String>) validation.get("errors");
|
||||
String errorMsg = "테이블 생성 검증 실패: " + String.join(", ", errors);
|
||||
logDdlOperation(userId, companyCode, "CREATE_TABLE", tableName, "VALIDATION_FAILED", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "VALIDATION_FAILED");
|
||||
}
|
||||
|
||||
// 2. 테이블 존재 여부 확인
|
||||
if (tableExists(tableName)) {
|
||||
String errorMsg = "테이블 '" + tableName + "'이 이미 존재합니다.";
|
||||
logDdlOperation(userId, companyCode, "CREATE_TABLE", tableName, "TABLE_EXISTS", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "TABLE_EXISTS");
|
||||
}
|
||||
|
||||
// 3. DDL 쿼리 생성
|
||||
String ddlQuery = generateCreateTableQuery(tableName, columns);
|
||||
|
||||
// 4. 트랜잭션으로 DDL 실행 + 메타데이터 저장
|
||||
try {
|
||||
final String finalTableName = tableName;
|
||||
final List<Map<String, Object>> finalColumns = columns;
|
||||
final String finalCompanyCode = companyCode;
|
||||
final String finalDescription = description;
|
||||
transactionTemplate.execute(status -> {
|
||||
jdbcTemplate.execute(ddlQuery);
|
||||
saveTableMetadata(finalTableName, finalDescription);
|
||||
saveColumnMetadata(finalTableName, finalColumns, finalCompanyCode);
|
||||
return null;
|
||||
});
|
||||
|
||||
// 5. 성공 로그 (트랜잭션 밖)
|
||||
logDdlOperation(userId, companyCode, "CREATE_TABLE", tableName, ddlQuery, true, null);
|
||||
log.info("테이블 생성 성공: {}, 사용자: {}, 컬럼수: {}", tableName, userId, columns.size());
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"message", "테이블 '" + tableName + "'이 성공적으로 생성되었습니다.",
|
||||
"tableName", tableName,
|
||||
"columnCount", columns.size(),
|
||||
"executedQuery", ddlQuery
|
||||
);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "테이블 생성 실패: " + e.getMessage();
|
||||
logDdlOperation(userId, companyCode, "CREATE_TABLE", tableName,
|
||||
"FAILED: " + e.getMessage(), false, errorMsg);
|
||||
log.error("테이블 생성 실패: {}, 사용자: {}, 오류: {}", tableName, userId, e.getMessage(), e);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "EXECUTION_FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// ADD COLUMN
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public Map<String, Object> addColumn(String tableName, Map<String, Object> column,
|
||||
String companyCode, String userId) {
|
||||
// 1. 검증
|
||||
List<String> errors = validateColumnForAddition(tableName, column);
|
||||
if (!errors.isEmpty()) {
|
||||
String errorMsg = "컬럼 추가 검증 실패: " + String.join(", ", errors);
|
||||
logDdlOperation(userId, companyCode, "ADD_COLUMN", tableName, "VALIDATION_FAILED", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "VALIDATION_FAILED");
|
||||
}
|
||||
|
||||
// 2. 테이블 존재 여부 확인
|
||||
if (!tableExists(tableName)) {
|
||||
String errorMsg = "테이블 '" + tableName + "'이 존재하지 않습니다.";
|
||||
logDdlOperation(userId, companyCode, "ADD_COLUMN", tableName, "TABLE_NOT_EXISTS", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "TABLE_NOT_EXISTS");
|
||||
}
|
||||
|
||||
// 3. 컬럼 존재 여부 확인
|
||||
String colName = (String) column.get("name");
|
||||
if (columnExists(tableName, colName)) {
|
||||
String errorMsg = "컬럼 '" + colName + "'이 이미 존재합니다.";
|
||||
logDdlOperation(userId, companyCode, "ADD_COLUMN", tableName, "COLUMN_EXISTS", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "COLUMN_EXISTS");
|
||||
}
|
||||
|
||||
// 4. DDL 쿼리 생성
|
||||
String ddlQuery = generateAddColumnQuery(tableName, column);
|
||||
|
||||
// 5. 트랜잭션으로 DDL 실행 + 메타데이터 저장
|
||||
try {
|
||||
transactionTemplate.execute(status -> {
|
||||
jdbcTemplate.execute(ddlQuery);
|
||||
String inputType = convertToInputType(column);
|
||||
String detailSettings = column.containsKey("detailSettings")
|
||||
? column.get("detailSettings").toString() : "{}";
|
||||
Integer maxOrder = jdbcTemplate.queryForObject(
|
||||
"SELECT COALESCE(MAX(display_order), 0) FROM table_type_columns " +
|
||||
"WHERE table_name = ? AND company_code = ?",
|
||||
Integer.class, tableName, companyCode);
|
||||
saveColumnMeta(tableName, colName, companyCode, inputType,
|
||||
detailSettings, (maxOrder != null ? maxOrder : 0) + 1);
|
||||
return null;
|
||||
});
|
||||
|
||||
// 6. 성공 로그 (트랜잭션 밖)
|
||||
logDdlOperation(userId, companyCode, "ADD_COLUMN", tableName, ddlQuery, true, null);
|
||||
log.info("컬럼 추가 성공: {}.{}, 사용자: {}", tableName, colName, userId);
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"message", "컬럼 '" + colName + "'이 성공적으로 추가되었습니다.",
|
||||
"tableName", tableName,
|
||||
"columnName", colName,
|
||||
"executedQuery", ddlQuery
|
||||
);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "컬럼 추가 실패: " + e.getMessage();
|
||||
logDdlOperation(userId, companyCode, "ADD_COLUMN", tableName,
|
||||
"FAILED: " + e.getMessage(), false, errorMsg);
|
||||
log.error("컬럼 추가 실패: {}.{}, 사용자: {}, 오류: {}", tableName, colName, userId, e.getMessage(), e);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "EXECUTION_FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// DROP TABLE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public Map<String, Object> dropTable(String tableName, String companyCode, String userId) {
|
||||
// 1. 시스템 테이블 보호
|
||||
if (SYSTEM_TABLES.contains(tableName.toLowerCase())) {
|
||||
String errorMsg = "'" + tableName + "'은 시스템 테이블이므로 삭제할 수 없습니다.";
|
||||
logDdlOperation(userId, companyCode, "DROP_TABLE", tableName,
|
||||
"SYSTEM_TABLE_PROTECTED", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "SYSTEM_TABLE_PROTECTED");
|
||||
}
|
||||
|
||||
// 2. 테이블 존재 여부 확인
|
||||
if (!tableExists(tableName)) {
|
||||
String errorMsg = "테이블 '" + tableName + "'이 존재하지 않습니다.";
|
||||
logDdlOperation(userId, companyCode, "DROP_TABLE", tableName, "TABLE_NOT_FOUND", false, errorMsg);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "TABLE_NOT_FOUND");
|
||||
}
|
||||
|
||||
String ddlQuery = "DROP TABLE IF EXISTS \"" + sanitize(tableName) + "\" CASCADE";
|
||||
|
||||
try {
|
||||
transactionTemplate.execute(status -> {
|
||||
jdbcTemplate.execute(ddlQuery);
|
||||
jdbcTemplate.update("DELETE FROM table_type_columns WHERE table_name = ?", tableName);
|
||||
jdbcTemplate.update("DELETE FROM table_labels WHERE table_name = ?", tableName);
|
||||
return null;
|
||||
});
|
||||
|
||||
logDdlOperation(userId, companyCode, "DROP_TABLE", tableName, ddlQuery, true, null);
|
||||
log.info("테이블 삭제 성공: {}, 사용자: {}", tableName, userId);
|
||||
|
||||
return Map.of(
|
||||
"success", true,
|
||||
"message", "테이블 '" + tableName + "'이 성공적으로 삭제되었습니다.",
|
||||
"tableName", tableName,
|
||||
"executedQuery", ddlQuery
|
||||
);
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "테이블 삭제 실패: " + e.getMessage();
|
||||
logDdlOperation(userId, companyCode, "DROP_TABLE", tableName,
|
||||
"FAILED: " + e.getMessage(), false, errorMsg);
|
||||
log.error("테이블 삭제 실패: {}, 사용자: {}, 오류: {}", tableName, userId, e.getMessage(), e);
|
||||
return Map.of("success", false, "message", errorMsg, "errorCode", "EXECUTION_FAILED");
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// VALIDATE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public Map<String, Object> validateTableCreation(String tableName, List<Map<String, Object>> columns) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
List<String> warnings = new ArrayList<>();
|
||||
|
||||
errors.addAll(validateTableName(tableName));
|
||||
|
||||
if (columns == null || columns.isEmpty()) {
|
||||
errors.add("최소 1개의 컬럼이 필요합니다.");
|
||||
} else {
|
||||
Set<String> seen = new LinkedHashSet<>();
|
||||
Set<String> duplicates = new LinkedHashSet<>();
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
Map<String, Object> col = columns.get(i);
|
||||
errors.addAll(validateSingleColumn(col, i + 1));
|
||||
String colName = col.get("name") != null ? col.get("name").toString().toLowerCase() : "";
|
||||
if (!colName.isEmpty() && !seen.add(colName)) {
|
||||
duplicates.add(colName);
|
||||
}
|
||||
}
|
||||
if (!duplicates.isEmpty()) {
|
||||
errors.add("중복된 컬럼명이 있습니다: " + String.join(", ", duplicates));
|
||||
}
|
||||
}
|
||||
|
||||
int colCount = columns != null ? columns.size() : 0;
|
||||
return Map.of(
|
||||
"isValid", errors.isEmpty(),
|
||||
"errors", errors,
|
||||
"warnings", warnings,
|
||||
"summary", buildValidationSummary(tableName, colCount, errors, warnings)
|
||||
);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// LOG QUERIES
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
public List<Map<String, Object>> getDdlLogs(int limit, String userId, String ddlType) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("limit", Math.min(limit, 200));
|
||||
params.put("userId", userId);
|
||||
params.put("ddlType", ddlType);
|
||||
return ddlMapper.selectDdlLogs(params);
|
||||
}
|
||||
|
||||
public Map<String, Object> getDdlStatistics(String fromDate, String toDate) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("fromDate", fromDate);
|
||||
params.put("toDate", toDate);
|
||||
|
||||
Map<String, Object> totalStats = ddlMapper.selectDdlTotalStats(params);
|
||||
List<Map<String, Object>> byType = ddlMapper.selectDdlStatsByType(params);
|
||||
List<Map<String, Object>> byUser = ddlMapper.selectDdlStatsByUser(params);
|
||||
List<Map<String, Object>> recentFailures = ddlMapper.selectRecentFailures(params);
|
||||
|
||||
Map<String, Long> byDdlType = new LinkedHashMap<>();
|
||||
for (Map<String, Object> row : byType) {
|
||||
byDdlType.put(String.valueOf(row.get("ddlType")), toLong(row.get("count")));
|
||||
}
|
||||
Map<String, Long> byUserMap = new LinkedHashMap<>();
|
||||
for (Map<String, Object> row : byUser) {
|
||||
byUserMap.put(String.valueOf(row.get("userId")), toLong(row.get("count")));
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"totalExecutions", toLong(totalStats != null ? totalStats.get("totalExecutions") : null),
|
||||
"successfulExecutions", toLong(totalStats != null ? totalStats.get("successfulExecutions") : null),
|
||||
"failedExecutions", toLong(totalStats != null ? totalStats.get("failedExecutions") : null),
|
||||
"byDDLType", byDdlType,
|
||||
"byUser", byUserMap,
|
||||
"recentFailures", recentFailures
|
||||
);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getTableDdlHistory(String tableName) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("tableName", tableName);
|
||||
return ddlMapper.selectTableDdlHistory(params);
|
||||
}
|
||||
|
||||
public Map<String, Object> getTableInfo(String tableName) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("tableName", tableName);
|
||||
Map<String, Object> tableInfo = ddlMapper.selectTableInfo(params);
|
||||
if (tableInfo == null) return null;
|
||||
List<Map<String, Object>> columns = ddlMapper.selectTableColumns(params);
|
||||
return Map.of("tableInfo", tableInfo, "columns", columns);
|
||||
}
|
||||
|
||||
public int cleanupOldLogs(int retentionDays) {
|
||||
LocalDateTime cutoff = LocalDateTime.now().minusDays(retentionDays);
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("cutoffDate", cutoff.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
int deleted = ddlMapper.deleteOldDdlLogs(params);
|
||||
log.info("DDL 로그 정리 완료: {}개 삭제, 보존 기간: {}일", deleted, retentionDays);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// DDL QUERY GENERATION
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private String generateCreateTableQuery(String tableName, List<Map<String, Object>> columns) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("CREATE TABLE \"").append(sanitize(tableName)).append("\" (\n");
|
||||
sb.append(" \"id\" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,\n");
|
||||
sb.append(" \"created_date\" timestamp DEFAULT now(),\n");
|
||||
sb.append(" \"updated_date\" timestamp DEFAULT now(),\n");
|
||||
sb.append(" \"writer\" varchar(500) DEFAULT NULL,\n");
|
||||
sb.append(" \"company_code\" varchar(500)");
|
||||
|
||||
for (Map<String, Object> col : columns) {
|
||||
sb.append(",\n \"").append(sanitize((String) col.get("name"))).append("\" varchar(500)");
|
||||
Boolean nullable = col.containsKey("nullable") ? (Boolean) col.get("nullable") : true;
|
||||
if (Boolean.FALSE.equals(nullable)) sb.append(" NOT NULL");
|
||||
if (col.get("defaultValue") != null && !col.get("defaultValue").toString().isBlank()) {
|
||||
sb.append(" DEFAULT '").append(col.get("defaultValue").toString().replace("'", "''")).append("'");
|
||||
}
|
||||
}
|
||||
sb.append("\n);");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String generateAddColumnQuery(String tableName, Map<String, Object> column) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("ALTER TABLE \"").append(sanitize(tableName)).append("\" ADD COLUMN ");
|
||||
sb.append("\"").append(sanitize((String) column.get("name"))).append("\" varchar(500)");
|
||||
Boolean nullable = column.containsKey("nullable") ? (Boolean) column.get("nullable") : true;
|
||||
if (Boolean.FALSE.equals(nullable)) sb.append(" NOT NULL");
|
||||
if (column.get("defaultValue") != null && !column.get("defaultValue").toString().isBlank()) {
|
||||
sb.append(" DEFAULT '").append(column.get("defaultValue").toString().replace("'", "''")).append("'");
|
||||
}
|
||||
sb.append(";");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// TABLE / COLUMN EXISTS
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private boolean tableExists(String tableName) {
|
||||
Integer cnt = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.tables " +
|
||||
"WHERE table_schema = 'public' AND table_name = ?",
|
||||
Integer.class, tableName);
|
||||
return cnt != null && cnt > 0;
|
||||
}
|
||||
|
||||
private boolean columnExists(String tableName, String columnName) {
|
||||
Integer cnt = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM information_schema.columns " +
|
||||
"WHERE table_schema = 'public' AND table_name = ? AND column_name = ?",
|
||||
Integer.class, tableName, columnName);
|
||||
return cnt != null && cnt > 0;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// METADATA SAVE
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void saveTableMetadata(String tableName, String description) {
|
||||
String desc = description != null && !description.isBlank()
|
||||
? description : "사용자 생성 테이블: " + tableName;
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO table_labels (table_name, table_label, description, created_date, updated_date) " +
|
||||
"VALUES (?, ?, ?, now(), now()) " +
|
||||
"ON CONFLICT (table_name) DO UPDATE SET table_label = ?, description = ?, updated_date = now()",
|
||||
tableName, tableName, desc, tableName, desc);
|
||||
}
|
||||
|
||||
private void saveColumnMetadata(String tableName, List<Map<String, Object>> columns, String companyCode) {
|
||||
// 기본 시스템 컬럼
|
||||
List<Object[]> defaultCols = List.of(
|
||||
new Object[]{"id", "text", -5},
|
||||
new Object[]{"created_date", "date", -4},
|
||||
new Object[]{"updated_date", "date", -3},
|
||||
new Object[]{"writer", "text", -2},
|
||||
new Object[]{"company_code", "text", -1}
|
||||
);
|
||||
for (Object[] dc : defaultCols) {
|
||||
saveColumnMeta(tableName, (String) dc[0], companyCode, (String) dc[1], "{}", (int) dc[2]);
|
||||
}
|
||||
// 사용자 정의 컬럼
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
Map<String, Object> col = columns.get(i);
|
||||
String detailSettings = col.containsKey("detailSettings")
|
||||
? col.get("detailSettings").toString() : "{}";
|
||||
saveColumnMeta(tableName, (String) col.get("name"), companyCode,
|
||||
convertToInputType(col), detailSettings, i);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveColumnMeta(String tableName, String colName, String companyCode,
|
||||
String inputType, String detailSettings, int order) {
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO table_type_columns " +
|
||||
" (table_name, column_name, company_code, input_type, detail_settings, is_nullable, display_order, created_date, updated_date) " +
|
||||
"VALUES (?, ?, ?, ?, ?::jsonb, 'Y', ?, now(), now()) " +
|
||||
"ON CONFLICT (table_name, column_name, company_code) DO UPDATE SET " +
|
||||
" input_type = ?, detail_settings = ?::jsonb, display_order = ?, updated_date = now()",
|
||||
tableName, colName, companyCode, inputType, detailSettings, order,
|
||||
inputType, detailSettings, order);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// VALIDATION HELPERS
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private List<String> validateTableName(String tableName) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
if (tableName == null || tableName.isBlank()) {
|
||||
errors.add("테이블명은 필수입니다.");
|
||||
return errors;
|
||||
}
|
||||
if (!tableName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
errors.add("유효하지 않은 테이블명입니다. 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용 가능합니다.");
|
||||
}
|
||||
if (tableName.length() > 63) errors.add("테이블명은 63자를 초과할 수 없습니다.");
|
||||
if (tableName.length() < 2) errors.add("테이블명은 최소 2자 이상이어야 합니다.");
|
||||
if (SYSTEM_TABLES.contains(tableName.toLowerCase())) {
|
||||
errors.add("'" + tableName + "'은 시스템 테이블명으로 사용할 수 없습니다.");
|
||||
}
|
||||
if (RESERVED_WORDS.contains(tableName.toLowerCase())) {
|
||||
errors.add("'" + tableName + "'은 SQL 예약어이므로 테이블명으로 사용할 수 없습니다.");
|
||||
}
|
||||
if (tableName.startsWith("_") || tableName.endsWith("_")) {
|
||||
errors.add("테이블명은 언더스코어로 시작하거나 끝날 수 없습니다.");
|
||||
}
|
||||
if (tableName.contains("__")) {
|
||||
errors.add("테이블명에 연속된 언더스코어는 사용할 수 없습니다.");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private List<String> validateSingleColumn(Map<String, Object> column, int position) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
String name = column.get("name") != null ? column.get("name").toString().trim() : "";
|
||||
String prefix = "컬럼 " + position + "(" + name + "): ";
|
||||
|
||||
if (name.isBlank()) {
|
||||
errors.add(prefix + "컬럼명은 필수입니다.");
|
||||
return errors;
|
||||
}
|
||||
if (!name.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
||||
errors.add(prefix + "유효하지 않은 컬럼명입니다. 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용 가능합니다.");
|
||||
}
|
||||
if (name.length() > 63) errors.add(prefix + "컬럼명은 63자를 초과할 수 없습니다.");
|
||||
if (name.length() < 2) errors.add(prefix + "컬럼명은 최소 2자 이상이어야 합니다.");
|
||||
if (RESERVED_COLUMNS.contains(name.toLowerCase())) {
|
||||
errors.add(prefix + "'" + name + "'은 예약된 컬럼명입니다 (id, created_date, updated_date, company_code).");
|
||||
}
|
||||
if (RESERVED_WORDS.contains(name.toLowerCase())) {
|
||||
errors.add(prefix + "'" + name + "'은 SQL 예약어이므로 컬럼명으로 사용할 수 없습니다.");
|
||||
}
|
||||
if (name.contains("__")) {
|
||||
errors.add(prefix + "컬럼명에 연속된 언더스코어는 사용할 수 없습니다.");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
private List<String> validateColumnForAddition(String tableName, Map<String, Object> column) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
if (SYSTEM_TABLES.contains(tableName.toLowerCase())) {
|
||||
errors.add("'" + tableName + "'은 시스템 테이블이므로 컬럼을 추가할 수 없습니다.");
|
||||
}
|
||||
errors.addAll(validateSingleColumn(column, 1));
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// UTILITIES
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private String sanitize(String name) {
|
||||
return name.replaceAll("[^a-zA-Z0-9_]", "");
|
||||
}
|
||||
|
||||
private String convertToInputType(Map<String, Object> col) {
|
||||
Object inputType = col.get("inputType");
|
||||
Object webType = col.get("webType");
|
||||
String type = inputType != null ? inputType.toString()
|
||||
: (webType != null ? webType.toString() : "text");
|
||||
return switch (type) {
|
||||
case "number", "decimal" -> "number";
|
||||
case "date", "datetime" -> "date";
|
||||
case "select", "dropdown" -> "select";
|
||||
case "checkbox", "boolean" -> "checkbox";
|
||||
case "radio" -> "radio";
|
||||
case "code" -> "code";
|
||||
case "entity" -> "entity";
|
||||
default -> "text";
|
||||
};
|
||||
}
|
||||
|
||||
private void logDdlOperation(String userId, String companyCode, String ddlType,
|
||||
String tableName, String ddlQuery, boolean success, String errorMessage) {
|
||||
try {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("userId", userId);
|
||||
params.put("companyCode", companyCode);
|
||||
params.put("ddlType", ddlType);
|
||||
params.put("tableName", tableName);
|
||||
params.put("ddlQuery", ddlQuery);
|
||||
params.put("success", success);
|
||||
params.put("errorMessage", errorMessage);
|
||||
ddlMapper.insertDdlLog(params);
|
||||
} catch (Exception e) {
|
||||
log.error("DDL 로그 기록 실패: userId={}, ddlType={}, tableName={}", userId, ddlType, tableName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private long toLong(Object value) {
|
||||
if (value == null) return 0L;
|
||||
if (value instanceof Number) return ((Number) value).longValue();
|
||||
try { return Long.parseLong(value.toString()); } catch (NumberFormatException ex) { return 0L; }
|
||||
}
|
||||
|
||||
private String buildValidationSummary(String tableName, int columnCount,
|
||||
List<String> errors, List<String> warnings) {
|
||||
StringBuilder sb = new StringBuilder("테이블 '").append(tableName).append("' 검증 완료. ");
|
||||
sb.append("컬럼 ").append(columnCount).append("개 중 ");
|
||||
sb.append(errors.isEmpty() ? "모든 검증 통과." : errors.size() + "개 오류 발견.");
|
||||
if (!warnings.isEmpty()) sb.append(" ").append(warnings.size()).append("개 경고 있음.");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user