|
|
@@ -10,14 +10,26 @@ import org.apache.commons.lang3.StringUtils;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import shop.alien.entity.result.R;
|
|
|
import shop.alien.entity.store.CommonPushTask;
|
|
|
+import shop.alien.entity.store.dto.CommonPushTaskStatsDto;
|
|
|
+import shop.alien.entity.store.vo.*;
|
|
|
import shop.alien.mapper.CommonPushTaskMapper;
|
|
|
import shop.alien.store.service.CommonPushTaskService;
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Calendar;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
@RequiredArgsConstructor
|
|
|
public class CommonPushTaskServiceImpl extends ServiceImpl<CommonPushTaskMapper, CommonPushTask> implements CommonPushTaskService {
|
|
|
|
|
|
+ private static final String[] WEEKDAY_NAMES = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
|
|
|
+
|
|
|
@Override
|
|
|
public R<String> add(CommonPushTask task) {
|
|
|
log.info("CommonPushTaskServiceImpl.add, param={}", task);
|
|
|
@@ -69,4 +81,260 @@ public class CommonPushTaskServiceImpl extends ServiceImpl<CommonPushTaskMapper,
|
|
|
wrapper.orderByDesc(CommonPushTask::getUpdatedTime);
|
|
|
return R.data(this.page(page, wrapper));
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<CommonPushTaskStatisticsPageVo> statisticsList(Integer pageNum, Integer pageSize, String taskNo, String keyword,
|
|
|
+ String status, Integer pushType, String channel,
|
|
|
+ Date startTime, Date endTime) {
|
|
|
+ log.info("CommonPushTaskServiceImpl.statisticsList, pageNum={}, pageSize={}, taskNo={}, keyword={}, status={}, pushType={}, channel={}, startTime={}, endTime={}",
|
|
|
+ pageNum, pageSize, taskNo, keyword, status, pushType, channel, startTime, endTime);
|
|
|
+ Page<CommonPushTaskStatsDto> page = new Page<>(pageNum, pageSize);
|
|
|
+ IPage<CommonPushTaskStatsDto> statsPage = baseMapper.selectStatisticsPage(page, taskNo, keyword, status, pushType, channel, startTime, endTime);
|
|
|
+
|
|
|
+ List<CommonPushTaskStatsDto> allStats = baseMapper.selectStatisticsList(taskNo, keyword, status, pushType, channel, startTime, endTime);
|
|
|
+
|
|
|
+ CommonPushTaskStatisticsPageVo result = new CommonPushTaskStatisticsPageVo();
|
|
|
+ result.setSummary(buildSummary(allStats));
|
|
|
+ result.setPage(statsPage.convert(this::toStatisticsItem));
|
|
|
+ return R.data(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<CommonPushFunnelVo> funnel(Long pushTaskId, Date startTime, Date endTime) {
|
|
|
+ log.info("CommonPushTaskServiceImpl.funnel, pushTaskId={}, startTime={}, endTime={}", pushTaskId, startTime, endTime);
|
|
|
+ CommonPushTaskStatsDto stats = baseMapper.selectUserStats(pushTaskId, startTime, endTime);
|
|
|
+ if (stats == null) {
|
|
|
+ stats = new CommonPushTaskStatsDto();
|
|
|
+ stats.setSentCount(0L);
|
|
|
+ stats.setDeliveredCount(0L);
|
|
|
+ stats.setShowCount(0L);
|
|
|
+ stats.setClickCount(0L);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long plannedCount = null;
|
|
|
+ if (pushTaskId != null) {
|
|
|
+ CommonPushTask task = this.getById(pushTaskId);
|
|
|
+ if (task != null) {
|
|
|
+ plannedCount = task.getEstimatedCount() != null ? task.getEstimatedCount().longValue() : null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ CommonPushFunnelVo funnelVo = new CommonPushFunnelVo();
|
|
|
+ funnelVo.setPushTaskId(pushTaskId);
|
|
|
+ funnelVo.setSteps(buildFunnelSteps(plannedCount, stats));
|
|
|
+ return R.data(funnelVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<CommonPushReportVo> report(Integer reportType, Date reportDate) {
|
|
|
+ log.info("CommonPushTaskServiceImpl.report, reportType={}, reportDate={}", reportType, reportDate);
|
|
|
+ if (reportType == null) {
|
|
|
+ reportType = 1;
|
|
|
+ }
|
|
|
+ if (reportDate == null) {
|
|
|
+ reportDate = truncateToDay(new Date());
|
|
|
+ } else {
|
|
|
+ reportDate = truncateToDay(reportDate);
|
|
|
+ }
|
|
|
+
|
|
|
+ Date[] range = resolveReportRange(reportType, reportDate);
|
|
|
+ Date startTime = range[0];
|
|
|
+ Date endTime = range[1];
|
|
|
+
|
|
|
+ CommonPushTaskStatsDto stats = baseMapper.selectUserStats(null, startTime, endTime);
|
|
|
+ if (stats == null) {
|
|
|
+ stats = new CommonPushTaskStatsDto();
|
|
|
+ stats.setSentCount(0L);
|
|
|
+ stats.setDeliveredCount(0L);
|
|
|
+ stats.setClickCount(0L);
|
|
|
+ }
|
|
|
+
|
|
|
+ CommonPushReportVo reportVo = new CommonPushReportVo();
|
|
|
+ reportVo.setReportType(reportType);
|
|
|
+ reportVo.setReportDate(reportDate);
|
|
|
+ reportVo.setStartTime(startTime);
|
|
|
+ reportVo.setEndTime(endTime);
|
|
|
+ reportVo.setReportTitle(buildReportTitle(reportType, reportDate));
|
|
|
+ reportVo.setSummary(buildUserStatsSummary(stats));
|
|
|
+ reportVo.setTopList(buildTopList(startTime, endTime));
|
|
|
+ return R.data(reportVo);
|
|
|
+ }
|
|
|
+
|
|
|
+ private CommonPushTaskStatisticsItemVo toStatisticsItem(CommonPushTaskStatsDto dto) {
|
|
|
+ CommonPushTaskStatisticsItemVo item = new CommonPushTaskStatisticsItemVo();
|
|
|
+ item.setId(dto.getId());
|
|
|
+ item.setTaskNo(dto.getTaskNo());
|
|
|
+ item.setTitle(dto.getTitle());
|
|
|
+ item.setPushType(dto.getPushType());
|
|
|
+ item.setStatus(dto.getStatus());
|
|
|
+ item.setChannels(dto.getChannels());
|
|
|
+ item.setEstimatedCount(dto.getEstimatedCount());
|
|
|
+ item.setCreatedTime(dto.getCreatedTime());
|
|
|
+ item.setSentCount(defaultCount(dto.getSentCount()));
|
|
|
+ item.setDeliveredCount(defaultCount(dto.getDeliveredCount()));
|
|
|
+ item.setClickCount(defaultCount(dto.getClickCount()));
|
|
|
+ item.setPushRate(calcRate(item.getSentCount(), dto.getEstimatedCount() != null ? dto.getEstimatedCount().longValue() : 0L));
|
|
|
+ item.setDeliveryRate(calcRate(item.getDeliveredCount(), item.getSentCount()));
|
|
|
+ item.setClickRate(calcRate(item.getClickCount(), item.getDeliveredCount()));
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ private CommonPushStatisticsSummaryVo buildSummary(List<CommonPushTaskStatsDto> statsList) {
|
|
|
+ long sentCount = 0L;
|
|
|
+ long deliveredCount = 0L;
|
|
|
+ long clickCount = 0L;
|
|
|
+ if (statsList != null) {
|
|
|
+ for (CommonPushTaskStatsDto dto : statsList) {
|
|
|
+ sentCount += defaultCount(dto.getSentCount());
|
|
|
+ deliveredCount += defaultCount(dto.getDeliveredCount());
|
|
|
+ clickCount += defaultCount(dto.getClickCount());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return buildSummary(sentCount, deliveredCount, clickCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ private CommonPushStatisticsSummaryVo buildUserStatsSummary(CommonPushTaskStatsDto stats) {
|
|
|
+ return buildSummary(defaultCount(stats.getSentCount()), defaultCount(stats.getDeliveredCount()), defaultCount(stats.getClickCount()));
|
|
|
+ }
|
|
|
+
|
|
|
+ private CommonPushStatisticsSummaryVo buildSummary(long sentCount, long deliveredCount, long clickCount) {
|
|
|
+ CommonPushStatisticsSummaryVo summary = new CommonPushStatisticsSummaryVo();
|
|
|
+ summary.setPushVolume(sentCount);
|
|
|
+ summary.setDeliveredCount(deliveredCount);
|
|
|
+ summary.setClickCount(clickCount);
|
|
|
+ summary.setDeliveryRate(calcRate(deliveredCount, sentCount));
|
|
|
+ summary.setClickRate(calcRate(clickCount, deliveredCount));
|
|
|
+ summary.setConversionRate(calcRate(clickCount, sentCount));
|
|
|
+ return summary;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<CommonPushFunnelStepVo> buildFunnelSteps(Long plannedCount, CommonPushTaskStatsDto stats) {
|
|
|
+ long actualSend = defaultCount(stats.getSentCount());
|
|
|
+ long arrived = defaultCount(stats.getDeliveredCount());
|
|
|
+ long show = defaultCount(stats.getShowCount());
|
|
|
+ long click = defaultCount(stats.getClickCount());
|
|
|
+
|
|
|
+ List<CommonPushFunnelStepVo> steps = new ArrayList<>();
|
|
|
+
|
|
|
+ CommonPushFunnelStepVo planned = new CommonPushFunnelStepVo();
|
|
|
+ planned.setStepCode("planned");
|
|
|
+ planned.setStepName("计划发送");
|
|
|
+ planned.setCount(plannedCount);
|
|
|
+ planned.setConversionRate(plannedCount != null && plannedCount > 0 ? BigDecimal.valueOf(100) : null);
|
|
|
+ steps.add(planned);
|
|
|
+
|
|
|
+ CommonPushFunnelStepVo actual = new CommonPushFunnelStepVo();
|
|
|
+ actual.setStepCode("actual");
|
|
|
+ actual.setStepName("实际发送");
|
|
|
+ actual.setCount(actualSend);
|
|
|
+ actual.setConversionRate(calcRate(actualSend, plannedCount != null ? plannedCount : 0L));
|
|
|
+ steps.add(actual);
|
|
|
+
|
|
|
+ CommonPushFunnelStepVo arrivedStep = new CommonPushFunnelStepVo();
|
|
|
+ arrivedStep.setStepCode("arrived");
|
|
|
+ arrivedStep.setStepName("抵达设备");
|
|
|
+ arrivedStep.setCount(arrived);
|
|
|
+ arrivedStep.setConversionRate(calcRate(arrived, actualSend));
|
|
|
+ steps.add(arrivedStep);
|
|
|
+
|
|
|
+ CommonPushFunnelStepVo showStep = new CommonPushFunnelStepVo();
|
|
|
+ showStep.setStepCode("show");
|
|
|
+ showStep.setStepName("展示通知");
|
|
|
+ showStep.setCount(show);
|
|
|
+ showStep.setConversionRate(calcRate(show, arrived));
|
|
|
+ steps.add(showStep);
|
|
|
+
|
|
|
+ CommonPushFunnelStepVo clickStep = new CommonPushFunnelStepVo();
|
|
|
+ clickStep.setStepCode("click");
|
|
|
+ clickStep.setStepName("用户点击");
|
|
|
+ clickStep.setCount(click);
|
|
|
+ clickStep.setConversionRate(calcRate(click, show));
|
|
|
+ steps.add(clickStep);
|
|
|
+
|
|
|
+ return steps;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<CommonPushReportTopItemVo> buildTopList(Date startTime, Date endTime) {
|
|
|
+ List<CommonPushTaskStatsDto> topStats = baseMapper.selectTopByClickRate(startTime, endTime, 5);
|
|
|
+ List<CommonPushReportTopItemVo> topList = new ArrayList<>();
|
|
|
+ if (topStats == null) {
|
|
|
+ return topList;
|
|
|
+ }
|
|
|
+ int rank = 1;
|
|
|
+ for (CommonPushTaskStatsDto dto : topStats) {
|
|
|
+ CommonPushReportTopItemVo item = new CommonPushReportTopItemVo();
|
|
|
+ item.setRank(rank++);
|
|
|
+ item.setPushTaskId(dto.getId());
|
|
|
+ item.setTitle(dto.getTitle());
|
|
|
+ item.setDeliveredCount(defaultCount(dto.getDeliveredCount()));
|
|
|
+ item.setClickCount(defaultCount(dto.getClickCount()));
|
|
|
+ item.setClickRate(calcRate(item.getClickCount(), item.getDeliveredCount()));
|
|
|
+ topList.add(item);
|
|
|
+ }
|
|
|
+ return topList;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildReportTitle(Integer reportType, Date reportDate) {
|
|
|
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ String dateStr = dateFormat.format(reportDate);
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(reportDate);
|
|
|
+ String weekday = WEEKDAY_NAMES[calendar.get(Calendar.DAY_OF_WEEK) - 1];
|
|
|
+ if (reportType == 2) {
|
|
|
+ return "推送周报 — " + dateStr;
|
|
|
+ }
|
|
|
+ if (reportType == 3) {
|
|
|
+ SimpleDateFormat monthFormat = new SimpleDateFormat("yyyy-MM");
|
|
|
+ return "推送月报 — " + monthFormat.format(reportDate);
|
|
|
+ }
|
|
|
+ return "推送日报 — " + dateStr + " (" + weekday + ")";
|
|
|
+ }
|
|
|
+
|
|
|
+ private Date[] resolveReportRange(Integer reportType, Date reportDate) {
|
|
|
+ Calendar start = Calendar.getInstance();
|
|
|
+ start.setTime(reportDate);
|
|
|
+ start.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ start.set(Calendar.MINUTE, 0);
|
|
|
+ start.set(Calendar.SECOND, 0);
|
|
|
+ start.set(Calendar.MILLISECOND, 0);
|
|
|
+
|
|
|
+ Calendar end = (Calendar) start.clone();
|
|
|
+ if (reportType == 2) {
|
|
|
+ int dayOfWeek = start.get(Calendar.DAY_OF_WEEK);
|
|
|
+ int diffToMonday = (dayOfWeek + 5) % 7;
|
|
|
+ start.add(Calendar.DAY_OF_MONTH, -diffToMonday);
|
|
|
+ end.setTime(start.getTime());
|
|
|
+ end.add(Calendar.DAY_OF_MONTH, 7);
|
|
|
+ } else if (reportType == 3) {
|
|
|
+ start.set(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ end.setTime(start.getTime());
|
|
|
+ end.add(Calendar.MONTH, 1);
|
|
|
+ } else {
|
|
|
+ end.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ }
|
|
|
+ return new Date[]{start.getTime(), end.getTime()};
|
|
|
+ }
|
|
|
+
|
|
|
+ private Date truncateToDay(Date date) {
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(date);
|
|
|
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
|
|
|
+ calendar.set(Calendar.MINUTE, 0);
|
|
|
+ calendar.set(Calendar.SECOND, 0);
|
|
|
+ calendar.set(Calendar.MILLISECOND, 0);
|
|
|
+ return calendar.getTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ private long defaultCount(Long count) {
|
|
|
+ return count == null ? 0L : count;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal calcRate(long numerator, long denominator) {
|
|
|
+ if (denominator <= 0) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ return BigDecimal.valueOf(numerator)
|
|
|
+ .multiply(BigDecimal.valueOf(100))
|
|
|
+ .divide(BigDecimal.valueOf(denominator), 1, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
}
|