feat(enum): 统一结构化枚举值传输与同步

This commit is contained in:
2026-05-24 21:12:14 +08:00
parent bd8bfeb607
commit e37e8dfca6
23 changed files with 793 additions and 78 deletions

View File

@@ -2,12 +2,14 @@ package com.bruce.common.enumconfig;
import com.bruce.common.enums.CommonStatusEnum;
import com.bruce.common.enums.EnableStatusEnum;
import com.bruce.rag.enums.RagIndexStatusEnum;
import com.bruce.common.enums.PersistableSysEnumDefinition;
import com.bruce.rag.enums.RagChunkStrategyEnum;
import com.bruce.rag.enums.RagIndexStatusEnum;
import com.bruce.rag.enums.RagParseStatusEnum;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class EnumDefinitionTests {
@@ -46,4 +48,33 @@ class EnumDefinitionTests {
assertEquals("按分隔符切片", RagChunkStrategyEnum.DELIMITER.getLabel());
assertEquals("语义切片", RagChunkStrategyEnum.SEMANTIC.getLabel());
}
@Test
void enumsShouldExposeStableSysEnumMetadata() {
PersistableSysEnumDefinition chunkStrategy = RagChunkStrategyEnum.DELIMITER;
PersistableSysEnumDefinition parseStatus = RagParseStatusEnum.PARSED;
PersistableSysEnumDefinition enableStatus = EnableStatusEnum.ENABLED;
assertEquals("rag", chunkStrategy.getCatalog());
assertEquals("chunk_strategy", chunkStrategy.getType());
assertEquals("按分隔符切片", chunkStrategy.getName());
assertEquals(5, chunkStrategy.getValue());
assertEquals(5, chunkStrategy.getSort());
assertEquals("RAG文档切片方式", chunkStrategy.getRemark());
assertEquals("rag", parseStatus.getCatalog());
assertEquals("parse_status", parseStatus.getType());
assertEquals("已解析", parseStatus.getName());
assertEquals("common", enableStatus.getCatalog());
assertEquals("enable_status", enableStatus.getType());
assertEquals("启用", enableStatus.getName());
}
@Test
void ragChunkStrategyShouldResolveByIntegerValue() {
assertEquals(RagChunkStrategyEnum.FIXED_LENGTH, RagChunkStrategyEnum.fromValue(1));
assertEquals(RagChunkStrategyEnum.DELIMITER, RagChunkStrategyEnum.fromValue(5));
assertThrows(IllegalArgumentException.class, () -> RagChunkStrategyEnum.fromValue(999));
}
}

View File

@@ -104,4 +104,12 @@ class SysEnumComponentStructureTests {
assertNotNull(initMethod);
}
@Test
void sysEnumServiceShouldExposeReplaceByCatalogAndType() throws NoSuchMethodException {
Method replaceMethod = ISysEnumService.class.getMethod("replaceByCatalogAndType", String.class, String.class, List.class);
assertNotNull(replaceMethod);
assertEquals(boolean.class, replaceMethod.getReturnType());
}
}

View File

