检查实体类是否少字段
This commit is contained in:
parent
b9ff5f0035
commit
2b617f99cb
@ -0,0 +1,500 @@
|
|||||||
|
package com.zhgd.xmgl.modules.validator;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||||
|
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||||
|
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
|
||||||
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DatabaseMetaData;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class BulkEntityValidator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("db1DataSource")
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
private final Map<String, List<String>> tableColumnsCache = new ConcurrentHashMap<>();
|
||||||
|
private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
|
||||||
|
private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用启动时自动验证所有实体类
|
||||||
|
*/
|
||||||
|
@PostConstruct
|
||||||
|
public void autoValidateOnStartup() {
|
||||||
|
log.info("开始自动验证实体类与数据库表映射...");
|
||||||
|
try {
|
||||||
|
Map<String, ValidationResult> results = validateAllEntitiesInProject();
|
||||||
|
printValidationSummary(results);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("实体类映射验证失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证项目中所有实体类 - 返回验证结果
|
||||||
|
*/
|
||||||
|
public Map<String, ValidationResult> validateAllEntitiesInProject() {
|
||||||
|
// 常见的实体类包路径,可以根据实际情况调整
|
||||||
|
String[] commonEntityPackages = {
|
||||||
|
"com.zhgd.xmgl.modules.**.entity"
|
||||||
|
};
|
||||||
|
|
||||||
|
Map<String, ValidationResult> allResults = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (String basePackage : commonEntityPackages) {
|
||||||
|
try {
|
||||||
|
Map<String, ValidationResult> packageResults = validatePackageEntities(basePackage);
|
||||||
|
allResults.putAll(packageResults);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("扫描包 {} 失败: {}", basePackage, e.getMessage());
|
||||||
|
allResults.put(basePackage, ValidationResult.error(basePackage, "扫描包失败: " + e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证指定包下的所有实体类
|
||||||
|
*/
|
||||||
|
public Map<String, ValidationResult> validatePackageEntities(String basePackage) {
|
||||||
|
log.info("开始扫描包: {}", basePackage);
|
||||||
|
Map<String, ValidationResult> results = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
Set<Class<?>> entityClasses = findEntitiesInPackage(basePackage);
|
||||||
|
log.info("在包 {} 中找到 {} 个实体类", basePackage, entityClasses.size());
|
||||||
|
|
||||||
|
for (Class<?> entityClass : entityClasses) {
|
||||||
|
try {
|
||||||
|
ValidationResult result = validateSingleEntity(entityClass);
|
||||||
|
results.put(entityClass.getSimpleName(), result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("验证实体类 {} 失败: {}", entityClass.getSimpleName(), e.getMessage());
|
||||||
|
results.put(entityClass.getSimpleName(),
|
||||||
|
ValidationResult.error(entityClass.getSimpleName(), e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证单个实体类
|
||||||
|
*/
|
||||||
|
public ValidationResult validateSingleEntity(Class<?> entityClass) {
|
||||||
|
TableName tableAnnotation = entityClass.getAnnotation(TableName.class);
|
||||||
|
if (tableAnnotation == null) {
|
||||||
|
return ValidationResult.error(entityClass.getSimpleName(), "缺少 @TableName 注解");
|
||||||
|
}
|
||||||
|
|
||||||
|
String tableName = tableAnnotation.value();
|
||||||
|
if (tableName.isEmpty()) {
|
||||||
|
return ValidationResult.error(entityClass.getSimpleName(), "@TableName 注解值为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查表是否存在
|
||||||
|
if (!isTableExists(tableName)) {
|
||||||
|
return ValidationResult.error(entityClass.getSimpleName(),
|
||||||
|
"数据库表不存在: " + tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> dbColumns = getTableColumns(tableName);
|
||||||
|
List<EntityFieldInfo> entityFields = getEntityFields(entityClass);
|
||||||
|
|
||||||
|
// 对比字段映射
|
||||||
|
List<String> missingInEntity = findMissingInEntity(dbColumns, entityFields);
|
||||||
|
List<String> missingInDb = findMissingInDatabase(entityFields, dbColumns);
|
||||||
|
List<String> typeMismatches = findTypeMismatches(entityClass, tableName, entityFields, dbColumns);
|
||||||
|
|
||||||
|
return ValidationResult.of(entityClass.getSimpleName(), tableName,
|
||||||
|
missingInEntity, missingInDb, typeMismatches);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描包下的所有实体类
|
||||||
|
*/
|
||||||
|
private Set<Class<?>> findEntitiesInPackage(String basePackage) {
|
||||||
|
Set<Class<?>> entities = new LinkedHashSet<>();
|
||||||
|
try {
|
||||||
|
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
|
||||||
|
ClassUtils.convertClassNameToResourcePath(basePackage) + "/**/*.class";
|
||||||
|
|
||||||
|
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
|
||||||
|
|
||||||
|
for (Resource resource : resources) {
|
||||||
|
if (resource.isReadable()) {
|
||||||
|
try {
|
||||||
|
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
|
||||||
|
String className = metadataReader.getClassMetadata().getClassName();
|
||||||
|
|
||||||
|
// 过滤内部类、匿名类等
|
||||||
|
if (className.contains("$")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> clazz = Class.forName(className);
|
||||||
|
if (clazz.isAnnotationPresent(TableName.class)) {
|
||||||
|
entities.add(clazz);
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
log.warn("无法加载类: {}", resource.getFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("扫描包 {} 失败: {}", basePackage, e.getMessage());
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库表的所有字段
|
||||||
|
*/
|
||||||
|
private List<String> getTableColumns(String tableName) {
|
||||||
|
return tableColumnsCache.computeIfAbsent(tableName, this::fetchTableColumnsFromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> fetchTableColumnsFromDb(String tableName) {
|
||||||
|
List<String> columns = new ArrayList<>();
|
||||||
|
try (Connection conn = dataSource.getConnection()) {
|
||||||
|
DatabaseMetaData metaData = conn.getMetaData();
|
||||||
|
|
||||||
|
// 尝试不同的表名匹配策略
|
||||||
|
ResultSet rs = metaData.getColumns(null, null, tableName, null);
|
||||||
|
if (!rs.next()) {
|
||||||
|
// 尝试小写
|
||||||
|
rs = metaData.getColumns(null, null, tableName.toLowerCase(), null);
|
||||||
|
}
|
||||||
|
if (!rs.next()) {
|
||||||
|
// 尝试大写
|
||||||
|
rs = metaData.getColumns(null, null, tableName.toUpperCase(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置游标
|
||||||
|
rs.beforeFirst();
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String columnName = rs.getString("COLUMN_NAME");
|
||||||
|
// 清理列名中的特殊字符
|
||||||
|
columnName = cleanColumnName(columnName);
|
||||||
|
columns.add(columnName.toLowerCase());
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("获取表 {} 字段失败: {}", tableName, e.getMessage());
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查表是否存在
|
||||||
|
*/
|
||||||
|
private boolean isTableExists(String tableName) {
|
||||||
|
try (Connection conn = dataSource.getConnection()) {
|
||||||
|
DatabaseMetaData metaData = conn.getMetaData();
|
||||||
|
ResultSet rs = metaData.getTables(null, null, tableName, null);
|
||||||
|
if (rs.next()) return true;
|
||||||
|
|
||||||
|
// 尝试小写
|
||||||
|
rs = metaData.getTables(null, null, tableName.toLowerCase(), null);
|
||||||
|
if (rs.next()) return true;
|
||||||
|
|
||||||
|
// 尝试大写
|
||||||
|
rs = metaData.getTables(null, null, tableName.toUpperCase(), null);
|
||||||
|
return rs.next();
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error("检查表是否存在失败: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体类的所有字段信息
|
||||||
|
*/
|
||||||
|
private List<EntityFieldInfo> getEntityFields(Class<?> entityClass) {
|
||||||
|
List<EntityFieldInfo> fields = new ArrayList<>();
|
||||||
|
Class<?> currentClass = entityClass;
|
||||||
|
|
||||||
|
// 遍历所有父类直到 Object
|
||||||
|
while (currentClass != null && currentClass != Object.class) {
|
||||||
|
Field[] declaredFields = currentClass.getDeclaredFields();
|
||||||
|
|
||||||
|
for (Field field : declaredFields) {
|
||||||
|
// 忽略静态字段、序列化字段等
|
||||||
|
if (shouldIgnoreField(field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityFieldInfo fieldInfo = extractFieldInfo(field);
|
||||||
|
if (fieldInfo != null) {
|
||||||
|
fields.add(fieldInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClass = currentClass.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否应该忽略该字段
|
||||||
|
*/
|
||||||
|
private boolean shouldIgnoreField(Field field) {
|
||||||
|
int modifiers = field.getModifiers();
|
||||||
|
|
||||||
|
// 忽略静态字段
|
||||||
|
if (Modifier.isStatic(modifiers)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略序列化字段
|
||||||
|
if ("serialVersionUID".equals(field.getName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 忽略被 @TableField(exist = false) 标记的字段
|
||||||
|
TableField tableField = field.getAnnotation(TableField.class);
|
||||||
|
if (tableField != null && !tableField.exist()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取字段信息
|
||||||
|
*/
|
||||||
|
private EntityFieldInfo extractFieldInfo(Field field) {
|
||||||
|
String fieldName = field.getName();
|
||||||
|
String columnName;
|
||||||
|
|
||||||
|
TableField tableField = field.getAnnotation(TableField.class);
|
||||||
|
if (tableField != null && !tableField.value().isEmpty()) {
|
||||||
|
columnName = tableField.value();
|
||||||
|
} else {
|
||||||
|
columnName = StrUtil.toUnderlineCase(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理列名中的特殊字符
|
||||||
|
columnName = cleanColumnName(columnName);
|
||||||
|
|
||||||
|
return new EntityFieldInfo(
|
||||||
|
fieldName,
|
||||||
|
columnName.toLowerCase(),
|
||||||
|
field.getType().getSimpleName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 清理列名,处理各种特殊情况
|
||||||
|
*/
|
||||||
|
private String cleanColumnName(String columnName) {
|
||||||
|
if (columnName == null || columnName.isEmpty()) {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉反引号、单引号、双引号等
|
||||||
|
columnName = columnName.replace("`", "")
|
||||||
|
.replace("'", "")
|
||||||
|
.replace("\"", "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// 如果列名被方括号包围(SQL Server风格),去掉方括号
|
||||||
|
if (columnName.startsWith("[") && columnName.endsWith("]")) {
|
||||||
|
columnName = columnName.substring(1, columnName.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 找出数据库中有的但实体类缺少的字段
|
||||||
|
*/
|
||||||
|
private List<String> findMissingInEntity(List<String> dbColumns, List<EntityFieldInfo> entityFields) {
|
||||||
|
Set<String> entityColumnNames = entityFields.stream()
|
||||||
|
.map(EntityFieldInfo::getColumnName)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return dbColumns.stream()
|
||||||
|
.filter(column -> !entityColumnNames.contains(column))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找出实体类有的但数据库缺少的字段
|
||||||
|
*/
|
||||||
|
private List<String> findMissingInDatabase(List<EntityFieldInfo> entityFields, List<String> dbColumns) {
|
||||||
|
Set<String> dbColumnSet = new HashSet<>(dbColumns);
|
||||||
|
|
||||||
|
return entityFields.stream()
|
||||||
|
.map(EntityFieldInfo::getColumnName)
|
||||||
|
.filter(columnName -> !dbColumnSet.contains(columnName))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找出类型不匹配的字段(基础实现)
|
||||||
|
*/
|
||||||
|
private List<String> findTypeMismatches(Class<?> entityClass, String tableName,
|
||||||
|
List<EntityFieldInfo> entityFields, List<String> dbColumns) {
|
||||||
|
// 这里可以实现更复杂的类型匹配检查
|
||||||
|
// 目前返回空列表,可以根据需要扩展
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印验证摘要
|
||||||
|
*/
|
||||||
|
public void printValidationSummary(Map<String, ValidationResult> results) {
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
log.info("🎉 没有找到需要验证的实体类!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long errorCount = results.values().stream()
|
||||||
|
.filter(ValidationResult::hasIssues)
|
||||||
|
.count();
|
||||||
|
long successCount = results.size() - errorCount;
|
||||||
|
|
||||||
|
log.info("验证完成!总计验证 {} 个实体类,成功: {},存在问题: {}",
|
||||||
|
results.size(), successCount, errorCount);
|
||||||
|
|
||||||
|
if (errorCount == 0) {
|
||||||
|
log.info("🎉 所有实体类映射验证通过!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warn("⚠️ 发现 {} 个实体类存在映射问题:", errorCount);
|
||||||
|
|
||||||
|
results.forEach((className, result) -> {
|
||||||
|
if (result.hasIssues()) {
|
||||||
|
log.warn("\n==========================================");
|
||||||
|
log.warn("实体类: {} -> 表: {}", className, result.getTableName());
|
||||||
|
|
||||||
|
if (!result.getMissingInEntity().isEmpty()) {
|
||||||
|
log.warn("❌ 数据库有但实体缺少: {}", result.getMissingInEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.getMissingInDb().isEmpty()) {
|
||||||
|
log.warn("❌ 实体有但数据库缺少: {}", result.getMissingInDb());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.getTypeMismatches().isEmpty()) {
|
||||||
|
log.warn("⚠️ 类型不匹配: {}", result.getTypeMismatches());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getError() != null) {
|
||||||
|
log.warn("💥 验证错误: {}", result.getError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.warn("\n==========================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成详细的验证报告
|
||||||
|
*/
|
||||||
|
public String generateDetailedReport() {
|
||||||
|
Map<String, ValidationResult> results = validateAllEntitiesInProject();
|
||||||
|
|
||||||
|
StringBuilder report = new StringBuilder();
|
||||||
|
report.append("实体类映射验证报告\n");
|
||||||
|
report.append("生成时间: ").append(new Date()).append("\n\n");
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
report.append("✅ 没有找到需要验证的实体类\n");
|
||||||
|
return report.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
long errorCount = results.values().stream()
|
||||||
|
.filter(ValidationResult::hasIssues)
|
||||||
|
.count();
|
||||||
|
long successCount = results.size() - errorCount;
|
||||||
|
|
||||||
|
report.append("验证统计: 总计 ").append(results.size())
|
||||||
|
.append(" 个实体类,成功: ").append(successCount)
|
||||||
|
.append(",存在问题: ").append(errorCount).append("\n\n");
|
||||||
|
|
||||||
|
if (errorCount == 0) {
|
||||||
|
report.append("✅ 所有实体类映射正确\n");
|
||||||
|
return report.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
report.append("发现 ").append(errorCount).append(" 个问题:\n\n");
|
||||||
|
|
||||||
|
results.forEach((className, result) -> {
|
||||||
|
if (result.hasIssues()) {
|
||||||
|
report.append("实体类: ").append(className)
|
||||||
|
.append(" -> 表: ").append(result.getTableName()).append("\n");
|
||||||
|
|
||||||
|
if (!result.getMissingInEntity().isEmpty()) {
|
||||||
|
report.append(" 数据库有但实体缺少: ").append(result.getMissingInEntity()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.getMissingInDb().isEmpty()) {
|
||||||
|
report.append(" 实体有但数据库缺少: ").append(result.getMissingInDb()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.getTypeMismatches().isEmpty()) {
|
||||||
|
report.append(" 类型不匹配: ").append(result.getTypeMismatches()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.getError() != null) {
|
||||||
|
report.append(" 错误: ").append(result.getError()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
report.append("\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return report.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证统计信息
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getValidationStatistics() {
|
||||||
|
Map<String, ValidationResult> results = validateAllEntitiesInProject();
|
||||||
|
|
||||||
|
long total = results.size();
|
||||||
|
long errorCount = results.values().stream()
|
||||||
|
.filter(ValidationResult::hasIssues)
|
||||||
|
.count();
|
||||||
|
long successCount = total - errorCount;
|
||||||
|
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
stats.put("totalEntities", total);
|
||||||
|
stats.put("successCount", successCount);
|
||||||
|
stats.put("errorCount", errorCount);
|
||||||
|
stats.put("successRate", total > 0 ? (successCount * 100.0 / total) : 0);
|
||||||
|
stats.put("lastValidated", new Date());
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.zhgd.xmgl.modules.validator;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体字段信息类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class EntityFieldInfo {
|
||||||
|
private String fieldName;
|
||||||
|
private String columnName;
|
||||||
|
private String fieldType;
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
package com.zhgd.xmgl.modules.validator;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/xmgl/entity-validator")
|
||||||
|
public class EntityValidatorController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private BulkEntityValidator validator;
|
||||||
|
|
||||||
|
@GetMapping("/validate-all")
|
||||||
|
public Map<String, Object> validateAll() {
|
||||||
|
Map<String, ValidationResult> results = validator.validateAllEntitiesInProject();
|
||||||
|
|
||||||
|
long errorCount = results.values().stream()
|
||||||
|
.filter(ValidationResult::hasIssues)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("totalEntities", results.size());
|
||||||
|
response.put("errorCount", errorCount);
|
||||||
|
response.put("successCount", results.size() - errorCount);
|
||||||
|
response.put("results", results);
|
||||||
|
response.put("report", validator.generateDetailedReport());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/validate-package")
|
||||||
|
public Map<String, Object> validatePackage(@RequestParam String packageName) {
|
||||||
|
Map<String, ValidationResult> results = validator.validatePackageEntities(packageName);
|
||||||
|
|
||||||
|
long errorCount = results.values().stream()
|
||||||
|
.filter(ValidationResult::hasIssues)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("package", packageName);
|
||||||
|
response.put("totalEntities", results.size());
|
||||||
|
response.put("errorCount", errorCount);
|
||||||
|
response.put("successCount", results.size() - errorCount);
|
||||||
|
response.put("results", results);
|
||||||
|
response.put("hasIssues", errorCount > 0);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/status")
|
||||||
|
public Map<String, Object> getValidationStatus() {
|
||||||
|
return validator.getValidationStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/report")
|
||||||
|
public String getDetailedReport() {
|
||||||
|
return validator.generateDetailedReport();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.zhgd.xmgl.modules.validator;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证结果类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class ValidationResult {
|
||||||
|
private String className;
|
||||||
|
private String tableName;
|
||||||
|
private List<String> missingInEntity;
|
||||||
|
private List<String> missingInDb;
|
||||||
|
private List<String> typeMismatches;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
public static ValidationResult of(String className, String tableName,
|
||||||
|
List<String> missingInEntity, List<String> missingInDb,
|
||||||
|
List<String> typeMismatches) {
|
||||||
|
return new ValidationResult(className, tableName, missingInEntity, missingInDb, typeMismatches, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValidationResult error(String className, String error) {
|
||||||
|
return new ValidationResult(className, null, Collections.emptyList(),
|
||||||
|
Collections.emptyList(), Collections.emptyList(), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasIssues() {
|
||||||
|
return error != null ||
|
||||||
|
!missingInEntity.isEmpty() ||
|
||||||
|
!missingInDb.isEmpty() ||
|
||||||
|
!typeMismatches.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.zhgd.xmgl.modules.validator;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ValidatorConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
public BulkEntityValidator bulkEntityValidator() {
|
||||||
|
return new BulkEntityValidator();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user