|
|
@@ -0,0 +1,322 @@
|
|
|
+package com.fs.course.service.impl;
|
|
|
+
|
|
|
+import com.fs.common.utils.DateUtils;
|
|
|
+import com.fs.course.domain.FinishCourseStatistics;
|
|
|
+import com.fs.course.mapper.FinishCourseStatisticsSyncMapper;
|
|
|
+import com.fs.course.service.IFinishCourseStatisticsSyncService;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class FinishCourseStatisticsSyncServiceImpl implements IFinishCourseStatisticsSyncService {
|
|
|
+
|
|
|
+ private final FinishCourseStatisticsSyncMapper syncMapper;
|
|
|
+
|
|
|
+ public FinishCourseStatisticsSyncServiceImpl(FinishCourseStatisticsSyncMapper syncMapper) {
|
|
|
+ this.syncMapper = syncMapper;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 维度定义
|
|
|
+ private static final List<String> DIMENSIONS = Arrays.asList(
|
|
|
+ "company", "course", "video"
|
|
|
+ );
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void syncMultiDimensionStatistics() {
|
|
|
+ try {
|
|
|
+ // 获取昨天的日期
|
|
|
+ Date yesterday = DateUtils.addDays(new Date(), -1);
|
|
|
+ String dateStr = DateUtils.parseDateToStr("yyyy-MM-dd", yesterday);
|
|
|
+
|
|
|
+ log.info("开始同步{}的统计数据", dateStr);
|
|
|
+
|
|
|
+ // 按维度分别检查和处理
|
|
|
+ for (String dimensionType : DIMENSIONS) {
|
|
|
+ try {
|
|
|
+ // 检查该维度是否已经同步
|
|
|
+ int exists = syncMapper.checkDimensionExists(yesterday, dimensionType);
|
|
|
+ if (exists > 0) {
|
|
|
+ log.info("维度 {} 的 {} 数据已同步,跳过",
|
|
|
+ dimensionType, dateStr);
|
|
|
+ continue; // 跳过该维度,继续处理其他维度
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同步该维度数据
|
|
|
+ log.info("开始同步维度 {} 的数据", dimensionType);
|
|
|
+ long dimStartTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ // 获取时间范围
|
|
|
+ Date startTime = getStartOfDay(yesterday);
|
|
|
+
|
|
|
+ Date endTime = getEndOfDay(yesterday);
|
|
|
+
|
|
|
+ // 同步单个维度
|
|
|
+ syncSingleDimension(dimensionType, yesterday, startTime, endTime);
|
|
|
+
|
|
|
+ long dimEndTime = System.currentTimeMillis();
|
|
|
+ log.info("维度 {} 同步完成,耗时:{}ms",
|
|
|
+ dimensionType, dimEndTime - dimStartTime);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("维度 {} 同步失败:{}", dimensionType, e.getMessage(), e);
|
|
|
+ // 继续处理其他维度
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理旧数据(保留90天)
|
|
|
+ syncMapper.cleanOldData(90);
|
|
|
+
|
|
|
+ log.info("每日同步完成");
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("每日同步任务执行失败:{}", e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步单个维度数据
|
|
|
+ */
|
|
|
+ private void syncSingleDimension(String dimensionType, Date statDate,
|
|
|
+ Date startTime, Date endTime) {
|
|
|
+ // 1. 先删除已存在的该维度数据
|
|
|
+ int deleted = syncMapper.deleteByDimension(statDate, dimensionType);
|
|
|
+ if (deleted > 0) {
|
|
|
+ log.debug("删除维度 {} 的{}条旧数据", dimensionType, deleted);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 查询该维度的统计数据
|
|
|
+ List<Map<String, Object>> statsData = queryDimensionStatistics(
|
|
|
+ dimensionType, startTime, endTime);
|
|
|
+
|
|
|
+ if (statsData.isEmpty()) {
|
|
|
+ log.debug("维度 {} 没有数据", dimensionType);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 为每条数据添加日期和同步信息
|
|
|
+ // 3. 转换为实体
|
|
|
+ List<FinishCourseStatistics> statisticsList = convertToStatistics(
|
|
|
+ statsData, statDate, dimensionType, "DAILY");
|
|
|
+
|
|
|
+ // 4. 批量插入
|
|
|
+ int inserted = syncMapper.batchInsertStatistics(statisticsList);
|
|
|
+ log.debug("维度 {} 插入{}条数据", dimensionType, inserted);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询指定维度的统计数据
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> queryDimensionStatistics(String dimensionType,Date startTime, Date endTime) {
|
|
|
+ switch (dimensionType) {
|
|
|
+ case "company":
|
|
|
+ return syncMapper.selectCompanyStatistics(startTime,endTime);
|
|
|
+ case "course":
|
|
|
+ return syncMapper.selectCourseStatistics(startTime,endTime);
|
|
|
+ case "video":
|
|
|
+ return syncMapper.selectVideoStatistics(startTime,endTime);
|
|
|
+ default:
|
|
|
+ throw new IllegalArgumentException("不支持的维度类型:" + dimensionType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换统计数据为实体
|
|
|
+ */
|
|
|
+ private List<FinishCourseStatistics> convertToStatistics(List<Map<String, Object>> statisticsData,
|
|
|
+ Date statDate, String dimensionType, String syncType) {
|
|
|
+ return statisticsData.stream()
|
|
|
+ .map(map -> {
|
|
|
+ FinishCourseStatistics stats = new FinishCourseStatistics();
|
|
|
+
|
|
|
+ // 设置维度ID
|
|
|
+ stats.setCompanyId(getLongValue(map.get("company_id")));
|
|
|
+ stats.setCourseId(getLongValue(map.get("course_id")));
|
|
|
+ stats.setVideoId(getLongValue(map.get("video_id")));
|
|
|
+ stats.setDimensionType(dimensionType);
|
|
|
+ stats.setStatDate(statDate);
|
|
|
+
|
|
|
+ // 设置统计指标
|
|
|
+ stats.setFinishedCount(getIntValue(map.get("finished_count")));
|
|
|
+ stats.setCourseCompleteTimes(getIntValue(map.get("course_complete_times")));
|
|
|
+ stats.setAccessCount(getIntValue(map.get("access_count")));
|
|
|
+ stats.setFinishRate(getBigDecimalValue(map.get("finish_rate")));
|
|
|
+
|
|
|
+ // 设置同步信息
|
|
|
+ stats.setSyncType(syncType);
|
|
|
+ stats.setSyncTime(new Date());
|
|
|
+ stats.setCreateTime(new Date());
|
|
|
+ stats.setUpdateTime(new Date());
|
|
|
+
|
|
|
+ return stats;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void syncDailyStatistics(Date startDate, Date endDate) {
|
|
|
+ try {
|
|
|
+ String startDateStr = DateUtils.parseDateToStr("yyyy-MM-dd", startDate);
|
|
|
+ String endDateStr = DateUtils.parseDateToStr("yyyy-MM-dd", endDate);
|
|
|
+ log.info("开始同步{}到{}的统计数据", startDateStr, endDateStr);
|
|
|
+
|
|
|
+ // 计算日期范围内所有日期
|
|
|
+ List<Date> dateRange = getDateRange(startDate, endDate);
|
|
|
+
|
|
|
+ // 按维度分别检查和处理
|
|
|
+ for (String dimensionType : DIMENSIONS) {
|
|
|
+ log.info("开始同步维度 {} 的数据", dimensionType);
|
|
|
+ long dimStartTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ // 对日期范围内的每一天进行处理
|
|
|
+ for (Date statDate : dateRange) {
|
|
|
+ try {
|
|
|
+ // 检查该维度当天是否已经同步
|
|
|
+ int exists = syncMapper.checkDimensionExists(statDate, dimensionType);
|
|
|
+ if (exists > 0) {
|
|
|
+ log.info("维度 {} 的 {} 数据已同步,跳过",
|
|
|
+ dimensionType, DateUtils.parseDateToStr("yyyy-MM-dd", statDate));
|
|
|
+ continue; // 跳过该日期,继续处理下一天
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当天的时间范围
|
|
|
+ Date startTime = getStartOfDay(statDate);
|
|
|
+ Date endTime = getEndOfDay(statDate);
|
|
|
+
|
|
|
+ // 同步单个维度当天数据
|
|
|
+ syncSingleDimension(dimensionType, statDate, startTime, endTime);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("维度 {} 在日期 {} 同步失败:{}",
|
|
|
+ dimensionType, DateUtils.parseDateToStr("yyyy-MM-dd", statDate), e.getMessage(), e);
|
|
|
+ // 继续处理下一天
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ long dimEndTime = System.currentTimeMillis();
|
|
|
+ log.info("维度 {} 同步完成,耗时:{}ms", dimensionType, dimEndTime - dimStartTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理旧数据(保留90天)
|
|
|
+ syncMapper.cleanOldData(90);
|
|
|
+
|
|
|
+ log.info("指定日期范围同步完成");
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("指定日期范围同步任务执行失败:{}", e.getMessage(), e);
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取日期范围内的所有日期列表
|
|
|
+ */
|
|
|
+ private List<Date> getDateRange(Date startDate, Date endDate) {
|
|
|
+ List<Date> dateList = new ArrayList<>();
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(startDate);
|
|
|
+
|
|
|
+ Calendar endCalendar = Calendar.getInstance();
|
|
|
+ endCalendar.setTime(endDate);
|
|
|
+
|
|
|
+ while (!calendar.getTime().after(endCalendar.getTime())) {
|
|
|
+ dateList.add(calendar.getTime());
|
|
|
+ calendar.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ return dateList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取一天的开始时间(00:00:00.000)
|
|
|
+ */
|
|
|
+ private Date getStartOfDay(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 Date getEndOfDay(Date date) {
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.setTime(date);
|
|
|
+ calendar.set(Calendar.HOUR_OF_DAY, 23);
|
|
|
+ calendar.set(Calendar.MINUTE, 59);
|
|
|
+ calendar.set(Calendar.SECOND, 59);
|
|
|
+ calendar.set(Calendar.MILLISECOND, 999);
|
|
|
+ return calendar.getTime();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void syncHistoryStatistics() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void syncIncrementalStatistics() {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void resyncStatistics(Date startDate, Date endDate) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 辅助方法
|
|
|
+ private Long getLongValue(Object value) {
|
|
|
+ if (value == null) return null;
|
|
|
+ if (value instanceof Long) return (Long) value;
|
|
|
+ if (value instanceof Integer) return ((Integer) value).longValue();
|
|
|
+ if (value instanceof String) {
|
|
|
+ try {
|
|
|
+ return Long.parseLong((String) value);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer getIntValue(Object value) {
|
|
|
+ if (value == null) return 0;
|
|
|
+ if (value instanceof Integer) return (Integer) value;
|
|
|
+ if (value instanceof Long) return ((Long) value).intValue();
|
|
|
+ if (value instanceof String) {
|
|
|
+ try {
|
|
|
+ return Integer.parseInt((String) value);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal getBigDecimalValue(Object value) {
|
|
|
+ if (value == null) return BigDecimal.ZERO;
|
|
|
+ if (value instanceof BigDecimal) return (BigDecimal) value;
|
|
|
+ if (value instanceof Double) return BigDecimal.valueOf((Double) value);
|
|
|
+ if (value instanceof Float) return BigDecimal.valueOf((Float) value);
|
|
|
+ if (value instanceof String) {
|
|
|
+ try {
|
|
|
+ return new BigDecimal((String) value);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+}
|