Browse Source

feat(dict): 票09 步3 DictQueryService 加 listTree/listChildren 树形读

- listTree(groupCode): 复用扁平启用列表内存组两级树, 一级为根带 children(仅启用子项), 不新建缓存key (Q4)
- listChildren(groupCode, parentCode): 取一级项直接启用子项, 父不存在返空
- DictItemDTO 加 children (仅 listTree 填充); getItem/listEnabledItems 扁平接口不变, 现存消费方零改动
- 测试助手 newChildItem; 新增2测试 listTree_twoLevel/listChildren_byParentCode; 50 全绿 (+2)
master
luoweijian 2 weeks ago
parent
commit
08d95df19a
  1. 3
      crm-dict/src/main/java/com/crm/dict/domain/dto/DictItemDTO.java
  2. 13
      crm-dict/src/main/java/com/crm/dict/service/DictQueryService.java
  3. 38
      crm-dict/src/main/java/com/crm/dict/service/impl/DictQueryServiceImpl.java
  4. 8
      crm-dict/src/test/java/com/crm/dict/AbstractDictH2Test.java
  5. 32
      crm-dict/src/test/java/com/crm/dict/service/impl/DictQueryServiceTest.java

3
crm-dict/src/main/java/com/crm/dict/domain/dto/DictItemDTO.java