@@ -2,6 +2,7 @@ package com.bruce.common.enumconfig;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.enums.PersistableSysEnumDefinition;
import com.bruce.common.enums.CommonStatusEnum;
import com.bruce.common.enums.EnableStatusEnum;
import com.bruce.common.service.ISysEnumService;
@@ -13,6 +14,8 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
@EnabledIfSystemProperty(named = "runEnumInit", matches = "true")
class SysEnumDataInitTests {
@@ -22,49 +25,22 @@ class SysEnumDataInitTests {
@Test
public void initDefaultEnums() {
saveOrUpdate("common", "enable_status", EnableStatusEnum.DISABLED.getLabel(), EnableStatusEnum.DISABLED.getValue(), 0, "通用启用状态");
saveOrUpdate("common", "enable_status", EnableStatusEnum.ENABLED.getLabel(), EnableStatusEnum.ENABLED.getValue(), 1, "通用启用状态");
List<SysEnumDefinitionSyncSupport.EnumGroup> groups = List.of(
buildGroup(EnableStatusEnum.values()),
buildGroup(CommonStatusEnum.values()),
buildGroup(RagParseStatusEnum.values()),
buildGroup(RagIndexStatusEnum.values()),
buildGroup(RagChunkStrategyEnum.values())
);
SysEnumDefinitionSyncSupport.validateUniqueGroupKeys(groups);
saveOrUpdate("common", "common_status", CommonStatusEnum.DISABLED.getLabel(), CommonStatusEnum.DISABLED.getValue(), 0, "通用状态");
saveOrUpdate("common", "common_status", CommonStatusEnum.ENABLED.getLabel(), CommonStatusEnum.ENABLED.getValue(), 1, "通用状态");
saveOrUpdate("common", "common_status", CommonStatusEnum.DRAFT.getLabel(), CommonStatusEnum.DRAFT.getValue(), 2, "通用状态");
saveOrUpdate("common", "common_status", CommonStatusEnum.PROCESSING.getLabel(), CommonStatusEnum.PROCESSING.getValue(), 3, "通用状态");
saveOrUpdate("common", "common_status", CommonStatusEnum.COMPLETED.getLabel(), CommonStatusEnum.COMPLETED.getValue(), 4, "通用状态");
saveOrUpdate("common", "common_status", CommonStatusEnum.FAILED.getLabel(), CommonStatusEnum.FAILED.getValue(), 5, "通用状态");
saveOrUpdate("rag", "parse_status", RagParseStatusEnum.UPLOADED.getLabel(), RagParseStatusEnum.UPLOADED.getValue(), 1, "RAG文档解析状态");
saveOrUpdate("rag", "parse_status", RagParseStatusEnum.PARSING.getLabel(), RagParseStatusEnum.PARSING.getValue(), 2, "RAG文档解析状态");
saveOrUpdate("rag", "parse_status", RagParseStatusEnum.PARSED.getLabel(), RagParseStatusEnum.PARSED.getValue(), 3, "RAG文档解析状态");
saveOrUpdate("rag", "parse_status", RagParseStatusEnum.FAILED.getLabel(), RagParseStatusEnum.FAILED.getValue(), 4, "RAG文档解析状态");
saveOrUpdate("rag", "index_status", RagIndexStatusEnum.PENDING.getLabel(), RagIndexStatusEnum.PENDING.getValue(), 1, "RAG文档索引状态");
saveOrUpdate("rag", "index_status", RagIndexStatusEnum.INDEXING.getLabel(), RagIndexStatusEnum.INDEXING.getValue(), 2, "RAG文档索引状态");
saveOrUpdate("rag", "index_status", RagIndexStatusEnum.INDEXED.getLabel(), RagIndexStatusEnum.INDEXED.getValue(), 3, "RAG文档索引状态");
saveOrUpdate("rag", "index_status", RagIndexStatusEnum.FAILED.getLabel(), RagIndexStatusEnum.FAILED.getValue(), 4, "RAG文档索引状态");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.FIXED_LENGTH.getLabel(), RagChunkStrategyEnum.FIXED_LENGTH.getValue(), 1, "RAG文档切片方式");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.PARAGRAPH.getLabel(), RagChunkStrategyEnum.PARAGRAPH.getValue(), 2, "RAG文档切片方式");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.HEADING.getLabel(), RagChunkStrategyEnum.HEADING.getValue(), 3, "RAG文档切片方式");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.TABLE_ROW.getLabel(), RagChunkStrategyEnum.TABLE_ROW.getValue(), 4, "RAG文档切片方式");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.DELIMITER.getLabel(), RagChunkStrategyEnum.DELIMITER.getValue(), 5, "RAG文档切片方式");
saveOrUpdate("rag", "chunk_strategy", RagChunkStrategyEnum.SEMANTIC.getLabel(), RagChunkStrategyEnum.SEMANTIC.getValue(), 6, "RAG文档切片方式");
for (SysEnumDefinitionSyncSupport.EnumGroup group : groups) {
List<SysEnum> rows = SysEnumDefinitionSyncSupport.toEntities(group);
sysEnumService.replaceByCatalogAndType(group.catalog(), group.type(), rows);
}
}
private void saveOrUpdate(String catalog, String type, String name, Integer value, Integer sort, String remark) {
SysEnum sysEnum = sysEnumService.getOne(new LambdaQueryWrapper<SysEnum>()
.eq(SysEnum::getCatalog, catalog)
.eq(SysEnum::getType, type)
.eq(SysEnum::getName, name));
if (sysEnum == null) {
sysEnum = new SysEnum();
}
sysEnum.setCatalog(catalog);
sysEnum.setType(type);
sysEnum.setName(name);
sysEnum.setValue(value);
sysEnum.setStrvalue(null);
sysEnum.setSort(sort);
sysEnum.setRemark(remark);
sysEnumService.saveOrUpdate(sysEnum);
private SysEnumDefinitionSyncSupport.EnumGroup buildGroup(PersistableSysEnumDefinition[] definitions) {
return SysEnumDefinitionSyncSupport.groupOf(List.of(definitions));
}
}

