Explorar o código

删除用户以及相关看课信息

xgb hai 3 semanas
pai
achega
0e9bf8f6c9

+ 68 - 0
fs-company/src/main/java/com/fs/company/controller/delete/DataCleanController.java

@@ -0,0 +1,68 @@
+package com.fs.company.controller.delete;
+
+import com.fs.delete.service.DataCleanService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping("/api/data-clean")
+public class DataCleanController {
+
+    @Autowired
+    private DataCleanService dataCleanService;
+
+//    /**
+//     * 统计需要删除的数据量
+//     */
+//    @GetMapping("/statistics")
+//    public Map<String, Object> statistics() {
+//        log.info("统计需要删除的数据");
+//        Map<String, Object> result = new HashMap<>();
+//
+//        try {
+//            dataCleanService.printStatistics();
+//            result.put("code", 200);
+//            result.put("message", "统计完成,请查看日志");
+//        } catch (Exception e) {
+//            log.error("统计失败", e);
+//            result.put("code", 500);
+//            result.put("message", "统计失败: " + e.getMessage());
+//        }
+//
+//        return result;
+//    }
+//
+//    /**
+//     * 执行数据清理
+//     */
+//    @GetMapping("/execute")
+//    public Map<String, Object> execute() {
+//        log.info("收到数据清理请求");
+//        Map<String, Object> result = new HashMap<>();
+//
+//        try {
+//            // 异步执行
+//            new Thread(() -> {
+//                try {
+//                    dataCleanService.cleanData();
+//                } catch (Exception e) {
+//                    log.error("数据清理执行失败", e);
+//                }
+//            }).start();
+//
+//            result.put("code", 200);
+//            result.put("message", "数据清理任务已启动,请查看日志监控进度");
+//        } catch (Exception e) {
+//            log.error("启动数据清理任务失败", e);
+//            result.put("code", 500);
+//            result.put("message", "启动失败: " + e.getMessage());
+//        }
+//
+//        return result;
+//    }
+}

+ 87 - 0
fs-service/src/main/java/com/fs/delete/mapper/DataCleanMapper.java

@@ -0,0 +1,87 @@
+package com.fs.delete.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+@Mapper
+public interface DataCleanMapper {
+
+    /**
+     * 查询拉黑用户ID
+     */
+    List<Long> selectBlackUserIds(@Param("projectId") Long projectId,
+                                  @Param("statusList") List<Integer> statusList,
+                                  @Param("limit") int limit);
+
+    /**
+     * 查询0观看用户ID
+     */
+    List<Long> selectZeroWatchUserIds(@Param("projectId") Long projectId,
+                                      @Param("dateLimit") String dateLimit,
+                                      @Param("limit") int limit);
+
+    /**
+     * 统计拉黑用户数量
+     */
+    int countBlackUsers(@Param("projectId") Long projectId,
+                        @Param("statusList") List<Integer> statusList);
+
+    /**
+     * 统计0观看用户数量
+     */
+    int countZeroWatchUsers(@Param("projectId") Long projectId,
+                            @Param("dateLimit") String dateLimit);
+
+    /**
+     * 备份用户关系表
+     */
+    int backupUserCompanyUser(@Param("tableSuffix") String tableSuffix,
+                              @Param("projectId") Long projectId,
+                              @Param("userIds") List<Long> userIds);
+
+    /**
+     * 备份看课记录表
+     */
+    int backupCourseWatchLog(@Param("tableSuffix") String tableSuffix,
+                             @Param("projectId") Long projectId,
+                             @Param("userIds") List<Long> userIds);
+
+    /**
+     * 备份答题记录表(关联看课记录)
+     */
+    int backupCourseAnswerLogs(@Param("tableSuffix") String tableSuffix,
+                               @Param("projectId") Long projectId,
+                               @Param("userIds") List<Long> userIds);
+
+    /**
+     * 备份红包记录表(关联看课记录)
+     */
+    int backupCourseRedPacketLog(@Param("tableSuffix") String tableSuffix,
+                                 @Param("projectId") Long projectId,
+                                 @Param("userIds") List<Long> userIds);
+
+    /**
+     * 删除答题记录(关联看课记录)
+     */
+    int deleteAnswerLogs(@Param("projectId") Long projectId,
+                         @Param("userIds") List<Long> userIds);
+
+    /**
+     * 删除红包记录(关联看课记录)
+     */
+    int deleteRedPacketLogs(@Param("projectId") Long projectId,
+                            @Param("userIds") List<Long> userIds);
+
+    /**
+     * 删除看课记录
+     */
+    int deleteWatchLogs(@Param("projectId") Long projectId,
+                        @Param("userIds") List<Long> userIds);
+
+    /**
+     * 删除用户关系
+     */
+    int deleteUserCompanyUser(@Param("projectId") Long projectId,
+                              @Param("userIds") List<Long> userIds);
+}

