diff --git a/1-sql/2026-01-19/pg-/file_storage.sql b/1-sql/2026-01-19/pg-/file_storage.sql new file mode 100644 index 00000000..b7e57bd4 --- /dev/null +++ b/1-sql/2026-01-19/pg-/file_storage.sql @@ -0,0 +1,60 @@ +-- 1. 创建表结构 +CREATE TABLE "file_storage" ( + -- [兼容模式] 支持 AUTO_INCREMENT + "id" BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + + "fileName" VARCHAR(255) NOT NULL DEFAULT '', + "fileId" BIGINT NOT NULL, + "userGroupId" BIGINT DEFAULT NULL, + "tenantId" BIGINT DEFAULT NULL, + "userId" BIGINT DEFAULT NULL, + "dirId" BIGINT NOT NULL, + "fileBizType" INTEGER DEFAULT NULL, + "fileSuffix" VARCHAR(50) NOT NULL DEFAULT '', + + -- [兼容模式] 去掉 UNSIGNED,使用 BIGINT (范围足够覆盖) + "fileSize" BIGINT NOT NULL, + + -- [兼容模式] 支持 DATETIME 类型 + "createTime" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + + -- [兼容模式] 支持 ON UPDATE 语法,无需触发器实现自动更新 + "updateTime" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + "fullPath" VARCHAR(1024) NOT NULL DEFAULT '', + + -- [修复核心报错] + -- 将 date_format 替换为标准的提取拼接逻辑,确保是 IMMUTABLE (不可变) 表达式 + "createYearMonth" VARCHAR(7) GENERATED ALWAYS AS ( + CAST(EXTRACT(YEAR FROM "createTime") AS VARCHAR) || '-' || + LPAD(CAST(EXTRACT(MONTH FROM "createTime") AS VARCHAR), 2, '0') + ) STORED +); + +-- 2. 创建索引 +CREATE INDEX "idx_dirid_size" ON "file_storage" ("dirId", "fileSize"); +CREATE INDEX "idx_userid_size" ON "file_storage" ("userId", "fileSize"); +CREATE INDEX "idx_dirid_createtime_size" ON "file_storage" ("dirId", "createTime", "fileSize"); +CREATE INDEX "idx_userid_createtime_size" ON "file_storage" ("userId", "createTime", "fileSize"); +CREATE INDEX "idx_dirid_createmonth_size" ON "file_storage" ("dirId", "createYearMonth", "fileSize"); +CREATE INDEX "idx_userid_createmonth_size" ON "file_storage" ("userId", "createYearMonth", "fileSize"); +CREATE INDEX "idx_dir_file_suffix_biz_createtime_size" ON "file_storage" ("dirId", "fileName", "fileSuffix", "fileBizType", "createTime", "fileSize"); +CREATE INDEX "idx_filename" ON "file_storage" ("fileName"); + +-- 3. 添加字段注释 +COMMENT ON TABLE "file_storage" IS '文件存储统计主表(支持项目/学科/用户维度的存储占用统计)'; +COMMENT ON COLUMN "file_storage"."id" IS '主键ID(自增)'; +COMMENT ON COLUMN "file_storage"."fileName" IS '文件名(含后缀)'; +COMMENT ON COLUMN "file_storage"."fileId" IS '文件唯一标识ID(关联文件元数据表)'; +COMMENT ON COLUMN "file_storage"."userGroupId" IS '用户组ID'; +COMMENT ON COLUMN "file_storage"."tenantId" IS '租户ID'; +COMMENT ON COLUMN "file_storage"."userId" IS '文件所属用户ID'; +COMMENT ON COLUMN "file_storage"."dirId" IS '保存所有父目录ID(项目/学科等目录的唯一标识)'; +COMMENT ON COLUMN "file_storage"."fileBizType" IS '文件业务类型(1:模型文件 2:仿真报告、3:计算文件、4:曲线文件、5:云图文件,6:网格文件,7:计算过程文件)'; +COMMENT ON COLUMN "file_storage"."fileSuffix" IS '文件后缀(如txt、jpg、pdf)'; +COMMENT ON COLUMN "file_storage"."fileSize" IS '文件大小(字节数,存储占用计算依据)'; +COMMENT ON COLUMN "file_storage"."createTime" IS '文件创建时间'; +COMMENT ON COLUMN "file_storage"."updateTime" IS '记录更新时间(自动更新)'; +COMMENT ON COLUMN "file_storage"."fullPath" IS '文件完整路径(冗余字段,优化查询)'; +COMMENT ON COLUMN "file_storage"."createYearMonth" IS '创建年月(自动生成)'; + diff --git a/data/pom.xml b/data/pom.xml index 7cefee39..63def4ba 100644 --- a/data/pom.xml +++ b/data/pom.xml @@ -118,6 +118,18 @@ ${org.mapstruct.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.postgresql + postgresql + test + + diff --git a/data/src/test/java/com/sdm/data/dao/MapperCompatibilityTest.java b/data/src/test/java/com/sdm/data/dao/MapperCompatibilityTest.java new file mode 100644 index 00000000..e718850c --- /dev/null +++ b/data/src/test/java/com/sdm/data/dao/MapperCompatibilityTest.java @@ -0,0 +1,447 @@ +package com.sdm.data.dao; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.sdm.data.model.dto.NodeSizeDTO; +import com.sdm.data.model.dto.UserTotalFileSizeDTO; +import com.sdm.data.model.entity.*; +import com.sdm.data.model.req.QueryBigFileReq; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Mapper层兼容性测试 - MySQL转PostgreSQL验证 + * 覆盖所有Mapper接口的基础CRUD和自定义SQL方法 + */ +@SpringBootTest +@ActiveProfiles("test") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Transactional +@Rollback(true) // 确保测试后回滚,不污染数据库 +public class MapperCompatibilityTest { + + @Autowired + private FileStorageMapper fileStorageMapper; + + @Autowired + private FileMetadataInfoMapper fileMetadataInfoMapper; + + @Autowired + private DimensionTemplateMapper dimensionTemplateMapper; + + @Autowired + private DimensionTemplateHierarchyMapper dimensionTemplateHierarchyMapper; + + @Autowired + private FileStorageQuotaMapper fileStorageQuotaMapper; + + @Autowired + private SimulationParameterLibraryMapper simulationParameterLibraryMapper; + + @Autowired + private SimulationParameterLibraryCategoryMapper simulationParameterLibraryCategoryMapper; + + @Autowired + private SimulationParameterLibraryCategoryObjectMapper simulationParameterLibraryCategoryObjectMapper; + + @Autowired + private TrainingModelMapper trainingModelMapper; + + @Autowired + private TrainingModelAlgorithmParamMapper trainingModelAlgorithmParamMapper; + + @Autowired + private FileMetadataExtensionMapper fileMetadataExtensionMapper; + + @Autowired + private FilePermissionDictMapper filePermissionDictMapper; + + @Autowired + private FileUserPermissionMapper fileUserPermissionMapper; + + @Autowired + private FileSimulationMappingMapper fileSimulationMappingMapper; + + // ==================== FileStorageMapper Tests ==================== + + @Test + @Order(1) + @DisplayName("FileStorageMapper - 基础CRUD测试") + void testFileStorageMapperCRUD() { + // INSERT + FileStorage entity = new FileStorage(); + entity.setFileName("mapper_test.txt"); + entity.setFileId(100L); + entity.setTenantId(1L); + entity.setUserId(1L); + entity.setDirId(1L); + entity.setFileSuffix("txt"); + entity.setFileBizType(1); + entity.setFileSize(2048L); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + entity.setCreateYearMonth("2026-01"); + + int insertResult = fileStorageMapper.insert(entity); + assertEquals(1, insertResult); + assertNotNull(entity.getId()); + + // SELECT BY ID + FileStorage selected = fileStorageMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("mapper_test.txt", selected.getFileName()); + + // UPDATE + selected.setFileName("mapper_test_updated.txt"); + selected.setFileSize(4096L); + int updateResult = fileStorageMapper.updateById(selected); + assertEquals(1, updateResult); + + // VERIFY UPDATE + FileStorage updated = fileStorageMapper.selectById(entity.getId()); + assertEquals("mapper_test_updated.txt", updated.getFileName()); + assertEquals(4096L, updated.getFileSize()); + + // DELETE + int deleteResult = fileStorageMapper.deleteById(entity.getId()); + assertEquals(1, deleteResult); + + // VERIFY DELETE + FileStorage deleted = fileStorageMapper.selectById(entity.getId()); + assertNull(deleted); + } + + @Test + @Order(2) + @DisplayName("FileStorageMapper - selectNodeSizeByNodeType自定义SQL测试") + void testFileStorageMapperSelectNodeSizeByNodeType() { + List result = fileStorageMapper.selectNodeSizeByNodeType( + Arrays.asList(1L, 2L, 3L), + 3, + 1L + ); + assertNotNull(result); + // 验证SQL执行不报错,结果可能为空但不应该抛异常 + } + + @Test + @Order(3) + @DisplayName("FileStorageMapper - statDirStorageByTargetYm自定义SQL测试") + void testFileStorageMapperStatDirStorageByTargetYm() { + List result = fileStorageMapper.statDirStorageByTargetYm( + Arrays.asList(1L, 2L), + "2026-01", + 1L + ); + assertNotNull(result); + } + + @Test + @Order(4) + @DisplayName("FileStorageMapper - getTotalFileSizeByCreator自定义SQL测试") + void testFileStorageMapperGetTotalFileSizeByCreator() { + List result = fileStorageMapper.getTotalFileSizeByCreator( + Arrays.asList(1L), + 6, + 1L + ); + assertNotNull(result); + } + + @Test + @Order(5) + @DisplayName("FileStorageMapper - getTotalFileSizeByCreatorAndTargetYm自定义SQL测试") + void testFileStorageMapperGetTotalFileSizeByCreatorAndTargetYm() { + List result = fileStorageMapper.getTotalFileSizeByCreatorAndTargetYm( + Arrays.asList(1L), + "2026-01", + 1L + ); + assertNotNull(result); + } + + @Test + @Order(6) + @DisplayName("FileStorageMapper - selectBigFiles自定义SQL测试") + void testFileStorageMapperSelectBigFiles() { + QueryBigFileReq req = new QueryBigFileReq(); + req.setIsLatest(true); + req.setDirIds(Arrays.asList(1L)); + + List result = fileStorageMapper.selectBigFiles(req, 1000L, 1L); + assertNotNull(result); + } + + // ==================== FileMetadataInfoMapper Tests ==================== + + @Test + @Order(10) + @DisplayName("FileMetadataInfoMapper - 基础CRUD测试") + void testFileMetadataInfoMapperCRUD() { + // INSERT + FileMetadataInfo entity = new FileMetadataInfo(); + entity.setRelatedResourceUuid("test-uuid-mapper"); + entity.setRelatedResourceUuidOwnType("node"); + entity.setOriginalName("mapper_metadata.txt"); + entity.setDataType(2); + entity.setIsLatest(true); + entity.setTenantId(1L); + entity.setCreatorId(1L); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + + int insertResult = fileMetadataInfoMapper.insert(entity); + assertEquals(1, insertResult); + assertNotNull(entity.getId()); + + // SELECT + FileMetadataInfo selected = fileMetadataInfoMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("mapper_metadata.txt", selected.getOriginalName()); + + // UPDATE + selected.setOriginalName("mapper_metadata_updated.txt"); + fileMetadataInfoMapper.updateById(selected); + + // DELETE + fileMetadataInfoMapper.deleteById(entity.getId()); + } + + @Test + @Order(11) + @DisplayName("FileMetadataInfoMapper - listSimulationNodeDir自定义SQL测试") + void testFileMetadataInfoMapperListSimulationNodeDir() { + List result = fileMetadataInfoMapper.listSimulationNodeDir( + Arrays.asList(1L, 2L), + false, + 1L + ); + assertNotNull(result); + } + + @Test + @Order(12) + @DisplayName("FileMetadataInfoMapper - listSimulationNodeFiles自定义SQL测试") + void testFileMetadataInfoMapperListSimulationNodeFiles() { + List result = fileMetadataInfoMapper.listSimulationNodeFiles( + Arrays.asList(1L), + Arrays.asList(1L), + false, + 1L + ); + assertNotNull(result); + } + + // ==================== DimensionTemplateMapper Tests ==================== + + @Test + @Order(20) + @DisplayName("DimensionTemplateMapper - 基础CRUD测试") + void testDimensionTemplateMapperCRUD() { + // INSERT + DimensionTemplate entity = new DimensionTemplate(); + entity.setTemplateName("Mapper测试模板"); + entity.setTemplateType(1); + entity.setDescription("用于Mapper测试"); + entity.setTenantId(1L); + entity.setCreatedBy(1L); + entity.setCreatedAt(LocalDateTime.now()); + + int insertResult = dimensionTemplateMapper.insert(entity); + assertEquals(1, insertResult); + + // SELECT + DimensionTemplate selected = dimensionTemplateMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("Mapper测试模板", selected.getTemplateName()); + + // UPDATE & DELETE + selected.setTemplateName("Mapper测试模板_更新"); + dimensionTemplateMapper.updateById(selected); + dimensionTemplateMapper.deleteById(entity.getId()); + } + + // ==================== FileStorageQuotaMapper Tests ==================== + + @Test + @Order(30) + @DisplayName("FileStorageQuotaMapper - 基础CRUD测试") + void testFileStorageQuotaMapperCRUD() { + // INSERT + FileStorageQuota entity = new FileStorageQuota(); + entity.setUserId(100L); + entity.setTenantId(1L); + entity.setQuotaValue(5368709120L); // 5GB + entity.setQuotaUnit("GB"); + entity.setUsedValue(0L); + entity.setStatus("NORMAL"); + + int insertResult = fileStorageQuotaMapper.insert(entity); + assertEquals(1, insertResult); + + // SELECT + FileStorageQuota selected = fileStorageQuotaMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("GB", selected.getQuotaUnit()); + + // UPDATE & DELETE + selected.setStatus("WARNING"); + fileStorageQuotaMapper.updateById(selected); + fileStorageQuotaMapper.deleteById(entity.getId()); + } + + // ==================== SimulationParameterLibraryMapper Tests ==================== + + @Test + @Order(40) + @DisplayName("SimulationParameterLibraryMapper - 基础CRUD测试") + void testSimulationParameterLibraryMapperCRUD() { + // INSERT + SimulationParameterLibrary entity = new SimulationParameterLibrary(); + entity.setParameterLibraryName("Mapper测试参数库"); + entity.setTenantId(1L); + entity.setCreatorId(1L); + entity.setCreateTime(LocalDateTime.now()); + + int insertResult = simulationParameterLibraryMapper.insert(entity); + assertEquals(1, insertResult); + + // SELECT + SimulationParameterLibrary selected = simulationParameterLibraryMapper.selectById(entity.getId()); + assertNotNull(selected); + + // DELETE + simulationParameterLibraryMapper.deleteById(entity.getId()); + } + + // ==================== TrainingModelMapper Tests ==================== + + @Test + @Order(50) + @DisplayName("TrainingModelMapper - 基础CRUD测试") + void testTrainingModelMapperCRUD() { + // INSERT + TrainingModel entity = new TrainingModel(); + entity.setModelName("Mapper测试模型"); + entity.setAlgorithmType("SVM"); + entity.setTrainer("tester"); + entity.setHandleStatus("待开始"); + entity.setTrainingStatus("待开始"); + entity.setPredStatus("待开始"); + entity.setTenantId(1L); + entity.setCreator(1L); + entity.setCreateTime(LocalDateTime.now()); + + int insertResult = trainingModelMapper.insert(entity); + assertEquals(1, insertResult); + + // SELECT + TrainingModel selected = trainingModelMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("Mapper测试模型", selected.getModelName()); + + // UPDATE & DELETE + selected.setModelName("Mapper测试模型_更新"); + trainingModelMapper.updateById(selected); + trainingModelMapper.deleteById(entity.getId()); + } + + // ==================== 条件查询测试 ==================== + + @Test + @Order(60) + @DisplayName("LambdaQueryWrapper条件查询测试") + void testLambdaQueryWrapper() { + // 测试MyBatis-Plus的LambdaQueryWrapper在PostgreSQL下的兼容性 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FileStorage::getTenantId, 1L) + .like(FileStorage::getFileName, "test") + .orderByDesc(FileStorage::getCreateTime); + + List result = fileStorageMapper.selectList(wrapper); + assertNotNull(result); + } + + @Test + @Order(61) + @DisplayName("批量查询测试") + void testBatchSelect() { + List result = fileStorageMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L)); + assertNotNull(result); + } + + // ==================== 其他Mapper基础测试 ==================== + + @Test + @Order(70) + @DisplayName("FileMetadataExtensionMapper - 基础测试") + void testFileMetadataExtensionMapper() { + List result = fileMetadataExtensionMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(71) + @DisplayName("FilePermissionDictMapper - 基础测试") + void testFilePermissionDictMapper() { + List result = filePermissionDictMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(72) + @DisplayName("FileUserPermissionMapper - 基础测试") + void testFileUserPermissionMapper() { + List result = fileUserPermissionMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(73) + @DisplayName("FileSimulationMappingMapper - 基础测试") + void testFileSimulationMappingMapper() { + List result = fileSimulationMappingMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(74) + @DisplayName("DimensionTemplateHierarchyMapper - 基础测试") + void testDimensionTemplateHierarchyMapper() { + List result = dimensionTemplateHierarchyMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(75) + @DisplayName("SimulationParameterLibraryCategoryMapper - 基础测试") + void testSimulationParameterLibraryCategoryMapper() { + List result = simulationParameterLibraryCategoryMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(76) + @DisplayName("SimulationParameterLibraryCategoryObjectMapper - 基础测试") + void testSimulationParameterLibraryCategoryObjectMapper() { + List result = simulationParameterLibraryCategoryObjectMapper.selectList(null); + assertNotNull(result); + } + + @Test + @Order(77) + @DisplayName("TrainingModelAlgorithmParamMapper - 基础测试") + void testTrainingModelAlgorithmParamMapper() { + List result = trainingModelAlgorithmParamMapper.selectList(null); + assertNotNull(result); + } +} diff --git a/data/src/test/java/com/sdm/data/dao/PostgreSQLCompatibilityTest.java b/data/src/test/java/com/sdm/data/dao/PostgreSQLCompatibilityTest.java new file mode 100644 index 00000000..1e18f4c0 --- /dev/null +++ b/data/src/test/java/com/sdm/data/dao/PostgreSQLCompatibilityTest.java @@ -0,0 +1,351 @@ +package com.sdm.data.dao; + +import com.sdm.data.model.dto.NodeSizeDTO; +import com.sdm.data.model.dto.UserTotalFileSizeDTO; +import com.sdm.data.model.entity.FileMetadataInfo; +import com.sdm.data.model.entity.FileStorage; +import com.sdm.data.model.req.QueryBigFileReq; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * PostgreSQL SQL语法兼容性专项测试 + * + * 重点验证以下MySQL到PostgreSQL的语法差异: + * 1. DATE_SUB函数 -> PostgreSQL的INTERVAL语法 + * 2. LIMIT在UNION中的使用 + * 3. CONCAT函数 + * 4. Boolean字段处理 + * 5. 字段名大小写敏感性 + */ +@SpringBootTest +@ActiveProfiles("test") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Transactional +@Rollback(true) // 确保测试后回滚,不污染数据库 +public class PostgreSQLCompatibilityTest { + + @Autowired + private FileStorageMapper fileStorageMapper; + + @Autowired + private FileMetadataInfoMapper fileMetadataInfoMapper; + + // ==================== DATE_SUB/INTERVAL 语法测试 ==================== + + @Test + @Order(1) + @DisplayName("测试时间间隔查询 - selectNodeSizeByNodeType (MySQL: DATE_SUB)") + void testDateIntervalQuery() { + // 准备测试数据 + FileStorage entity = createTestFileStorage("interval_test.txt"); + entity.setCreateTime(LocalDateTime.now().minusMonths(1)); // 1个月前 + fileStorageMapper.insert(entity); + + // 测试3个月内的数据查询 + List result = fileStorageMapper.selectNodeSizeByNodeType( + Arrays.asList(entity.getDirId()), + 3, // 3个月 + 1L + ); + + assertNotNull(result); + // 验证能够查询到1个月前的数据 + } + + @Test + @Order(2) + @DisplayName("测试时间间隔查询 - getTotalFileSizeByCreator (MySQL: DATE_SUB)") + void testDateIntervalQueryForUser() { + // 测试用户文件大小统计的时间间隔查询 + List result = fileStorageMapper.getTotalFileSizeByCreator( + Arrays.asList(1L), + 6, // 6个月 + 1L + ); + + assertNotNull(result); + } + + // ==================== UNION ALL + LIMIT 语法测试 ==================== + + @Test + @Order(10) + @DisplayName("测试UNION ALL查询 - statDirStorageByTargetYm") + void testUnionAllQuery() { + // 准备不同月份的测试数据 + FileStorage entity1 = createTestFileStorage("union_test_1.txt"); + entity1.setCreateYearMonth("2026-01"); + fileStorageMapper.insert(entity1); + + FileStorage entity2 = createTestFileStorage("union_test_2.txt"); + entity2.setCreateYearMonth("2025-12"); + fileStorageMapper.insert(entity2); + + // 测试UNION ALL查询(历史累计 + 当月增量) + List result = fileStorageMapper.statDirStorageByTargetYm( + Arrays.asList(entity1.getDirId()), + "2026-01", + 1L + ); + + assertNotNull(result); + // 应该返回BEFORE和INCREMENT两种类型的统计 + } + + @Test + @Order(11) + @DisplayName("测试带LIMIT的UNION查询 - getTotalFileSizeByCreatorAndTargetYm (CTE)") + void testUnionWithLimitAndCTE() { + // 测试使用CTE(WITH)的复杂查询 + List result = fileStorageMapper.getTotalFileSizeByCreatorAndTargetYm( + null, // 不传userIds,测试LIMIT 10的分支 + "2026-01", + 1L + ); + + assertNotNull(result); + } + + @Test + @Order(12) + @DisplayName("测试带LIMIT的查询 - getdefaultNodeNameByNodeSize") + void testLimitQuery() { + List result = fileStorageMapper.getdefaultNodeNameByNodeSize( + "node", + 5 // LIMIT 5 + ); + + assertNotNull(result); + assertTrue(result.size() <= 5); + } + + // ==================== CONCAT 函数测试 ==================== + + @Test + @Order(20) + @DisplayName("测试CONCAT函数 - selectBigFiles的LIKE查询") + void testConcatInLikeQuery() { + // 准备测试数据 + FileStorage fileStorage = createTestFileStorage("concat_test_file.txt"); + fileStorageMapper.insert(fileStorage); + + FileMetadataInfo metadata = new FileMetadataInfo(); + metadata.setId(fileStorage.getFileId()); + metadata.setOriginalName("concat_test_file.txt"); + metadata.setIsLatest(true); + metadata.setTenantId(1L); + metadata.setDataType(2); + metadata.setApproveType(0); + metadata.setCreateTime(LocalDateTime.now()); + metadata.setUpdateTime(LocalDateTime.now()); + fileMetadataInfoMapper.insert(metadata); + + // 测试CONCAT在LIKE中的使用 + QueryBigFileReq req = new QueryBigFileReq(); + req.setIsLatest(true); + req.setFileName("concat"); // 模糊匹配 + + List result = fileStorageMapper.selectBigFiles(req, 0L, 1L); + + assertNotNull(result); + } + + // ==================== Boolean 字段测试 ==================== + + @Test + @Order(30) + @DisplayName("测试Boolean字段 - isLatest = true") + void testBooleanField() { + // 测试PostgreSQL的Boolean类型处理 + FileMetadataInfo entityTrue = new FileMetadataInfo(); + entityTrue.setOriginalName("boolean_true.txt"); + entityTrue.setIsLatest(true); + entityTrue.setIsRoot(false); + entityTrue.setTenantId(1L); + entityTrue.setDataType(2); + entityTrue.setCreateTime(LocalDateTime.now()); + entityTrue.setUpdateTime(LocalDateTime.now()); + fileMetadataInfoMapper.insert(entityTrue); + + FileMetadataInfo entityFalse = new FileMetadataInfo(); + entityFalse.setOriginalName("boolean_false.txt"); + entityFalse.setIsLatest(false); + entityFalse.setIsRoot(true); + entityFalse.setTenantId(1L); + entityFalse.setDataType(2); + entityFalse.setCreateTime(LocalDateTime.now()); + entityFalse.setUpdateTime(LocalDateTime.now()); + fileMetadataInfoMapper.insert(entityFalse); + + // 测试listSimulationNodeFiles中的 isLatest = true + List result = fileMetadataInfoMapper.listSimulationNodeFiles( + Arrays.asList(entityTrue.getParentId() != null ? entityTrue.getParentId() : 0L), + Collections.emptyList(), + false, + 1L + ); + + assertNotNull(result); + } + + // ==================== 字段名大小写敏感性测试 ==================== + + @Test + @Order(40) + @DisplayName("测试驼峰字段名映射 - fileName/fileSize等") + void testCamelCaseFieldMapping() { + FileStorage entity = new FileStorage(); + entity.setFileName("camelCase_test.txt"); + entity.setFileId(System.currentTimeMillis()); + entity.setFileSize(2048L); + entity.setFileBizType(1); + entity.setFileSuffix("txt"); + entity.setTenantId(1L); + entity.setUserId(1L); + entity.setDirId(1L); + entity.setCreateYearMonth("2026-01"); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + entity.setFullPath("/test/camelCase_test.txt"); + + // 插入 + int insertResult = fileStorageMapper.insert(entity); + assertEquals(1, insertResult); + + // 查询并验证所有驼峰命名字段 + FileStorage selected = fileStorageMapper.selectById(entity.getId()); + assertNotNull(selected); + assertEquals("camelCase_test.txt", selected.getFileName()); + assertEquals(2048L, selected.getFileSize()); + assertEquals(1, selected.getFileBizType()); + assertEquals("txt", selected.getFileSuffix()); + assertEquals("2026-01", selected.getCreateYearMonth()); + assertEquals("/test/camelCase_test.txt", selected.getFullPath()); + } + + // ==================== NULL值处理测试 ==================== + + @Test + @Order(50) + @DisplayName("测试NULL值条件查询") + void testNullConditionQuery() { + // 测试IS NULL查询 + List result = fileMetadataInfoMapper.listSimulationNodeDir( + Arrays.asList(1L), + false, // 不过滤空数据 + 1L + ); + + assertNotNull(result); + } + + // ==================== 空集合参数测试 ==================== + + @Test + @Order(60) + @DisplayName("测试空集合参数处理") + void testEmptyCollectionParameter() { + // 测试传入空列表时的处理 + List result = fileStorageMapper.getTotalFileSizeByCreator( + Collections.emptyList(), // 空列表 + 6, + 1L + ); + + assertNotNull(result); + // 应该返回前10个用户(根据SQL逻辑) + } + + // ==================== ORDER BY 测试 ==================== + + @Test + @Order(70) + @DisplayName("测试ORDER BY子句") + void testOrderByClause() { + // 准备多条测试数据 + for (int i = 1; i <= 3; i++) { + FileStorage entity = createTestFileStorage("order_test_" + i + ".txt"); + entity.setFileSize(i * 1000L); + entity.setUpdateTime(LocalDateTime.now().minusHours(i)); + fileStorageMapper.insert(entity); + } + + // 测试selectBigFiles中的ORDER BY updateTime DESC + QueryBigFileReq req = new QueryBigFileReq(); + req.setIsLatest(true); + req.setFileName("order_test"); + + List result = fileStorageMapper.selectBigFiles(req, 0L, 1L); + + assertNotNull(result); + // 验证是否按updateTime降序排列 + if (result.size() >= 2) { + assertTrue( + result.get(0).getUpdateTime().isAfter(result.get(1).getUpdateTime()) || + result.get(0).getUpdateTime().isEqual(result.get(1).getUpdateTime()) + ); + } + } + + // ==================== 聚合函数测试 ==================== + + @Test + @Order(80) + @DisplayName("测试SUM聚合函数") + void testSumAggregation() { + // 准备测试数据 + Long testDirId = 99999L; + FileStorage entity1 = createTestFileStorage("sum_test_1.txt"); + entity1.setDirId(testDirId); + entity1.setFileSize(1000L); + fileStorageMapper.insert(entity1); + + FileStorage entity2 = createTestFileStorage("sum_test_2.txt"); + entity2.setDirId(testDirId); + entity2.setFileSize(2000L); + fileStorageMapper.insert(entity2); + + // 测试SUM聚合 + List result = fileStorageMapper.selectNodeSizeByNodeType( + Arrays.asList(testDirId), + 12, + 1L + ); + + assertNotNull(result); + if (!result.isEmpty()) { + // 验证总大小 = 1000 + 2000 = 3000 + assertEquals(3000L, result.get(0).getTotalSize()); + } + } + + // ==================== Helper Methods ==================== + + private FileStorage createTestFileStorage(String fileName) { + FileStorage entity = new FileStorage(); + entity.setFileName(fileName); + entity.setFileId(System.currentTimeMillis()); + entity.setTenantId(1L); + entity.setUserId(1L); + entity.setDirId(1L); + entity.setFileSuffix("txt"); + entity.setFileBizType(1); + entity.setFileSize(1024L); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + entity.setCreateYearMonth("2026-01"); + return entity; + } +} diff --git a/data/src/test/java/com/sdm/data/service/ServiceCrudTest.java b/data/src/test/java/com/sdm/data/service/ServiceCrudTest.java new file mode 100644 index 00000000..9247e226 --- /dev/null +++ b/data/src/test/java/com/sdm/data/service/ServiceCrudTest.java @@ -0,0 +1,517 @@ +package com.sdm.data.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.sdm.data.model.entity.*; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.Rollback; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Service层CRUD测试 - MySQL转PostgreSQL验证 + * 覆盖所有Service的基础CRUD操作 + */ +@SpringBootTest +@ActiveProfiles("test") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Transactional +@Rollback(true) // 确保测试后回滚,不污染数据库 +public class ServiceCrudTest { + + @Autowired + private IFileStorageService fileStorageService; + + @Autowired + private IFileMetadataInfoService fileMetadataInfoService; + + @Autowired + private IDimensionTemplateService dimensionTemplateService; + + @Autowired + private IFileStorageQuotaService fileStorageQuotaService; + + @Autowired + private ISimulationParameterLibraryService simulationParameterLibraryService; + + @Autowired + private ITrainingModelService trainingModelService; + + @Autowired + private IFileMetadataExtensionService fileMetadataExtensionService; + + @Autowired + private IFileSimulationMappingService fileSimulationMappingService; + + @Autowired + private IFileUserPermissionService fileUserPermissionService; + + @Autowired + private IDimensionTemplateHierarchyService dimensionTemplateHierarchyService; + + @Autowired + private ISimulationParameterLibraryCategoryService simulationParameterLibraryCategoryService; + + @Autowired + private ISimulationParameterLibraryCategoryObjectService simulationParameterLibraryCategoryObjectService; + + @Autowired + private ITrainingModelAlgorithmParamService trainingModelAlgorithmParamService; + + // ==================== IFileStorageService Tests ==================== + + @Test + @Order(1) + @DisplayName("IFileStorageService - save保存测试") + void testFileStorageServiceSave() { + FileStorage entity = createFileStorageEntity("service_test.txt"); + + boolean result = fileStorageService.save(entity); + + assertTrue(result); + assertNotNull(entity.getId()); + } + + @Test + @Order(2) + @DisplayName("IFileStorageService - getById查询测试") + void testFileStorageServiceGetById() { + // 先插入 + FileStorage entity = createFileStorageEntity("service_get_test.txt"); + fileStorageService.save(entity); + + // 再查询 + FileStorage result = fileStorageService.getById(entity.getId()); + + assertNotNull(result); + assertEquals("service_get_test.txt", result.getFileName()); + } + + @Test + @Order(3) + @DisplayName("IFileStorageService - updateById更新测试") + void testFileStorageServiceUpdate() { + FileStorage entity = createFileStorageEntity("service_update_test.txt"); + fileStorageService.save(entity); + + entity.setFileName("service_update_test_modified.txt"); + entity.setFileSize(8192L); + boolean result = fileStorageService.updateById(entity); + + assertTrue(result); + + FileStorage updated = fileStorageService.getById(entity.getId()); + assertEquals("service_update_test_modified.txt", updated.getFileName()); + assertEquals(8192L, updated.getFileSize()); + } + + @Test + @Order(4) + @DisplayName("IFileStorageService - removeById删除测试") + void testFileStorageServiceRemove() { + FileStorage entity = createFileStorageEntity("service_remove_test.txt"); + fileStorageService.save(entity); + + boolean result = fileStorageService.removeById(entity.getId()); + + assertTrue(result); + assertNull(fileStorageService.getById(entity.getId())); + } + + @Test + @Order(5) + @DisplayName("IFileStorageService - list列表查询测试") + void testFileStorageServiceList() { + List result = fileStorageService.list(); + assertNotNull(result); + } + + @Test + @Order(6) + @DisplayName("IFileStorageService - 条件查询测试") + void testFileStorageServiceListWithWrapper() { + FileStorage entity = createFileStorageEntity("service_wrapper_test.txt"); + entity.setFileBizType(99); + fileStorageService.save(entity); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FileStorage::getFileBizType, 99); + + List result = fileStorageService.list(wrapper); + + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + @Order(7) + @DisplayName("IFileStorageService - 分页查询测试") + void testFileStorageServicePage() { + Page page = new Page<>(1, 10); + + Page result = fileStorageService.page(page); + + assertNotNull(result); + assertNotNull(result.getRecords()); + } + + @Test + @Order(8) + @DisplayName("IFileStorageService - 批量保存测试") + void testFileStorageServiceSaveBatch() { + List entities = Arrays.asList( + createFileStorageEntity("batch_1.txt"), + createFileStorageEntity("batch_2.txt"), + createFileStorageEntity("batch_3.txt") + ); + + boolean result = fileStorageService.saveBatch(entities); + + assertTrue(result); + entities.forEach(e -> assertNotNull(e.getId())); + } + + @Test + @Order(9) + @DisplayName("IFileStorageService - 自定义方法selectNodeSizeByNodeType测试") + void testFileStorageServiceSelectNodeSizeByNodeType() { + var result = fileStorageService.selectNodeSizeByNodeType( + Arrays.asList(1L, 2L), + 3, + 1L + ); + assertNotNull(result); + } + + // ==================== IFileMetadataInfoService Tests ==================== + + @Test + @Order(20) + @DisplayName("IFileMetadataInfoService - CRUD完整测试") + void testFileMetadataInfoServiceCRUD() { + // CREATE + FileMetadataInfo entity = createFileMetadataInfoEntity("service_metadata.txt"); + boolean saveResult = fileMetadataInfoService.save(entity); + assertTrue(saveResult); + + // READ + FileMetadataInfo read = fileMetadataInfoService.getById(entity.getId()); + assertNotNull(read); + + // UPDATE + read.setOriginalName("service_metadata_updated.txt"); + boolean updateResult = fileMetadataInfoService.updateById(read); + assertTrue(updateResult); + + // DELETE + boolean deleteResult = fileMetadataInfoService.removeById(entity.getId()); + assertTrue(deleteResult); + } + + @Test + @Order(21) + @DisplayName("IFileMetadataInfoService - listSimulationNodeDir测试") + void testFileMetadataInfoServiceListSimulationNodeDir() { + var result = fileMetadataInfoService.listSimulationNodeDir( + Arrays.asList(1L, 2L), + false, + 1L + ); + assertNotNull(result); + } + + // ==================== IDimensionTemplateService Tests ==================== + + @Test + @Order(30) + @DisplayName("IDimensionTemplateService - CRUD完整测试") + void testDimensionTemplateServiceCRUD() { + // CREATE + DimensionTemplate entity = createDimensionTemplateEntity("Service测试模板"); + boolean saveResult = dimensionTemplateService.save(entity); + assertTrue(saveResult); + + // READ + DimensionTemplate read = dimensionTemplateService.getById(entity.getId()); + assertNotNull(read); + assertEquals("Service测试模板", read.getTemplateName()); + + // UPDATE + read.setTemplateName("Service测试模板_更新"); + boolean updateResult = dimensionTemplateService.updateById(read); + assertTrue(updateResult); + + // DELETE + boolean deleteResult = dimensionTemplateService.removeById(entity.getId()); + assertTrue(deleteResult); + } + + // ==================== IFileStorageQuotaService Tests ==================== + + @Test + @Order(40) + @DisplayName("IFileStorageQuotaService - CRUD完整测试") + void testFileStorageQuotaServiceCRUD() { + // CREATE + FileStorageQuota entity = createFileStorageQuotaEntity(200L); + boolean saveResult = fileStorageQuotaService.save(entity); + assertTrue(saveResult); + + // READ + FileStorageQuota read = fileStorageQuotaService.getById(entity.getId()); + assertNotNull(read); + + // UPDATE + read.setStatus("WARNING"); + boolean updateResult = fileStorageQuotaService.updateById(read); + assertTrue(updateResult); + + // DELETE + boolean deleteResult = fileStorageQuotaService.removeById(entity.getId()); + assertTrue(deleteResult); + } + + // ==================== ISimulationParameterLibraryService Tests ==================== + + @Test + @Order(50) + @DisplayName("ISimulationParameterLibraryService - CRUD完整测试") + void testSimulationParameterLibraryServiceCRUD() { + // CREATE + SimulationParameterLibrary entity = createSimulationParameterLibraryEntity("Service测试参数库"); + boolean saveResult = simulationParameterLibraryService.save(entity); + assertTrue(saveResult); + + // READ + SimulationParameterLibrary read = simulationParameterLibraryService.getById(entity.getId()); + assertNotNull(read); + + // UPDATE + read.setParameterLibraryName("Service测试参数库_更新"); + boolean updateResult = simulationParameterLibraryService.updateById(read); + assertTrue(updateResult); + + // DELETE + boolean deleteResult = simulationParameterLibraryService.removeById(entity.getId()); + assertTrue(deleteResult); + } + + // ==================== ITrainingModelService Tests ==================== + + @Test + @Order(60) + @DisplayName("ITrainingModelService - CRUD完整测试") + void testTrainingModelServiceCRUD() { + // CREATE + TrainingModel entity = createTrainingModelEntity("Service测试模型"); + boolean saveResult = trainingModelService.save(entity); + assertTrue(saveResult); + + // READ + TrainingModel read = trainingModelService.getById(entity.getId()); + assertNotNull(read); + assertEquals("Service测试模型", read.getModelName()); + + // UPDATE + read.setModelName("Service测试模型_更新"); + boolean updateResult = trainingModelService.updateById(read); + assertTrue(updateResult); + + // DELETE + boolean deleteResult = trainingModelService.removeById(entity.getId()); + assertTrue(deleteResult); + } + + // ==================== 其他Service基础CRUD测试 ==================== + + @Test + @Order(70) + @DisplayName("IFileMetadataExtensionService - 基础测试") + void testFileMetadataExtensionService() { + List result = fileMetadataExtensionService.list(); + assertNotNull(result); + } + + @Test + @Order(71) + @DisplayName("IFileSimulationMappingService - 基础测试") + void testFileSimulationMappingService() { + List result = fileSimulationMappingService.list(); + assertNotNull(result); + } + + @Test + @Order(72) + @DisplayName("IFileUserPermissionService - 基础测试") + void testFileUserPermissionService() { + List result = fileUserPermissionService.list(); + assertNotNull(result); + } + + @Test + @Order(73) + @DisplayName("IDimensionTemplateHierarchyService - 基础测试") + void testDimensionTemplateHierarchyService() { + List result = dimensionTemplateHierarchyService.list(); + assertNotNull(result); + } + + @Test + @Order(74) + @DisplayName("ISimulationParameterLibraryCategoryService - 基础测试") + void testSimulationParameterLibraryCategoryService() { + List result = simulationParameterLibraryCategoryService.list(); + assertNotNull(result); + } + + @Test + @Order(75) + @DisplayName("ISimulationParameterLibraryCategoryObjectService - 基础测试") + void testSimulationParameterLibraryCategoryObjectService() { + List result = simulationParameterLibraryCategoryObjectService.list(); + assertNotNull(result); + } + + @Test + @Order(76) + @DisplayName("ITrainingModelAlgorithmParamService - 基础测试") + void testTrainingModelAlgorithmParamService() { + List result = trainingModelAlgorithmParamService.list(); + assertNotNull(result); + } + + // ==================== 复杂查询测试 ==================== + + @Test + @Order(80) + @DisplayName("复杂条件组合查询测试") + void testComplexQuery() { + // 准备测试数据 + FileStorage entity1 = createFileStorageEntity("complex_1.txt"); + entity1.setFileBizType(100); + entity1.setFileSize(1000L); + fileStorageService.save(entity1); + + FileStorage entity2 = createFileStorageEntity("complex_2.txt"); + entity2.setFileBizType(100); + entity2.setFileSize(2000L); + fileStorageService.save(entity2); + + // 复杂查询 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FileStorage::getFileBizType, 100) + .ge(FileStorage::getFileSize, 1000L) + .orderByDesc(FileStorage::getFileSize); + + List result = fileStorageService.list(wrapper); + + assertNotNull(result); + assertEquals(2, result.size()); + // 验证排序 + assertTrue(result.get(0).getFileSize() >= result.get(1).getFileSize()); + } + + @Test + @Order(81) + @DisplayName("统计count测试") + void testCount() { + long count = fileStorageService.count(); + assertTrue(count >= 0); + } + + @Test + @Order(82) + @DisplayName("条件统计count测试") + void testCountWithWrapper() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(FileStorage::getTenantId, 1L); + + long count = fileStorageService.count(wrapper); + assertTrue(count >= 0); + } + + // ==================== Helper Methods ==================== + + private FileStorage createFileStorageEntity(String fileName) { + FileStorage entity = new FileStorage(); + entity.setFileName(fileName); + entity.setFileId(System.currentTimeMillis()); + entity.setTenantId(1L); + entity.setUserId(1L); + entity.setDirId(1L); + entity.setFileSuffix("txt"); + entity.setFileBizType(1); + entity.setFileSize(1024L); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + entity.setCreateYearMonth("2026-01"); + return entity; + } + + private FileMetadataInfo createFileMetadataInfoEntity(String originalName) { + FileMetadataInfo entity = new FileMetadataInfo(); + entity.setRelatedResourceUuid("uuid-" + System.currentTimeMillis()); + entity.setRelatedResourceUuidOwnType("node"); + entity.setOriginalName(originalName); + entity.setDataType(2); + entity.setIsLatest(true); + entity.setTenantId(1L); + entity.setCreatorId(1L); + entity.setCreateTime(LocalDateTime.now()); + entity.setUpdateTime(LocalDateTime.now()); + return entity; + } + + private DimensionTemplate createDimensionTemplateEntity(String templateName) { + DimensionTemplate entity = new DimensionTemplate(); + entity.setTemplateName(templateName); + entity.setTemplateType(1); + entity.setDescription("测试描述"); + entity.setTenantId(1L); + entity.setCreatedBy(1L); + entity.setCreatedAt(LocalDateTime.now()); + return entity; + } + + private FileStorageQuota createFileStorageQuotaEntity(Long userId) { + FileStorageQuota entity = new FileStorageQuota(); + entity.setUserId(userId); + entity.setTenantId(1L); + entity.setQuotaValue(5368709120L); + entity.setQuotaUnit("GB"); + entity.setUsedValue(0L); + entity.setStatus("NORMAL"); + return entity; + } + + private SimulationParameterLibrary createSimulationParameterLibraryEntity(String name) { + SimulationParameterLibrary entity = new SimulationParameterLibrary(); + entity.setParameterLibraryName(name); + entity.setTenantId(1L); + entity.setCreatorId(1L); + entity.setCreateTime(LocalDateTime.now()); + return entity; + } + + private TrainingModel createTrainingModelEntity(String modelName) { + TrainingModel entity = new TrainingModel(); + entity.setModelName(modelName); + entity.setAlgorithmType("SVM"); + entity.setTrainer("tester"); + entity.setHandleStatus("待开始"); + entity.setTrainingStatus("待开始"); + entity.setPredStatus("待开始"); + entity.setTenantId(1L); + entity.setCreator(1L); + entity.setCreateTime(LocalDateTime.now()); + return entity; + } +} diff --git a/data/src/test/resources/application-test.yml b/data/src/test/resources/application-test.yml new file mode 100644 index 00000000..8f46148f --- /dev/null +++ b/data/src/test/resources/application-test.yml @@ -0,0 +1,28 @@ +spring: + datasource: + # 直接连接PostgreSQL数据库进行单元测试 + url: jdbc:postgresql://192.168.65.161:25432/spdm?currentSchema=public&stringtype=unspecified + driver-class-name: org.postgresql.Driver + username: spdm + password: Spdm@2026 + hikari: + maximum-pool-size: 5 + minimum-idle: 2 + connection-timeout: 30000 + +mybatis-plus: + mapper-locations: classpath*:/mapper/**/*.xml + configuration: + map-underscore-to-camel-case: false + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + id-type: auto + logic-delete-value: 1 + logic-not-delete-value: 0 + +logging: + level: + com.sdm.data: DEBUG + org.springframework.jdbc: DEBUG + org.apache.ibatis: DEBUG