View File

@@ -0,0 +1,90 @@
package com.bruce.common.enumconfig;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.enums.PersistableSysEnumDefinition;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* sys_enum 初始化测试的辅助工具。
* <p>
* 该类只服务于测试入口,用于把代码里的枚举定义组装成可落库的数据结构,
* 并在真正写库前完成组级唯一性校验。
*/
final class SysEnumDefinitionSyncSupport {
private SysEnumDefinitionSyncSupport() {
}
static EnumGroup groupOf(List<? extends PersistableSysEnumDefinition> definitions) {
if (definitions == null || definitions.isEmpty()) {
throw new IllegalArgumentException("枚举定义不能为空");
}
PersistableSysEnumDefinition first = definitions.getFirst();
validateGroupMembers(first, definitions);
validateUniqueValuesAndSorts(first, definitions);
return new EnumGroup(first.getCatalog(), first.getType(), List.copyOf(definitions));
}
static void validateUniqueGroupKeys(List<EnumGroup> groups) {
Set<String> keys = new HashSet<>();
for (EnumGroup group : groups) {
String key = group.catalog() + "/" + group.type();
if (!keys.add(key)) {
throw new IllegalArgumentException("存在重复的枚举分组: " + key);
}
}
}
static List<SysEnum> toEntities(EnumGroup group) {
return group.definitions().stream()
.map(item -> {
SysEnum sysEnum = new SysEnum();
sysEnum.setCatalog(group.catalog());
sysEnum.setType(group.type());
sysEnum.setName(item.getName());
sysEnum.setValue(item.getValue());
sysEnum.setStrvalue(item.getStrvalue());
sysEnum.setSort(item.getSort());
sysEnum.setRemark(item.getRemark());
return sysEnum;
})
.toList();
}
private static void validateGroupMembers(
PersistableSysEnumDefinition first,
List<? extends PersistableSysEnumDefinition> definitions
) {
for (PersistableSysEnumDefinition item : definitions) {
if (!first.getCatalog().equals(item.getCatalog()) || !first.getType().equals(item.getType())) {
throw new IllegalArgumentException("同一枚举组中的 catalog/type 必须一致");
}
}
}
private static void validateUniqueValuesAndSorts(
PersistableSysEnumDefinition first,
List<? extends PersistableSysEnumDefinition> definitions
) {
Set<Integer> values = new HashSet<>();
Set<Integer> sorts = new HashSet<>();
for (PersistableSysEnumDefinition item : definitions) {
if (!values.add(item.getValue())) {
throw new IllegalArgumentException("枚举值重复: " + first.getCatalog() + "/" + first.getType() + "/" + item.getValue());
}
if (!sorts.add(item.getSort())) {
throw new IllegalArgumentException("枚举排序重复: " + first.getCatalog() + "/" + first.getType() + "/" + item.getSort());
}
}
}
record EnumGroup(
String catalog,
String type,
List<? extends PersistableSysEnumDefinition> definitions
) {
}
}

View File

