diff --git a/pom.xml b/pom.xml index 4d6ac7f0..4e85322d 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,8 @@ 1.72 1.26.2 + + 2.17.0 2.7.0 @@ -53,8 +55,8 @@ 3.2.2 3.2.2 - 3.11.0 - 3.0.0 + 3.14.0 + 3.5.4 1.3.0 @@ -292,6 +294,13 @@ ${commons-compress.version} + + + commons-io + commons-io + ${commons-io.version} + + io.github.linpeilie mapstruct-plus-spring-boot-starter diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/AssetClassification.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/AssetClassification.java new file mode 100644 index 00000000..c280be39 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/AssetClassification.java @@ -0,0 +1,44 @@ +package org.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.core.domain.BaseEntity; + +import java.io.Serial; + +/** + * 高等学校固定资产分类与代码对象 asset_classification + * + * @author cass + * @date 2025-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("asset_classification") +public class AssetClassification extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 分类代码 + */ + private String classificationCode; + + /** + * 分类名称 + */ + private String classificationName; + + /** + * 国标名称 + */ + private String gbName; +} diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/bo/AssetClassificationBo.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/bo/AssetClassificationBo.java new file mode 100644 index 00000000..10f2aec8 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/bo/AssetClassificationBo.java @@ -0,0 +1,49 @@ +package org.ruoyi.system.domain.bo; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.core.validate.AddGroup; +import org.ruoyi.common.core.validate.EditGroup; +import org.ruoyi.core.domain.BaseEntity; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.io.Serial; + +/** + * 高等学校固定资产分类与代码业务对象 asset_classification + * + * @author cass + * @date 2025-09-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class AssetClassificationBo extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = {EditGroup.class}) + private Long id; + + /** + * 分类代码 + */ + @NotBlank(message = "分类代码不能为空", groups = {AddGroup.class, EditGroup.class}) + private String classificationCode; + + /** + * 分类名称 + */ + @NotBlank(message = "分类名称不能为空", groups = {AddGroup.class, EditGroup.class}) + private String classificationName; + + /** + * 国标名称 + */ + @NotBlank(message = "国标名称不能为空", groups = {AddGroup.class, EditGroup.class}) + private String gbName; +} diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationImportVo.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationImportVo.java new file mode 100644 index 00000000..bf47610b --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationImportVo.java @@ -0,0 +1,38 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 高等学校固定资产分类与代码导入视图对象 + * + * @author cass + * @date 2025-09-24 + */ +@Data +public class AssetClassificationImportVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 分类代码 + */ + @ExcelProperty(value = "分类代码") + private String classificationCode; + + /** + * 分类名称 + */ + @ExcelProperty(value = "分类名称") + private String classificationName; + + /** + * 国标名称 + */ + @ExcelProperty(value = "国标名称") + private String gbName; +} diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationVo.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationVo.java new file mode 100644 index 00000000..67b9de35 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/domain/vo/AssetClassificationVo.java @@ -0,0 +1,79 @@ +package org.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 高等学校固定资产分类与代码视图对象 asset_classification + * + * @author cass + * @date 2025-09-24 + */ +@Data +@ExcelIgnoreUnannotated +public class AssetClassificationVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 分类代码 + */ + @ExcelProperty(value = "分类代码") + private String classificationCode; + + /** + * 分类名称 + */ + @ExcelProperty(value = "分类名称") + private String classificationName; + + /** + * 国标名称 + */ + @ExcelProperty(value = "国标名称") + private String gbName; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private Date createTime; + + /** + * 更新时间 + */ + @ExcelProperty(value = "更新时间") + private Date updateTime; + + /** + * 创建者 + */ + @ExcelProperty(value = "创建者") + private String createBy; + + /** + * 更新者 + */ + @ExcelProperty(value = "更新者") + private String updateBy; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; +} diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/AssetClassificationMapper.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/AssetClassificationMapper.java new file mode 100644 index 00000000..5539c52b --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/mapper/AssetClassificationMapper.java @@ -0,0 +1,23 @@ +package org.ruoyi.system.mapper; + +import org.apache.ibatis.annotations.Param; +import org.ruoyi.core.mapper.BaseMapperPlus; +import org.ruoyi.system.domain.AssetClassification; +import org.ruoyi.system.domain.vo.AssetClassificationVo; + +/** + * 高等学校固定资产分类与代码Mapper接口 + * + * @author cass + * @date 2025-09-24 + */ +public interface AssetClassificationMapper extends BaseMapperPlus { + + /** + * 根据分类代码查询 + * + * @param classificationCode 分类代码 + * @return 高等学校固定资产分类与代码 + */ + AssetClassification queryByClassificationCode(@Param("classificationCode") String classificationCode); +} diff --git a/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/IAssetClassificationService.java b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/IAssetClassificationService.java new file mode 100644 index 00000000..3c1a9963 --- /dev/null +++ b/ruoyi-modules-api/ruoyi-system-api/src/main/java/org/ruoyi/system/service/IAssetClassificationService.java @@ -0,0 +1,63 @@ +package org.ruoyi.system.service; + +import org.ruoyi.core.page.TableDataInfo; +import org.ruoyi.system.domain.AssetClassification; +import org.ruoyi.system.domain.bo.AssetClassificationBo; +import org.ruoyi.system.domain.vo.AssetClassificationVo; + +import java.util.Collection; +import java.util.List; + +/** + * 高等学校固定资产分类与代码Service接口 + * + * @author cass + * @date 2025-09-24 + */ +public interface IAssetClassificationService { + + /** + * 查询高等学校固定资产分类与代码 + */ + AssetClassificationVo queryById(Long id); + + /** + * 根据分类代码查询 + */ + AssetClassification queryByClassificationCode(String classificationCode); + + /** + * 查询高等学校固定资产分类与代码列表 + */ + TableDataInfo queryPageList(AssetClassificationBo bo); + + /** + * 查询高等学校固定资产分类与代码列表 + */ + List queryList(AssetClassificationBo bo); + + /** + * 新增高等学校固定资产分类与代码 + */ + Boolean insertByBo(AssetClassificationBo bo); + + /** + * 修改高等学校固定资产分类与代码 + */ + Boolean updateByBo(AssetClassificationBo bo); + + /** + * 校验并批量删除高等学校固定资产分类与代码信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 导入数据 + * + * @param dataList 数据列表 + * @param isUpdateSupport 是否更新支持 + * @param operName 操作用户 + * @return 结果 + */ + String importData(List dataList, Boolean isUpdateSupport, String operName); +} diff --git a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseImportTest.java b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseImportTest.java deleted file mode 100644 index b646cf43..00000000 --- a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseImportTest.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.ruoyi.system.service; - -import com.alibaba.excel.EasyExcel; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; -import org.ruoyi.system.domain.MinUsagePeriod; -import org.ruoyi.system.domain.vo.MinUsagePeriodImportVo; -import org.ruoyi.system.listener.MinUsagePeriodImportListener; -import org.ruoyi.system.mapper.MinUsagePeriodMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.transaction.annotation.Transactional; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.List; - -/** - * 数据库导入测试 - * - * @author cass - * @date 2025-09-24 - */ -@Slf4j -@SpringBootTest(classes = {org.ruoyi.RuoYiAIApplication.class}) -@ActiveProfiles("test") -public class DatabaseImportTest { - - @Autowired - private MinUsagePeriodMapper minUsagePeriodMapper; - - @Autowired - private IMinUsagePeriodService minUsagePeriodService; - - /** - * Excel文件路径 - */ - private static final String EXCEL_FILE_PATH = "E:/z. WorkSpace/ruoyi-ai/workspace/教育部直属高校固定资产最低使用年限表.xlsx"; - - /** - * 测试Excel数据导入到数据库 - */ - @Test - @Transactional - public void testExcelImportToDatabase() { - try { - log.info("=== 开始Excel数据导入到数据库测试 ==="); - - // 1. 清空测试数据 - log.info("1. 清空测试数据..."); - minUsagePeriodMapper.delete(null); - log.info(" 清空完成"); - - // 2. 读取Excel文件 - log.info("2. 读取Excel文件..."); - File excelFile = new File(EXCEL_FILE_PATH); - if (!excelFile.exists()) { - log.error("Excel文件不存在: {}", EXCEL_FILE_PATH); - return; - } - - List dataList; - try (InputStream inputStream = new FileInputStream(excelFile)) { - dataList = EasyExcel.read(inputStream) - .head(MinUsagePeriodImportVo.class) - .sheet() - .doReadSync(); - } - log.info(" 读取到 {} 条数据", dataList.size()); - - // 3. 处理数据(填充固定资产类别) - log.info("3. 处理数据..."); - processData(dataList); - log.info(" 数据处理完成"); - - // 4. 批量插入数据库 - log.info("4. 批量插入数据库..."); - int successCount = 0; - int failCount = 0; - - for (MinUsagePeriodImportVo importVo : dataList) { - try { - // 转换为实体对象 - MinUsagePeriod entity = convertToEntity(importVo); - - // 插入数据库 - minUsagePeriodMapper.insert(entity); - successCount++; - - if (successCount <= 5) { - log.info(" 插入成功: {} - {}", entity.getContent(), entity.getGbCode()); - } - } catch (Exception e) { - failCount++; - log.error(" 插入失败: {} - {}", importVo.getContent(), importVo.getGbCode(), e); - } - } - - log.info(" 插入完成: 成功 {} 条, 失败 {} 条", successCount, failCount); - - // 5. 验证数据库中的数据 - log.info("5. 验证数据库中的数据..."); - List dbData = minUsagePeriodMapper.selectList(null); - log.info(" 数据库中共有 {} 条记录", dbData.size()); - - // 显示前10条数据 - for (int i = 0; i < Math.min(10, dbData.size()); i++) { - MinUsagePeriod data = dbData.get(i); - log.info(" 第{}条: 类别={}, 内容={}, 年限={}, 国标代码={}", - i + 1, data.getCategory(), data.getContent(), data.getMinYears(), data.getGbCode()); - } - - // 6. 验证数据完整性 - log.info("6. 验证数据完整性..."); - validateDatabaseData(dbData); - - log.info("=== Excel数据导入到数据库测试完成 ==="); - - } catch (Exception e) { - log.error("Excel数据导入到数据库测试失败", e); - } - } - - /** - * 测试使用Service层导入数据 - */ - @Test - @Transactional - public void testServiceImportData() { - try { - log.info("=== 开始Service层导入数据测试 ==="); - - // 1. 清空测试数据 - log.info("1. 清空测试数据..."); - minUsagePeriodMapper.delete(null); - log.info(" 清空完成"); - - // 2. 使用Service层导入数据 - log.info("2. 使用Service层导入数据..."); - File excelFile = new File(EXCEL_FILE_PATH); - if (!excelFile.exists()) { - log.error("Excel文件不存在: {}", EXCEL_FILE_PATH); - return; - } - - try (InputStream inputStream = new FileInputStream(excelFile)) { - // 使用自定义监听器导入数据 - MinUsagePeriodImportListener listener = new MinUsagePeriodImportListener(true); - - EasyExcel.read(inputStream) - .head(MinUsagePeriodImportVo.class) - .registerReadListener(listener) - .sheet() - .doRead(); - - log.info(" Service层导入完成"); - } - - // 3. 验证数据库中的数据 - log.info("3. 验证数据库中的数据..."); - List dbData = minUsagePeriodMapper.selectList(null); - log.info(" 数据库中共有 {} 条记录", dbData.size()); - - // 显示前10条数据 - for (int i = 0; i < Math.min(10, dbData.size()); i++) { - MinUsagePeriod data = dbData.get(i); - log.info(" 第{}条: 类别={}, 内容={}, 年限={}, 国标代码={}", - i + 1, data.getCategory(), data.getContent(), data.getMinYears(), data.getGbCode()); - } - - log.info("=== Service层导入数据测试完成 ==="); - - } catch (Exception e) { - log.error("Service层导入数据测试失败", e); - } - } - - /** - * 处理数据,填充固定资产类别 - */ - private void processData(List dataList) { - String currentCategory = null; - - for (MinUsagePeriodImportVo data : dataList) { - // 如果当前行有固定资产类别,更新当前类别 - if (data.getCategory() != null && !data.getCategory().trim().isEmpty()) { - currentCategory = data.getCategory().trim(); - } - - // 如果当前行没有固定资产类别,使用当前类别 - if (data.getCategory() == null || data.getCategory().trim().isEmpty()) { - data.setCategory(currentCategory); - } - } - } - - /** - * 转换为实体对象 - */ - private MinUsagePeriod convertToEntity(MinUsagePeriodImportVo importVo) { - MinUsagePeriod entity = new MinUsagePeriod(); - entity.setCategory(importVo.getCategory()); - entity.setContent(importVo.getContent()); - entity.setMinYears(importVo.getMinYears()); - entity.setGbCode(importVo.getGbCode()); - return entity; - } - - /** - * 验证数据库中的数据 - */ - private void validateDatabaseData(List dbData) { - log.info(" 数据验证结果:"); - log.info(" - 总记录数: {}", dbData.size()); - - // 统计各类别的数量 - long categoryCount = dbData.stream() - .filter(data -> data.getCategory() != null && !data.getCategory().trim().isEmpty()) - .count(); - log.info(" - 有固定资产类别的记录: {}/{}", categoryCount, dbData.size()); - - // 统计各年限的数量 - long yearsCount = dbData.stream() - .filter(data -> data.getMinYears() != null) - .count(); - log.info(" - 有最低使用年限的记录: {}/{}", yearsCount, dbData.size()); - - // 统计各国标代码的数量 - long gbCodeCount = dbData.stream() - .filter(data -> data.getGbCode() != null && !data.getGbCode().trim().isEmpty()) - .count(); - log.info(" - 有国标代码的记录: {}/{}", gbCodeCount, dbData.size()); - - // 检查重复的国标代码 - long uniqueGbCodeCount = dbData.stream() - .filter(data -> data.getGbCode() != null && !data.getGbCode().trim().isEmpty()) - .map(MinUsagePeriod::getGbCode) - .distinct() - .count(); - log.info(" - 唯一国标代码数量: {}", uniqueGbCodeCount); - - if (uniqueGbCodeCount != gbCodeCount) { - log.warn(" - 发现重复的国标代码!"); - } else { - log.info(" - 国标代码唯一性检查通过"); - } - - // 数据质量评估 - if (categoryCount == dbData.size() && - yearsCount == dbData.size() && - gbCodeCount == dbData.size()) { - log.info(" - 数据质量: 优秀 (所有字段都完整)"); - } else if (yearsCount == dbData.size() && gbCodeCount == dbData.size()) { - log.info(" - 数据质量: 良好 (核心字段完整)"); - } else { - log.warn(" - 数据质量: 需要改进 (存在缺失字段)"); - } - } -} diff --git a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseInitTest.java b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseInitTest.java deleted file mode 100644 index 06f476bb..00000000 --- a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/DatabaseInitTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.ruoyi.system.service; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; -import org.ruoyi.system.mapper.MinUsagePeriodMapper; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.Statement; - -/** - * 数据库初始化测试 - * - * @author cass - * @date 2025-09-24 - */ -@Slf4j -@SpringBootTest(classes = {org.ruoyi.RuoYiAIApplication.class}) -@ActiveProfiles("test") -public class DatabaseInitTest { - - @Autowired - private MinUsagePeriodMapper minUsagePeriodMapper; - - /** - * 测试数据库连接和表创建 - */ - @Test - public void testDatabaseConnectionAndTableCreation() { - try { - log.info("=== 开始数据库连接和表创建测试 ==="); - - // 1. 测试数据库连接 - log.info("1. 测试数据库连接..."); - String url = "jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8"; - String username = "root"; - String password = "666666"; - - try (Connection connection = DriverManager.getConnection(url, username, password)) { - log.info(" 数据库连接成功"); - - // 2. 检查表是否存在 - log.info("2. 检查min_usage_period表是否存在..."); - boolean tableExists = checkTableExists(connection); - - if (!tableExists) { - log.info(" 表不存在,开始创建表..."); - createTable(connection); - log.info(" 表创建完成"); - } else { - log.info(" 表已存在"); - } - - // 3. 测试Mapper - log.info("3. 测试Mapper..."); - try { - Long count = minUsagePeriodMapper.selectCount(null); - log.info(" 当前表中有 {} 条记录", count); - } catch (Exception e) { - log.error(" Mapper测试失败", e); - } - - } - - log.info("=== 数据库连接和表创建测试完成 ==="); - - } catch (Exception e) { - log.error("数据库连接和表创建测试失败", e); - } - } - - /** - * 检查表是否存在 - */ - private boolean checkTableExists(Connection connection) { - try (Statement statement = connection.createStatement()) { - statement.executeQuery("SELECT 1 FROM min_usage_period LIMIT 1"); - return true; - } catch (Exception e) { - return false; - } - } - - /** - * 创建表 - */ - private void createTable(Connection connection) { - String createTableSql = """ - CREATE TABLE `min_usage_period` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `category` varchar(100) NOT NULL COMMENT '固定资产类别', - `content` varchar(200) NOT NULL COMMENT '内容', - `min_years` int(11) NOT NULL COMMENT '最低使用年限(年)', - `gb_code` varchar(20) NOT NULL COMMENT '国标代码', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `create_by` varchar(64) DEFAULT '' COMMENT '创建者', - `update_by` varchar(64) DEFAULT '' COMMENT '更新者', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_gb_code` (`gb_code`), - KEY `idx_category` (`category`), - KEY `idx_content` (`content`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='最低使用年限表' - """; - - try (Statement statement = connection.createStatement()) { - statement.execute(createTableSql); - } - } -} diff --git a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/NewExcelAnalysisTest.java b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/NewExcelAnalysisTest.java new file mode 100644 index 00000000..ec77171c --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/NewExcelAnalysisTest.java @@ -0,0 +1,180 @@ +package org.ruoyi.system.service; + +import com.alibaba.excel.EasyExcel; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +/** + * 新Excel文件分析测试 + * + * @author cass + * @date 2025-09-24 + */ +@Slf4j +public class NewExcelAnalysisTest { + + /** + * 新Excel文件路径 + */ + private static final String NEW_EXCEL_FILE_PATH = "E:/z. WorkSpace/ruoyi-ai/workspace/高等学校固定资产分类与代码.xlsx"; + + /** + * 分析新Excel文件结构 + */ + @Test + public void analyzeNewExcelFile() { + try { + log.info("=== 开始分析新Excel文件结构 ==="); + + File excelFile = new File(NEW_EXCEL_FILE_PATH); + if (!excelFile.exists()) { + log.error("Excel文件不存在: {}", NEW_EXCEL_FILE_PATH); + return; + } + + log.info("文件信息:"); + log.info("- 文件路径: {}", excelFile.getAbsolutePath()); + log.info("- 文件大小: {} bytes", excelFile.length()); + log.info("- 文件是否存在: {}", excelFile.exists()); + log.info("- 文件是否可读: {}", excelFile.canRead()); + + // 1. 读取原始数据(不指定头部) + log.info("\n1. 读取原始数据(前20行)..."); + try (InputStream inputStream = new FileInputStream(excelFile)) { + List> rawData = EasyExcel.read(inputStream) + .sheet() + .headRowNumber(0) // 不跳过头部 + .doReadSync(); + + log.info(" 总共读取到 {} 行数据", rawData.size()); + + // 显示前20行数据 + for (int i = 0; i < Math.min(20, rawData.size()); i++) { + Map row = rawData.get(i); + log.info(" 第{}行: {}", i + 1, row); + } + } + + // 2. 分析数据结构 + log.info("\n2. 分析数据结构..."); + analyzeDataStructure(excelFile); + + // 3. 尝试按指定字段读取 + log.info("\n3. 尝试按指定字段读取..."); + tryReadWithFields(excelFile); + + log.info("=== 新Excel文件结构分析完成 ==="); + + } catch (Exception e) { + log.error("分析新Excel文件失败", e); + } + } + + /** + * 分析数据结构 + */ + private void analyzeDataStructure(File excelFile) throws Exception { + try (InputStream inputStream = new FileInputStream(excelFile)) { + List> rawData = EasyExcel.read(inputStream) + .sheet() + .headRowNumber(0) + .doReadSync(); + + log.info(" 数据行数: {}", rawData.size()); + + // 统计空行 + int emptyRows = 0; + int headerRows = 0; + int categoryTitleRows = 0; + int dataRows = 0; + + for (int i = 0; i < rawData.size(); i++) { + Map row = rawData.get(i); + + // 检查是否为空行 + boolean isEmpty = row.values().stream().allMatch(value -> + value == null || value.trim().isEmpty()); + + if (isEmpty) { + emptyRows++; + } else { + // 检查是否是表头行 + String firstCell = row.get(0); + if (firstCell != null && firstCell.contains("分类代码")) { + headerRows++; + } else if (firstCell != null && firstCell.trim().length() > 0 && + (row.get(1) == null || row.get(1).trim().isEmpty())) { + // 只有第一列有值,其他列为空,可能是分类标题 + categoryTitleRows++; + } else { + dataRows++; + } + } + } + + log.info(" 空行数量: {}", emptyRows); + log.info(" 表头行数量: {}", headerRows); + log.info(" 分类标题行数量: {}", categoryTitleRows); + log.info(" 数据行数量: {}", dataRows); + + // 显示一些分类标题行的例子 + log.info(" 分类标题行示例:"); + int categoryCount = 0; + for (int i = 0; i < rawData.size() && categoryCount < 5; i++) { + Map row = rawData.get(i); + String firstCell = row.get(0); + if (firstCell != null && firstCell.trim().length() > 0 && + (row.get(1) == null || row.get(1).trim().isEmpty())) { + log.info(" - 第{}行: {}", i + 1, firstCell); + categoryCount++; + } + } + } + } + + /** + * 尝试按指定字段读取 + */ + private void tryReadWithFields(File excelFile) throws Exception { + // 创建一个简单的VO类来测试字段映射 + try (InputStream inputStream = new FileInputStream(excelFile)) { + List> rawData = EasyExcel.read(inputStream) + .sheet() + .headRowNumber(0) + .doReadSync(); + + log.info(" 尝试识别有效数据行..."); + + int validDataCount = 0; + for (int i = 0; i < rawData.size(); i++) { + Map row = rawData.get(i); + + // 检查是否是有效的数据行 + // 有效数据行应该:第一列有值(分类代码),第二列有值(分类名称),第三列有值(国标名称) + String code = row.get(0); + String name = row.get(1); + String gbName = row.get(2); + + if (code != null && !code.trim().isEmpty() && + name != null && !name.trim().isEmpty() && + gbName != null && !gbName.trim().isEmpty() && + !code.contains("分类代码")) { // 排除表头行 + + validDataCount++; + if (validDataCount <= 10) { + log.info(" 有效数据第{}行: 代码={}, 名称={}, 国标名称={}", + validDataCount, code, name, gbName); + } + } + } + + log.info(" 识别到 {} 条有效数据", validDataCount); + } + } +} diff --git a/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/SimpleAssetClassificationTest.java b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/SimpleAssetClassificationTest.java new file mode 100644 index 00000000..8a364ec0 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/test/java/org/ruoyi/system/service/SimpleAssetClassificationTest.java @@ -0,0 +1,303 @@ +package org.ruoyi.system.service; + +import com.alibaba.excel.EasyExcel; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.List; +import java.util.Map; + +/** + * 简单的高等学校固定资产分类与代码导入测试 + * + * @author cass + * @date 2025-09-24 + */ +@Slf4j +public class SimpleAssetClassificationTest { + + /** + * 新Excel文件路径 + */ + private static final String NEW_EXCEL_FILE_PATH = "E:/z. WorkSpace/ruoyi-ai/workspace/高等学校固定资产分类与代码.xlsx"; + + /** + * 数据库连接信息 + */ + private static final String DB_URL = "jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8"; + private static final String DB_USERNAME = "root"; + private static final String DB_PASSWORD = "666666"; + + /** + * 测试Excel数据导入到数据库 + */ + @Test + public void testAssetClassificationImportToDatabase() { + try { + log.info("=== 开始高等学校固定资产分类与代码导入测试 ==="); + + // 1. 连接数据库 + log.info("1. 连接数据库..."); + try (Connection connection = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD)) { + log.info(" 数据库连接成功"); + + // 2. 创建表(如果不存在) + log.info("2. 创建表(如果不存在)..."); + createTableIfNotExists(connection); + log.info(" 表创建完成"); + + // 3. 清空测试数据 + log.info("3. 清空测试数据..."); + clearTestData(connection); + log.info(" 清空完成"); + + // 4. 读取Excel文件 + log.info("4. 读取Excel文件..."); + List> dataList = readExcelFile(); + log.info(" 读取到 {} 条原始数据", dataList.size()); + + // 5. 数据清洗 + log.info("5. 数据清洗..."); + List> cleanedData = cleanData(dataList); + log.info(" 清洗后有效数据: {} 条", cleanedData.size()); + + // 6. 批量插入数据库 + log.info("6. 批量插入数据库..."); + int successCount = insertDataToDatabase(connection, cleanedData); + log.info(" 插入完成: 成功 {} 条", successCount); + + // 7. 验证数据库中的数据 + log.info("7. 验证数据库中的数据..."); + int dbCount = getDatabaseRecordCount(connection); + log.info(" 数据库中共有 {} 条记录", dbCount); + + // 8. 显示部分数据 + log.info("8. 显示部分数据..."); + showSampleData(connection); + + // 9. 数据质量分析 + log.info("9. 数据质量分析..."); + analyzeDataQuality(connection); + + } + + log.info("=== 高等学校固定资产分类与代码导入测试完成 ==="); + + } catch (Exception e) { + log.error("高等学校固定资产分类与代码导入测试失败", e); + } + } + + /** + * 创建表(如果不存在) + */ + private void createTableIfNotExists(Connection connection) throws Exception { + String createTableSql = """ + CREATE TABLE IF NOT EXISTS `asset_classification` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `classification_code` varchar(20) NOT NULL COMMENT '分类代码', + `classification_name` varchar(200) NOT NULL COMMENT '分类名称', + `gb_name` varchar(200) NOT NULL COMMENT '国标名称', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_classification_code` (`classification_code`), + KEY `idx_classification_name` (`classification_name`), + KEY `idx_gb_name` (`gb_name`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='高等学校固定资产分类与代码表' + """; + + try (Statement statement = connection.createStatement()) { + statement.execute(createTableSql); + } + } + + /** + * 清空测试数据 + */ + private void clearTestData(Connection connection) throws Exception { + String deleteSql = "DELETE FROM asset_classification"; + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(deleteSql); + } + } + + /** + * 读取Excel文件 + */ + private List> readExcelFile() throws Exception { + File excelFile = new File(NEW_EXCEL_FILE_PATH); + if (!excelFile.exists()) { + throw new RuntimeException("Excel文件不存在: " + NEW_EXCEL_FILE_PATH); + } + + try (InputStream inputStream = new FileInputStream(excelFile)) { + return EasyExcel.read(inputStream) + .sheet() + .doReadSync(); + } + } + + /** + * 数据清洗 + */ + private List> cleanData(List> dataList) { + return dataList.stream() + .filter(this::isValidData) + .collect(java.util.stream.Collectors.toList()); + } + + /** + * 验证数据是否有效 + */ + private boolean isValidData(Map data) { + // 过滤空行 + if (data == null) { + return false; + } + + String code = data.get(0); + String name = data.get(1); + String gbName = data.get(2); + + // 过滤表头行 + if ("分类代码".equals(code) || + "分类名称".equals(name) || + "国标名称".equals(gbName)) { + return false; + } + + // 过滤分类标题行(只有第一列有值,其他列为空) + if (code != null && !code.trim().isEmpty() && + (name == null || name.trim().isEmpty()) && + (gbName == null || gbName.trim().isEmpty())) { + return false; + } + + // 过滤包含"表"、"续表"等标题行 + if (code != null && + (code.contains("表") || + code.contains("续表"))) { + return false; + } + + // 验证必填字段 + return code != null && !code.trim().isEmpty() && + name != null && !name.trim().isEmpty() && + gbName != null && !gbName.trim().isEmpty(); + } + + /** + * 插入数据到数据库 + */ + private int insertDataToDatabase(Connection connection, List> dataList) throws Exception { + String insertSql = "INSERT INTO asset_classification (classification_code, classification_name, gb_name) VALUES (?, ?, ?)"; + + int successCount = 0; + try (PreparedStatement statement = connection.prepareStatement(insertSql)) { + for (Map data : dataList) { + try { + String code = data.get(0); + String name = data.get(1); + String gbName = data.get(2); + + statement.setString(1, code); + statement.setString(2, name); + statement.setString(3, gbName); + + statement.executeUpdate(); + successCount++; + + if (successCount <= 5) { + log.info(" 插入成功: {} - {}", code, name); + } + } catch (Exception e) { + log.error(" 插入失败: {} - {}", data.get(0), data.get(1), e); + } + } + } + + return successCount; + } + + /** + * 获取数据库记录数 + */ + private int getDatabaseRecordCount(Connection connection) throws Exception { + String countSql = "SELECT COUNT(*) FROM asset_classification"; + try (Statement statement = connection.createStatement()) { + var resultSet = statement.executeQuery(countSql); + if (resultSet.next()) { + return resultSet.getInt(1); + } + } + return 0; + } + + /** + * 显示部分数据 + */ + private void showSampleData(Connection connection) throws Exception { + String selectSql = "SELECT * FROM asset_classification LIMIT 10"; + try (Statement statement = connection.createStatement()) { + var resultSet = statement.executeQuery(selectSql); + int count = 0; + while (resultSet.next() && count < 10) { + count++; + String code = resultSet.getString("classification_code"); + String name = resultSet.getString("classification_name"); + String gbName = resultSet.getString("gb_name"); + + log.info(" 第{}条: 代码={}, 名称={}, 国标名称={}", + count, code, name, gbName); + } + } + } + + /** + * 数据质量分析 + */ + private void analyzeDataQuality(Connection connection) throws Exception { + log.info(" 数据质量分析:"); + + // 总记录数 + int totalCount = getDatabaseRecordCount(connection); + log.info(" - 总记录数: {}", totalCount); + + // 统计分类代码长度分布 + String lengthSql = "SELECT LENGTH(classification_code) as code_length, COUNT(*) as count FROM asset_classification GROUP BY LENGTH(classification_code) ORDER BY code_length"; + try (Statement statement = connection.createStatement()) { + var resultSet = statement.executeQuery(lengthSql); + log.info(" - 分类代码长度分布:"); + while (resultSet.next()) { + int length = resultSet.getInt("code_length"); + int count = resultSet.getInt("count"); + log.info(" * {}位: {} 条", length, count); + } + } + + // 统计国标名称分布 + String gbNameSql = "SELECT gb_name, COUNT(*) as count FROM asset_classification GROUP BY gb_name ORDER BY count DESC LIMIT 10"; + try (Statement statement = connection.createStatement()) { + var resultSet = statement.executeQuery(gbNameSql); + log.info(" - 国标名称分布(前10):"); + while (resultSet.next()) { + String gbName = resultSet.getString("gb_name"); + int count = resultSet.getInt("count"); + log.info(" * {}: {} 条", gbName, count); + } + } + + log.info(" - 数据质量: 优秀 (所有字段都完整)"); + } +} diff --git a/ruoyi-modules/ruoyi-system/src/test/resources/application-test.yml b/ruoyi-modules/ruoyi-system/src/test/resources/application-test.yml new file mode 100644 index 00000000..97e3ae2d --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/test/resources/application-test.yml @@ -0,0 +1,57 @@ +# 测试环境配置 +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + dynamic: + p6spy: false + primary: master + strict: true + datasource: + master: + type: ${spring.datasource.type} + driverClassName: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true + username: root + password: 666666 + hikari: + maxPoolSize: 10 + minIdle: 5 + connectionTimeout: 30000 + validationTimeout: 5000 + idleTimeout: 600000 + maxLifetime: 1800000 + connectionTestQuery: SELECT 1 + keepaliveTime: 30000 + + # Redis配置(测试环境可以禁用) + data: + redis: + host: 127.0.0.1 + port: 6379 + database: 0 + timeout: 10S + +# MyBatis Plus配置 +mybatis-plus: + mapper-locations: classpath*:mapper/**/*Mapper.xml + type-aliases-package: org.ruoyi.**.domain + configuration: + map-underscore-to-camel-case: true + cache-enabled: false + call-setters-on-nulls: true + jdbc-type-for-null: 'null' + global-config: + db-config: + id-type: AUTO + logic-delete-field: delFlag + logic-delete-value: 2 + logic-not-delete-value: 0 + +# 日志配置 +logging: + level: + org.ruoyi: DEBUG + org.springframework: WARN + com.baomidou.mybatisplus: WARN + pattern: + console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n' diff --git a/script/sql/asset_classification.sql b/script/sql/asset_classification.sql new file mode 100644 index 00000000..52755abd --- /dev/null +++ b/script/sql/asset_classification.sql @@ -0,0 +1,16 @@ +-- 高等学校固定资产分类与代码表 +CREATE TABLE `asset_classification` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `classification_code` varchar(20) NOT NULL COMMENT '分类代码', + `classification_name` varchar(200) NOT NULL COMMENT '分类名称', + `gb_name` varchar(200) NOT NULL COMMENT '国标名称', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` varchar(64) DEFAULT '' COMMENT '创建者', + `update_by` varchar(64) DEFAULT '' COMMENT '更新者', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_classification_code` (`classification_code`), + KEY `idx_classification_name` (`classification_name`), + KEY `idx_gb_name` (`gb_name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='高等学校固定资产分类与代码表'; diff --git a/workspace/报废审核test.xlsx b/workspace/报废审核test.xlsx new file mode 100644 index 00000000..275870f6 Binary files /dev/null and b/workspace/报废审核test.xlsx differ