فهرست منبع

备份看课记录,备份答题记录,备份红包记录12月以前的

yfh 1 هفته پیش
والد
کامیت
165881c98a

+ 51 - 0
fs-admin/src/main/java/com/fs/task/FsCourseBackupTask.java

@@ -0,0 +1,51 @@
+package com.fs.task;
+
+import com.fs.course.domain.FsCourseWatchLog;
+import com.fs.course.service.IFsCourseAnswerLogsService;
+import com.fs.course.service.IFsCourseRedPacketLogService;
+import com.fs.course.service.IFsCourseWatchLogService;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 备份看课记录,答题记录,红包记录数据
+ */
+@AllArgsConstructor
+@Component("fsCourseBackupTask")
+@Slf4j
+public class FsCourseBackupTask {
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private IFsCourseAnswerLogsService fsCourseAnswerLogsService;
+
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
+
+    /**
+     * 备份看课记录
+     */
+    public void backupWatchLog(){
+        fsCourseWatchLogService.backupWatchLog();
+    }
+
+    /**
+     * 备份答题记录
+     */
+    public void backupAnswerLog(){
+        fsCourseAnswerLogsService.backupAnswerLog();
+    }
+
+    /**
+     * 备份红包记录
+     */
+    public void backupRedPacketLog(){
+        fsCourseRedPacketLogService.backupRedPacketLog();
+    }
+}

+ 15 - 0
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.app.annotation.Login;
 import com.fs.app.config.ImageStorageConfig;
 import com.fs.common.annotation.Log;
+import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.common.enums.BusinessType;
@@ -79,6 +80,10 @@ public class FsUserCourseVideoController extends AppBaseController {
 
     @Autowired
     private IFsCourseWatchLogService fsCourseWatchLogService;
+    @Autowired
+    private IFsCourseAnswerLogsService fsCourseAnswerLogsService;
+    @Autowired
+    private IFsCourseRedPacketLogService fsCourseRedPacketLogService;
 
     @Autowired
     private OpenIMService openIMService;
@@ -399,4 +404,14 @@ public class FsUserCourseVideoController extends AppBaseController {
         return imMsgSendLogService.deleteFsImMsgSendLogAndDetail(logId);
     }
 
+
+
+    @GetMapping("/backupLog")
+    public AjaxResult backupLog()
+    {
+        fsCourseWatchLogService.backupWatchLog();
+        fsCourseAnswerLogsService.backupAnswerLog();
+        fsCourseRedPacketLogService.backupRedPacketLog();
+        return null;
+    }
 }

+ 15 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseAnswerLogsMapper.java

@@ -138,4 +138,19 @@ public interface FsCourseAnswerLogsMapper
 
     @Select("select * from fs_course_answer_logs where video_id = #{videoId} and user_id = #{userId} and is_right = 1 limit 1")
     FsCourseAnswerLogs selectRightLogByCourseVideoIsOpen(@Param("videoId") Long videoId,@Param("userId") Long userId);
+
+
+    /**
+     * 备份答题数据
+     * @param logs
+     * @return
+     */
+    int batchInsert(@Param("list") List<FsCourseAnswerLogs> logs);
+
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
 }

+ 13 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketLogMapper.java

@@ -189,4 +189,17 @@ public interface FsCourseRedPacketLogMapper
     List<CourseRedPacketStatisticsDTO> statistics(CourseRedPacketStatisticsParam param);
 
     List<FsCourseRedPacketLog> selectFsCourseRedPacketLogListBySending(@Param("maps") Map<String, Object> map);
+
+    /**
+     * 批量备份红包记录表
+     * @param logs
+     * @return
+     */
+    int batchInsert(@Param("list") List<FsCourseRedPacketLog> logs);
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
 }

+ 13 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -719,4 +719,17 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
             "</script>"
     })
     List<Long> getExContactIdsIdsByWatchLogIds(@Param("watchLogIds")List<Long> watchLogIds);
+
+    /**
+     * 批量插入到目标表
+     * @param logs 日志列表
+     * @return 插入成功的条数
+     */
+    int batchInsert(@Param("list") List<FsCourseWatchLog> logs);
+
+    /**
+     * 批量删除ID列表
+     * @return 删除的行数
+     */
+    int batchDeleteByIds(@Param("ids") List<Long> ids);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseAnswerLogsService.java

