diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java index e0a8308..38cf86c 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java @@ -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 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 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 parents = opsBusAreaMapper.selectBatchIds(deduplicatedIds); - if (parents == null || parents.isEmpty()) { - log.warn("未找到父级区域: areaId={}, parentIds={}", areaId, deduplicatedIds); - return area.getAreaName(); - } - - // 8. 构建ID到区域的映射 - Map parentNameMap = parents.stream() - .collect(Collectors.toMap( - OpsBusAreaDO::getId, - OpsBusAreaDO::getAreaName, - (existing, replacement) -> existing // 处理重复key - )); - - // 9. 按顺序拼接区域路径(保持ID顺序) - List 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(); - } } diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/area/AreaPathBuilder.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/area/AreaPathBuilder.java new file mode 100644 index 0000000..a37ea40 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/area/AreaPathBuilder.java @@ -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; + +/** + * 区域路径构建器 + *

+ * 根据 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 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 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 parents = opsBusAreaMapper.selectBatchIds(deduplicatedIds); + if (parents == null || parents.isEmpty()) { + log.warn("未找到父级区域: areaId={}, parentIds={}", area.getId(), deduplicatedIds); + return area.getAreaName(); + } + + Map parentNameMap = parents.stream() + .collect(Collectors.toMap(OpsBusAreaDO::getId, OpsBusAreaDO::getAreaName, (a, b) -> a)); + + List 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); + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderFalseAlarmReqVO.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderFalseAlarmReqVO.java new file mode 100644 index 0000000..9bf0cab --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderFalseAlarmReqVO.java @@ -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; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderIdReqVO.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderIdReqVO.java new file mode 100644 index 0000000..78342c7 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderIdReqVO.java @@ -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 + *

+ * 用于误报标记、开放接口确认等只需要工单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; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderOpenConfirmReqVO.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderOpenConfirmReqVO.java new file mode 100644 index 0000000..8a5b35e --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/vo/SecurityOrderOpenConfirmReqVO.java @@ -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; + +}