support mock data (#67)

* feat:support generate mock sql

* feat: update frontend resources

* feat: update readme

* fix: check failed
This commit is contained in:
vran
2022-04-06 19:21:12 +08:00
committed by GitHub
parent 30765be4d4
commit e4428a3890
117 changed files with 2501 additions and 258 deletions

View File

@@ -29,6 +29,7 @@ dependencies {
// others
implementation 'com.auth0:java-jwt:3.18.3'
implementation 'org.commonmark:commonmark:0.18.1'
implementation 'com.github.javafaker:javafaker:1.0.2'
implementation 'com.alibaba:easyexcel'
implementation "org.freemarker:freemarker"

View File

@@ -34,6 +34,14 @@ public enum DomainErrors implements DatabasirErrors {
INVALID_DATABASE_TYPE_URL_PATTERN("A_10019", "不合法的 url pattern"),
DOCUMENT_VERSION_IS_INVALID("A_10020", "文档版本不合法"),
CANNOT_UPDATE_SELF_ENABLED_STATUS("A_10021", "无法对自己执行启用禁用操作"),
MOCK_DATA_SCRIPT_MUST_NOT_BE_BLANK("A_10022", "脚本内容不能为空"),
TABLE_META_NOT_FOUND("A_10023", "不存在的数据库表"),
DEPENDENT_COLUMN_NAME_MUST_NOT_BE_BLANK("A_10024", "必须指定依赖的字段"),
DEPENDENT_REF_MUST_NOT_BE_BLANK("A_10025", "请选择关联表和字段"),
MUST_NOT_REF_SELF("A_10026", "不能引用自身"),
CIRCLE_REFERENCE("A_10027", "检查到循环引用"),
DUPLICATE_COLUMN("A_10028", "重复的列"),
INVALID_MOCK_DATA_SCRIPT("A_10029", "不合法的表达式"),
;
private final String errCode;

View File

@@ -0,0 +1,25 @@
package com.databasir.core.domain.document.converter;
import com.databasir.core.domain.document.data.TableResponse;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import com.databasir.dao.tables.pojos.TableDocumentPojo;
import org.mapstruct.Mapper;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
public interface TableResponseConverter {
default List<TableResponse> from(List<TableDocumentPojo> tables,
Map<Integer, List<TableColumnDocumentPojo>> columnMapByTableId) {
return tables.stream()
.map(table -> from(table, columnMapByTableId.get(table.getId())))
.collect(Collectors.toList());
}
TableResponse from(TableDocumentPojo table, List<TableColumnDocumentPojo> columns);
TableResponse.ColumnResponse from(TableColumnDocumentPojo column);
}

View File

@@ -16,12 +16,16 @@ import java.util.List;
@AllArgsConstructor
public class DocumentTemplatePropertiesResponse {
@Builder.Default
private List<DocumentTemplatePropertyResponse> columnFieldNameProperties = Collections.emptyList();
@Builder.Default
private List<DocumentTemplatePropertyResponse> indexFieldNameProperties = Collections.emptyList();
@Builder.Default
private List<DocumentTemplatePropertyResponse> triggerFieldNameProperties = Collections.emptyList();
@Builder.Default
private List<DocumentTemplatePropertyResponse> foreignKeyFieldNameProperties = Collections.emptyList();
@Data

View File

@@ -0,0 +1,27 @@
package com.databasir.core.domain.document.data;
import lombok.Data;
import java.util.Collections;
import java.util.List;
@Data
public class TableResponse {
private Integer id;
private String name;
private List<ColumnResponse> columns = Collections.emptyList();
@Data
public static class ColumnResponse {
private Integer id;
private String name;
private String type;
}
}

View File

@@ -6,14 +6,8 @@ import com.databasir.core.diff.Diffs;
import com.databasir.core.diff.data.DiffType;
import com.databasir.core.diff.data.RootDiff;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.document.converter.DatabaseMetaConverter;
import com.databasir.core.domain.document.converter.DocumentPojoConverter;
import com.databasir.core.domain.document.converter.DocumentResponseConverter;
import com.databasir.core.domain.document.converter.DocumentSimpleResponseConverter;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.domain.document.data.DatabaseDocumentSimpleResponse;
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
import com.databasir.core.domain.document.data.TableDocumentResponse;
import com.databasir.core.domain.document.converter.*;
import com.databasir.core.domain.document.data.*;
import com.databasir.core.domain.document.event.DocumentUpdated;
import com.databasir.core.domain.document.generator.DocumentFileGenerator;
import com.databasir.core.domain.document.generator.DocumentFileType;
@@ -82,6 +76,8 @@ public class DocumentService {
private final DatabaseMetaConverter databaseMetaConverter;
private final TableResponseConverter tableResponseConverter;
private final JsonConverter jsonConverter;
private final List<DocumentFileGenerator> documentFileGenerators;
@@ -373,4 +369,23 @@ public class DocumentService {
DatabaseMeta originalMeta = retrieveOriginalDatabaseMeta(original);
return Diffs.diff(originalMeta, currMeta);
}
public List<TableResponse> getTableAndColumns(Integer projectId, Long version) {
Optional<DatabaseDocumentPojo> documentOption;
if (version == null) {
documentOption = databaseDocumentDao.selectNotArchivedByProjectId(projectId);
} else {
documentOption = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, version);
}
if (documentOption.isEmpty()) {
return Collections.emptyList();
} else {
DatabaseDocumentPojo databaseDoc = documentOption.get();
var tables = tableDocumentDao.selectByDatabaseDocumentId(databaseDoc.getId());
var columns = tableColumnDocumentDao.selectByDatabaseDocumentId(databaseDoc.getId());
var columnMapByTableId = columns.stream()
.collect(Collectors.groupingBy(TableColumnDocumentPojo::getTableDocumentId));
return tableResponseConverter.from(tables, columnMapByTableId);
}
}
}

View File

@@ -0,0 +1,96 @@
package com.databasir.core.domain.mock;
import com.databasir.core.domain.mock.converter.MockDataRulePojoConverter;
import com.databasir.core.domain.mock.converter.MockDataRuleResponseConverter;
import com.databasir.core.domain.mock.data.ColumnMockRuleSaveRequest;
import com.databasir.core.domain.mock.data.MockDataGenerateCondition;
import com.databasir.core.domain.mock.data.MockDataRuleListCondition;
import com.databasir.core.domain.mock.data.MockDataRuleResponse;
import com.databasir.core.domain.mock.generator.MockDataGenerator;
import com.databasir.core.domain.mock.validator.MockDataSaveValidator;
import com.databasir.core.domain.mock.validator.MockDataValidator;
import com.databasir.dao.enums.MockDataType;
import com.databasir.dao.impl.MockDataRuleDao;
import com.databasir.dao.impl.TableColumnDocumentDao;
import com.databasir.dao.tables.pojos.DatabaseDocumentPojo;
import com.databasir.dao.tables.pojos.MockDataRulePojo;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import com.databasir.dao.tables.pojos.TableDocumentPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class MockDataService {
private final MockDataGenerator mockDataGenerator;
private final MockDataRuleDao mockDataRuleDao;
private final TableColumnDocumentDao tableColumnDocumentDao;
private final MockDataRulePojoConverter mockDataRulePojoConverter;
private final MockDataRuleResponseConverter mockDataRuleResponseConverter;
private final MockDataSaveValidator mockDataSaveValidator;
private final MockDataValidator mockDataValidator;
public String generateMockInsertSql(Integer projectId, MockDataGenerateCondition condition) {
mockDataValidator.validProject(projectId);
DatabaseDocumentPojo databaseDoc =
mockDataValidator.validAndGetDatabaseDocumentPojo(projectId, condition.getVersion());
TableDocumentPojo tableDoc =
mockDataValidator.validAndGetTableDocumentPojo(databaseDoc.getId(), condition.getTableId());
return mockDataGenerator.createInsertSql(projectId, databaseDoc.getId(), tableDoc.getName());
}
public void saveMockRules(Integer projectId,
Integer tableId,
List<ColumnMockRuleSaveRequest> rules) {
mockDataValidator.validProject(projectId);
DatabaseDocumentPojo doc =
mockDataValidator.validAndGetDatabaseDocumentPojo(projectId, null);
TableDocumentPojo tableDoc =
mockDataValidator.validAndGetTableDocumentPojo(doc.getId(), tableId);
List<String> columnNames = rules.stream()
.map(ColumnMockRuleSaveRequest::getColumnName)
.collect(Collectors.toList());
mockDataSaveValidator.validTableColumn(tableDoc.getId(), columnNames);
mockDataSaveValidator.validScriptMockType(rules);
mockDataSaveValidator.validRefMockType(doc.getId(), rules);
// verify
mockDataGenerator.createInsertSql(projectId, doc.getId(), tableDoc.getName());
List<MockDataRulePojo> pojo = mockDataRulePojoConverter.from(projectId, rules);
mockDataRuleDao.batchSave(pojo);
}
public List<MockDataRuleResponse> listRules(Integer projectId, MockDataRuleListCondition condition) {
mockDataValidator.validProject(projectId);
DatabaseDocumentPojo databaseDoc =
mockDataValidator.validAndGetDatabaseDocumentPojo(projectId, condition.getVersion());
TableDocumentPojo tableDoc =
mockDataValidator.validAndGetTableDocumentPojo(databaseDoc.getId(), condition.getTableId());
List<TableColumnDocumentPojo> columns =
tableColumnDocumentDao.selectByTableDocumentId(condition.getTableId());
var ruleMapByColumnName = mockDataRuleDao.selectByProjectIdAndTableName(projectId, tableDoc.getName())
.stream()
.collect(Collectors.toMap(MockDataRulePojo::getColumnName, Function.identity()));
return columns.stream()
.map(col -> {
if (ruleMapByColumnName.containsKey(col.getName())) {
return mockDataRuleResponseConverter.from(ruleMapByColumnName.get(col.getName()), col);
} else {
return mockDataRuleResponseConverter.from(tableDoc.getName(), MockDataType.AUTO, col);
}
})
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,20 @@
package com.databasir.core.domain.mock.converter;
import com.databasir.core.domain.mock.data.ColumnMockRuleSaveRequest;
import com.databasir.dao.tables.pojos.MockDataRulePojo;
import org.mapstruct.Mapper;
import java.util.List;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
public interface MockDataRulePojoConverter {
MockDataRulePojo from(Integer projectId, ColumnMockRuleSaveRequest request);
default List<MockDataRulePojo> from(Integer projectId, List<ColumnMockRuleSaveRequest> request) {
return request.stream()
.map(rule -> from(projectId, rule))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,19 @@
package com.databasir.core.domain.mock.converter;
import com.databasir.core.domain.mock.data.MockDataRuleResponse;
import com.databasir.dao.enums.MockDataType;
import com.databasir.dao.tables.pojos.MockDataRulePojo;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface MockDataRuleResponseConverter {
@Mapping(target = "columnType", source = "column.type")
MockDataRuleResponse from(MockDataRulePojo pojo, TableColumnDocumentPojo column);
@Mapping(target = "columnName", source = "pojo.name")
@Mapping(target = "columnType", source = "pojo.type")
MockDataRuleResponse from(String tableName, MockDataType mockDataType, TableColumnDocumentPojo pojo);
}

View File

@@ -0,0 +1,27 @@
package com.databasir.core.domain.mock.data;
import com.databasir.dao.enums.MockDataType;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class ColumnMockRuleSaveRequest {
@NotBlank
private String tableName;
@NotBlank
private String columnName;
private String dependentTableName;
private String dependentColumnName;
@NotNull
private MockDataType mockDataType;
private String mockDataScript;
}

View File

@@ -0,0 +1,11 @@
package com.databasir.core.domain.mock.data;
import lombok.Data;
@Data
public class MockDataGenerateCondition {
private Long version;
private Integer tableId;
}

View File

@@ -0,0 +1,11 @@
package com.databasir.core.domain.mock.data;
import lombok.Data;
@Data
public class MockDataRuleListCondition {
private Long version;
private Integer tableId;
}

View File

@@ -0,0 +1,23 @@
package com.databasir.core.domain.mock.data;
import com.databasir.dao.enums.MockDataType;
import lombok.Data;
@Data
public class MockDataRuleResponse {
private String tableName;
private String columnName;
private String columnType;
private String dependentTableName;
private String dependentColumnName;
private MockDataType mockDataType;
private String mockDataScript;
}

View File

@@ -0,0 +1,74 @@
package com.databasir.core.domain.mock.factory;
import com.databasir.dao.enums.MockDataType;
import com.databasir.dao.impl.TableColumnDocumentDao;
import lombok.RequiredArgsConstructor;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
@Component
@Order
@RequiredArgsConstructor
public class AutoMockDataFactory implements MockDataFactory {
private final TableColumnDocumentDao tableColumnDocumentDao;
public static final Map<Integer, String> DATA_TYPE_VALUE_MAP = new HashMap<>();
static {
DATA_TYPE_VALUE_MAP.put(9999, "''");
DATA_TYPE_VALUE_MAP.put(Types.BIT, "1");
DATA_TYPE_VALUE_MAP.put(Types.TINYINT, "1");
DATA_TYPE_VALUE_MAP.put(Types.SMALLINT, "1");
DATA_TYPE_VALUE_MAP.put(Types.INTEGER, "1");
DATA_TYPE_VALUE_MAP.put(Types.BIGINT, "1");
DATA_TYPE_VALUE_MAP.put(Types.FLOAT, "1.1");
DATA_TYPE_VALUE_MAP.put(Types.REAL, "''");
DATA_TYPE_VALUE_MAP.put(Types.DOUBLE, "1.2");
DATA_TYPE_VALUE_MAP.put(Types.NUMERIC, "1");
DATA_TYPE_VALUE_MAP.put(Types.DECIMAL, "1.1");
DATA_TYPE_VALUE_MAP.put(Types.CHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.VARCHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.LONGVARCHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.DATE, "'1970-12-31'");
DATA_TYPE_VALUE_MAP.put(Types.TIME, "'00:00:00'");
DATA_TYPE_VALUE_MAP.put(Types.TIMESTAMP, "'2001-01-01 00:00:00'");
DATA_TYPE_VALUE_MAP.put(Types.BINARY, "''");
DATA_TYPE_VALUE_MAP.put(Types.VARBINARY, "''");
DATA_TYPE_VALUE_MAP.put(Types.LONGVARBINARY, "''");
DATA_TYPE_VALUE_MAP.put(Types.NULL, "null");
DATA_TYPE_VALUE_MAP.put(Types.OTHER, "''");
DATA_TYPE_VALUE_MAP.put(Types.JAVA_OBJECT, "''");
DATA_TYPE_VALUE_MAP.put(Types.DISTINCT, "''");
DATA_TYPE_VALUE_MAP.put(Types.STRUCT, "''");
DATA_TYPE_VALUE_MAP.put(Types.ARRAY, "'{}'");
DATA_TYPE_VALUE_MAP.put(Types.BLOB, "''");
DATA_TYPE_VALUE_MAP.put(Types.CLOB, "''");
DATA_TYPE_VALUE_MAP.put(Types.REF, "''");
DATA_TYPE_VALUE_MAP.put(Types.DATALINK, "''");
DATA_TYPE_VALUE_MAP.put(Types.BOOLEAN, "true");
DATA_TYPE_VALUE_MAP.put(Types.ROWID, "''");
DATA_TYPE_VALUE_MAP.put(Types.NCHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.NVARCHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.LONGNVARCHAR, "''");
DATA_TYPE_VALUE_MAP.put(Types.NCLOB, "''");
DATA_TYPE_VALUE_MAP.put(Types.SQLXML, "''");
DATA_TYPE_VALUE_MAP.put(Types.REF_CURSOR, "''");
DATA_TYPE_VALUE_MAP.put(Types.TIME_WITH_TIMEZONE, "''");
DATA_TYPE_VALUE_MAP.put(Types.TIMESTAMP_WITH_TIMEZONE, "''");
}
@Override
public boolean accept(MockColumnRule rule) {
return rule == null || rule.getMockDataType() == MockDataType.AUTO;
}
@Override
public String create(MockColumnRule rule) {
return DATA_TYPE_VALUE_MAP.getOrDefault(rule.getDataType(), "''");
}
}

View File

@@ -0,0 +1,48 @@
package com.databasir.core.domain.mock.factory;
import com.databasir.dao.enums.MockDataType;
import com.github.javafaker.Faker;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.StringJoiner;
import java.util.UUID;
@Component
@Order(0)
public class FakerMockDataFactory implements MockDataFactory {
@Override
public boolean accept(MockColumnRule rule) {
return MockDataType.fakerTypes().contains(rule.getMockDataType());
}
@Override
public String create(MockColumnRule rule) {
Faker faker = new Faker();
StringJoiner joiner = new StringJoiner("", "'", "'");
switch (rule.getMockDataType()) {
case FULL_NAME:
joiner.add(faker.name().username());
return joiner.toString();
case PHONE:
joiner.add(faker.phoneNumber().cellPhone());
return joiner.toString();
case FULL_ADDRESS:
joiner.add(faker.address().fullAddress());
return joiner.toString();
case AVATAR_URL:
joiner.add(faker.avatar().image());
return joiner.toString();
case UUID:
joiner.add(UUID.randomUUID().toString());
return joiner.toString();
case EMAIL:
joiner.add(faker.name().username() + "@generated" + UUID.randomUUID().toString()
.replace("-", "").toString());
return joiner.toString();
default:
return "''";
}
}
}

View File

@@ -0,0 +1,36 @@
package com.databasir.core.domain.mock.factory;
import com.databasir.dao.enums.MockDataType;
import lombok.Builder;
import lombok.Getter;
import java.util.Optional;
@Getter
@Builder
public class MockColumnRule {
private String tableName;
private String columnName;
private String columnType;
private Integer dataType;
private MockDataType mockDataType;
private String mockDataScript;
public static MockColumnRule auto(String tableName, String columnName) {
return MockColumnRule.builder()
.tableName(tableName)
.columnName(columnName)
.mockDataType(MockDataType.AUTO)
.build();
}
public Optional<String> getMockDataScript() {
return Optional.ofNullable(mockDataScript);
}
}

View File

@@ -0,0 +1,9 @@
package com.databasir.core.domain.mock.factory;
public interface MockDataFactory {
boolean accept(MockColumnRule rule);
String create(MockColumnRule rule);
}

View File

@@ -0,0 +1,26 @@
package com.databasir.core.domain.mock.factory;
import com.databasir.core.domain.mock.script.MockScriptEvaluator;
import com.databasir.dao.enums.MockDataType;
import lombok.RequiredArgsConstructor;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(0)
@RequiredArgsConstructor
public class ScriptMockDataFactory implements MockDataFactory {
private final MockScriptEvaluator mockScriptEvaluator;
@Override
public boolean accept(MockColumnRule rule) {
return rule.getMockDataType() == MockDataType.SCRIPT;
}
@Override
public String create(MockColumnRule rule) {
return mockScriptEvaluator.evaluate(rule.getMockDataScript().get(), new MockScriptEvaluator.ScriptContext());
}
}

View File

@@ -0,0 +1,16 @@
package com.databasir.core.domain.mock.generator;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class ColumnMockData {
private String columnName;
private String columnType;
private String mockData;
}

View File

@@ -0,0 +1,173 @@
package com.databasir.core.domain.mock.generator;
import com.databasir.core.domain.DomainErrors;
import com.databasir.dao.exception.DataNotExistsException;
import com.databasir.dao.tables.pojos.MockDataRulePojo;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import lombok.Builder;
import lombok.Getter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Getter
@Builder
public class MockDataContext {
private Integer projectId;
private Integer databaseDocumentId;
@Builder.Default
private Map<String, TableMockData> tableMockDataMap = new LinkedHashMap<>();
@Builder.Default
private Map<String, Set<String>> fromReference = new LinkedHashMap<>();
@Builder.Default
private Map<String, Set<String>> toReference = new LinkedHashMap<>();
@Builder.Default
private Map<String, Map<String, MockDataRulePojo>> ruleMap = new LinkedHashMap<>(16);
@Builder.Default
private Map<String, Map<String, TableColumnDocumentPojo>> tableColumnMap = new LinkedHashMap<>(16);
@Builder.Default
private Map<String, Set<String>> mockInProgress = new HashMap<>();
public void addTableMockRules(String tableName, List<MockDataRulePojo> rules) {
var columnRuleMap = rules.stream()
.collect(Collectors.toMap(MockDataRulePojo::getColumnName, Function.identity()));
this.ruleMap.put(tableName, columnRuleMap);
}
public boolean containMockRule(String tableName) {
return ruleMap.containsKey(tableName);
}
public boolean containMockRule(String tableName, String columnName) {
if (!ruleMap.containsKey(tableName)) {
return false;
}
return ruleMap.get(tableName).containsKey(columnName);
}
public Optional<MockDataRulePojo> getMockRule(String tableName, String columnName) {
if (!ruleMap.containsKey(tableName)) {
return Optional.empty();
}
return Optional.ofNullable(ruleMap.get(tableName).get(columnName));
}
public void addTableColumns(String tableName, List<TableColumnDocumentPojo> columns) {
Map<String, TableColumnDocumentPojo> columnMap = new LinkedHashMap<>();
for (TableColumnDocumentPojo column : columns) {
columnMap.put(column.getName(), column);
}
this.tableColumnMap.put(tableName, columnMap);
}
public boolean containsTable(String tableName) {
return tableColumnMap.containsKey(tableName);
}
public boolean containsTableColumn(String tableName, String columnName) {
if (!tableColumnMap.containsKey(tableName)) {
return false;
}
return tableColumnMap.get(tableName).containsKey(columnName);
}
public TableColumnDocumentPojo getTableColumn(String tableName, String columnName) {
if (!tableColumnMap.containsKey(tableName)) {
return null;
}
return tableColumnMap.get(tableName).get(columnName);
}
public boolean containsTableMockData(String tableName) {
return tableMockDataMap.containsKey(tableName);
}
public boolean containsColumnMockData(String tableName, String columName) {
return tableMockDataMap.containsKey(tableName) && tableMockDataMap.get(tableName).containsColumn(columName);
}
public void addTableMockData(TableMockData tableMockData) {
this.tableMockDataMap.put(tableMockData.getTableName(), tableMockData);
}
public TableMockData getTableMockData(String tableName) {
return this.tableMockDataMap.get(tableName);
}
public void addColumnMockData(String tableName, ColumnMockData columnMockData) {
TableMockData mock =
tableMockDataMap.computeIfAbsent(tableName, key -> TableMockData.of(tableName, new ArrayList<>()));
mock.addColumnIfNotExists(columnMockData);
// sort to last
tableMockDataMap.remove(tableName);
tableMockDataMap.put(tableName, mock);
}
public String getRawColumnMockData(String tableName, String columnName) {
if (!this.containsTableMockData(tableName)) {
throw new DataNotExistsException("can't find table mock data by " + tableName);
}
return getTableMockData(tableName)
.getColumnMockData()
.stream()
.filter(t -> t.getColumnName().equals(columnName))
.findFirst()
.map(ColumnMockData::getMockData)
.orElseThrow(DataNotExistsException::new);
}
public boolean isMockInProgress(String tableName, String columnName) {
return this.mockInProgress.containsKey(tableName) && this.mockInProgress.get(tableName).contains(columnName);
}
public void addMockInProgress(String tableName, String columnName) {
Set<String> inProgress = this.mockInProgress.computeIfAbsent(tableName, key -> new HashSet<>());
inProgress.add(columnName);
}
public void removeMockInProgress(String tableName, String columnName) {
Set<String> inProgress = this.mockInProgress.computeIfAbsent(tableName, key -> new HashSet<>());
inProgress.remove(columnName);
}
public String toInsertSql() {
return tableMockDataMap.entrySet()
.stream()
.map(entry -> {
String tableName = entry.getKey();
List<ColumnMockData> columns = entry.getValue().getColumnMockData();
String format = "insert into %s (%s)\nvalues\n(%s);";
String columnNames = columns.stream()
.map(c -> "" + c.getColumnName() + "")
.collect(Collectors.joining(", "));
String columnValues = columns.stream()
.map(ColumnMockData::getMockData)
.collect(Collectors.joining(", "));
return String.format(format, tableName, columnNames, columnValues);
})
.collect(Collectors.joining("\n\n"));
}
public void saveReference(String fromTable, String fromColumn, String toTable, String toColumn) {
if (toReference.containsKey(fromTable) && toReference.get(fromTable).contains(fromColumn)) {
if (fromReference.containsKey(toTable) && fromReference.get(toTable).contains(toColumn)) {
String format = "%s 和 %s 出现了循环引用";
String message = String.format(format, fromTable + "." + fromColumn, toTable + "." + toColumn);
throw DomainErrors.CIRCLE_REFERENCE.exception(message);
}
}
Set<String> fromColumns = this.fromReference.computeIfAbsent(fromTable, key -> new HashSet<>());
fromColumns.add(fromColumn);
Set<String> toColumns = this.toReference.computeIfAbsent(toTable, key -> new HashSet<>());
toColumns.add(toColumn);
}
}

View File

@@ -0,0 +1,129 @@
package com.databasir.core.domain.mock.generator;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.mock.factory.MockColumnRule;
import com.databasir.core.domain.mock.factory.MockDataFactory;
import com.databasir.dao.enums.MockDataType;
import com.databasir.dao.impl.MockDataRuleDao;
import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.impl.TableColumnDocumentDao;
import com.databasir.dao.impl.TableDocumentDao;
import com.databasir.dao.tables.pojos.MockDataRulePojo;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
@RequiredArgsConstructor
@Slf4j
public class MockDataGenerator {
private final List<MockDataFactory> mockDataFactories;
private final MockDataRuleDao mockDataRuleDao;
private final TableDocumentDao tableDocumentDao;
private final TableColumnDocumentDao tableColumnDocumentDao;
private final ProjectDao projectDao;
public String createInsertSql(Integer projectId,
Integer databaseDocId,
String tableName) {
if (!projectDao.existsById(projectId)) {
throw DomainErrors.PROJECT_NOT_FOUND.exception();
}
MockDataContext context = MockDataContext.builder()
.databaseDocumentId(databaseDocId)
.projectId(projectId)
.build();
create(context, tableName);
return context.toInsertSql();
}
private void create(MockDataContext context, String tableName) {
if (!context.containsTable(tableName)) {
var tableOption =
tableDocumentDao.selectByDatabaseDocumentIdAndTableName(context.getDatabaseDocumentId(), tableName);
if (tableOption.isEmpty()) {
log.warn("can not find table => " + tableName);
return;
}
var table = tableOption.get();
var columns = tableColumnDocumentDao.selectByTableDocumentId(table.getId());
context.addTableColumns(tableName, columns);
}
if (!context.containMockRule(tableName)) {
var columnRules = mockDataRuleDao.selectByProjectIdAndTableName(context.getProjectId(), tableName);
context.addTableMockRules(tableName, columnRules);
}
for (TableColumnDocumentPojo column : context.getTableColumnMap().get(tableName).values()) {
if (context.containsColumnMockData(tableName, column.getName())
|| context.isMockInProgress(tableName, column.getName())) {
continue;
}
create(context, tableName, column.getName());
}
}
private void create(MockDataContext context, String tableName, String columnName) {
if (context.containsColumnMockData(tableName, columnName)) {
return;
}
TableColumnDocumentPojo column = context.getTableColumn(tableName, columnName);
Optional<MockDataRulePojo> ruleOption = context.getMockRule(tableName, columnName);
String rawData;
if (ruleOption.isPresent()) {
MockDataRulePojo rule = ruleOption.get();
if (rule.getMockDataType() == MockDataType.REF) {
context.addMockInProgress(tableName, columnName);
context.saveReference(
rule.getTableName(), rule.getColumnName(),
rule.getDependentTableName(), rule.getDependentColumnName()
);
if (context.containsTable(rule.getDependentTableName())) {
create(context, rule.getDependentTableName(), rule.getDependentColumnName());
} else {
create(context, rule.getDependentTableName());
}
context.removeMockInProgress(tableName, columnName);
rawData = context.getRawColumnMockData(rule.getDependentTableName(), rule.getDependentColumnName());
} else {
rawData = createByFactory(column, rule);
}
} else {
rawData = createByFactory(column, null);
}
context.addColumnMockData(tableName, toData(rawData, column));
}
private String createByFactory(TableColumnDocumentPojo column, MockDataRulePojo rule) {
MockDataType mockType = rule == null ? MockDataType.AUTO : rule.getMockDataType();
MockColumnRule colRule = MockColumnRule.builder()
.dataType(column.getDataType())
.mockDataType(mockType)
.mockDataScript(null == rule ? null : rule.getMockDataScript())
.columnName(column.getName())
.columnType(column.getType())
.build();
return mockDataFactories.stream()
.filter(factory -> factory.accept(colRule))
.findFirst()
.map(factory -> factory.create(colRule))
.orElseThrow();
}
private ColumnMockData toData(String data, TableColumnDocumentPojo column) {
return ColumnMockData.builder()
.columnName(column.getName())
.columnType(column.getType())
.mockData(data)
.build();
}
}

View File

@@ -0,0 +1,34 @@
package com.databasir.core.domain.mock.generator;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
import java.util.Objects;
@Getter
@AllArgsConstructor
public class TableMockData {
private String tableName;
private List<ColumnMockData> columnMockData;
public static TableMockData of(String tableName, List<ColumnMockData> columnMockData) {
return new TableMockData(tableName, columnMockData);
}
public void addColumnIfNotExists(ColumnMockData data) {
boolean present = columnMockData.stream()
.anyMatch(col -> Objects.equals(col.getColumnName(), data.getColumnName()));
if (present) {
return;
}
this.columnMockData.add(data);
}
public boolean containsColumn(String columnName) {
return columnMockData.stream()
.anyMatch(col -> Objects.equals(col.getColumnName(), columnName));
}
}

View File

@@ -0,0 +1,14 @@
package com.databasir.core.domain.mock.script;
import lombok.Data;
public interface MockScriptEvaluator {
String evaluate(String script, ScriptContext context);
@Data
class ScriptContext {
}
}

View File

@@ -0,0 +1,21 @@
package com.databasir.core.domain.mock.script;
import lombok.RequiredArgsConstructor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class SpelScriptEvaluator implements MockScriptEvaluator {
private final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
@Override
public String evaluate(String script, ScriptContext context) {
Expression expression = spelExpressionParser.parseExpression(script);
StandardEvaluationContext spelContext = new StandardEvaluationContext(context);
return expression.getValue(spelContext, String.class);
}
}

View File

@@ -0,0 +1,127 @@
package com.databasir.core.domain.mock.validator;
import com.alibaba.excel.util.StringUtils;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.mock.data.ColumnMockRuleSaveRequest;
import com.databasir.core.domain.mock.script.MockScriptEvaluator;
import com.databasir.core.domain.mock.script.SpelScriptEvaluator;
import com.databasir.dao.enums.MockDataType;
import com.databasir.dao.impl.TableColumnDocumentDao;
import com.databasir.dao.impl.TableDocumentDao;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class MockDataSaveValidator {
private final TableDocumentDao tableDocumentDao;
private final TableColumnDocumentDao tableColumnDocumentDao;
private final SpelScriptEvaluator spelScriptEvaluator;
public void validScriptMockType(List<ColumnMockRuleSaveRequest> rules) {
for (ColumnMockRuleSaveRequest request : rules) {
if (request.getMockDataType() != MockDataType.SCRIPT) {
continue;
}
if (StringUtils.isBlank(request.getMockDataScript())) {
throw DomainErrors.MOCK_DATA_SCRIPT_MUST_NOT_BE_BLANK.exception();
}
try {
spelScriptEvaluator.evaluate(request.getMockDataScript(), new MockScriptEvaluator.ScriptContext());
} catch (Exception e) {
throw DomainErrors.INVALID_MOCK_DATA_SCRIPT.exception(e.getMessage());
}
}
}
public void validTableColumn(Integer tableDocId, List<String> requestColumnNames) {
var existsColumnNames = tableColumnDocumentDao.selectByTableDocumentId(tableDocId)
.stream()
.map(TableColumnDocumentPojo::getName)
.collect(Collectors.toSet());
for (String colName : requestColumnNames) {
if (!existsColumnNames.contains(colName)) {
throw DomainErrors.TABLE_META_NOT_FOUND.exception("column "
+ colName
+ " not exists in "
+ tableDocId);
}
}
}
public void validRefMockType(Integer databaseDocId,
List<ColumnMockRuleSaveRequest> rules) {
Map<String, Set<String>> fromTableAndColumn = new HashMap<>();
Map<String, Set<String>> toTableAndColumn = new HashMap<>();
for (ColumnMockRuleSaveRequest request : rules) {
if (request.getMockDataType() != MockDataType.REF) {
continue;
}
forbiddenIfMissRequireParams(request);
forbiddenIfSelfReference(request);
forbiddenIfCircleReference(request, fromTableAndColumn, toTableAndColumn);
forbiddenIfIsInvalidTableOrColumn(databaseDocId, request);
}
}
private void forbiddenIfSelfReference(ColumnMockRuleSaveRequest request) {
if (!Objects.equals(request.getTableName(), request.getDependentTableName())) {
return;
}
if (!Objects.equals(request.getColumnName(), request.getDependentColumnName())) {
return;
}
throw DomainErrors.MUST_NOT_REF_SELF.exception();
}
private void forbiddenIfMissRequireParams(ColumnMockRuleSaveRequest request) {
if (StringUtils.isBlank(request.getDependentTableName())) {
throw DomainErrors.DEPENDENT_COLUMN_NAME_MUST_NOT_BE_BLANK.exception();
}
if (StringUtils.isBlank(request.getDependentColumnName())) {
throw DomainErrors.DEPENDENT_COLUMN_NAME_MUST_NOT_BE_BLANK.exception();
}
}
private void forbiddenIfIsInvalidTableOrColumn(Integer docId, ColumnMockRuleSaveRequest request) {
String dependentTableName = request.getDependentTableName();
var dependentTable = tableDocumentDao.selectByDatabaseDocumentIdAndTableName(docId, dependentTableName)
.orElseThrow(DomainErrors.TABLE_META_NOT_FOUND::exception);
if (!tableColumnDocumentDao.exists(dependentTable.getId(), request.getDependentColumnName())) {
throw DomainErrors.TABLE_META_NOT_FOUND.exception("列字段 "
+ request.getDependentColumnName()
+ "不存在");
}
}
private void forbiddenIfCircleReference(ColumnMockRuleSaveRequest request,
Map<String, Set<String>> fromTableAndColumn,
Map<String, Set<String>> toTableAndColumn) {
if (toTableAndColumn.containsKey(request.getTableName())
&& toTableAndColumn.get(request.getTableName()).contains(request.getColumnName())) {
if (fromTableAndColumn.containsKey(request.getDependentTableName())
&& fromTableAndColumn.get(request.getDependentTableName())
.contains(request.getDependentColumnName())) {
String format = "%s 和 %s 出现了循环引用";
String from = request.getTableName() + "." + request.getColumnName();
String to = request.getDependentTableName() + "." + request.getDependentColumnName();
String message = String.format(format, from, to);
throw DomainErrors.CIRCLE_REFERENCE.exception(message);
}
}
Set<String> fromColumns =
fromTableAndColumn.computeIfAbsent(request.getTableName(), key -> new HashSet<String>());
fromColumns.add(request.getColumnName());
Set<String> toColumns =
toTableAndColumn.computeIfAbsent(request.getDependentTableName(), key -> new HashSet<String>());
toColumns.add(request.getDependentColumnName());
}
}

View File

@@ -0,0 +1,52 @@
package com.databasir.core.domain.mock.validator;
import com.databasir.core.domain.DomainErrors;
import com.databasir.dao.impl.DatabaseDocumentDao;
import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.impl.TableDocumentDao;
import com.databasir.dao.tables.pojos.DatabaseDocumentPojo;
import com.databasir.dao.tables.pojos.TableDocumentPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
@RequiredArgsConstructor
public class MockDataValidator {
private final ProjectDao projectDao;
private final DatabaseDocumentDao databaseDocumentDao;
private final TableDocumentDao tableDocumentDao;
public void validProject(Integer projectId) {
if (!projectDao.existsById(projectId)) {
throw DomainErrors.PROJECT_NOT_FOUND.exception();
}
}
public DatabaseDocumentPojo validAndGetDatabaseDocumentPojo(Integer projectId, Long version) {
Optional<DatabaseDocumentPojo> databaseDoc;
if (version == null) {
databaseDoc = databaseDocumentDao.selectNotArchivedByProjectId(projectId);
} else {
databaseDoc = databaseDocumentDao.selectOptionalByProjectIdAndVersion(projectId, version);
}
if (databaseDoc.isEmpty()) {
throw DomainErrors.DATABASE_META_NOT_FOUND.exception();
}
return databaseDoc.get();
}
public TableDocumentPojo validAndGetTableDocumentPojo(Integer databaseDocId, Integer tableId) {
Optional<TableDocumentPojo> tableOption =
tableDocumentDao.selectByDatabaseDocumentIdAndId(databaseDocId, tableId);
if (tableOption.isEmpty()) {
throw DomainErrors.DATABASE_META_NOT_FOUND.exception("表数据不存在");
}
return tableOption.get();
}
}