@@ -66,4 +66,9 @@ public interface IFsCourseAnswerLogsService
     public List<FsCourseAnswerLogsListVO> selectFsCourseAnswerLogsListVONew(FsCourseAnswerLogsParam param);
 
     public Long selectFsCourseAnswerLogsListVONewCount(FsCourseAnswerLogsParam param);
+
+    /**
+     * 备份答题记录
+     */
+    void backupAnswerLog();
 }

+ 5 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseRedPacketLogService.java

@@ -89,4 +89,9 @@ public interface IFsCourseRedPacketLogService
     void queryRedPacketResult(String startTime, String endTime);
 
     R getBillsByTransferBillNo(String batchId);
+
+    /**
+     * 备份红包记录
+     */
+    void backupRedPacketLog();
 }

+ 6 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -154,4 +154,10 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
      * @return
      */
     List<Long> getExContactIdsIdsByWatchLogIds(List<Long> watchLogIds);
+
+
+    /**
+     * 备份看课记录
+     */
+    void backupWatchLog();
 }

+ 197 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsCourseAnswerLogsServiceImpl.java

@@ -8,6 +8,7 @@ import com.fs.company.cache.ICompanyUserCacheService;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.domain.FsCourseAnswerLogs;
+import com.fs.course.domain.FsCourseRedPacketLog;
 import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.mapper.FsCourseAnswerLogsMapper;
 import com.fs.course.param.FsCourseAnswerLogsParam;
@@ -17,14 +18,15 @@ import com.fs.course.vo.FsCourseAnswerLogsListVO;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.store.service.cache.IFsUserCacheService;
+import com.github.pagehelper.PageHelper;
 import com.hc.openapi.tool.util.StringUtils;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
+import java.text.SimpleDateFormat;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -34,6 +36,7 @@ import java.util.stream.Collectors;
  * @date 2024-10-26
  */
 @Service
+@Slf4j
 @RequiredArgsConstructor
 public class FsCourseAnswerLogsServiceImpl implements IFsCourseAnswerLogsService
 {
@@ -201,4 +204,195 @@ public class FsCourseAnswerLogsServiceImpl implements IFsCourseAnswerLogsService
         return fsCourseAnswerLogsMapper.selectFsCourseAnswerLogsListVONewCount(param);
     }
 
