diff --git a/src/main/java/com/zhgd/xmgl/modules/validator/BulkEntityValidator.java b/src/main/java/com/zhgd/xmgl/modules/validator/BulkEntityValidator.java deleted file mode 100644 index 54a1a13a7..000000000 --- a/src/main/java/com/zhgd/xmgl/modules/validator/BulkEntityValidator.java +++ /dev/null @@ -1,499 +0,0 @@ -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> tableColumnsCache = new ConcurrentHashMap<>(); - private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); - private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); - - /** - * 应用启动时自动验证所有实体类 - */ - public void autoValidateOnStartup() { - log.info("开始自动验证实体类与数据库表映射..."); - try { - Map results = validateAllEntitiesInProject(); - printValidationSummary(results); - } catch (Exception e) { - log.error("实体类映射验证失败", e); - } - } - - /** - * 验证项目中所有实体类 - 返回验证结果 - */ - public Map validateAllEntitiesInProject() { - // 常见的实体类包路径,可以根据实际情况调整 - String[] commonEntityPackages = { - "com.zhgd.xmgl.modules.**.entity" - }; - - Map allResults = new LinkedHashMap<>(); - - for (String basePackage : commonEntityPackages) { - try { - Map 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 validatePackageEntities(String basePackage) { - log.info("开始扫描包: {}", basePackage); - Map results = new LinkedHashMap<>(); - - Set> 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 dbColumns = getTableColumns(tableName); - List entityFields = getEntityFields(entityClass); - - // 对比字段映射 - List missingInEntity = findMissingInEntity(dbColumns, entityFields); - List missingInDb = findMissingInDatabase(entityFields, dbColumns); - List typeMismatches = findTypeMismatches(entityClass, tableName, entityFields, dbColumns); - - return ValidationResult.of(entityClass.getSimpleName(), tableName, - missingInEntity, missingInDb, typeMismatches); - } - - /** - * 扫描包下的所有实体类 - */ - private Set> findEntitiesInPackage(String basePackage) { - Set> 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 getTableColumns(String tableName) { - return tableColumnsCache.computeIfAbsent(tableName, this::fetchTableColumnsFromDb); - } - - private List fetchTableColumnsFromDb(String tableName) { - List 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 getEntityFields(Class entityClass) { - List 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 findMissingInEntity(List dbColumns, List entityFields) { - Set entityColumnNames = entityFields.stream() - .map(EntityFieldInfo::getColumnName) - .collect(Collectors.toSet()); - - return dbColumns.stream() - .filter(column -> !entityColumnNames.contains(column)) - .collect(Collectors.toList()); - } - - /** - * 找出实体类有的但数据库缺少的字段 - */ - private List findMissingInDatabase(List entityFields, List dbColumns) { - Set dbColumnSet = new HashSet<>(dbColumns); - - return entityFields.stream() - .map(EntityFieldInfo::getColumnName) - .filter(columnName -> !dbColumnSet.contains(columnName)) - .collect(Collectors.toList()); - } - - /** - * 找出类型不匹配的字段(基础实现) - */ - private List findTypeMismatches(Class entityClass, String tableName, - List entityFields, List dbColumns) { - // 这里可以实现更复杂的类型匹配检查 - // 目前返回空列表,可以根据需要扩展 - return new ArrayList<>(); - } - - /** - * 打印验证摘要 - */ - public void printValidationSummary(Map 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 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 getValidationStatistics() { - Map results = validateAllEntitiesInProject(); - - long total = results.size(); - long errorCount = results.values().stream() - .filter(ValidationResult::hasIssues) - .count(); - long successCount = total - errorCount; - - Map 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; - } - -} diff --git a/src/main/java/com/zhgd/xmgl/modules/validator/EntityFieldInfo.java b/src/main/java/com/zhgd/xmgl/modules/validator/EntityFieldInfo.java deleted file mode 100644 index 055033990..000000000 --- a/src/main/java/com/zhgd/xmgl/modules/validator/EntityFieldInfo.java +++ /dev/null @@ -1,15 +0,0 @@ -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; -} diff --git a/src/main/java/com/zhgd/xmgl/modules/validator/EntityValidatorController.java b/src/main/java/com/zhgd/xmgl/modules/validator/EntityValidatorController.java deleted file mode 100644 index ede100d66..000000000 --- a/src/main/java/com/zhgd/xmgl/modules/validator/EntityValidatorController.java +++ /dev/null @@ -1,62 +0,0 @@ -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 validateAll() { - Map results = validator.validateAllEntitiesInProject(); - - long errorCount = results.values().stream() - .filter(ValidationResult::hasIssues) - .count(); - - Map 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 validatePackage(@RequestParam String packageName) { - Map results = validator.validatePackageEntities(packageName); - - long errorCount = results.values().stream() - .filter(ValidationResult::hasIssues) - .count(); - - Map 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 getValidationStatus() { - return validator.getValidationStatistics(); - } - - @GetMapping("/report") - public String getDetailedReport() { - return validator.generateDetailedReport(); - } -} diff --git a/src/main/java/com/zhgd/xmgl/modules/validator/ValidationResult.java b/src/main/java/com/zhgd/xmgl/modules/validator/ValidationResult.java deleted file mode 100644 index 03fd04bec..000000000 --- a/src/main/java/com/zhgd/xmgl/modules/validator/ValidationResult.java +++ /dev/null @@ -1,40 +0,0 @@ -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 missingInEntity; - private List missingInDb; - private List typeMismatches; - private String error; - - public static ValidationResult of(String className, String tableName, - List missingInEntity, List missingInDb, - List 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(); - } -} - diff --git a/src/main/java/com/zhgd/xmgl/modules/validator/ValidatorConfig.java b/src/main/java/com/zhgd/xmgl/modules/validator/ValidatorConfig.java deleted file mode 100644 index 3f740e988..000000000 --- a/src/main/java/com/zhgd/xmgl/modules/validator/ValidatorConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -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(); - } -} diff --git a/src/main/java/com/zhgd/xmgl/task/Mcs8Task.java b/src/main/java/com/zhgd/xmgl/task/Mcs8Task.java index 209fc909e..d0a8f62fb 100644 --- a/src/main/java/com/zhgd/xmgl/task/Mcs8Task.java +++ b/src/main/java/com/zhgd/xmgl/task/Mcs8Task.java @@ -21,7 +21,7 @@ import com.zhgd.xmgl.util.AsyncTaskUtil; import com.zhgd.xmgl.util.HikVideoUtil; import com.zhgd.xmgl.util.PathUtil; import lombok.extern.slf4j.Slf4j; -import net.javacrumbs.shedlock.core.SchedulerLock; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; @@ -68,7 +68,7 @@ public class Mcs8Task { /** * 定时从Mcs8获取执法记录仪文件 */ - @SchedulerLock(name = "getPoliceCameraItemFile", lockAtMostFor = 1000 * 60 * 5, lockAtLeastFor = 1000 * 60 * 2) + @SchedulerLock(name = "getPoliceCameraItemFile", lockAtMostFor = "PT300S", lockAtLeastFor = "PT120S") @Scheduled(cron = "0 */5 * * * ?") @RequestMapping("getPoliceCameraItemFile") public void getPoliceCameraItemFile() { @@ -91,7 +91,7 @@ public class Mcs8Task { /** * 更新执法记录仪的封面 */ - @SchedulerLock(name = "updatePoliceCameraItemCoverUrl", lockAtMostFor = 1000 * 60 * 10, lockAtLeastFor = 1000 * 60 * 5) + @SchedulerLock(name = "updatePoliceCameraItemCoverUrl", lockAtMostFor = "PT600S", lockAtLeastFor = "PT300S") @Scheduled(cron = "* */30 * * * ?") @RequestMapping("updatePoliceCameraItemCoverUrl") public void updatePoliceCameraItemCoverUrl() {