diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/BadgeSimpleRespDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/BadgeSimpleRespDTO.java new file mode 100644 index 0000000..cc3be19 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/BadgeSimpleRespDTO.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 = "管理后台 - 工牌设备精简信息(下拉列表用)") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BadgeSimpleRespDTO { + + @Schema(description = "设备ID", example = "31") + private Long deviceId; + + @Schema(description = "设备Key", example = "09207455611") + private String deviceKey; + + @Schema(description = "设备备注名称", example = "1号工牌") + private String nickname; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/DeviceCurrentLocationDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/DeviceCurrentLocationDTO.java new file mode 100644 index 0000000..9881f6b --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/DeviceCurrentLocationDTO.java @@ -0,0 +1,39 @@ +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 DeviceCurrentLocationDTO { + + @Schema(description = "设备ID", example = "31") + private Long deviceId; + + @Schema(description = "当前所在区域ID", example = "1301") + private Long areaId; + + @Schema(description = "区域名称", example = "A座2楼男卫") + private String areaName; + + @Schema(description = "进入时间(毫秒时间戳)", example = "1711872600000") + private Long enterTime; + + @Schema(description = "匹配的Beacon MAC", example = "F0:C8:60:1D:10:BB") + private String beaconMac; + + @Schema(description = "是否在某区域内") + private Boolean inArea; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryPageReqDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryPageReqDTO.java new file mode 100644 index 0000000..14e7758 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryPageReqDTO.java @@ -0,0 +1,35 @@ +package com.viewsh.module.ops.service.trajectory.dto; + +import com.viewsh.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.viewsh.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 轨迹分页查询 Request DTO + * + * @author lzh + */ +@Schema(description = "管理后台 - 设备轨迹分页查询 Request DTO") +@Data +@EqualsAndHashCode(callSuper = true) +public class TrajectoryPageReqDTO extends PageParam { + + @Schema(description = "设备ID", example = "31") + private Long deviceId; + + @Schema(description = "区域ID", example = "1301") + private Long areaId; + + @Schema(description = "进入时间范围(开始时间, 结束时间)") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @Size(min = 2, max = 2, message = "进入时间范围必须包含开始时间和结束时间") + private LocalDateTime[] enterTime; + +} 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 new file mode 100644 index 0000000..07b4847 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryRespDTO.java @@ -0,0 +1,59 @@ +package com.viewsh.module.ops.service.trajectory.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 轨迹记录 Response DTO + * + * @author lzh + */ +@Schema(description = "管理后台 - 设备轨迹记录 Response DTO") +@Data +public class TrajectoryRespDTO { + + @Schema(description = "记录ID", example = "1") + private Long id; + + @Schema(description = "设备ID", example = "31") + private Long deviceId; + + @Schema(description = "设备名称", example = "badge_001") + private String deviceName; + + @Schema(description = "设备备注名称", example = "1号工牌") + private String nickname; + + @Schema(description = "区域ID", example = "1301") + private Long areaId; + + @Schema(description = "区域名称", example = "A座2楼男卫") + private String areaName; + + @Schema(description = "楼栋名称", example = "A栋") + private String buildingName; + + @Schema(description = "楼层号", example = "2") + private Integer floorNo; + + @Schema(description = "Beacon MAC", example = "F0:C8:60:1D:10:BB") + private String beaconMac; + + @Schema(description = "进入时间") + private LocalDateTime enterTime; + + @Schema(description = "离开时间") + private LocalDateTime leaveTime; + + @Schema(description = "停留时长(秒)", example = "300") + private Integer durationSeconds; + + @Schema(description = "离开原因", example = "SIGNAL_LOSS") + private String leaveReason; + + @Schema(description = "进入时RSSI", example = "-65") + private Integer enterRssi; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectorySummaryDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectorySummaryDTO.java new file mode 100644 index 0000000..1f651fc --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectorySummaryDTO.java @@ -0,0 +1,41 @@ +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 + *
+ * 用于 KPI 卡片展示
+ *
+ * @author lzh
+ */
+@Schema(description = "管理后台 - 轨迹统计摘要 Response DTO")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TrajectorySummaryDTO {
+
+ @Schema(description = "总轨迹记录数(含未关闭)", example = "42")
+ private Long totalRecords;
+
+ @Schema(description = "已完成记录数(有离开时间)", example = "38")
+ private Long completedRecords;
+
+ @Schema(description = "覆盖区域数", example = "8")
+ private Long coveredAreaCount;
+
+ @Schema(description = "总停留时长(秒)", example = "28800")
+ private Long totalDurationSeconds;
+
+ @Schema(description = "平均停留时长(秒)", example = "685")
+ private Long avgDurationSeconds;
+
+ @Schema(description = "最长单次停留(秒)", example = "3600")
+ private Long maxDurationSeconds;
+
+}
diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryTimelineReqDTO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryTimelineReqDTO.java
new file mode 100644
index 0000000..d76acad
--- /dev/null
+++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/trajectory/dto/TrajectoryTimelineReqDTO.java
@@ -0,0 +1,28 @@
+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
+ *
+ * @author lzh
+ */
+@Schema(description = "管理后台 - 设备轨迹时间线查询 Request DTO")
+@Data
+public class TrajectoryTimelineReqDTO {
+
+ @Schema(description = "设备ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "31")
+ @NotNull(message = "设备ID不能为空")
+ private Long deviceId;
+
+ @Schema(description = "查询日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-03-30")
+ @NotNull(message = "查询日期不能为空")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
+ private LocalDate date;
+
+}
diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/trajectory/TrajectoryController.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/trajectory/TrajectoryController.java
new file mode 100644
index 0000000..28f15e1
--- /dev/null
+++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/trajectory/TrajectoryController.java
@@ -0,0 +1,181 @@
+package com.viewsh.module.ops.controller.admin.trajectory;
+
+import com.viewsh.framework.common.pojo.CommonResult;
+import com.viewsh.framework.common.pojo.PageResult;
+import com.viewsh.module.iot.api.device.IotDeviceQueryApi;
+import com.viewsh.module.iot.api.device.dto.IotDeviceSimpleRespDTO;
+import com.viewsh.module.iot.api.trajectory.DeviceLocationDTO;
+import com.viewsh.module.iot.api.trajectory.TrajectoryStateApi;
+import com.viewsh.module.ops.dal.dataobject.area.OpsAreaDeviceRelationDO;
+import com.viewsh.module.ops.dal.dataobject.vo.area.OpsBusAreaRespVO;
+import com.viewsh.module.ops.environment.service.trajectory.DeviceTrajectoryService;
+import com.viewsh.module.ops.service.area.AreaDeviceService;
+import com.viewsh.module.ops.service.area.OpsBusAreaService;
+import com.viewsh.module.ops.service.trajectory.dto.*;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.viewsh.framework.common.pojo.CommonResult.success;
+
+/**
+ * 管理后台 - 设备轨迹
+ *
+ * @author lzh
+ */
+@Tag(name = "管理后台 - 设备轨迹")
+@Slf4j
+@RestController
+@RequestMapping("/ops/trajectory")
+@Validated
+public class TrajectoryController {
+
+ @Resource
+ private DeviceTrajectoryService trajectoryService;
+
+ @Resource
+ private TrajectoryStateApi trajectoryStateApi;
+
+ @Resource
+ private AreaDeviceService areaDeviceService;
+
+ @Resource
+ private OpsBusAreaService opsBusAreaService;
+
+ @Resource
+ private IotDeviceQueryApi iotDeviceQueryApi;
+
+ // ==================== 工牌设备下拉列表 ====================
+
+ @GetMapping("/badge-list")
+ @Operation(summary = "获取工牌设备下拉列表(轨迹页面用)")
+ @PreAuthorize("@ss.hasPermission('ops:trajectory:query')")
+ public CommonResult> getBadgeSimpleList() {
+ // 查询所有 relationType=BADGE 的设备关联
+ List
> deviceResult =
+ iotDeviceQueryApi.batchGetDevices(deviceIds);
+ if (deviceResult != null && deviceResult.getData() != null) {
+ deviceMap = deviceResult.getData().stream()
+ .collect(Collectors.toMap(IotDeviceSimpleRespDTO::getId, Function.identity()));
+ }
+ } catch (Exception e) {
+ log.warn("[badge-list] 批量查询设备信息失败,降级返回无 nickname", e);
+ }
+ }
+
+ Map
> getTimeline(@Valid TrajectoryTimelineReqDTO req) {
+ return success(trajectoryService.getTimeline(req.getDeviceId(), req.getDate()));
+ }
+
+ @GetMapping("/summary")
+ @Operation(summary = "获得设备某天的轨迹统计摘要")
+ @PreAuthorize("@ss.hasPermission('ops:trajectory:query')")
+ public CommonResult