+    @Override
+    public void backupAnswerLog() {
+        log.info("开始执行数据迁移任务");
+
+        // 检查是否在凌晨3点之后
+//        if (shouldStopAt3AM()) {
+//            log.info("已到凌晨3点,停止数据迁移任务");
+//            return;
+//        }
+
+        // 设置查询条件:获取12月之前的数据
+        FsCourseAnswerLogs queryLog = createQueryCondition();
+
+        // 分页参数配置
+        int pageSize = 1000; // 每批次处理数量,可根据性能调整
+        int pageNum = 1;
+        int totalProcessed = 0;
+        boolean hasMoreData = true;
+
+        try {
+            while (hasMoreData) {
+                // 检查是否到凌晨3点
+               /* if (shouldStopAt3AM()) {
+                    log.info("已到凌晨3点,停止数据迁移任务,已处理{}条数据", totalProcessed);
+                    break;
+                }*/
+
+                log.info("开始查询第{}批数据,查询条件:beginTime={}, endTime={}",
+                        pageNum, queryLog.getBeginTime(), queryLog.getEndTime());
+
+                // 使用分页查询
+                PageHelper.startPage(pageNum, pageSize);
+                List<FsCourseAnswerLogs> fsCourseAnswerLogs = fsCourseAnswerLogsMapper.selectFsCourseAnswerLogsList(queryLog);
+
+                if (fsCourseAnswerLogs == null || fsCourseAnswerLogs.isEmpty()) {
+                    log.info("所有12月之前的数据已迁移完成");
+                    hasMoreData = false;
+                    continue;
+                }
+
+                log.info("第{}批查询到{}条数据,开始处理", pageNum, fsCourseAnswerLogs.size());
+
+                // 批量处理数据(迁移到目标表并删除源表数据)
+                int processedCount = processBatchData(fsCourseAnswerLogs);
+                totalProcessed += processedCount;
+
+                log.info("第{}批数据处理完成,成功迁移{}条数据,累计迁移{}条",
+                        pageNum, processedCount, totalProcessed);
+
+                pageNum++;
+
+                // 清理分页参数,避免影响下次查询
+                PageHelper.clearPage();
+
+                // 小批量提交后稍作休息,避免数据库压力过大
+                if (hasMoreData) {
+                    Thread.sleep(100);
+                }
+            }
+
+            log.info("数据迁移任务完成,总计迁移{}条数据", totalProcessed);
+
+        } catch (InterruptedException e) {
+            log.warn("数据迁移任务被中断,已处理{}条数据", totalProcessed);
+            Thread.currentThread().interrupt();
+        } catch (Exception e) {
+            log.error("数据迁移过程中发生异常,已处理{}条数据", totalProcessed, e);
+        } finally {
+            // 确保清理分页参数
+            PageHelper.clearPage();
+        }
+    }
+
+    /**
+     * 创建查询条件 - 获取所有12月之前的数据
+     */
+    private FsCourseAnswerLogs createQueryCondition() {
+        FsCourseAnswerLogs queryLog = new FsCourseAnswerLogs();
+
+        // 设置开始时间为系统允许的最早时间(可根据实际情况调整)
+        queryLog.setBeginTime(getEarliestTimeString());
+
+        // 设置结束时间为今年12月1日之前(包含历史所有年份的12月之前数据)
+        queryLog.setEndTime(getDecemberFirstTimeString());
+
+        return queryLog;
+    }
+
+    /**
+     * 批量处理日志数据
+     * @return 成功处理的数据条数
+     */
+    private int processBatchData(List<FsCourseAnswerLogs> logs) {
+        if (logs == null || logs.isEmpty()) {
+            return 0;
+        }
+
+        int successCount = 0;
+
+        try {
+            // 1. 批量插入到目标表
+            log.debug("开始批量插入数据到目标表,数量:{}", logs.size   ());
+            int insertCount = fsCourseAnswerLogsMapper.batchInsert(logs);
+            log.debug("批量插入完成,插入{}条数据", insertCount);
+
+            if (insertCount > 0) {
+                // 2. 获取成功插入的数据ID
+                List<Long> idsToDelete = logs.stream()
+                        .limit(insertCount) // 只删除成功插入的数据
+                        .map(FsCourseAnswerLogs::getLogId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+
+                if (!idsToDelete.isEmpty()) {
+                    // 3. 批量删除源表数据
+                    log.debug("开始批量删除源表数据,数量:{}", idsToDelete.size());
+                    int deleteCount = fsCourseAnswerLogsMapper.batchDeleteByIds(idsToDelete);
+                    log.debug("批量删除完成,删除{}条数据", deleteCount);
+
+                    // 返回成功处理的数量(以插入成功为准)
+                    successCount = insertCount;
+                }
+            }
+
+        } catch (Exception e) {
+            log.error("批量处理数据时发生异常", e);
+            // 这里可以根据需要添加重试逻辑
+            // retryProcessBatchData(logs);
+        }
+
+        return successCount;
+    }
+
+    /**
+     * 检查是否应该停止(凌晨3点之后)
+     */
+    private boolean shouldStopAt3AM() {
+        Calendar calendar = Calendar.getInstance();
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int minute = calendar.get(Calendar.MINUTE);
+
+        // 凌晨3点及之后返回true
+        return hour >= 3;
+    }
+
+    /**
+     * 获取最早时间字符串(String格式)
+     */
+    private String getEarliestTimeString() {
+        try {
+            // 这里可以根据业务设置一个最早的开始时间
+            // 例如:如果只迁移最近3年的数据
+            Calendar calendar = Calendar.getInstance();
+            calendar.add(Calendar.YEAR, -3); // 3年前
+            calendar.set(Calendar.MONTH, 0);
+            calendar.set(Calendar.DAY_OF_MONTH, 1);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            return sdf.format(calendar.getTime());
+        } catch (Exception e) {
+            log.warn("设置最早时间失败,使用默认值null", e);
+            return null; // 返回null表示不限制最早时间
+        }
+    }
+
+    /**
+     * 获取今年12月1日之前的时间字符串
+     */
+    private String getDecemberFirstTimeString() {
+        try {
+            Calendar calendar = Calendar.getInstance();
+            int currentYear = calendar.get(Calendar.YEAR);
+
+            // 获取当前年份的12月1日
+            calendar.set(currentYear, Calendar.DECEMBER, 1, 0, 0, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            return sdf.format(calendar.getTime());
+        } catch (Exception e) {
+            log.error("获取12月1日时间失败", e);
+            // 返回一个很早的时间作为兜底
+            return "2020-12-01 00:00:00";
+        }
+    }
+
+
 }

+ 195 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRedPacketLogServiceImpl.java

@@ -1,6 +1,7 @@
 package com.fs.course.service.impl;
 
 import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -40,6 +41,8 @@ import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import com.github.pagehelper.PageHelper;
+import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
@@ -57,6 +60,7 @@ import org.springframework.transaction.annotation.Transactional;
  * @date 2024-10-24
  */
 @Service
+@Slf4j
 public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogService
 {
     private static final Logger logger = LoggerFactory.getLogger(FsCourseRedPacketLogServiceImpl.class);
@@ -553,4 +557,195 @@ public class FsCourseRedPacketLogServiceImpl implements IFsCourseRedPacketLogSer
         }
 
     }
