diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/AppStatisticsServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/AppStatisticsServiceImpl.java index 199b4707..1a192606 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/AppStatisticsServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/AppStatisticsServiceImpl.java @@ -161,33 +161,100 @@ public class AppStatisticsServiceImpl implements AppStatisticsService { query.getStartTime() != null ? DateUtil.formatDate(query.getStartTime()) : "null"); if (!query.isRealtime()) { if (!(DateUtil.isIn(query.getStartTime(), DateUtil.yesterday(), DateUtil.tomorrow()) && DateUtil.isIn(query.getEndTime(), DateUtil.yesterday(), DateUtil.tomorrow()))) { - // 查询缓存数据 - List list = statisticsMapper.listStatByScenic(query.getScenicId(), query.getStartTime(), query.getEndTime()); - AppStatisticsFunnelVO resp = new AppStatisticsFunnelVO(); - if (list != null && !list.isEmpty()) { - for (AppStatisticsFunnelVO item : list) { - // Integer fields - resp.setCameraShotOfMemberNum(addIntSafely(resp.getCameraShotOfMemberNum(), item.getCameraShotOfMemberNum())); - resp.setScanCodeVisitorOfMemberNum(addIntSafely(resp.getScanCodeVisitorOfMemberNum(), item.getScanCodeVisitorOfMemberNum())); - resp.setUploadFaceOfMemberNum(addIntSafely(resp.getUploadFaceOfMemberNum(), item.getUploadFaceOfMemberNum())); - resp.setPushOfMemberNum(addIntSafely(resp.getPushOfMemberNum(), item.getPushOfMemberNum())); - resp.setCompleteVideoOfMemberNum(addIntSafely(resp.getCompleteVideoOfMemberNum(), item.getCompleteVideoOfMemberNum())); - resp.setPreviewVideoOfMemberNum(addIntSafely(resp.getPreviewVideoOfMemberNum(), item.getPreviewVideoOfMemberNum())); - resp.setClickOnPayOfMemberNum(addIntSafely(resp.getClickOnPayOfMemberNum(), item.getClickOnPayOfMemberNum())); - resp.setPayOfMemberNum(addIntSafely(resp.getPayOfMemberNum(), item.getPayOfMemberNum())); - resp.setTotalVisitorOfMemberNum(addIntSafely(resp.getTotalVisitorOfMemberNum(), item.getTotalVisitorOfMemberNum())); - resp.setCompleteOfVideoNum(addIntSafely(resp.getCompleteOfVideoNum(), item.getCompleteOfVideoNum())); - resp.setPreviewOfVideoNum(addIntSafely(resp.getPreviewOfVideoNum(), item.getPreviewOfVideoNum())); - resp.setPayOfOrderNum(addIntSafely(resp.getPayOfOrderNum(), item.getPayOfOrderNum())); - resp.setRefundOfOrderNum(addIntSafely(resp.getRefundOfOrderNum(), item.getRefundOfOrderNum())); + // 判断是否为跨范围查询且包含今天 + Date today = DateUtil.beginOfDay(new Date()); + boolean containsToday = query.getEndTime() != null && !query.getEndTime().before(today); + boolean isMultiDayQuery = query.getStartTime() != null && query.getStartTime().before(today); - // BigDecimal fields - resp.setPayOfOrderAmount(addBigDecimalSafely(resp.payOfOrderAmount(), item.payOfOrderAmount())); - resp.setRefundOfOrderAmount(addBigDecimalSafely(resp.refundOfOrderAmount(), item.refundOfOrderAmount())); + if (containsToday && isMultiDayQuery) { + // 跨范围查询且包含今天:需要分别查询历史数据和今天数据 + AppStatisticsFunnelVO result = new AppStatisticsFunnelVO(); + + // 1. 查询历史数据(从 startDate 到昨天结束) + Date yesterday = DateUtil.endOfDay(DateUtil.yesterday()); + List historyList = statisticsMapper.listStatByScenic( + query.getScenicId(), + query.getStartTime(), + yesterday + ); + + // 累加历史数据 + if (historyList != null && !historyList.isEmpty()) { + for (AppStatisticsFunnelVO item : historyList) { + result.setCameraShotOfMemberNum(addIntSafely(result.getCameraShotOfMemberNum(), item.getCameraShotOfMemberNum())); + result.setScanCodeVisitorOfMemberNum(addIntSafely(result.getScanCodeVisitorOfMemberNum(), item.getScanCodeVisitorOfMemberNum())); + result.setUploadFaceOfMemberNum(addIntSafely(result.getUploadFaceOfMemberNum(), item.getUploadFaceOfMemberNum())); + result.setPushOfMemberNum(addIntSafely(result.getPushOfMemberNum(), item.getPushOfMemberNum())); + result.setCompleteVideoOfMemberNum(addIntSafely(result.getCompleteVideoOfMemberNum(), item.getCompleteVideoOfMemberNum())); + result.setPreviewVideoOfMemberNum(addIntSafely(result.getPreviewVideoOfMemberNum(), item.getPreviewVideoOfMemberNum())); + result.setClickOnPayOfMemberNum(addIntSafely(result.getClickOnPayOfMemberNum(), item.getClickOnPayOfMemberNum())); + result.setPayOfMemberNum(addIntSafely(result.getPayOfMemberNum(), item.getPayOfMemberNum())); + result.setTotalVisitorOfMemberNum(addIntSafely(result.getTotalVisitorOfMemberNum(), item.getTotalVisitorOfMemberNum())); + result.setCompleteOfVideoNum(addIntSafely(result.getCompleteOfVideoNum(), item.getCompleteOfVideoNum())); + result.setPreviewOfVideoNum(addIntSafely(result.getPreviewOfVideoNum(), item.getPreviewOfVideoNum())); + result.setPayOfOrderNum(addIntSafely(result.getPayOfOrderNum(), item.getPayOfOrderNum())); + result.setRefundOfOrderNum(addIntSafely(result.getRefundOfOrderNum(), item.getRefundOfOrderNum())); + result.setPayOfOrderAmount(addBigDecimalSafely(result.payOfOrderAmount(), item.payOfOrderAmount())); + result.setRefundOfOrderAmount(addBigDecimalSafely(result.refundOfOrderAmount(), item.refundOfOrderAmount())); + } } - return ApiResponse.success(resp); + + // 2. 查询今天的实时数据 + CommonQueryReq todayQuery = new CommonQueryReq(); + todayQuery.setScenicId(query.getScenicId()); + todayQuery.setStartTime(today); + todayQuery.setEndTime(query.getEndTime()); + + // 执行今天的实时查询 + AppStatisticsFunnelVO todayData = queryRealtimeData(todayQuery); + + // 3. 合并今天的数据到结果中 + result.setCameraShotOfMemberNum(addIntSafely(result.getCameraShotOfMemberNum(), todayData.getCameraShotOfMemberNum())); + result.setScanCodeVisitorOfMemberNum(addIntSafely(result.getScanCodeVisitorOfMemberNum(), todayData.getScanCodeVisitorOfMemberNum())); + result.setUploadFaceOfMemberNum(addIntSafely(result.getUploadFaceOfMemberNum(), todayData.getUploadFaceOfMemberNum())); + result.setPushOfMemberNum(addIntSafely(result.getPushOfMemberNum(), todayData.getPushOfMemberNum())); + result.setCompleteVideoOfMemberNum(addIntSafely(result.getCompleteVideoOfMemberNum(), todayData.getCompleteVideoOfMemberNum())); + result.setPreviewVideoOfMemberNum(addIntSafely(result.getPreviewVideoOfMemberNum(), todayData.getPreviewVideoOfMemberNum())); + result.setClickOnPayOfMemberNum(addIntSafely(result.getClickOnPayOfMemberNum(), todayData.getClickOnPayOfMemberNum())); + result.setPayOfMemberNum(addIntSafely(result.getPayOfMemberNum(), todayData.getPayOfMemberNum())); + result.setTotalVisitorOfMemberNum(addIntSafely(result.getTotalVisitorOfMemberNum(), todayData.getTotalVisitorOfMemberNum())); + result.setCompleteOfVideoNum(addIntSafely(result.getCompleteOfVideoNum(), todayData.getCompleteOfVideoNum())); + result.setPreviewOfVideoNum(addIntSafely(result.getPreviewOfVideoNum(), todayData.getPreviewOfVideoNum())); + result.setPayOfOrderNum(addIntSafely(result.getPayOfOrderNum(), todayData.getPayOfOrderNum())); + result.setRefundOfOrderNum(addIntSafely(result.getRefundOfOrderNum(), todayData.getRefundOfOrderNum())); + result.setPayOfOrderAmount(addBigDecimalSafely(result.payOfOrderAmount(), todayData.payOfOrderAmount())); + result.setRefundOfOrderAmount(addBigDecimalSafely(result.refundOfOrderAmount(), todayData.refundOfOrderAmount())); + + return ApiResponse.success(result); } else { - query.setRealtime(true); + // 纯历史查询(不包含今天) + List list = statisticsMapper.listStatByScenic(query.getScenicId(), query.getStartTime(), query.getEndTime()); + AppStatisticsFunnelVO resp = new AppStatisticsFunnelVO(); + if (list != null && !list.isEmpty()) { + for (AppStatisticsFunnelVO item : list) { + // Integer fields + resp.setCameraShotOfMemberNum(addIntSafely(resp.getCameraShotOfMemberNum(), item.getCameraShotOfMemberNum())); + resp.setScanCodeVisitorOfMemberNum(addIntSafely(resp.getScanCodeVisitorOfMemberNum(), item.getScanCodeVisitorOfMemberNum())); + resp.setUploadFaceOfMemberNum(addIntSafely(resp.getUploadFaceOfMemberNum(), item.getUploadFaceOfMemberNum())); + resp.setPushOfMemberNum(addIntSafely(resp.getPushOfMemberNum(), item.getPushOfMemberNum())); + resp.setCompleteVideoOfMemberNum(addIntSafely(resp.getCompleteVideoOfMemberNum(), item.getCompleteVideoOfMemberNum())); + resp.setPreviewVideoOfMemberNum(addIntSafely(resp.getPreviewVideoOfMemberNum(), item.getPreviewVideoOfMemberNum())); + resp.setClickOnPayOfMemberNum(addIntSafely(resp.getClickOnPayOfMemberNum(), item.getClickOnPayOfMemberNum())); + resp.setPayOfMemberNum(addIntSafely(resp.getPayOfMemberNum(), item.getPayOfMemberNum())); + resp.setTotalVisitorOfMemberNum(addIntSafely(resp.getTotalVisitorOfMemberNum(), item.getTotalVisitorOfMemberNum())); + resp.setCompleteOfVideoNum(addIntSafely(resp.getCompleteOfVideoNum(), item.getCompleteOfVideoNum())); + resp.setPreviewOfVideoNum(addIntSafely(resp.getPreviewOfVideoNum(), item.getPreviewOfVideoNum())); + resp.setPayOfOrderNum(addIntSafely(resp.getPayOfOrderNum(), item.getPayOfOrderNum())); + resp.setRefundOfOrderNum(addIntSafely(resp.getRefundOfOrderNum(), item.getRefundOfOrderNum())); + + // BigDecimal fields + resp.setPayOfOrderAmount(addBigDecimalSafely(resp.payOfOrderAmount(), item.payOfOrderAmount())); + resp.setRefundOfOrderAmount(addBigDecimalSafely(resp.refundOfOrderAmount(), item.refundOfOrderAmount())); + } + return ApiResponse.success(resp); + } else { + query.setRealtime(true); + } } } } @@ -215,64 +282,9 @@ public class AppStatisticsServiceImpl implements AppStatisticsService { } } } - //镜头检测游客数 -// Integer cameraShotOfMemberNum=statisticsMapper.countCameraShotOfMember(query); - //扫码访问人数 - // 扫小程序码或景区码进入访问的用户数,包括授权用户(使用OpenID进行精准统计)和未授权用户(使用 UUID统计访问)。但当用户授权时,获取OpenID并与UUID关联,删除本地UUID,避免重复记录。 - Integer scanCodeVisitorOfMemberNum=statsQueryService.countScanCodeOfMember(query); - //上传头像(人脸)人数 - // 上传了人脸的用户数(包括本地临时ID和获取到OpenID的,同一设备微信获取到OpenID要覆盖掉之前生成的临时ID),上传多张人脸都只算一个人。 - Integer uploadFaceOfMemberNum=statsQueryService.countUploadFaceOfMember(query); - //推送订阅人数 - // 只要点了允许通知,哪怕只勾选1条订阅都算 - Integer pushOfMemberNum =statsQueryService.countPushOfMember(query); - //生成视频人数 - // 生成过Vlog视频的用户ID数,要注意屏蔽掉以前没有片段也能生成的情况 - Integer completeVideoOfMemberNum =statsQueryService.countCompleteVideoOfMember(query); - //预览视频人数 - // 购买前播放了5秒的视频条数。 - Integer previewVideoOfMemberNum =statsQueryService.countPreviewVideoOfMember(query); - if (previewVideoOfMemberNum==null){ - previewVideoOfMemberNum=0; - } - //点击购买人数 - // 点了立即购买按钮的用户ID就算,包括支付的和未支付的都算,只要点击了。 - Integer clickOnPayOfMemberNum =statisticsMapper.countClickPayOfMember(query); - //支付订单人数 - Integer payOfMemberNum =statisticsMapper.countPayOfMember(query); - //总访问人数 - // 通过任何途径访问到小程序的总人数,包括授权用户和未授权用户。 - Integer totalVisitorOfMemberNum =statsQueryService.countTotalVisitorOfMember(query); -// Integer totalVisitorOfMemberNum =scanCodeVisitorOfMemberNum; - //生成视频条数 - // 仅指代生成的Vlog条数,不包含录像原片。 - Integer completeOfVideoNum =statsQueryService.countCompleteOfVideo(query); - //预览视频条数 - Integer previewOfVideoNum =statsQueryService.countPreviewOfVideo(query); - //支付订单数 - Integer payOfOrderNum =statisticsMapper.countPayOfOrder(query); - //支付订单金额 - BigDecimal payOfOrderAmount =statisticsMapper.countOrderAmount(query); - //退款订单数 - Integer refundOfOrderNum =statisticsMapper.countRefundOfOrder(query); - //退款订单金额 - BigDecimal refundOfOrderAmount =statisticsMapper.countRefundAmount(query); - vo.setScanCodeVisitorOfMemberNum(scanCodeVisitorOfMemberNum); - vo.setUploadFaceOfMemberNum(uploadFaceOfMemberNum); - vo.setPushOfMemberNum(pushOfMemberNum); - vo.setCompleteVideoOfMemberNum(completeVideoOfMemberNum); - vo.setPreviewVideoOfMemberNum(previewVideoOfMemberNum); - vo.setClickOnPayOfMemberNum(clickOnPayOfMemberNum); - vo.setPayOfMemberNum(payOfMemberNum); - - vo.setTotalVisitorOfMemberNum(totalVisitorOfMemberNum); - vo.setCompleteOfVideoNum(completeOfVideoNum); - vo.setPreviewOfVideoNum(previewOfVideoNum); - vo.setPayOfOrderNum(payOfOrderNum); - vo.setPayOfOrderAmount(payOfOrderAmount.setScale(2, RoundingMode.HALF_UP)); - vo.setRefundOfOrderNum(refundOfOrderNum); - vo.setRefundOfOrderAmount(refundOfOrderAmount.setScale(2, RoundingMode.HALF_UP)); + // 执行实时查询 + vo = queryRealtimeData(query); // 仅对当天数据启用 Redis 缓存(短期缓存,减少实时查询压力) // 历史数据已在 scenic_stats 表中持久化,不需要 Redis 缓存 @@ -302,6 +314,64 @@ public class AppStatisticsServiceImpl implements AppStatisticsService { return int1 == null ? 0 : int1 + (int2 == null ? 0 : int2); } + /** + * 执行实时数据查询(从 ClickHouse 和 MySQL 查询最新数据) + * @param query 查询条件 + * @return 实时统计数据 + */ + private AppStatisticsFunnelVO queryRealtimeData(CommonQueryReq query) { + AppStatisticsFunnelVO vo = new AppStatisticsFunnelVO(); + + //扫码访问人数 + Integer scanCodeVisitorOfMemberNum = statsQueryService.countScanCodeOfMember(query); + //上传头像(人脸)人数 + Integer uploadFaceOfMemberNum = statsQueryService.countUploadFaceOfMember(query); + //推送订阅人数 + Integer pushOfMemberNum = statsQueryService.countPushOfMember(query); + //生成视频人数 + Integer completeVideoOfMemberNum = statsQueryService.countCompleteVideoOfMember(query); + //预览视频人数 + Integer previewVideoOfMemberNum = statsQueryService.countPreviewVideoOfMember(query); + if (previewVideoOfMemberNum == null) { + previewVideoOfMemberNum = 0; + } + //点击购买人数 + Integer clickOnPayOfMemberNum = statisticsMapper.countClickPayOfMember(query); + //支付订单人数 + Integer payOfMemberNum = statisticsMapper.countPayOfMember(query); + //总访问人数 + Integer totalVisitorOfMemberNum = statsQueryService.countTotalVisitorOfMember(query); + //生成视频条数 + Integer completeOfVideoNum = statsQueryService.countCompleteOfVideo(query); + //预览视频条数 + Integer previewOfVideoNum = statsQueryService.countPreviewOfVideo(query); + //支付订单数 + Integer payOfOrderNum = statisticsMapper.countPayOfOrder(query); + //支付订单金额 + BigDecimal payOfOrderAmount = statisticsMapper.countOrderAmount(query); + //退款订单数 + Integer refundOfOrderNum = statisticsMapper.countRefundOfOrder(query); + //退款订单金额 + BigDecimal refundOfOrderAmount = statisticsMapper.countRefundAmount(query); + + vo.setScanCodeVisitorOfMemberNum(scanCodeVisitorOfMemberNum); + vo.setUploadFaceOfMemberNum(uploadFaceOfMemberNum); + vo.setPushOfMemberNum(pushOfMemberNum); + vo.setCompleteVideoOfMemberNum(completeVideoOfMemberNum); + vo.setPreviewVideoOfMemberNum(previewVideoOfMemberNum); + vo.setClickOnPayOfMemberNum(clickOnPayOfMemberNum); + vo.setPayOfMemberNum(payOfMemberNum); + vo.setTotalVisitorOfMemberNum(totalVisitorOfMemberNum); + vo.setCompleteOfVideoNum(completeOfVideoNum); + vo.setPreviewOfVideoNum(previewOfVideoNum); + vo.setPayOfOrderNum(payOfOrderNum); + vo.setPayOfOrderAmount(payOfOrderAmount != null ? payOfOrderAmount.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO); + vo.setRefundOfOrderNum(refundOfOrderNum); + vo.setRefundOfOrderAmount(refundOfOrderAmount != null ? refundOfOrderAmount.setScale(2, RoundingMode.HALF_UP) : BigDecimal.ZERO); + + return vo; + } + @Override public ApiResponse orderChart(CommonQueryReq query) { if(query.getEndTime()==null && query.getStartTime()==null){