fix(ops): 修复工单location字段重复和parent_path格式问题
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

1. 修改 parent_path 格式为以 "/" 开头(如:/1/2/3)
   - buildParentPath: 父级是根节点时返回 "/1" 而非 "1"

2. 修复 buildAreaPath 方法的去重逻辑
   - 从名称层面去重改为 ID 层面去重
   - 避免误删不同ID但名称相同的合法情况
   - 只去除数据错误导致的重复ID
   - 添加警告日志记录重复ID

3. 调整 isDescendant 方法以适配新的 parent_path 格式
   - 简化判断逻辑,移除冗余的 startsWith 检查

4. 更新测试用例以匹配新格式
   - Mock数据: parentPath("10") -> parentPath("/10")
   - 期望值: "10/1" -> "/10/1"

5. 统一 location 路径格式(不带前导斜杠)
   - 示例: "徐汇万科中心一期/A座写字楼/11楼/电梯厅"

变更影响:
- ops_bus_area.parent_path: "1" -> "/1", "1/2" -> "/1/2"
- ops_order.location: 无前导斜杠

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-10 16:14:59 +08:00
parent 631612951c
commit 113e90c726
3 changed files with 37 additions and 18 deletions

View File

@@ -30,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -485,22 +486,34 @@ public class CleanOrderServiceImpl implements CleanOrderService {
} }
}) })
.map(Long::parseLong) .map(Long::parseLong)
.filter(pid -> !pid.equals(areaId)) // 排除当前区域,避免重复 .filter(pid -> !pid.equals(areaId)) // 排除当前区域,避免循环引用
.collect(Collectors.toList()); .collect(Collectors.toList());
// 5. 无有效父级,直接返回区域名称 // 5. 在ID层面去重相邻重复的ID只去除数据错误导致的重复保留不同ID的相同名称
if (parentIds.isEmpty()) { List<Long> deduplicatedIds = new ArrayList<>();
Long lastId = null;
for (Long parentId : parentIds) {
if (!parentId.equals(lastId)) {
deduplicatedIds.add(parentId);
lastId = parentId;
} else {
log.warn("检测到parent_path中重复的ID: areaId={}, duplicateId={}", areaId, parentId);
}
}
// 6. 无有效父级,直接返回区域名称
if (deduplicatedIds.isEmpty()) {
return area.getAreaName(); return area.getAreaName();
} }
// 6. 批量查询所有父级区域(避免 N+1 查询) // 7. 批量查询所有父级区域(避免 N+1 查询)
List<OpsBusAreaDO> parents = opsBusAreaMapper.selectBatchIds(parentIds); List<OpsBusAreaDO> parents = opsBusAreaMapper.selectBatchIds(deduplicatedIds);
if (parents == null || parents.isEmpty()) { if (parents == null || parents.isEmpty()) {
log.warn("未找到父级区域: areaId={}, parentIds={}", areaId, parentIds); log.warn("未找到父级区域: areaId={}, parentIds={}", areaId, deduplicatedIds);
return area.getAreaName(); return area.getAreaName();
} }
// 7. 构建ID到区域的映射 // 8. 构建ID到区域的映射
Map<Long, String> parentNameMap = parents.stream() Map<Long, String> parentNameMap = parents.stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
OpsBusAreaDO::getId, OpsBusAreaDO::getId,
@@ -508,13 +521,18 @@ public class CleanOrderServiceImpl implements CleanOrderService {
(existing, replacement) -> existing // 处理重复key (existing, replacement) -> existing // 处理重复key
)); ));
// 8. 按顺序拼接区域路径 // 9. 按顺序拼接区域路径保持ID顺序
String path = parentIds.stream() List<String> pathSegments = deduplicatedIds.stream()
.filter(parentNameMap::containsKey) // 过滤掉不存在的父级 .filter(parentNameMap::containsKey) // 过滤掉不存在的父级
.map(parentNameMap::get) .map(parentNameMap::get)
.collect(Collectors.joining("/")); .collect(Collectors.toList());
// 9. 拼接当前区域名称 // 10. 拼接完整路径
return StrUtil.isNotBlank(path) ? path + "/" + area.getAreaName() : area.getAreaName(); String path = String.join("/", pathSegments);
if (StrUtil.isBlank(path)) {
return area.getAreaName();
}
return path + "/" + area.getAreaName();
} }
} }

View File

@@ -170,7 +170,7 @@ public class OpsBusAreaServiceImpl implements OpsBusAreaService {
return null; return null;
} }
if (parent.getParentPath() == null || parent.getParentPath().isEmpty()) { if (parent.getParentPath() == null || parent.getParentPath().isEmpty()) {
return String.valueOf(parentId); return "/" + parentId;
} }
return parent.getParentPath() + "/" + parentId; return parent.getParentPath() + "/" + parentId;
} }
@@ -187,9 +187,10 @@ public class OpsBusAreaServiceImpl implements OpsBusAreaService {
if (target == null || target.getParentPath() == null) { if (target == null || target.getParentPath() == null) {
return false; return false;
} }
return target.getParentPath().contains("/" + sourceId + "/") // parent_path 格式:/1/2/3
|| target.getParentPath().startsWith(sourceId + "/") // 判断 sourceId 是否在 parent_path 中
|| target.getParentPath().endsWith("/" + sourceId); String path = target.getParentPath();
return path.contains("/" + sourceId + "/") || path.endsWith("/" + sourceId);
} }
@Override @Override

View File

@@ -149,7 +149,7 @@ class OpsBusAreaServiceTest {
OpsBusAreaDO parentArea = OpsBusAreaDO.builder() OpsBusAreaDO parentArea = OpsBusAreaDO.builder()
.id(1L) .id(1L)
.parentPath("10") .parentPath("/10") // parent_path 现在以 / 开头
.build(); .build();
when(opsBusAreaMapper.selectById(1L)).thenReturn(parentArea); when(opsBusAreaMapper.selectById(1L)).thenReturn(parentArea);
@@ -166,7 +166,7 @@ class OpsBusAreaServiceTest {
assertNotNull(areaId); assertNotNull(areaId);
verify(opsBusAreaMapper, times(1)).insert(argThat((OpsBusAreaDO area) -> verify(opsBusAreaMapper, times(1)).insert(argThat((OpsBusAreaDO area) ->
"A栋".equals(area.getAreaName()) && "A栋".equals(area.getAreaName()) &&
"10/1".equals(area.getParentPath()) // PBT: parent_path 不变性 "/10/1".equals(area.getParentPath()) // PBT: parent_path 以 / 开头
)); ));
} }