+
+    @Override
+    public void backupRedPacketLog() {
+        log.info("开始执行数据迁移任务");
+
+       /* // 检查是否在凌晨3点之后
+        if (shouldStopAt3AM()) {
+            log.info("已到凌晨3点,停止数据迁移任务");
+            return;
+        }*/
+
+        // 设置查询条件:获取12月之前的数据
+        FsCourseRedPacketLog queryLog = createQueryCondition();
+
+        // 分页参数配置
+        int pageSize = 1000; // 每批次处理数量,可根据性能调整
+        int pageNum = 1;
+        int totalProcessed = 0;
+        boolean hasMoreData = true;
+
+        try {
+            while (hasMoreData) {
+                // 检查是否到凌晨3点
+              /*  if (shouldStopAt3AM()) {
+                    log.info("已到凌晨3点,停止数据迁移任务,已处理{}条数据", totalProcessed);
+                    break;
+                }*/
+
+                log.info("开始查询第{}批数据,查询条件:beginTime={}, endTime={}",
+                        pageNum, queryLog.getBeginTime(), queryLog.getEndTime());
+
+                // 使用分页查询
+                PageHelper.startPage(pageNum, pageSize);
+                List<FsCourseRedPacketLog> fsCourseRedPacketLogs = fsCourseRedPacketLogMapper.selectFsCourseRedPacketLogList(queryLog);
+
+                if (fsCourseRedPacketLogs == null || fsCourseRedPacketLogs.isEmpty()) {
+                    log.info("所有12月之前的数据已迁移完成");
+                    hasMoreData = false;
+                    continue;
+                }
+
+                log.info("第{}批查询到{}条数据,开始处理", pageNum, fsCourseRedPacketLogs.size());
+
+                // 批量处理数据(迁移到目标表并删除源表数据)
+                int processedCount = processBatchData(fsCourseRedPacketLogs);
+                totalProcessed += processedCount;
+
+                log.info("第{}批数据处理完成,成功迁移{}条数据,累计迁移{}条",
+                        pageNum, processedCount, totalProcessed);
+
+                pageNum++;
+
+                // 清理分页参数,避免影响下次查询
+                PageHelper.clearPage();
+
+                // 小批量提交后稍作休息,避免数据库压力过大
+                if (hasMoreData) {
+                    Thread.sleep(100);
+                }
+            }
+
+            log.info("数据迁移任务完成,总计迁移{}条数据", totalProcessed);
+
+        } catch (InterruptedException e) {
+            log.warn("数据迁移任务被中断,已处理{}条数据", totalProcessed);
+            Thread.currentThread().interrupt();
+        } catch (Exception e) {
+            log.error("数据迁移过程中发生异常,已处理{}条数据", totalProcessed, e);
+        } finally {
+            // 确保清理分页参数
+            PageHelper.clearPage();
+        }
+    }
+
+    /**
+     * 创建查询条件 - 获取所有12月之前的数据
+     */
+    private FsCourseRedPacketLog createQueryCondition() {
+        FsCourseRedPacketLog queryLog = new FsCourseRedPacketLog();
+
+        // 设置开始时间为系统允许的最早时间(可根据实际情况调整)
+        queryLog.setBeginTime(getEarliestTimeString());
+
+        // 设置结束时间为今年12月1日之前(包含历史所有年份的12月之前数据)
+        queryLog.setEndTime(getDecemberFirstTimeString());
+
+        return queryLog;
+    }
+
+    /**
+     * 批量处理日志数据
+     * @return 成功处理的数据条数
+     */
+    private int processBatchData(List<FsCourseRedPacketLog> logs) {
+        if (logs == null || logs.isEmpty()) {
+            return 0;
+        }
+
+        int successCount = 0;
+
+        try {
+            // 1. 批量插入到目标表
+            log.debug("开始批量插入数据到目标表,数量:{}", logs.size   ());
+            int insertCount = fsCourseRedPacketLogMapper.batchInsert(logs);
+            log.debug("批量插入完成,插入{}条数据", insertCount);
+
+            if (insertCount > 0) {
+                // 2. 获取成功插入的数据ID
+                List<Long> idsToDelete = logs.stream()
+                        .limit(insertCount) // 只删除成功插入的数据
+                        .map(FsCourseRedPacketLog::getLogId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+
+                if (!idsToDelete.isEmpty()) {
+                    // 3. 批量删除源表数据
+                    log.debug("开始批量删除源表数据,数量:{}", idsToDelete.size());
+                    int deleteCount = fsCourseRedPacketLogMapper.batchDeleteByIds(idsToDelete);
+                    log.debug("批量删除完成,删除{}条数据", deleteCount);
+
+                    // 返回成功处理的数量(以插入成功为准)
+                    successCount = insertCount;
+                }
+            }
+
+        } catch (Exception e) {
+            log.error("批量处理数据时发生异常", e);
+            // 这里可以根据需要添加重试逻辑
+            // retryProcessBatchData(logs);
+        }
+
+        return successCount;
+    }
+
+    /**
+     * 检查是否应该停止(凌晨3点之后)
+     */
+    private boolean shouldStopAt3AM() {
+        Calendar calendar = Calendar.getInstance();
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int minute = calendar.get(Calendar.MINUTE);
+
+        // 凌晨3点及之后返回true
+        return hour >= 3;
+    }
+
+    /**
+     * 获取最早时间字符串(String格式)
+     */
+    private String getEarliestTimeString() {
+        try {
+            // 这里可以根据业务设置一个最早的开始时间
+            // 例如:如果只迁移最近3年的数据
+            Calendar calendar = Calendar.getInstance();
+            calendar.add(Calendar.YEAR, -3); // 3年前
+            calendar.set(Calendar.MONTH, 0);
+            calendar.set(Calendar.DAY_OF_MONTH, 1);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            return sdf.format(calendar.getTime());
+        } catch (Exception e) {
+            log.warn("设置最早时间失败,使用默认值null", e);
+            return null; // 返回null表示不限制最早时间
+        }
+    }
+
+    /**
+     * 获取今年12月1日之前的时间字符串
+     */
+    private String getDecemberFirstTimeString() {
+        try {
+            Calendar calendar = Calendar.getInstance();
+            int currentYear = calendar.get(Calendar.YEAR);
+
+            // 获取当前年份的12月1日
+            calendar.set(currentYear, Calendar.DECEMBER, 1, 0, 0, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            return sdf.format(calendar.getTime());
+        } catch (Exception e) {
+            log.error("获取12月1日时间失败", e);
+            // 返回一个很早的时间作为兜底
+            return "2020-12-01 00:00:00";
+        }
+    }
+
 }

+ 201 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -51,6 +51,7 @@ import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
 import com.fs.tag.service.FsTagUpdateService;
+import com.github.pagehelper.PageHelper;
 import com.hc.openapi.tool.util.StringUtils;
 import org.apache.commons.collections4.CollectionUtils;
 import org.slf4j.Logger;
@@ -63,6 +64,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.SimpleDateFormat;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -1459,6 +1461,205 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return fsCourseWatchLogMapper.getExContactIdsIdsByWatchLogIds(watchLogIds);
     }
 