@ -48,6 +48,9 @@ public class DictItemDTO extends BaseDTO {
@Schema(description = "是否有效可选(分组与项均启用)") @Schema(description = "是否有效可选(分组与项均启用)")
private Boolean effectiveSelectable; private Boolean effectiveSelectable;
@Schema(description = "二级子项(仅 listTree 返回时填充;二级项自身为空/null)")
private java.util.List<DictItemDTO> children;
/** /**
* Entity DTO出参转换Service 层调用 * Entity DTO出参转换Service 层调用
*/ */

13
crm-dict/src/main/java/com/crm/dict/service/DictQueryService.java

@ -25,6 +25,19 @@ public interface DictQueryService {
*/ */
List<DictItemDTO> listEnabledItems(String groupCode); List<DictItemDTO> listEnabledItems(String groupCode);
/**
* 分组启用字典项两级树业务树形选择器渲染用仅返回分组与项均启用的项
* 顶层parentId=null作为树根带 children也只含启用子项父启用但无启用子项时 children 为空列表
* 分组不存在/停用时返回空列表复用扁平启用列表内存组树Q4
*/
List<DictItemDTO> listTree(String groupCode);
/**
* 某一级项下的启用二级子项列表级联选择器选完一级再拉二级用
* 分组/父项不存在或停用时返回空列表 sort_no 排序空排最后
*/
List<DictItemDTO> listChildren(String groupCode, String parentCode);
/** /**
* 分组当前默认项联查 group.status分组停用休眠或未设默认时返回 null * 分组当前默认项联查 group.status分组停用休眠或未设默认时返回 null
*/ */

38
crm-dict/src/main/java/com/crm/dict/service/impl/DictQueryServiceImpl.java

@ -97,6 +97,44 @@ public class DictQueryServiceImpl implements DictQueryService {
return items.stream().map(DictItemDTO::fromEntity).toList(); return items.stream().map(DictItemDTO::fromEntity).toList();
} }
@Override
public List<DictItemDTO> listTree(String groupCode) {
// 复用已缓存的扁平启用列表,内存组两级树(不新建缓存 key,Q4)
List<DictItemDTO> flat = listEnabledItems(groupCode);
if (flat.isEmpty()) {
return List.of();
}
List<DictItemDTO> roots = new java.util.ArrayList<>();
java.util.Map<Long, List<DictItemDTO>> childrenByParent = new java.util.HashMap<>();
for (DictItemDTO dto : flat) {
if (dto.getParentId() == null) {
roots.add(dto);
} else {
childrenByParent.computeIfAbsent(dto.getParentId(), k -> new java.util.ArrayList<>()).add(dto);
}
}
for (DictItemDTO root : roots) {
root.setChildren(childrenByParent.getOrDefault(root.getId(), List.of()));
}
return roots;
}
@Override
public List<DictItemDTO> listChildren(String groupCode, String parentCode) {
// 启用且父启用才可选:复用启用列表,取 parentCode 对应一级项的直接子项
List<DictItemDTO> flat = listEnabledItems(groupCode);
if (flat.isEmpty()) {
return List.of();
}
DictItemDTO parent = flat.stream()
.filter(d -> d.getParentId() == null && parentCode.equals(d.getCode()))
.findFirst().orElse(null);
if (parent == null) {
return List.of();
}
return flat.stream().filter(d -> parent.getId().equals(d.getParentId())).toList();
}
@Override @Override
public DictItem getDefaultItem(String groupCode) { public DictItem getDefaultItem(String groupCode) {
String key = "defaultItem:" + groupCode; String key = "defaultItem:" + groupCode;

8
crm-dict/src/test/java/com/crm/dict/AbstractDictH2Test.java

@ -166,6 +166,14 @@ public abstract class AbstractDictH2Test {
return i; return i;
} }
/** 新建二级子项(parentId 指向同组一级项) */
protected DictItem newChildItem(Long groupId, Long parentId, String code, String value) {
DictItem i = newItem(groupId, code, value);
i.setParentId(parentId);
dictItemMapper.updateById(i);
return i;
}
/** 断言抛业务异常且错误码匹配 */ /** 断言抛业务异常且错误码匹配 */
protected void assertDictError(org.assertj.core.api.ThrowableAssert.ThrowingCallable callable, int expectedCode) { protected void assertDictError(org.assertj.core.api.ThrowableAssert.ThrowingCallable callable, int expectedCode) {
assertThatThrownBy(callable) assertThatThrownBy(callable)

32
crm-dict/src/test/java/com/crm/dict/service/impl/DictQueryServiceTest.java

@ -129,4 +129,36 @@ class DictQueryServiceTest extends AbstractDictH2Test {
Thread.sleep(2200); Thread.sleep(2200);
assertThat(dictQueryService.getItem("g", "late_item")).isNotNull(); assertThat(dictQueryService.getItem("g", "late_item")).isNotNull();
} }
@Test
@DisplayName("listTree:一级为根带 children,仅含启用项,分组停用返空")
void listTree_twoLevel() {
DictGroup g = newGroup("industry", "所属行业");
DictItem gov = newItem(g.getId(), "gov", "gov");
DictItem edu = newItem(g.getId(), "edu", "edu");
newChildItem(g.getId(), gov.getId(), "gov_ncpol", "gov_ncpol");
newChildItem(g.getId(), gov.getId(), "gov_emg", "gov_emg");
// edu 无启用子项
List<DictItemDTO> tree = dictQueryService.listTree("industry");
assertThat(tree).extracting(DictItemDTO::getCode).containsExactlyInAnyOrder("gov", "edu");
DictItemDTO govNode = tree.stream().filter(t -> "gov".equals(t.getCode())).findFirst().orElseThrow();
assertThat(govNode.getChildren()).extracting(DictItemDTO::getCode)
.containsExactlyInAnyOrder("gov_ncpol", "gov_emg");
DictItemDTO eduNode = tree.stream().filter(t -> "edu".equals(t.getCode())).findFirst().orElseThrow();
assertThat(eduNode.getChildren()).isEmpty();
}
@Test
@DisplayName("listChildren:取一级项直接启用子项;父不存在返空")
void listChildren_byParentCode() {
DictGroup g = newGroup("industry", "所属行业");
DictItem gov = newItem(g.getId(), "gov", "gov");
newChildItem(g.getId(), gov.getId(), "gov_ncpol", "gov_ncpol");
newChildItem(g.getId(), gov.getId(), "gov_emg", "gov_emg");
assertThat(dictQueryService.listChildren("industry", "gov"))
.extracting(DictItemDTO::getCode).containsExactlyInAnyOrder("gov_ncpol", "gov_emg");
assertThat(dictQueryService.listChildren("industry", "not_exist")).isEmpty();
}
} }

Loading…
Cancel
Save