《高等学校固定资产分类与代码》表入库代码

Signed-off-by: CCass <only_pop@qq.com>
This commit is contained in:
CCass
2025-09-25 09:30:26 +08:00
parent d2580d3fd0
commit 2ed4e2a39a
14 changed files with 863 additions and 377 deletions

13
pom.xml
View File

@@ -42,6 +42,8 @@
<bouncycastle.version>1.72</bouncycastle.version> <bouncycastle.version>1.72</bouncycastle.version>
<!-- Apache Commons Compress 版本 --> <!-- Apache Commons Compress 版本 -->
<commons-compress.version>1.26.2</commons-compress.version> <commons-compress.version>1.26.2</commons-compress.version>
<!-- Apache Commons IO 版本 -->
<commons-io.version>2.17.0</commons-io.version>
<!-- 离线IP地址定位库 --> <!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version> <ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 --> <!-- OSS 配置 -->
@@ -53,8 +55,8 @@
<!-- 插件版本 --> <!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version> <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version> <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison> <maven-compiler-plugin.verison>3.14.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version> <maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
</properties> </properties>
@@ -292,6 +294,13 @@
<version>${commons-compress.version}</version> <version>${commons-compress.version}</version>
</dependency> </dependency>
<!-- Apache Commons IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency> <dependency>
<groupId>io.github.linpeilie</groupId> <groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId> <artifactId>mapstruct-plus-spring-boot-starter</artifactId>

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<AssetClassification, AssetClassificationVo> {
/**
* 根据分类代码查询
*
* @param classificationCode 分类代码
* @return 高等学校固定资产分类与代码
*/
AssetClassification queryByClassificationCode(@Param("classificationCode") String classificationCode);
}

View File

@@ -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<AssetClassificationVo> queryPageList(AssetClassificationBo bo);
/**
* 查询高等学校固定资产分类与代码列表
*/
List<AssetClassificationVo> queryList(AssetClassificationBo bo);
/**
* 新增高等学校固定资产分类与代码
*/
Boolean insertByBo(AssetClassificationBo bo);
/**
* 修改高等学校固定资产分类与代码
*/
Boolean updateByBo(AssetClassificationBo bo);
/**
* 校验并批量删除高等学校固定资产分类与代码信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 导入数据
*
* @param dataList 数据列表
* @param isUpdateSupport 是否更新支持
* @param operName 操作用户
* @return 结果
*/
String importData(List<AssetClassificationBo> dataList, Boolean isUpdateSupport, String operName);
}

View File

@@ -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<MinUsagePeriodImportVo> 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<MinUsagePeriod> 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<MinUsagePeriod> 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<MinUsagePeriodImportVo> 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<MinUsagePeriod> 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(" - 数据质量: 需要改进 (存在缺失字段)");
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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<Map<Integer, String>> 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<Integer, String> 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<Map<Integer, String>> 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<Integer, String> 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<Integer, String> 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<Map<Integer, String>> rawData = EasyExcel.read(inputStream)
.sheet()
.headRowNumber(0)
.doReadSync();
log.info(" 尝试识别有效数据行...");
int validDataCount = 0;
for (int i = 0; i < rawData.size(); i++) {
Map<Integer, String> 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);
}
}
}

View File

@@ -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<Map<Integer, String>> dataList = readExcelFile();
log.info(" 读取到 {} 条原始数据", dataList.size());
// 5. 数据清洗
log.info("5. 数据清洗...");
List<Map<Integer, String>> 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<Map<Integer, String>> 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<Map<Integer, String>> cleanData(List<Map<Integer, String>> dataList) {
return dataList.stream()
.filter(this::isValidData)
.collect(java.util.stream.Collectors.toList());
}
/**
* 验证数据是否有效
*/
private boolean isValidData(Map<Integer, String> 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<Map<Integer, String>> 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<Integer, String> 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(" - 数据质量: 优秀 (所有字段都完整)");
}
}

View File

@@ -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'

View File

@@ -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='高等学校固定资产分类与代码表';

Binary file not shown.