+    @Override
+    public void backupWatchLog() {
+        log.info("开始执行数据迁移任务");
+
+        // 检查是否在凌晨3点之后
+/*        if (shouldStopAt3AM()) {
+            log.info("已到凌晨3点,停止数据迁移任务");
+            return;
+        }*/
+
+        // 设置查询条件:获取12月之前的数据
+        FsCourseWatchLog queryLog = createQueryCondition();
+
+        // 分页参数配置
+        int pageSize = 1000; // 每批次处理数量,可根据性能调整
+        int pageNum = 1;
+        int totalProcessed = 0;
+        boolean hasMoreData = true;
+
+        try {
+            while (hasMoreData) {
+                // 检查是否到凌晨3点
+               /* if (shouldStopAt3AM()) {
+                    log.info("已到凌晨3点,停止数据迁移任务,已处理{}条数据", totalProcessed);
+                    break;
+                }*/
+
+                log.info("开始查询第{}批数据,查询条件:beginTime={}, endTime={}",
+                        pageNum, queryLog.getBeginTime(), queryLog.getEndTime());
+
+                // 使用分页查询
+                PageHelper.startPage(pageNum, pageSize);
+                List<FsCourseWatchLog> fsCourseWatchLogs = fsCourseWatchLogMapper.selectFsCourseWatchLogList(queryLog);
+
+                if (fsCourseWatchLogs == null || fsCourseWatchLogs.isEmpty()) {
+                    log.info("所有12月之前的数据已迁移完成");
+                    hasMoreData = false;
+                    continue;
+                }
+
+                log.info("第{}批查询到{}条数据,开始处理", pageNum, fsCourseWatchLogs.size());
+
+                // 批量处理数据(迁移到目标表并删除源表数据)
+                int processedCount = processBatchData(fsCourseWatchLogs);
+                totalProcessed += processedCount;
+
+                log.info("第{}批数据处理完成,成功迁移{}条数据,累计迁移{}条",
+                        pageNum, processedCount, totalProcessed);
+
+                pageNum++;
+
+                // 清理分页参数,避免影响下次查询
+                PageHelper.clearPage();
+
+                // 小批量提交后稍作休息,避免数据库压力过大
+                if (hasMoreData) {
+                    Thread.sleep(100);
+                }
+            }
+
+            log.info("数据迁移任务完成,总计迁移{}条数据", totalProcessed);
+
+        } catch (InterruptedException e) {
+            log.warn("数据迁移任务被中断,已处理{}条数据", totalProcessed);
+            Thread.currentThread().interrupt();
+        } catch (Exception e) {
+            log.error("数据迁移过程中发生异常,已处理{}条数据", totalProcessed, e);
+        } finally {
+            // 确保清理分页参数
+            PageHelper.clearPage();
+        }
+    }
+
+    /**
+     * 创建查询条件 - 获取所有12月之前的数据
+     */
+    private FsCourseWatchLog createQueryCondition() {
+        FsCourseWatchLog queryLog = new FsCourseWatchLog();
+
+        // 设置开始时间为系统允许的最早时间(可根据实际情况调整)
+        queryLog.setBeginTime(getEarliestTimeString());
+
+        // 设置结束时间为今年12月1日之前(包含历史所有年份的12月之前数据)
+        queryLog.setEndTime(getLastYearDecemberFirstTimeString());
+
+        return queryLog;
+    }
+
+    /**
+     * 批量处理日志数据
+     * @return 成功处理的数据条数
+     */
+    private int processBatchData(List<FsCourseWatchLog> logs) {
+        if (logs == null || logs.isEmpty()) {
+            return 0;
+        }
+
+        int successCount = 0;
+
+        try {
+            // 1. 批量插入到目标表
+            log.debug("开始批量插入数据到目标表,数量:{}", logs.size());
+            int insertCount = fsCourseWatchLogMapper.batchInsert(logs);
+            log.debug("批量插入完成,插入{}条数据", insertCount);
+
+            if (insertCount > 0) {
+                // 2. 获取成功插入的数据ID
+                List<Long> idsToDelete = logs.stream()
+                        .limit(insertCount) // 只删除成功插入的数据
+                        .map(FsCourseWatchLog::getLogId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+
+                if (!idsToDelete.isEmpty()) {
+                    // 3. 批量删除源表数据
+                    log.debug("开始批量删除源表数据,数量:{}", idsToDelete.size());
+                    int deleteCount = fsCourseWatchLogMapper.batchDeleteByIds(idsToDelete);
+                    log.debug("批量删除完成,删除{}条数据", deleteCount);
+
+                    // 返回成功处理的数量(以插入成功为准)
+                    successCount = insertCount;
+                }
+            }
+
+        } catch (Exception e) {
+            log.error("批量处理数据时发生异常", e);
+            // 这里可以根据需要添加重试逻辑
+            // retryProcessBatchData(logs);
+        }
+
+        return successCount;
+    }
+
+    /**
+     * 检查是否应该停止(凌晨3点之后)
+     */
+    private boolean shouldStopAt3AM() {
+        Calendar calendar = Calendar.getInstance();
+        int hour = calendar.get(Calendar.HOUR_OF_DAY);
+        int minute = calendar.get(Calendar.MINUTE);
+
+        // 凌晨3点及之后返回true
+        return hour >= 3;
+    }
+
+    /**
+     * 获取最早时间字符串(String格式)
+     */
+    private String getEarliestTimeString() {
+        try {
+            // 这里可以根据业务设置一个最早的开始时间
+            // 例如:如果只迁移最近3年的数据
+            Calendar calendar = Calendar.getInstance();
+            calendar.add(Calendar.YEAR, -3); // 3年前
+            calendar.set(Calendar.MONTH, 0);
+            calendar.set(Calendar.DAY_OF_MONTH, 1);
+            calendar.set(Calendar.HOUR_OF_DAY, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            return sdf.format(calendar.getTime());
+        } catch (Exception e) {
+            log.warn("设置最早时间失败,使用默认值null", e);
+            return null; // 返回null表示不限制最早时间
+        }
+    }
+
+    /**
+     * 获取去年12月1日之前的时间字符串
+     * 注意:这个是时间上限,查询条件是小于这个时间的数据
+     */
+    private String getLastYearDecemberFirstTimeString() {
+        try {
+            Calendar calendar = Calendar.getInstance();
+            int currentYear = calendar.get(Calendar.YEAR);
+
+            // 🔴 关键修改:去年12月1日,不是今年
+            int lastYear = currentYear - 1;
+
+            // 设置去年12月1日 00:00:00
+            calendar.set(lastYear, Calendar.DECEMBER, 1, 0, 0, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String result = sdf.format(calendar.getTime());
+
+            log.info("📅 设置查询结束时间:去年12月1日 = {}", result);
+            return result;
+
+        } catch (Exception e) {
+            log.error("获取去年12月1日时间失败", e);
+            // 兜底:返回前年12月1日,确保能查到历史数据
+            return (Calendar.getInstance().get(Calendar.YEAR) - 2) + "-12-01 00:00:00";
+        }
+    }
+
+
 
 
 }

+ 43 - 1
fs-service/src/main/resources/mapper/course/FsCourseAnswerLogsMapper.xml

@@ -33,6 +33,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="companyId != null "> and company_id = #{companyId}</if>
             <if test="qwUserId != null "> and qw_user_id = #{qwUserId}</if>
+            <if test="beginTime != null "> and create_time &gt;= #{beginTime}</if>
+            <if test="endTime != null "> and create_time &lt;= #{endTime}</if>
         </where>
     </select>
 
@@ -213,5 +215,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             and period_id = #{periodId}
         </if>
     </select>
-
+    <insert id="batchInsert" parameterType="java.util.List">
+        insert into fs_course_answer_logs_1
+        (
+        log_id,
+        user_id,
+        video_id,
+        is_right,
+        create_time,
+        company_id,
+        company_user_id,
+        qw_user_id,
+        course_id,
+        question_json,
+        watch_log_id,
+        period_id
+        )
+        VALUES
+        <foreach collection="list" item="log" separator=",">
+            (
+            #{log.logId},
+            #{log.userId},
+            #{log.videoId},
+            #{log.isRight},
+            #{log.createTime},
+            #{log.companyId},
+            #{log.companyUserId},
+            #{log.qwUserId},
+            #{log.courseId},
+            #{log.questionJson},
+            #{log.watchLogId},
+            #{log.periodId}
+            )
+        </foreach>
+    </insert>
+    <delete id="batchDeleteByIds" parameterType="java.util.List">
+        DELETE FROM fs_course_answer_logs
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
 </mapper>

+ 64 - 0
fs-service/src/main/resources/mapper/course/FsCourseRedPacketLogMapper.xml

@@ -41,6 +41,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="amount != null "> and amount = #{amount}</if>
             <if test="status != null "> and `status` = #{status}</if>
             <if test="qwUserId != null  and qwUserId != ''"> and qw_user_id = #{qwUserId}</if>
+            <if test="beginTime != null "> and create_time &gt;= #{beginTime}</if>
+            <if test="endTime != null "> and create_time &lt;= #{endTime}</if>
         </where>
     </select>
 
@@ -216,4 +218,66 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFsCourseRedPacketLogVo"/> where status != 1 and create_time &gt;= #{maps.startTime} and create_time &lt;= #{maps.endTime}
 
     </select>
+
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        insert into fs_course_red_packet_log_1
+        (
+        log_id,
+        course_id,
+        user_id,
+        video_id,
+        company_user_id,
+        company_id,
+        amount,
+        create_time,
+        qw_user_id,
+        out_batch_no,
+        status,
+        update_time,
+        watch_log_id,
+        remark,
+        period_id,
+        result,
+        batch_id,
+        app_id,
+        acc_balance_before,
+        acc_balance_after,
+        mch_id
+        )
+        VALUES
+        <foreach collection="list" item="log" separator=",">
+            (
+            #{log.logId},
+            #{log.courseId},
+            #{log.userId},
+            #{log.videoId},
+            #{log.companyUserId},
+            #{log.companyId},
+            #{log.amount},
+            #{log.createTime},
+            #{log.qwUserId},
+            #{log.outBatchNo},
+            #{log.status},
+            #{log.updateTime},
+            #{log.watchLogId},
+            #{log.remark},
+            #{log.periodId},
+            #{log.result},
+            #{log.batchId},
+            #{log.appId},
+            #{log.accBalanceBefore},
+            #{log.accBalanceAfter},
+            #{log.mchId}
+            )
+        </foreach>
+    </insert>
+
+    <delete id="batchDeleteByIds" parameterType="java.util.List">
+        DELETE FROM fs_course_red_packet_log
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
 </mapper>

+ 64 - 0
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -51,6 +51,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="campPeriodTime != null "> and camp_period_time = #{campPeriodTime}</if>
             <if test="project != null "> and project = #{project}</if>
             <if test="watchType != null "> and watch_type = #{watchType}</if>
+            <if test="beginTime != null "> and create_time &gt;= #{beginTime}</if>
+            <if test="endTime != null "> and create_time &lt;= #{endTime}</if>
         </where>
     </select>
 
@@ -1120,4 +1122,66 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         DATE(o.create_time)
         ) AS t
     </select>
+
+
+
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO fs_course_watch_log_1 (
+        log_id,
+        user_id,
+        video_id,
+        log_type,
+        create_time,
+        update_time,
+        qw_external_contact_id,
+        duration,
+        qw_user_id,
+        company_user_id,
+        company_id,
+        course_id,
+        send_type,
+        reward_type,
+        sop_id,
+        camp_period_time,
+        project,
+        period_id,
+        im_msg_send_detail_id,
+        watch_type
+        )
+        VALUES
+        <foreach collection="list" item="log" separator=",">
+            (
+            #{log.logId},  <!-- 🔴 如果需要保留原id -->
+            #{log.userId},
+            #{log.videoId},
+            #{log.logType},
+            #{log.createTime},
+            #{log.updateTime},
+            #{log.qwExternalContactId},
+            #{log.duration},
+            #{log.qwUserId},
+            #{log.companyUserId},
+            #{log.companyId},
+            #{log.courseId},
+            #{log.sendType},
+            #{log.rewardType},
+            #{log.sopId},
+            #{log.campPeriodTime},
+            #{log.project},
+            #{log.periodId},
+            #{log.imMsgSendDetailId},
+            #{log.watchType}
+            )
+        </foreach>
+    </insert>
+
+
+    <delete id="batchDeleteByIds" parameterType="java.util.List">
+        DELETE FROM fs_course_watch_log
+        WHERE log_id IN
+        <foreach collection="ids" item="id" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
 </mapper>