From 55d3d36b81e263d0076f0ee9af6fbdd4b9105bb6 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 12 Feb 2026 16:40:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(device):=20=E6=B7=BB=E5=8A=A0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=8B=8D=E6=91=84=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增设备拍摄统计功能,支持查询拍摄总数、拍摄人数、售出张数等统计信息 - 实现设备拍摄时间线功能,按5分钟分桶统计type=2的拍摄数量 - 添加SourceMapper的数据访问方法,包括getDeviceSourceStats和getDeviceSourceTimeline - 集成日期时间参数处理,支持自定义统计时间段 - 实现时间轴数据补零逻辑,确保时间线图表显示连续性 - 添加相应的响应对象DeviceSourceStatsVO和DeviceSourceTimelineVO --- .../controller/pc/DeviceV2Controller.java | 99 +++++++++++++++++++ .../com/ycwl/basic/mapper/SourceMapper.java | 20 ++++ .../pc/device/resp/DeviceSourceStatsVO.java | 20 ++++ .../device/resp/DeviceSourceTimelineVO.java | 22 +++++ src/main/resources/mapper/SourceMapper.xml | 26 +++++ 5 files changed, 187 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceStatsVO.java create mode 100644 src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceTimelineVO.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java index 67ac8166..8618b1ff 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/pc/DeviceV2Controller.java @@ -7,13 +7,19 @@ import com.ycwl.basic.integration.device.dto.status.DeviceStatusDTO; import com.ycwl.basic.integration.device.service.DeviceConfigIntegrationService; import com.ycwl.basic.integration.device.service.DeviceIntegrationService; import com.ycwl.basic.integration.device.service.DeviceStatusIntegrationService; +import com.ycwl.basic.mapper.SourceMapper; +import com.ycwl.basic.model.pc.device.resp.DeviceSourceStatsVO; +import com.ycwl.basic.model.pc.device.resp.DeviceSourceTimelineVO; import com.ycwl.basic.utils.ApiResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import java.util.HashMap; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Map; @@ -32,6 +38,7 @@ public class DeviceV2Controller { private final DeviceIntegrationService deviceIntegrationService; private final DeviceConfigIntegrationService deviceConfigIntegrationService; private final DeviceStatusIntegrationService deviceStatusIntegrationService; + private final SourceMapper sourceMapper; // ========== 设备基础 CRUD 操作 ========== @@ -387,4 +394,96 @@ public class DeviceV2Controller { return ApiResponse.fail("获取景区所有设备列表失败: " + e.getMessage()); } } + + // ========== 设备拍摄统计 ========== + + /** + * 设备拍摄统计:拍摄总数、拍摄人数、售出张数、赠送张数、售出人数 + */ + @GetMapping("/{id}/source-stats") + public ApiResponse getDeviceSourceStats( + @PathVariable Long id, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) { + try { + java.util.Calendar cal = java.util.Calendar.getInstance(); + // startDate:归到当天 00:00:00(未传则默认今天) + if (startDate != null) { + cal.setTime(startDate); + } + cal.set(java.util.Calendar.HOUR_OF_DAY, 0); + cal.set(java.util.Calendar.MINUTE, 0); + cal.set(java.util.Calendar.SECOND, 0); + cal.set(java.util.Calendar.MILLISECOND, 0); + startDate = cal.getTime(); + // endDate:归到当天 23:59:59(未传则取 startDate 同一天) + if (endDate != null) { + cal.setTime(endDate); + } + cal.set(java.util.Calendar.HOUR_OF_DAY, 23); + cal.set(java.util.Calendar.MINUTE, 59); + cal.set(java.util.Calendar.SECOND, 59); + cal.set(java.util.Calendar.MILLISECOND, 999); + endDate = cal.getTime(); + DeviceSourceStatsVO stats = sourceMapper.getDeviceSourceStats(id, startDate, endDate); + return ApiResponse.success(stats); + } catch (Exception e) { + log.error("获取设备拍摄统计失败, deviceId: {}", id, e); + return ApiResponse.fail("获取设备拍摄统计失败: " + e.getMessage()); + } + } + + /** + * 设备拍摄时间线:按 5 分钟分桶统计 type=2 的拍摄数量,空桶补 0 + */ + @GetMapping("/{id}/source-timeline") + public ApiResponse> getDeviceSourceTimeline( + @PathVariable Long id, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, + @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) { + try { + java.util.Calendar cal = java.util.Calendar.getInstance(); + // startDate:归到当天 08:00:00(未传则默认今天) + if (startDate != null) { + cal.setTime(startDate); + } + cal.set(java.util.Calendar.HOUR_OF_DAY, 8); + cal.set(java.util.Calendar.MINUTE, 0); + cal.set(java.util.Calendar.SECOND, 0); + cal.set(java.util.Calendar.MILLISECOND, 0); + startDate = cal.getTime(); + // endDate:归到当天 19:59:59(未传则取 startDate 同一天) + if (endDate != null) { + cal.setTime(endDate); + } + cal.set(java.util.Calendar.HOUR_OF_DAY, 19); + cal.set(java.util.Calendar.MINUTE, 59); + cal.set(java.util.Calendar.SECOND, 59); + cal.set(java.util.Calendar.MILLISECOND, 999); + endDate = cal.getTime(); + + // 查询有数据的桶 + List rawData = sourceMapper.getDeviceSourceTimeline(id, startDate, endDate); + + // 将有数据的桶放入 Map,key 为对齐到 5 分钟的毫秒时间戳 + Map dataMap = new HashMap<>(); + for (DeviceSourceTimelineVO item : rawData) { + long aligned = (item.getTime().getTime() / 300_000) * 300_000; + dataMap.put(aligned, item.getCount()); + } + + // 生成完整时间轴并补零 + long startMs = (startDate.getTime() / 300_000) * 300_000; + long endMs = endDate.getTime(); + List timeline = new ArrayList<>(); + for (long ts = startMs; ts <= endMs; ts += 300_000) { + timeline.add(new DeviceSourceTimelineVO(new Date(ts), dataMap.getOrDefault(ts, 0))); + } + + return ApiResponse.success(timeline); + } catch (Exception e) { + log.error("获取设备拍摄时间线失败, deviceId: {}", id, e); + return ApiResponse.fail("获取设备拍摄时间线失败: " + e.getMessage()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/mapper/SourceMapper.java b/src/main/java/com/ycwl/basic/mapper/SourceMapper.java index 7064792c..c9210752 100644 --- a/src/main/java/com/ycwl/basic/mapper/SourceMapper.java +++ b/src/main/java/com/ycwl/basic/mapper/SourceMapper.java @@ -1,5 +1,7 @@ package com.ycwl.basic.mapper; +import com.ycwl.basic.model.pc.device.resp.DeviceSourceStatsVO; +import com.ycwl.basic.model.pc.device.resp.DeviceSourceTimelineVO; import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity; import com.ycwl.basic.model.pc.source.entity.SourceEntity; import com.ycwl.basic.model.pc.source.entity.SourceWatermarkEntity; @@ -203,4 +205,22 @@ public interface SourceMapper { List pageDeletedByFaceId(SourceReqQuery sourceReqQuery); MemberSourceEntity getMemberSourceById(Long id); + + /** + * 设备拍摄统计:拍摄总数、拍摄人数、售出张数、赠送张数、售出人数 + * @param deviceId 设备ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 统计结果 + */ + DeviceSourceStatsVO getDeviceSourceStats(Long deviceId, Date startTime, Date endTime); + + /** + * 按 5 分钟分桶统计设备 type=2 的拍摄数量(仅返回有数据的桶) + * @param deviceId 设备ID + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return 有数据的时间桶列表 + */ + List getDeviceSourceTimeline(Long deviceId, Date startTime, Date endTime); } diff --git a/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceStatsVO.java b/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceStatsVO.java new file mode 100644 index 00000000..1705c56e --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceStatsVO.java @@ -0,0 +1,20 @@ +package com.ycwl.basic.model.pc.device.resp; + +import lombok.Data; + +/** + * 设备拍摄统计 VO + */ +@Data +public class DeviceSourceStatsVO { + /** 拍摄总数(source type=2 记录数) */ + private Integer totalShots; + /** 拍摄人数(关联的不同 face_id 数) */ + private Integer totalFaces; + /** 售出张数(is_buy=1 的 member_source 记录数) */ + private Integer soldCount; + /** 赠送张数(is_free=1 的 member_source 记录数) */ + private Integer freeCount; + /** 售出人数(is_buy=1 的不同 face_id 数) */ + private Integer soldFaceCount; +} diff --git a/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceTimelineVO.java b/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceTimelineVO.java new file mode 100644 index 00000000..d13e4ab3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/pc/device/resp/DeviceSourceTimelineVO.java @@ -0,0 +1,22 @@ +package com.ycwl.basic.model.pc.device.resp; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + +/** + * 设备拍摄时间线数据点(5 分钟一个桶) + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DeviceSourceTimelineVO { + /** 时间桶起始时刻 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm", timezone = "GMT+8") + private Date time; + /** 该桶内 source type=2 的记录数 */ + private Integer count; +} diff --git a/src/main/resources/mapper/SourceMapper.xml b/src/main/resources/mapper/SourceMapper.xml index 3d241be2..5a46eab5 100644 --- a/src/main/resources/mapper/SourceMapper.xml +++ b/src/main/resources/mapper/SourceMapper.xml @@ -569,4 +569,30 @@ + +