From 368fa90156b1a646e67ff5192be6e5e478cd2fee Mon Sep 17 00:00:00 2001 From: lzh Date: Sun, 5 Apr 2026 15:25:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ops):=20=E8=BD=A8=E8=BF=B9=E5=8C=BA?= =?UTF-8?q?=E5=9F=9F=E5=B1=95=E7=A4=BA=E6=94=B9=E7=94=A8=20fullAreaName=20?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=20buildingName/floorNo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TrajectoryRespDTO 移除 buildingName、floorNo 字段,新增 fullAreaName (完整路径如"A园区/A栋/3层/男卫")。AreaPathBuilder 新增 buildPaths 批量方法,一次查询所有父级区域避免 N+1;正则预编译为静态常量。 Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../infrastructure/area/AreaPathBuilder.java | 93 ++++++++++++++++++- .../trajectory/dto/AreaStayStatsDTO.java | 33 +++++++ .../trajectory/dto/HourlyTrendDTO.java | 30 ++++++ .../trajectory/dto/TrajectoryRespDTO.java | 7 +- .../trajectory/dto/TrajectoryStatsReqDTO.java | 29 ++++++ 5 files changed, 182 insertions(+), 10 deletions(-) create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/AreaStayStatsDTO.java create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/HourlyTrendDTO.java create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryStatsReqDTO.java 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 index a37ea40..d9e8101 100644 --- 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 @@ -7,10 +7,8 @@ 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.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -25,6 +23,8 @@ import java.util.stream.Collectors; @Component public class AreaPathBuilder { + private static final Pattern DIGITS = Pattern.compile("\\d+"); + @Resource private OpsBusAreaMapper opsBusAreaMapper; @@ -47,7 +47,7 @@ public class AreaPathBuilder { // 解析父级ID列表 List parentIds = Arrays.stream(parentPath.split("/")) .filter(StrUtil::isNotBlank) - .filter(pid -> pid.matches("\\d+")) + .filter(pid -> DIGITS.matcher(pid).matches()) .map(Long::parseLong) .filter(pid -> !pid.equals(area.getId())) .collect(Collectors.toList()); @@ -105,4 +105,87 @@ public class AreaPathBuilder { return buildPath(area); } + /** + * 批量构建区域路径(只执行一次父级查询) + * + * @param areas 区域对象集合 + * @return areaId → 完整路径 的映射 + */ + public Map buildPaths(Collection areas) { + if (areas == null || areas.isEmpty()) { + return Collections.emptyMap(); + } + + // 1. 收集所有父级 ID + Set allParentIds = new HashSet<>(); + for (OpsBusAreaDO area : areas) { + if (StrUtil.isNotEmpty(area.getParentPath())) { + for (String idStr : area.getParentPath().split("/")) { + if (StrUtil.isNotBlank(idStr) && DIGITS.matcher(idStr).matches()) { + allParentIds.add(Long.parseLong(idStr)); + } + } + } + } + // 排除自身已在集合中的 + Set areaIds = areas.stream().map(OpsBusAreaDO::getId).collect(Collectors.toSet()); + allParentIds.removeAll(areaIds); + + // 2. 一次性查询所有父级 + Map parentNameMap = new HashMap<>(); + for (OpsBusAreaDO area : areas) { + parentNameMap.put(area.getId(), area.getAreaName()); + } + if (!allParentIds.isEmpty()) { + List parents = opsBusAreaMapper.selectBatchIds(allParentIds); + if (parents != null) { + for (OpsBusAreaDO parent : parents) { + parentNameMap.put(parent.getId(), parent.getAreaName()); + } + } + } + + // 3. 纯内存拼路径 + Map result = new HashMap<>(areas.size()); + for (OpsBusAreaDO area : areas) { + result.put(area.getId(), buildPathFromCache(area, parentNameMap)); + } + return result; + } + + /** + * 使用预加载的名称缓存构建路径(纯内存,无 DB 查询) + */ + private String buildPathFromCache(OpsBusAreaDO area, Map nameMap) { + String parentPath = area.getParentPath(); + if (StrUtil.isEmpty(parentPath)) { + return area.getAreaName(); + } + + List parentIds = Arrays.stream(parentPath.split("/")) + .filter(StrUtil::isNotBlank) + .filter(pid -> DIGITS.matcher(pid).matches()) + .map(Long::parseLong) + .filter(pid -> !pid.equals(area.getId())) + .collect(Collectors.toList()); + + // 去重相邻重复 + List segments = new ArrayList<>(); + Long lastId = null; + for (Long pid : parentIds) { + if (!pid.equals(lastId)) { + String name = nameMap.get(pid); + if (name != null) { + segments.add(name); + } + lastId = pid; + } + } + + if (segments.isEmpty()) { + return area.getAreaName(); + } + return String.join("/", segments) + "/" + area.getAreaName(); + } + } diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/AreaStayStatsDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/AreaStayStatsDTO.java new file mode 100644 index 0000000..39c2840 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/AreaStayStatsDTO.java @@ -0,0 +1,33 @@ +package com.viewsh.module.ops.service.trajectory.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 区域停留分布 Response DTO + * + * @author lzh + */ +@Schema(description = "管理后台 - 区域停留分布 Response DTO") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class AreaStayStatsDTO { + + @Schema(description = "区域名称", example = "男卫") + private String areaName; + + @Schema(description = "完整区域路径", example = "A园区/A栋/3层/男卫") + private String fullAreaName; + + @Schema(description = "总停留时长(秒)", example = "3600") + private Long totalStaySeconds; + + @Schema(description = "访问次数", example = "12") + private Long visitCount; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/HourlyTrendDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/HourlyTrendDTO.java new file mode 100644 index 0000000..d523b61 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/HourlyTrendDTO.java @@ -0,0 +1,30 @@ +package com.viewsh.module.ops.service.trajectory.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 时段出入趋势 Response DTO + * + * @author lzh + */ +@Schema(description = "管理后台 - 时段出入趋势 Response DTO") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class HourlyTrendDTO { + + @Schema(description = "小时(0-23)", example = "8") + private Integer hour; + + @Schema(description = "进入次数", example = "8") + private Long enterCount; + + @Schema(description = "离开次数", example = "6") + private Long leaveCount; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryRespDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryRespDTO.java index 07b4847..96bfdd1 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryRespDTO.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryRespDTO.java @@ -32,11 +32,8 @@ public class TrajectoryRespDTO { @Schema(description = "区域名称", example = "A座2楼男卫") private String areaName; - @Schema(description = "楼栋名称", example = "A栋") - private String buildingName; - - @Schema(description = "楼层号", example = "2") - private Integer floorNo; + @Schema(description = "完整区域路径", example = "A园区/A栋/3层/男卫") + private String fullAreaName; @Schema(description = "Beacon MAC", example = "F0:C8:60:1D:10:BB") private String beaconMac; diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryStatsReqDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryStatsReqDTO.java new file mode 100644 index 0000000..e670ad5 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryStatsReqDTO.java @@ -0,0 +1,29 @@ +package com.viewsh.module.ops.service.trajectory.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 轨迹统计查询 Request DTO + *

+ * 用于 summary / hourly-trend / area-stay-stats 三个统计接口 + * + * @author lzh + */ +@Schema(description = "管理后台 - 轨迹统计查询 Request DTO") +@Data +public class TrajectoryStatsReqDTO { + + @Schema(description = "查询日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-04-05") + @NotNull(message = "查询日期不能为空") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate date; + + @Schema(description = "设备ID(不传=全部设备汇总)", example = "31") + private Long deviceId; + +}