+ 216 - 0
fs-service/src/main/java/com/fs/delete/service/DataCleanService.java

@@ -0,0 +1,216 @@
+package com.fs.delete.service;
+
+import com.fs.delete.mapper.DataCleanMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.DefaultTransactionDefinition;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+@Service
+public class DataCleanService {
+
+    @Autowired
+    private DataCleanMapper dataCleanMapper;
+
+    @Autowired
+    private DataSourceTransactionManager transactionManager;
+
+    // 每批处理用户数量
+//    private static final int BATCH_SIZE = 1000;
+    private static final int BATCH_SIZE = 200;
+    // 项目ID
+    private static final Long PROJECT_ID = 60L;
+    // 日期限制
+    private static final String DATE_LIMIT = "2026-03-15";
+    // 备份表后缀
+    private static final String BACKUP_SUFFIX = "_20260324_bak";
+    // 拉黑状态列表
+    private static final List<Integer> BLACK_STATUS_LIST = Arrays.asList(0, 2);
+
+    /**
+     * 执行数据清理
+     */
+    public void cleanData() {
+        log.info("========== 开始数据清理任务 ==========");
+        long startTime = System.currentTimeMillis();
+
+        try {
+            // 1. 处理拉黑用户
+            processBlackUsers();
+
+            // 2. 处理0观看用户
+            processZeroWatchUsers();
+
+            long endTime = System.currentTimeMillis();
+            log.info("========== 数据清理任务完成,总耗时: {} 秒 ==========", (endTime - startTime) / 1000);
+
+        } catch (Exception e) {
+            log.error("数据清理任务失败", e);
+            throw new RuntimeException("数据清理任务失败", e);
+        }
+    }
+
+    /**
+     * 处理拉黑用户
+     */
+    private void processBlackUsers() {
+        log.info("开始处理拉黑用户");
+
+        int batchNo = 0;
+        int totalDeleted = 0;
+        int totalCount = dataCleanMapper.countBlackUsers(PROJECT_ID, BLACK_STATUS_LIST);
+        log.info("拉黑用户总数: {}", totalCount);
+
+        while (true) {
+            // 1. 查询一批需要删除的用户ID(1000个)
+            List<Long> userIds = dataCleanMapper.selectBlackUserIds(PROJECT_ID, BLACK_STATUS_LIST, BATCH_SIZE);
+
+            if (userIds.isEmpty()) {
+                log.info("拉黑用户处理完成,共处理 {} 批", batchNo);
+                break;
+            }
+
+            batchNo++;
+            log.info("========== 处理第 {} 批拉黑用户,数量: {} ==========", batchNo, userIds.size());
+
+            // 2. 处理这一批用户(备份+删除)
+            processUserBatch(userIds);
+
+            totalDeleted += userIds.size();
+            log.info("第 {} 批处理完成,累计删除: {}/{}", batchNo, totalDeleted, totalCount);
+            log.info("========================================\n");
+
+//            break;
+        }
+
+        log.info("拉黑用户处理完成,共删除: {} 条", totalDeleted);
+    }
+
+    /**
+     * 处理0观看用户
+     */
+    private void processZeroWatchUsers() {
+        log.info("开始处理0观看用户");
+
+        int batchNo = 0;
+        int totalDeleted = 0;
+//        int totalCount = dataCleanMapper.countZeroWatchUsers(PROJECT_ID, DATE_LIMIT);
+//        log.info("0观看用户总数: {}", totalCount);
+
+        while (true) {
+            // 1. 查询一批需要删除的用户ID(1000个)
+            List<Long> userIds = dataCleanMapper.selectZeroWatchUserIds(PROJECT_ID, DATE_LIMIT, BATCH_SIZE);
+
+            if (userIds.isEmpty()) {
+                log.info("0观看用户处理完成,共处理 {} 批", batchNo);
+                break;
+            }
+
+            batchNo++;
+            log.info("========== 处理第 {} 批0观看用户,数量: {} ==========", batchNo, userIds.size());
+
+            // 2. 处理这一批用户(备份+删除)
+            processUserBatch(userIds);
+
+            totalDeleted += userIds.size();
+            log.info("第 {} 批处理完成,累计删除: {}/{}", batchNo, totalDeleted);
+            log.info("==========================================\n");
+        }
+
+        log.info("0观看用户处理完成,共删除: {} 条", totalDeleted);
+    }
+
+    /**
+     * 处理单批用户(备份+删除)
+     * 删除顺序:先删除答题记录 -> 再删除红包记录 -> 再删除看课记录 -> 最后删除用户关系
+     */
+    private void processUserBatch(List<Long> userIds) {
+        if (userIds == null || userIds.isEmpty()) {
+            return;
+        }
+
+        // 开启新事务,确保这一批用户的数据要么全部成功,要么全部回滚
+        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
+        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+        TransactionStatus transactionStatus = transactionManager.getTransaction(def);
+
+        try {
+            log.info("开始处理 {} 个用户的数据备份和删除", userIds.size());
+
+            // 1. 备份用户相关数据
+            backupUserData(userIds);
+
+            // 2. 删除答题记录(通过关联看课记录表)
+            int answerDeleted = dataCleanMapper.deleteAnswerLogs(PROJECT_ID, userIds);
+            log.info("删除答题记录: {} 条", answerDeleted);
+
+            // 3. 删除红包记录(通过关联看课记录表)
+            int redPacketDeleted = dataCleanMapper.deleteRedPacketLogs(PROJECT_ID, userIds);
+            log.info("删除红包记录: {} 条", redPacketDeleted);
+
+            // 4. 删除看课记录
+            int watchDeleted = dataCleanMapper.deleteWatchLogs(PROJECT_ID, userIds);
+            log.info("删除看课记录: {} 条", watchDeleted);
+
+            // 5. 删除用户关系
+            int userDeleted = dataCleanMapper.deleteUserCompanyUser(PROJECT_ID, userIds);
+            log.info("删除用户关系: {} 条", userDeleted);
+
+            // 提交事务,这一批用户的所有操作完成
+            transactionManager.commit(transactionStatus);
+            log.info("{} 个用户处理完成", userIds.size());
+
+        } catch (Exception e) {
+            // 如果这一批有任何失败,全部回滚
+            transactionManager.rollback(transactionStatus);
+            log.error("处理用户批次失败,将回滚所有操作,用户数量: {}", userIds.size(), e);
+            throw new RuntimeException("处理用户批次失败", e);
+        }
+    }
+
+    /**
+     * 备份用户相关数据
+     */
+    private void backupUserData(List<Long> userIds) {
+        // 1. 备份用户关系表
+        int userCount = dataCleanMapper.backupUserCompanyUser(BACKUP_SUFFIX, PROJECT_ID, userIds);
+        log.debug("备份用户关系: {} 条", userCount);
+
+        // 2. 备份看课记录表
+        int watchCount = dataCleanMapper.backupCourseWatchLog(BACKUP_SUFFIX, PROJECT_ID, userIds);
+        log.debug("备份看课记录: {} 条", watchCount);
+
+        // 3. 备份答题记录表(关联看课记录)
+        int answerCount = dataCleanMapper.backupCourseAnswerLogs(BACKUP_SUFFIX, PROJECT_ID, userIds);
+        log.debug("备份答题记录: {} 条", answerCount);
+
+        // 4. 备份红包记录表(关联看课记录)
+        int redPacketCount = dataCleanMapper.backupCourseRedPacketLog(BACKUP_SUFFIX, PROJECT_ID, userIds);
+        log.debug("备份红包记录: {} 条", redPacketCount);
+
+        log.info("备份完成 - 用户关系: {}, 看课: {}, 答题: {}, 红包: {}",
+                userCount, watchCount, answerCount, redPacketCount);
+    }
+
+    /**
+     * 打印统计数据
+     */
+    public void printStatistics() {
+        log.info("========== 统计数据 ==========");
+
+        int blackCount = dataCleanMapper.countBlackUsers(PROJECT_ID, BLACK_STATUS_LIST);
+        int zeroCount = dataCleanMapper.countZeroWatchUsers(PROJECT_ID, DATE_LIMIT);
+
+        log.info("拉黑用户数量: {}", blackCount);
+        log.info("0观看用户数量: {}", zeroCount);
+        log.info("总计需要删除: {}", (blackCount + zeroCount));
+        log.info("==============================");
+    }
+}