@@ -0,0 +1,98 @@
package com.bruce.common.enumconfig;
import com.bruce.common.domain.entity.SysEnum;
import com.bruce.common.enums.EnableStatusEnum;
import com.bruce.common.enums.PersistableSysEnumDefinition;
import com.bruce.rag.enums.RagChunkStrategyEnum;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class SysEnumDefinitionSyncSupportTests {
@Test
void shouldRejectDuplicateCatalogAndTypeAcrossGroups() {
SysEnumDefinitionSyncSupport.EnumGroup left = SysEnumDefinitionSyncSupport.groupOf(
List.of(EnableStatusEnum.DISABLED, EnableStatusEnum.ENABLED)
);
SysEnumDefinitionSyncSupport.EnumGroup right = SysEnumDefinitionSyncSupport.groupOf(
List.of(
new FakeEnumDefinition("common", "enable_status", "草稿", 2, 2, "通用启用状态")
)
);
assertThrows(
IllegalArgumentException.class,
() -> SysEnumDefinitionSyncSupport.validateUniqueGroupKeys(List.of(left, right))
);
}
@Test
void shouldBuildSysEnumRowsFromDefinitions() {
SysEnumDefinitionSyncSupport.EnumGroup group = SysEnumDefinitionSyncSupport.groupOf(
List.of(RagChunkStrategyEnum.FIXED_LENGTH, RagChunkStrategyEnum.DELIMITER)
);
List<SysEnum> rows = SysEnumDefinitionSyncSupport.toEntities(group);
assertEquals(2, rows.size());
assertEquals("rag", rows.get(0).getCatalog());
assertEquals("chunk_strategy", rows.get(0).getType());
assertEquals("固定长度切片", rows.get(0).getName());
assertEquals(1, rows.get(0).getValue());
assertEquals(5, rows.get(1).getValue());
}
private record FakeEnumDefinition(
String catalog,
String type,
String name,
Integer value,
Integer sort,
String remark
) implements PersistableSysEnumDefinition {
@Override
public String getCatalog() {
return catalog;
}
@Override
public String getType() {
return type;
}
@Override
public String getName() {
return name;
}
@Override
public String getLabel() {
return name;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getStrvalue() {
return null;
}
@Override
public Integer getSort() {
return sort;
}
@Override
public String getRemark() {
return remark;
}
}
}

View File

@@ -10,6 +10,7 @@ import com.bruce.common.service.ISysAttachmentService;
import com.bruce.rag.dto.request.RagDocumentParseRequest;
import com.bruce.rag.dto.response.RagDocumentParseResponse;
import com.bruce.rag.entity.RagDocument;
import com.bruce.rag.enums.RagChunkStrategyEnum;
import com.bruce.rag.enums.RagParseStatusEnum;
import com.bruce.rag.service.IRagDocumentService;
import com.bruce.rag.service.impl.RagDocumentParseServiceImpl;
@@ -26,6 +27,7 @@ import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
@@ -121,7 +123,7 @@ class RagDocumentParseServiceImplTests {
);
RagDocumentParseRequest request = new RagDocumentParseRequest();
request.setDocumentIds(List.of(1002L));
request.setChunkStrategy("DELIMITER");
request.setChunkStrategy(RagChunkStrategyEnum.DELIMITER.getValue());
request.setDelimiter("");
when(ragDocumentService.getById(1002L)).thenReturn(document);
@@ -135,6 +137,23 @@ class RagDocumentParseServiceImplTests {
assertEquals(RagParseStatusEnum.PARSED.name(), responses.getFirst().getParseStatus());
}
@Test
void parseShouldRejectUnknownChunkStrategyValue() {
AttachmentProperties attachmentProperties = new AttachmentProperties();
attachmentProperties.setBasePath(tempDir.toString());
RagDocumentParseServiceImpl service = new RagDocumentParseServiceImpl(
ragDocumentService,
sysAttachmentService,
attachmentProperties,
new DocumentParserFactory(List.of(new FixedDocumentParser("batch profiles")))
);
RagDocumentParseRequest request = new RagDocumentParseRequest();
request.setDocumentIds(List.of(1002L));
request.setChunkStrategy(999);
assertThrows(IllegalArgumentException.class, () -> service.parse(request));
}
private static class FixedDocumentParser implements DocumentParser {
private final String text;