refactor(ops): 提取 AreaPathBuilder 公共组件,消除保洁/安保 buildAreaPath 重复代码
将 CleanOrderServiceImpl 中的 buildAreaPath 私有方法提取到 ops-biz 公共层
AreaPathBuilder 组件,供各业务模块(保洁、安保等)共享使用。同时优化:
- 用正则 matches("\d+") 替代 try-catch NumberFormatException 做数字校验
- 增加相邻重复ID去重保护
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,9 +10,7 @@ import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderEventPublisher;
|
||||
import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest;
|
||||
import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.OperatorTypeEnum;
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
@@ -21,6 +19,7 @@ import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqD
|
||||
import com.viewsh.module.ops.environment.dal.dataobject.workorder.OpsOrderCleanExtDO;
|
||||
import com.viewsh.module.ops.environment.dal.mysql.workorder.OpsOrderCleanExtMapper;
|
||||
import com.viewsh.module.ops.environment.integration.listener.CleanOrderEventListener;
|
||||
import com.viewsh.module.ops.infrastructure.area.AreaPathBuilder;
|
||||
import com.viewsh.module.ops.infrastructure.code.OrderCodeGenerator;
|
||||
import com.viewsh.module.ops.infrastructure.id.OrderIdGenerator;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -30,11 +29,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 保洁工单服务实现(重构版)
|
||||
@@ -88,7 +83,7 @@ public class CleanOrderServiceImpl implements CleanOrderService {
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Resource
|
||||
private OpsBusAreaMapper opsBusAreaMapper;
|
||||
private AreaPathBuilder areaPathBuilder;
|
||||
|
||||
// ==================== 工单创建 ====================
|
||||
|
||||
@@ -109,7 +104,7 @@ public class CleanOrderServiceImpl implements CleanOrderService {
|
||||
.priority(createReq.getPriority() != null ? createReq.getPriority() : PriorityEnum.P2.getPriority())
|
||||
.status(WorkOrderStatusEnum.PENDING.getStatus())
|
||||
.areaId(createReq.getAreaId())
|
||||
.location(buildAreaPath(createReq.getAreaId()))
|
||||
.location(areaPathBuilder.buildPath(createReq.getAreaId()))
|
||||
.sourceType(createReq.getSourceType() != null ? createReq.getSourceType() : "TRAFFIC")
|
||||
// IoT集成字段
|
||||
.triggerSource(createReq.getTriggerSource())
|
||||
@@ -448,91 +443,4 @@ public class CleanOrderServiceImpl implements CleanOrderService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据区域ID构建完整路径(如"园区/A栋/B层/电梯厅")
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 完整区域路径,用 "/" 分隔
|
||||
*/
|
||||
private String buildAreaPath(Long areaId) {
|
||||
// 1. 参数校验
|
||||
if (areaId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 2. 查询当前区域
|
||||
OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId);
|
||||
if (area == null) {
|
||||
log.warn("区域不存在: areaId={}", areaId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 无父级路径,直接返回区域名称
|
||||
String parentPath = area.getParentPath();
|
||||
if (StrUtil.isEmpty(parentPath)) {
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
// 4. 解析父级ID列表(使用 Stream 过滤无效ID)
|
||||
List<Long> parentIds = Arrays.stream(parentPath.split("/"))
|
||||
.filter(StrUtil::isNotBlank) // 过滤空字符串
|
||||
.filter(pid -> {
|
||||
try {
|
||||
Long.parseLong(pid);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("父级区域ID格式错误: areaId={}, parentId={}", areaId, pid);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(Long::parseLong)
|
||||
.filter(pid -> !pid.equals(areaId)) // 排除当前区域,避免循环引用
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 5. 在ID层面去重相邻重复的ID(只去除数据错误导致的重复,保留不同ID的相同名称)
|
||||
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();
|
||||
}
|
||||
|
||||
// 7. 批量查询所有父级区域(避免 N+1 查询)
|
||||
List<OpsBusAreaDO> parents = opsBusAreaMapper.selectBatchIds(deduplicatedIds);
|
||||
if (parents == null || parents.isEmpty()) {
|
||||
log.warn("未找到父级区域: areaId={}, parentIds={}", areaId, deduplicatedIds);
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
// 8. 构建ID到区域的映射
|
||||
Map<Long, String> parentNameMap = parents.stream()
|
||||
.collect(Collectors.toMap(
|
||||
OpsBusAreaDO::getId,
|
||||
OpsBusAreaDO::getAreaName,
|
||||
(existing, replacement) -> existing // 处理重复key
|
||||
));
|
||||
|
||||
// 9. 按顺序拼接区域路径(保持ID顺序)
|
||||
List<String> pathSegments = deduplicatedIds.stream()
|
||||
.filter(parentNameMap::containsKey) // 过滤掉不存在的父级
|
||||
.map(parentNameMap::get)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 10. 拼接完整路径
|
||||
String path = String.join("/", pathSegments);
|
||||
if (StrUtil.isBlank(path)) {
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
return path + "/" + area.getAreaName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.viewsh.module.ops.infrastructure.area;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO;
|
||||
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 区域路径构建器
|
||||
* <p>
|
||||
* 根据 areaId 拼接完整的区域路径,如 "A园区/A栋/3层/电梯厅"。
|
||||
* 供保洁、安保等各业务模块共享使用。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class AreaPathBuilder {
|
||||
|
||||
@Resource
|
||||
private OpsBusAreaMapper opsBusAreaMapper;
|
||||
|
||||
/**
|
||||
* 根据已查询到的区域对象构建完整路径
|
||||
*
|
||||
* @param area 区域对象(非 null)
|
||||
* @return 完整区域路径,用 "/" 分隔
|
||||
*/
|
||||
public String buildPath(OpsBusAreaDO area) {
|
||||
if (area == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String parentPath = area.getParentPath();
|
||||
if (StrUtil.isEmpty(parentPath)) {
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
// 解析父级ID列表
|
||||
List<Long> parentIds = Arrays.stream(parentPath.split("/"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.filter(pid -> pid.matches("\\d+"))
|
||||
.map(Long::parseLong)
|
||||
.filter(pid -> !pid.equals(area.getId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// ID层面去重相邻重复(数据异常保护)
|
||||
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={}", area.getId(), parentId);
|
||||
}
|
||||
}
|
||||
|
||||
if (deduplicatedIds.isEmpty()) {
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
// 批量查询父级区域
|
||||
List<OpsBusAreaDO> parents = opsBusAreaMapper.selectBatchIds(deduplicatedIds);
|
||||
if (parents == null || parents.isEmpty()) {
|
||||
log.warn("未找到父级区域: areaId={}, parentIds={}", area.getId(), deduplicatedIds);
|
||||
return area.getAreaName();
|
||||
}
|
||||
|
||||
Map<Long, String> parentNameMap = parents.stream()
|
||||
.collect(Collectors.toMap(OpsBusAreaDO::getId, OpsBusAreaDO::getAreaName, (a, b) -> a));
|
||||
|
||||
List<String> pathSegments = deduplicatedIds.stream()
|
||||
.filter(parentNameMap::containsKey)
|
||||
.map(parentNameMap::get)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String path = String.join("/", pathSegments);
|
||||
return StrUtil.isBlank(path) ? area.getAreaName() : path + "/" + area.getAreaName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 areaId 查询并构建完整路径
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 完整区域路径,用 "/" 分隔;区域不存在时返回 null
|
||||
*/
|
||||
public String buildPath(Long areaId) {
|
||||
if (areaId == null) {
|
||||
return null;
|
||||
}
|
||||
OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId);
|
||||
if (area == null) {
|
||||
log.warn("区域不存在: areaId={}", areaId);
|
||||
return null;
|
||||
}
|
||||
return buildPath(area);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单误报请求 VO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "安保工单误报请求")
|
||||
@Data
|
||||
public class SecurityOrderFalseAlarmReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单 - 仅含工单ID的通用请求 VO
|
||||
* <p>
|
||||
* 用于误报标记、开放接口确认等只需要工单ID的场景
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "安保工单ID请求")
|
||||
@Data
|
||||
public class SecurityOrderIdReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单开放接口确认请求 VO(无需传 userId,默认使用已分配人员)
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "安保工单确认请求(开放接口)")
|
||||
@Data
|
||||
public class SecurityOrderOpenConfirmReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user