+ 150 - 0
fs-service/src/main/resources/mapper/delete/DataCleanMapper.xml

@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.delete.mapper.DataCleanMapper">
+
+    <!-- 查询拉黑用户ID -->
+    <select id="selectBlackUserIds" resultType="java.lang.Long">
+        SELECT user_id
+        FROM fs_user_company_user
+        WHERE project_id = #{projectId}
+        AND status IN
+        <foreach collection="statusList" item="status" open="(" close=")" separator=",">
+            #{status}
+        </foreach>
+        and create_time &lt; CURDATE()
+        ORDER BY user_id
+        LIMIT #{limit}
+    </select>
+
+    <!-- 查询0观看用户ID -->
+    <select id="selectZeroWatchUserIds" resultType="java.lang.Long">
+        SELECT DISTINCT fucu.user_id
+        FROM fs_user_company_user fucu
+                 LEFT JOIN fs_course_watch_log fcwl
+                           ON fcwl.user_id = fucu.user_id
+                               AND fcwl.project = #{projectId}
+                               AND fcwl.log_type = 2
+        WHERE fucu.project_id = #{projectId}
+          AND fucu.create_time &lt; #{dateLimit}
+          AND fcwl.log_id IS NULL
+        ORDER BY fucu.user_id
+            LIMIT #{limit}
+    </select>
+
+    <!-- 统计拉黑用户数量 -->
+    <select id="countBlackUsers" resultType="int">
+        SELECT COUNT(*)
+        FROM fs_user_company_user
+        WHERE project_id = #{projectId}
+        AND status IN
+        <foreach collection="statusList" item="status" open="(" close=")" separator=",">
+            #{status}
+        </foreach>
+        and create_time &lt; CURDATE()
+    </select>
+
+    <!-- 统计0观看用户数量 -->
+    <select id="countZeroWatchUsers" resultType="int">
+        SELECT COUNT(DISTINCT fucu.user_id)
+        FROM fs_user_company_user fucu
+                 LEFT JOIN fs_course_watch_log fcwl
+                           ON fcwl.user_id = fucu.user_id
+                               AND fcwl.project = #{projectId}
+        WHERE fucu.project_id = #{projectId}
+          AND fucu.create_time &lt; #{dateLimit}
+          AND fcwl.log_id IS NULL
+    </select>
+
+    <!-- 备份用户关系表 -->
+    <insert id="backupUserCompanyUser">
+        INSERT INTO fs_user_company_user${tableSuffix}
+        SELECT * FROM fs_user_company_user
+        WHERE project_id = #{projectId}
+        AND user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </insert>
+
+    <!-- 备份看课记录表 -->
+    <insert id="backupCourseWatchLog">
+        INSERT INTO fs_course_watch_log${tableSuffix}
+        SELECT * FROM fs_course_watch_log
+        WHERE project = #{projectId}
+        AND user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </insert>
+
+    <!-- 备份答题记录表(关联看课记录) -->
+    <insert id="backupCourseAnswerLogs">
+        INSERT INTO fs_course_answer_logs${tableSuffix}
+        SELECT fcal.*
+        FROM fs_course_answer_logs fcal
+        INNER JOIN fs_course_watch_log fcwl ON fcwl.log_id = fcal.watch_log_id
+        WHERE fcwl.project = #{projectId}
+        AND fcwl.user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </insert>
+
+    <!-- 备份红包记录表(关联看课记录) -->
+    <insert id="backupCourseRedPacketLog">
+        INSERT INTO fs_course_red_packet_log${tableSuffix}
+        SELECT fcrpl.*
+        FROM fs_course_red_packet_log fcrpl
+        INNER JOIN fs_course_watch_log fcwl ON fcwl.log_id = fcrpl.watch_log_id
+        WHERE fcwl.project = #{projectId}
+        AND fcwl.user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </insert>
+
+    <!-- 删除答题记录(关联看课记录) -->
+    <delete id="deleteAnswerLogs">
+        DELETE fcal
+        FROM fs_course_answer_logs fcal
+        INNER JOIN fs_course_watch_log fcwl ON fcwl.log_id = fcal.watch_log_id
+        WHERE fcwl.project = #{projectId}
+        AND fcwl.user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </delete>
+
+    <!-- 删除红包记录(关联看课记录) -->
+    <delete id="deleteRedPacketLogs">
+        DELETE fcrpl
+        FROM fs_course_red_packet_log fcrpl
+        INNER JOIN fs_course_watch_log fcwl ON fcwl.log_id = fcrpl.watch_log_id
+        WHERE fcwl.project = #{projectId}
+        AND fcwl.user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </delete>
+
+    <!-- 删除看课记录 -->
+    <delete id="deleteWatchLogs">
+        DELETE FROM fs_course_watch_log
+        WHERE project = #{projectId}
+        AND user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </delete>
+
+    <!-- 删除用户关系 -->
+    <delete id="deleteUserCompanyUser">
+        DELETE FROM fs_user_company_user
+        WHERE project_id = #{projectId}
+        AND user_id IN
+        <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
+            #{userId}
+        </foreach>
+    </delete>
+
+</mapper>