refactor(statistics): 切换数据查询服务并优化扫码统计功能

- 将 BrokerBiz 和 OrderBiz 中的数据查询从 StatisticsMapper 切换到 StatsQueryService
- 更新 StatisticsServiceImpl 使用 StatsQueryService 进行数据查询
- 添加订单数据合并功能到扫码统计图表中
- 重构扫码统计查询逻辑以支持统计数据和订单数据的合并显示
- 新增按小时和按日期统计订单数据的查询方法
- 优化 SQL 查询以分离统计数据和订单数据的查询逻辑
This commit is contained in:
2026-01-12 18:30:27 +08:00
parent 3bd658cc1f
commit f8c6604a8a
6 changed files with 165 additions and 50 deletions

View File

@@ -1,6 +1,7 @@
package com.ycwl.basic.biz; package com.ycwl.basic.biz;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.mapper.BrokerMapper; import com.ycwl.basic.mapper.BrokerMapper;
import com.ycwl.basic.mapper.BrokerRecordMapper; import com.ycwl.basic.mapper.BrokerRecordMapper;
import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.mapper.StatisticsMapper;
@@ -34,7 +35,7 @@ public class BrokerBiz {
@Autowired @Autowired
private ScenicRepository scenicRepository; private ScenicRepository scenicRepository;
@Autowired @Autowired
private StatisticsMapper statisticsMapper; private StatsQueryService statsQueryService;
public void processOrder(Long orderId) { public void processOrder(Long orderId) {
log.info("开始处理订单分佣,订单ID:{}", orderId); log.info("开始处理订单分佣,订单ID:{}", orderId);
@@ -52,7 +53,7 @@ public class BrokerBiz {
if (scenicConfig.getInteger("sample_store_day") != null) { if (scenicConfig.getInteger("sample_store_day") != null) {
expireDay = scenicConfig.getInteger("sample_store_day"); expireDay = scenicConfig.getInteger("sample_store_day");
} }
List<Long> brokerIdList = statisticsMapper.getBrokerIdListForUser(order.getMemberId(), DateUtil.offsetDay(DateUtil.beginOfDay(order.getCreateAt()), -expireDay), order.getCreateAt()); List<Long> brokerIdList = statsQueryService.getBrokerIdListForUser(order.getMemberId(), DateUtil.offsetDay(DateUtil.beginOfDay(order.getCreateAt()), -expireDay), order.getCreateAt());
if (brokerIdList == null || brokerIdList.isEmpty()) { if (brokerIdList == null || brokerIdList.isEmpty()) {
log.info("用户与推客无关,订单ID:{}", orderId); log.info("用户与推客无关,订单ID:{}", orderId);
return; return;

View File

@@ -1,5 +1,6 @@
package com.ycwl.basic.biz; package com.ycwl.basic.biz;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.enums.StatisticEnum; import com.ycwl.basic.enums.StatisticEnum;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager; import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.mapper.OrderMapper; import com.ycwl.basic.mapper.OrderMapper;
@@ -66,6 +67,8 @@ public class OrderBiz {
private PrinterService printerService; private PrinterService printerService;
@Autowired @Autowired
private IPriceCalculationService iPriceCalculationService; private IPriceCalculationService iPriceCalculationService;
@Autowired
private StatsQueryService statsQueryService;
public PriceObj queryPrice(Long scenicId, Long memberId, int goodsType, Long goodsId) { public PriceObj queryPrice(Long scenicId, Long memberId, int goodsType, Long goodsId) {
PriceObj priceObj = new PriceObj(); PriceObj priceObj = new PriceObj();
@@ -254,7 +257,7 @@ public class OrderBiz {
orderRepository.clearOrderCache(orderId); // 更新完了,清理下 orderRepository.clearOrderCache(orderId); // 更新完了,清理下
StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq(); StatisticsRecordAddReq statisticsRecordAddReq = new StatisticsRecordAddReq();
statisticsRecordAddReq.setMemberId(order.getMemberId()); statisticsRecordAddReq.setMemberId(order.getMemberId());
Long enterType = statisticsMapper.getUserRecentEnterType(order.getMemberId(), order.getCreateAt()); Long enterType = statsQueryService.getUserRecentEnterType(order.getMemberId(), order.getCreateAt());
if(!Long.valueOf(1014).equals(enterType)){// if(!Long.valueOf(1014).equals(enterType)){//
statisticsRecordAddReq.setType(StatisticEnum.ON_SITE_PAYMENT.code); statisticsRecordAddReq.setType(StatisticEnum.ON_SITE_PAYMENT.code);
}else { }else {

View File

@@ -79,12 +79,14 @@ public interface StatsQueryService {
List<HashMap<String, Object>> getDailyScanStats(Long brokerId, Date startTime, Date endTime); List<HashMap<String, Object>> getDailyScanStats(Long brokerId, Date startTime, Date endTime);
/** /**
* 按小时统计扫码人数 * 按小时统计扫码人数(仅返回统计数据,不含订单)
* 返回格式: [{t: "MM-dd HH", count: "xxx"}, ...]
*/ */
List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query); List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query);
/** /**
* 按日期统计扫码人数 * 按日期统计扫码人数(仅返回统计数据,不含订单)
* 返回格式: [{t: "MM-dd", count: "xxx"}, ...]
*/ */
List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query); List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query);
} }

View File

@@ -105,10 +105,26 @@ public interface StatisticsMapper {
List<HashMap<String, String>> orderChartByHour(CommonQueryReq query); List<HashMap<String, String>> orderChartByHour(CommonQueryReq query);
/**
* 按小时统计扫码人数(仅统计数据,不含订单)
*/
List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query); List<HashMap<String, String>> scanCodeMemberChartByHour(CommonQueryReq query);
/**
* 按日期统计扫码人数(仅统计数据,不含订单)
*/
List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query); List<HashMap<String, String>> scanCodeMemberChartByDate(CommonQueryReq query);
/**
* 按小时统计订单数据
*/
List<HashMap<String, String>> orderChartByHourForMerge(CommonQueryReq query);
/**
* 按日期统计订单数据
*/
List<HashMap<String, String>> orderChartByDateForMerge(CommonQueryReq query);
/** /**
* 统计分销员扫码次数 * 统计分销员扫码次数
*/ */

View File

@@ -1,5 +1,6 @@
package com.ycwl.basic.service.pc.impl; package com.ycwl.basic.service.pc.impl;
import com.ycwl.basic.clickhouse.service.StatsQueryService;
import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.mapper.StatisticsMapper;
import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq;
import com.ycwl.basic.model.pc.statistics.resp.OrderStatisticsResp; import com.ycwl.basic.model.pc.statistics.resp.OrderStatisticsResp;
@@ -13,6 +14,8 @@ import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* @Author:longbinbin * @Author:longbinbin
@@ -21,17 +24,11 @@ import java.util.List;
@Service @Service
public class StatisticsServiceImpl implements StatisticsService { public class StatisticsServiceImpl implements StatisticsService {
@Autowired
private StatsQueryService statsQueryService;
@Autowired @Autowired
private StatisticsMapper statisticsMapper; private StatisticsMapper statisticsMapper;
List<HashMap<String, String>> getScanCodeMemberChartByHour(CommonQueryReq query) {
return statisticsMapper.scanCodeMemberChartByHour(query);
}
List<HashMap<String, String>> getScanCodeMemberChartByDate(CommonQueryReq query) {
return statisticsMapper.scanCodeMemberChartByDate(query);
}
@Override @Override
public List<HashMap<String, String>> getScanCodeMemberChartAuto(CommonQueryReq query) { public List<HashMap<String, String>> getScanCodeMemberChartAuto(CommonQueryReq query) {
// 检查时间范围 // 检查时间范围
@@ -40,34 +37,34 @@ public class StatisticsServiceImpl implements StatisticsService {
// 如果开始和结束时间均为空,则默认为当天0点到23时59分59秒 // 如果开始和结束时间均为空,则默认为当天0点到23时59分59秒
// 如果只有一个为空,则往前或往后推24小时 // 如果只有一个为空,则往前或往后推24小时
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
if (query.getStartTime() == null && query.getEndTime() == null) { if (query.getStartTime() == null && query.getEndTime() == null) {
// 都为空,设置为当天0点到23:59:59 // 都为空,设置为当天0点到23:59:59
LocalDateTime startOfDay = now.withHour(0).withMinute(0).withSecond(0).withNano(0); LocalDateTime startOfDay = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
LocalDateTime endOfDay = now.withHour(23).withMinute(59).withSecond(59).withNano(0); LocalDateTime endOfDay = now.withHour(23).withMinute(59).withSecond(59).withNano(0);
query.setStartTime(Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant())); query.setStartTime(Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant()));
query.setEndTime(Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant())); query.setEndTime(Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant()));
} else if (query.getStartTime() == null) { } else if (query.getStartTime() == null) {
// 开始时间为空,结束时间不为空,往前推24小时 // 开始时间为空,结束时间不为空,往前推24小时
LocalDateTime endDateTime = query.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime endDateTime = query.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime startDateTime = endDateTime.minusHours(24); LocalDateTime startDateTime = endDateTime.minusHours(24);
query.setStartTime(Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant())); query.setStartTime(Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant()));
} else { } else {
// 结束时间为空,开始时间不为空,往后推24小时 // 结束时间为空,开始时间不为空,往后推24小时
LocalDateTime startDateTime = query.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime startDateTime = query.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime endDateTime = startDateTime.plusHours(24); LocalDateTime endDateTime = startDateTime.plusHours(24);
query.setEndTime(Date.from(endDateTime.atZone(ZoneId.systemDefault()).toInstant())); query.setEndTime(Date.from(endDateTime.atZone(ZoneId.systemDefault()).toInstant()));
} }
} }
// 计算时间差(天数) // 计算时间差(天数)
LocalDateTime startDateTime = query.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime startDateTime = query.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime endDateTime = query.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime endDateTime = query.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
long daysBetween = ChronoUnit.DAYS.between(startDateTime, endDateTime); long daysBetween = ChronoUnit.DAYS.between(startDateTime, endDateTime);
// 超过7天使用日期级别,否则使用小时级别 // 超过7天使用日期级别,否则使用小时级别
if (daysBetween > 7) { if (daysBetween > 7) {
return getScanCodeMemberChartByDate(query); return getScanCodeMemberChartByDate(query);
@@ -76,6 +73,68 @@ public class StatisticsServiceImpl implements StatisticsService {
} }
} }
/**
* 按小时统计扫码人数(合并统计数据和订单数据)
*/
private List<HashMap<String, String>> getScanCodeMemberChartByHour(CommonQueryReq query) {
// 1. 从 StatsQueryService 获取统计数据(根据配置走 ClickHouse 或 MySQL)
List<HashMap<String, String>> statsData = statsQueryService.scanCodeMemberChartByHour(query);
// 2. 从 MySQL 获取订单数据
List<HashMap<String, String>> orderData = statisticsMapper.orderChartByHourForMerge(query);
// 3. 合并数据
return mergeChartData(statsData, orderData);
}
/**
* 按日期统计扫码人数(合并统计数据和订单数据)
*/
private List<HashMap<String, String>> getScanCodeMemberChartByDate(CommonQueryReq query) {
// 1. 从 StatsQueryService 获取统计数据(根据配置走 ClickHouse 或 MySQL)
List<HashMap<String, String>> statsData = statsQueryService.scanCodeMemberChartByDate(query);
// 2. 从 MySQL 获取订单数据
List<HashMap<String, String>> orderData = statisticsMapper.orderChartByDateForMerge(query);
// 3. 合并数据
return mergeChartData(statsData, orderData);
}
/**
* 合并统计数据和订单数据
* 统计数据包含: t, count
* 订单数据包含: t, orderCount, orderAmount
* 合并结果: t, count, orderCount, orderAmount
*/
private List<HashMap<String, String>> mergeChartData(
List<HashMap<String, String>> statsData,
List<HashMap<String, String>> orderData) {
// 将订单数据转为 Map 以便快速查找
Map<String, HashMap<String, String>> orderMap = orderData.stream()
.collect(Collectors.toMap(
m -> m.get("t"),
m -> m,
(existing, replacement) -> existing
));
// 合并数据
for (HashMap<String, String> stat : statsData) {
String timeKey = stat.get("t");
HashMap<String, String> order = orderMap.get(timeKey);
if (order != null) {
stat.put("orderCount", order.get("orderCount"));
stat.put("orderAmount", order.get("orderAmount"));
} else {
stat.put("orderCount", "0");
stat.put("orderAmount", "0");
}
}
return statsData;
}
@Override @Override
public OrderStatisticsResp getOrderStatistics(CommonQueryReq query) { public OrderStatisticsResp getOrderStatistics(CommonQueryReq query) {
// 时间参数兜底与规范化 // 时间参数兜底与规范化

View File

@@ -386,11 +386,11 @@
</insert> </insert>
<select id="scanCodeMemberChartByHour" resultType="java.util.HashMap"> <select id="scanCodeMemberChartByHour" resultType="java.util.HashMap">
WITH RECURSIVE hour_series AS ( WITH RECURSIVE hour_series AS (
SELECT SELECT
#{startTime} AS hour_time, #{startTime} AS hour_time,
DATE_FORMAT(#{startTime}, '%m-%d %H') AS HOUR DATE_FORMAT(#{startTime}, '%m-%d %H') AS HOUR
UNION ALL UNION ALL
SELECT SELECT
DATE_ADD(hour_time, INTERVAL 1 HOUR) AS hour_time, DATE_ADD(hour_time, INTERVAL 1 HOUR) AS hour_time,
DATE_FORMAT(DATE_ADD(hour_time, INTERVAL 1 HOUR), '%m-%d %H') AS HOUR DATE_FORMAT(DATE_ADD(hour_time, INTERVAL 1 HOUR), '%m-%d %H') AS HOUR
FROM hour_series FROM hour_series
@@ -398,18 +398,16 @@
) )
SELECT SELECT
hour_series.HOUR as t, hour_series.HOUR as t,
COALESCE(scan_data.member_count, 0) AS count, COALESCE(scan_data.member_count, 0) AS count
COALESCE(order_data.order_count, 0) AS orderCount,
COALESCE(order_data.order_amount, 0) AS orderAmount
FROM hour_series FROM hour_series
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
DATE_FORMAT(s.create_time, '%m-%d %H') AS hour_key, DATE_FORMAT(s.create_time, '%m-%d %H') AS hour_key,
COUNT(DISTINCT s.member_id) AS member_count COUNT(DISTINCT s.member_id) AS member_count
FROM `t_stats_record` r FROM `t_stats_record` r
LEFT JOIN `t_stats` s ON r.trace_id = s.trace_id LEFT JOIN `t_stats` s ON r.trace_id = s.trace_id
WHERE r.trace_id IN ( WHERE r.trace_id IN (
SELECT trace_id FROM `t_stats_record` SELECT trace_id FROM `t_stats_record`
WHERE action = 'ENTER_SCENIC' WHERE action = 'ENTER_SCENIC'
<if test="scenicId != null"> <if test="scenicId != null">
AND `identifier` = #{scenicId} AND `identifier` = #{scenicId}
@@ -420,8 +418,63 @@
AND s.create_time BETWEEN #{startTime} AND #{endTime} AND s.create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY DATE_FORMAT(s.create_time, '%m-%d %H') GROUP BY DATE_FORMAT(s.create_time, '%m-%d %H')
) scan_data ON scan_data.hour_key = hour_series.HOUR ) scan_data ON scan_data.hour_key = hour_series.HOUR
ORDER BY hour_series.hour_time
</select>
<select id="scanCodeMemberChartByDate" resultType="java.util.HashMap">
WITH RECURSIVE date_series AS (
SELECT
DATE(#{startTime}) AS date_value,
DATE_FORMAT(#{startTime}, '%m-%d') AS DATE_KEY
UNION ALL
SELECT
DATE_ADD(date_value, INTERVAL 1 DAY) AS date_value,
DATE_FORMAT(DATE_ADD(date_value, INTERVAL 1 DAY), '%m-%d') AS DATE_KEY
FROM date_series
WHERE DATE_ADD(date_value, INTERVAL 1 DAY) &lt;= DATE(#{endTime})
)
SELECT
date_series.DATE_KEY as t,
COALESCE(scan_data.member_count, 0) AS count
FROM date_series
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
DATE_FORMAT(s.create_time, '%m-%d') AS date_key,
COUNT(DISTINCT s.member_id) AS member_count
FROM `t_stats_record` r
LEFT JOIN `t_stats` s ON r.trace_id = s.trace_id
WHERE r.trace_id IN (
SELECT trace_id FROM `t_stats_record`
WHERE action = 'ENTER_SCENIC'
<if test="scenicId != null">
AND `identifier` = #{scenicId}
</if>
)
AND r.action = 'LAUNCH'
AND JSON_EXTRACT(`params`, '$.scene') IN (1047, 1048, 1049)
AND s.create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY DATE_FORMAT(s.create_time, '%m-%d')
) scan_data ON scan_data.date_key = date_series.DATE_KEY
ORDER BY date_series.date_value
</select>
<select id="orderChartByHourForMerge" resultType="java.util.HashMap">
WITH RECURSIVE hour_series AS (
SELECT
#{startTime} AS hour_time,
DATE_FORMAT(#{startTime}, '%m-%d %H') AS HOUR
UNION ALL
SELECT
DATE_ADD(hour_time, INTERVAL 1 HOUR) AS hour_time,
DATE_FORMAT(DATE_ADD(hour_time, INTERVAL 1 HOUR), '%m-%d %H') AS HOUR
FROM hour_series
WHERE DATE_ADD(hour_time, INTERVAL 1 HOUR) &lt;= #{endTime}
)
SELECT
hour_series.HOUR as t,
COALESCE(order_data.order_count, 0) AS orderCount,
COALESCE(order_data.order_amount, 0) AS orderAmount
FROM hour_series
LEFT JOIN (
SELECT
DATE_FORMAT(create_at, '%m-%d %H') AS hour_key, DATE_FORMAT(create_at, '%m-%d %H') AS hour_key,
COUNT(id) AS order_count, COUNT(id) AS order_count,
SUM(pay_price) AS order_amount SUM(pay_price) AS order_amount
@@ -435,13 +488,13 @@
) order_data ON order_data.hour_key = hour_series.HOUR ) order_data ON order_data.hour_key = hour_series.HOUR
ORDER BY hour_series.hour_time ORDER BY hour_series.hour_time
</select> </select>
<select id="scanCodeMemberChartByDate" resultType="java.util.HashMap"> <select id="orderChartByDateForMerge" resultType="java.util.HashMap">
WITH RECURSIVE date_series AS ( WITH RECURSIVE date_series AS (
SELECT SELECT
DATE(#{startTime}) AS date_value, DATE(#{startTime}) AS date_value,
DATE_FORMAT(#{startTime}, '%m-%d') AS DATE_KEY DATE_FORMAT(#{startTime}, '%m-%d') AS DATE_KEY
UNION ALL UNION ALL
SELECT SELECT
DATE_ADD(date_value, INTERVAL 1 DAY) AS date_value, DATE_ADD(date_value, INTERVAL 1 DAY) AS date_value,
DATE_FORMAT(DATE_ADD(date_value, INTERVAL 1 DAY), '%m-%d') AS DATE_KEY DATE_FORMAT(DATE_ADD(date_value, INTERVAL 1 DAY), '%m-%d') AS DATE_KEY
FROM date_series FROM date_series
@@ -449,30 +502,11 @@
) )
SELECT SELECT
date_series.DATE_KEY as t, date_series.DATE_KEY as t,
COALESCE(scan_data.member_count, 0) AS count,
COALESCE(order_data.order_count, 0) AS orderCount, COALESCE(order_data.order_count, 0) AS orderCount,
COALESCE(order_data.order_amount, 0) AS orderAmount COALESCE(order_data.order_amount, 0) AS orderAmount
FROM date_series FROM date_series
LEFT JOIN ( LEFT JOIN (
SELECT SELECT
DATE_FORMAT(s.create_time, '%m-%d') AS date_key,
COUNT(DISTINCT s.member_id) AS member_count
FROM `t_stats_record` r
LEFT JOIN `t_stats` s ON r.trace_id = s.trace_id
WHERE r.trace_id IN (
SELECT trace_id FROM `t_stats_record`
WHERE action = 'ENTER_SCENIC'
<if test="scenicId != null">
AND `identifier` = #{scenicId}
</if>
)
AND r.action = 'LAUNCH'
AND JSON_EXTRACT(`params`, '$.scene') IN (1047, 1048, 1049)
AND s.create_time BETWEEN #{startTime} AND #{endTime}
GROUP BY DATE_FORMAT(s.create_time, '%m-%d')
) scan_data ON scan_data.date_key = date_series.DATE_KEY
LEFT JOIN (
SELECT
DATE_FORMAT(create_at, '%m-%d') AS date_key, DATE_FORMAT(create_at, '%m-%d') AS date_key,
COUNT(id) AS order_count, COUNT(id) AS order_count,
SUM(pay_price) AS order_amount SUM(pay_price) AS order_amount