Parcourir la source

优化完课备注

ct il y a 1 semaine
Parent
commit
9f66c34c2f

+ 34 - 7
fs-qw-mq/src/main/java/com/fs/app/mq/RocketMQConsumerCourseFinishService.java

@@ -6,29 +6,56 @@ import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseFinishTempService;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.rocketmq.common.message.MessageExt;
 import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
+import org.apache.rocketmq.spring.annotation.SelectorType;
 import org.apache.rocketmq.spring.core.RocketMQListener;
 import org.springframework.stereotype.Service;
 
 @Slf4j
 @Service
 @AllArgsConstructor
-@RocketMQMessageListener(topic = "course-finish-notes", consumerGroup = "course-finish-group")
-public class RocketMQConsumerCourseFinishService implements RocketMQListener<String> {
+@RocketMQMessageListener(
+        topic = "course-finish-notes", consumerGroup = "course-finish-group",
+        selectorType = SelectorType.TAG,
+        selectorExpression = "TAG_1 || TAG_2 || TAG_3 || TAG_99")
+public class RocketMQConsumerCourseFinishService implements RocketMQListener<MessageExt> {
 
     private final IFsCourseFinishTempService courseFinishTempService;
 
-    @Override
-    public void onMessage(String message) {
+//    @Override
+//    public void onMessage(String message) {
+//
+//
+//        log.info("收到消息1:" + message);
+//
+//        FsCourseWatchLog watchLog = JSON.parseObject(message, FsCourseWatchLog.class);
+//        if (watchLog == null || watchLog.getQwExternalContactId() == null) {
+//            return;
+//        }
+//        courseFinishTempService.finishCourseExtContactIdByRemark(watchLog);
+//
+//    }
 
+    @Override
+    public void onMessage(MessageExt messageExt) {
+        String messageBody = new String(messageExt.getBody());
+        String tags = messageExt.getTags();
 
-        log.info("收到消息1:" + message);
+//        log.info("收到消息1:" + message);
+        log.info("收到消息, Tag: {}, 内容: {}", tags, messageBody);
 
-        FsCourseWatchLog watchLog = JSON.parseObject(message, FsCourseWatchLog.class);
+        FsCourseWatchLog watchLog = JSON.parseObject(messageBody, FsCourseWatchLog.class);
         if (watchLog == null || watchLog.getQwExternalContactId() == null) {
             return;
         }
-        courseFinishTempService.finishCourseExtContactIdByRemark(watchLog);
+        // 根据不同的Tag执行不同的业务逻辑(如果需要)
+        processByTag(tags, watchLog);
+    }
 
+    private void processByTag(String tag, FsCourseWatchLog watchLog) {
+        // 如果需要根据Tag区分处理逻辑,可以在这里实现
+        // 如果处理逻辑完全一样,直接调用统一方法
+        courseFinishTempService.finishCourseExtContactIdByRemark(watchLog);
     }
 }

+ 280 - 26
fs-qw-task/src/main/java/com/fs/app/taskService/impl/AsyncCourseWatchFinishService.java

@@ -10,14 +10,21 @@ import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+
+import org.apache.rocketmq.client.exception.MQClientException;
 import org.apache.rocketmq.client.producer.SendCallback;
 import org.apache.rocketmq.client.producer.SendResult;
+import org.apache.rocketmq.common.message.MessageConst;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.support.MessageBuilder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 import java.util.Optional;
+import java.util.concurrent.*;
 
 @Slf4j
 @Service
@@ -36,37 +43,52 @@ public class AsyncCourseWatchFinishService {
     @Autowired
     RedisCache redisCache;
 
+    // 重试队列和调度器
+    private final BlockingQueue<RetryMessage> retryQueue = new LinkedBlockingQueue<>(10000);
+    private final ScheduledExecutorService retryExecutor = Executors.newSingleThreadScheduledExecutor();
+
+    // 主题映射配置
+    private static final String TOPIC = "course-finish-notes";
+    private static final Integer DEFAULT_SERVER_NUM = 99;
+
+    @PostConstruct
+    public void init() {
+        // 启动重试任务,每5秒处理一次重试队列
+        retryExecutor.scheduleWithFixedDelay(this::processRetryQueue, 10, 5, TimeUnit.SECONDS);
+        log.info("AsyncCourseWatchFinishService 重试队列处理器已启动");
+    }
+
     /**
     * 异步处理完课打备注的
     */
     @Async("scheduledExecutorService")
     public void executeCourseWatchFinish(FsCourseWatchLog finishLog) {
-
-        FsCourseWatchLog watchLog = new FsCourseWatchLog();
-        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
-        watchLog.setFinishTime(finishLog.getFinishTime());
-        watchLog.setQwUserId(finishLog.getQwUserId());
-
-
-        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
-        if (qwUserByRedis == null) {
-            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
-            return;
-        }
-
-        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
-
-        if (qwCompany == null) {
-            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(),watchLog);
-            return;
-        }
-
-        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
-            @Override public void onSuccess(SendResult sendResult) {
-                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
-            }  // 空实现
-            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
-        });
+//        原代码
+//        FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
+//        watchLog.setFinishTime(finishLog.getFinishTime());
+//        watchLog.setQwUserId(finishLog.getQwUserId());
+//
+//
+//        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
+//        if (qwUserByRedis == null) {
+//            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
+//            return;
+//        }
+//
+//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
+//
+//        if (qwCompany == null) {
+//            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(),watchLog);
+//            return;
+//        }
+//
+//        rocketMQTemplate.asyncSend("course-finish-notes", JSON.toJSONString(finishLog),     new SendCallback() {
+//            @Override public void onSuccess(SendResult sendResult) {
+//                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+//            }  // 空实现
+//            @Override public void onException(Throwable e) {log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());}          // 空实现
+//        });
 
 
 //        // 定义默认值
@@ -102,6 +124,238 @@ public class AsyncCourseWatchFinishService {
 //        }
 
 
+        // 1. 数据验证和准备
+        ValidationResult validationResult = validateAndPrepareData(finishLog);
+        if (!validationResult.isValid()) {
+            return;
+        }
+
+
+        //  2. 发送消息(使用Tag区分)
+        sendWithFlowControl(finishLog, validationResult, 0);
+
+    }
+
+    /**
+     * 数据验证和准备
+     */
+    private ValidationResult validateAndPrepareData(FsCourseWatchLog finishLog) {
+        // 准备日志对象
+        FsCourseWatchLog watchLog = new FsCourseWatchLog();
+        watchLog.setQwExternalContactId(finishLog.getQwExternalContactId());
+        watchLog.setFinishTime(finishLog.getFinishTime());
+        watchLog.setQwUserId(finishLog.getQwUserId());
+
+        // 验证企微用户信息
+        QwUser qwUserByRedis = qwExternalContactService.getQwUserByRedisForId(String.valueOf(finishLog.getQwUserId()));
+        if (qwUserByRedis == null) {
+            log.error("无企微员工信息 {} 跳过处理。", finishLog.getQwUserId());
+            return ValidationResult.invalid();
+        }
+
+        // 验证企业主体
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUserByRedis.getCorpId());
+        if (qwCompany == null) {
+            log.error("企业微信主体为空 {} 跳过处理。{} ", qwUserByRedis.getCorpId(), watchLog);
+            return ValidationResult.invalid();
+        }
+
+        return ValidationResult.valid(watchLog, qwUserByRedis, qwCompany);
+    }
+
+    /**
+     * 获取目标Tag(替换原来的获取Topic方法)
+     */
+    private String getTargetTag(QwCompany qwCompany) {
+        Integer companyServerNum = Optional.ofNullable(qwCompany.getCompanyServerNum())
+                .orElse(DEFAULT_SERVER_NUM);
+        return "TAG_" + companyServerNum; // 生成对应的Tag
+    }
+
+    /**
+     * 带流控处理的消息发送
+     */
+    private void sendWithFlowControl(FsCourseWatchLog finishLog,
+                                     ValidationResult validationResult, int retryCount) {
+        if (retryCount >= 3) {
+            log.warn("消息重试超过最大次数,转入重试队列: topic={}, qwUserId={}",
+                    TOPIC, finishLog.getQwUserId());
+            offerToRetryQueue(finishLog, validationResult);
+            return;
+        }
+
+        // 获取Tag
+        String tag = getTargetTag(validationResult.getQwCompany());
+        String messageBody = JSON.toJSONString(finishLog);
+
+        // 构建带Tag的消息
+        org.springframework.messaging.Message<String> message = MessageBuilder
+                .withPayload(messageBody)
+                .setHeader(MessageConst.PROPERTY_TAGS, tag)
+                .build();
+
+        rocketMQTemplate.asyncSend(TOPIC, message, new SendCallback() {
+            @Override
+            public void onSuccess(SendResult sendResult) {
+//                log.info("推送完课打备注成功1:{},{}",JSON.toJSONString(finishLog),sendResult.getMsgId());
+                log.info("推送完课打备注成功1: tag={},finishLog={}, msgId={}",
+                        tag,JSON.toJSONString(finishLog), sendResult.getMsgId());
+            }
+
+            @Override
+            public void onException(Throwable e) {
+                if (isFlowControlException(e)) {
+                    // 流控异常处理
+                    handleFlowControlRetry(TOPIC, finishLog, validationResult, retryCount, e);
+                    log.error("推送完课打备注失败1流控异常:tag={},finishLog={},e={}",tag,JSON.toJSONString(finishLog),e.getMessage());
+                } else {
+                    // 其他异常
+//                    log.error("推送完课打备注失败1:{},{}",JSON.toJSONString(finishLog),e.getMessage());
+                    log.error("推送完课打备注失败: tag={}, finishLog={}, error={}",
+                            tag, JSON.toJSONString(finishLog), e.getMessage());
+                }
+            }
+        });
+    }
+
+    /**
+     * 放入重试队列
+     */
+    private void offerToRetryQueue(FsCourseWatchLog finishLog,
+                                   ValidationResult validationResult) {
+        RetryMessage retryMessage = new RetryMessage(finishLog, validationResult);
+        boolean offered = retryQueue.offer(retryMessage);
+        if (offered) {
+            log.info("消息已加入重试队列: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+        } else {
+            log.error("重试队列已满,消息可能丢失: topic={}, qwUserId={}", TOPIC, finishLog.getQwUserId());
+            // 这里可以接入告警系统
+        }
+    }
+
+    /**
+     * 处理重试队列
+     */
+    private void processRetryQueue() {
+        try {
+            int processedCount = 0;
+            RetryMessage retryMessage;
+
+            while (processedCount < 100 && (retryMessage = retryQueue.poll()) != null) {
+                try {
+                    // 重新发送消息
+                    sendWithFlowControl(retryMessage.getFinishLog(),
+                            retryMessage.getValidationResult(), 0);
+                    processedCount++;
+
+                    Thread.sleep(10);
+                } catch (Exception e) {
+                    log.error("重试队列处理失败: {}", e.getMessage());
+                    offerToRetryQueue(retryMessage.getFinishLog(), retryMessage.getValidationResult());
+                }
+            }
+
+            if (processedCount > 0) {
+                log.debug("重试队列处理完成,本次处理数量: {}", processedCount);
+            }
+        } catch (Exception e) {
+            log.error("处理重试队列异常: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 判断是否为流控异常
+     */
+    private boolean isFlowControlException(Throwable e) {
+        if (e instanceof MQClientException) {
+            return ((MQClientException) e).getResponseCode() == 215;
+        }
+        // 检查异常链
+        Throwable cause = e.getCause();
+        if (cause instanceof MQClientException) {
+            return ((MQClientException) cause).getResponseCode() == 215;
+        }
+        return false;
+    }
+
+    /**
+     * 流控重试处理
+     */
+    private void handleFlowControlRetry(String topic, FsCourseWatchLog finishLog,
+                                        ValidationResult validationResult, int retryCount, Throwable e) {
+        long backoffTime = calculateBackoffTime(retryCount);
+        log.warn("流控触发,{}ms后第{}次重试: topic={}, qwUserId={}",
+                backoffTime, retryCount + 1, topic, finishLog.getQwUserId());
+
+        // 使用 ScheduledExecutorService 进行延迟执行
+        retryExecutor.schedule(() -> {
+            try {
+                sendWithFlowControl(finishLog, validationResult, retryCount + 1);
+            } catch (Exception ex) {
+                log.error("延迟重试执行异常: {}", ex.getMessage(), ex);
+            }
+        }, backoffTime, TimeUnit.MILLISECONDS);
+    }
+    /**
+     * 计算退避时间(指数退避)
+     */
+    private long calculateBackoffTime(int retryCount) {
+        return Math.min(1000 * (long) Math.pow(2, retryCount), 10000); // 最大10秒
+    }
+
+    @PreDestroy
+    public void destroy() {
+        retryExecutor.shutdown();
+        try {
+            if (!retryExecutor.awaitTermination(10, TimeUnit.SECONDS)) {
+                retryExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            retryExecutor.shutdownNow();
+            Thread.currentThread().interrupt();
+        }
+        log.info("AsyncCourseWatchFinishService 已关闭");
+    }
+
+    // 内部辅助类
+    private static class ValidationResult {
+        private final boolean valid;
+        private final FsCourseWatchLog watchLog;
+        private final QwUser qwUser;
+        private final QwCompany qwCompany;
+
+        public ValidationResult(boolean valid, FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            this.valid = valid;
+            this.watchLog = watchLog;
+            this.qwUser = qwUser;
+            this.qwCompany = qwCompany;
+        }
+
+        public static ValidationResult valid(FsCourseWatchLog watchLog, QwUser qwUser, QwCompany qwCompany) {
+            return new ValidationResult(true, watchLog, qwUser, qwCompany);
+        }
+
+        public static ValidationResult invalid() {
+            return new ValidationResult(false, null, null, null);
+        }
+
+        public boolean isValid() { return valid; }
+        public FsCourseWatchLog getWatchLog() { return watchLog; }
+        public QwUser getQwUser() { return qwUser; }
+        public QwCompany getQwCompany() { return qwCompany; }
+    }
+
+    private static class RetryMessage {
+        private final FsCourseWatchLog finishLog;
+        private final ValidationResult validationResult;
+
+        public RetryMessage(FsCourseWatchLog finishLog, ValidationResult validationResult) {
+            this.finishLog = finishLog;
+            this.validationResult = validationResult;
+        }
+
+        public FsCourseWatchLog getFinishLog() { return finishLog; }
+        public ValidationResult getValidationResult() { return validationResult; }
     }
 
 }

+ 102 - 16
fs-service/src/main/java/com/fs/course/service/impl/FsCourseFinishTempServiceImpl.java

@@ -28,6 +28,7 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.voice.utils.StringUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDate;
@@ -209,9 +210,10 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     /**
      * 完课用户 打备注
      */
+    @Async
     @Override
     public void finishCourseExtContactIdByRemark(FsCourseWatchLog watchLog) {
-
+        addRandomDelay();
         Long qwExternalContactId = watchLog.getQwExternalContactId();
 
         Date finishTime = watchLog.getFinishTime();
@@ -300,7 +302,8 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
             remarkParam.setUserid(externalContact.getUserId());
             remarkParam.setExternal_userid(externalContact.getExternalUserId());
 
-            for (int attempt = 1; attempt <= 2; attempt++) {
+//            for (int attempt = 1; attempt <= 2; attempt++) {
+            for (int attempt = 1; attempt <= 3; attempt++) { // 增加到3次重试
                 try {
                     QwExternalContactRemarkResult qwResult = qwApiService.externalcontactRemark(remarkParam, externalContact.getCorpId());
                     if (qwResult.getErrcode() == 0) {
@@ -314,23 +317,46 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
 
                         break;
                     } else {
-                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
-                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
-                            remarkRty.setQwUserId(externalContact.getUserId());
-                            remarkRty.setCorpId(externalContact.getCorpId());
-                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
-                            remarkRty.setExternalId(externalContact.getId());
-                            remarkRty.setRemark(newRemark);
-                            remarkRty.setCreateTime(new Date());
-                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
-
+//                        if (attempt==2 && (qwResult.getErrcode() == 45033 || qwResult.getErrcode()== -1 || qwResult.getErrcode()== 60020 )) {
+//                            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+//                            remarkRty.setQwUserId(externalContact.getUserId());
+//                            remarkRty.setCorpId(externalContact.getCorpId());
+//                            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+//                            remarkRty.setExternalId(externalContact.getId());
+//                            remarkRty.setRemark(newRemark);
+//                            remarkRty.setCreateTime(new Date());
+//                            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+//
+//                        }
+//
+//                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
+
+                        // 根据错误码智能处理
+                        if (isRateLimitError(qwResult.getErrcode())) {
+                            // 保存到重试表
+                            saveToRetryTable(externalContact, newRemark);
+
+                            // 智能延迟
+                            if (attempt < 3) {
+                                smartDelayByErrorCode(qwResult.getErrcode(), attempt);
+                                continue; // 继续重试
+                            }
                         }
-
-                        log.error("完课加备注失败:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|原因" + qwResult.getErrmsg());
-
+                        log.error("完课加备注失败:{}|{}|{}|{}|{}|原因{}",
+                                externalContact.getName(), externalContact.getExternalUserId(),
+                                externalContact.getCorpId(), externalContact.getUserId(),
+                                newRemark, qwResult.getErrmsg());
                     }
                 } catch (Exception e) {
-                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+//                    log.error("添加备注异常 [尝试第 " + attempt + " 次]:" + externalContact.getName() + "|" + externalContact.getExternalUserId() + "|" + externalContact.getCorpId() + "|" + externalContact.getUserId() + "|" + newRemark + "|" + e.getMessage());
+                    log.error("添加备注异常 [尝试第 {} 次]:{}|{}|{}|{}|{}|{}",
+                            attempt, externalContact.getName(), externalContact.getExternalUserId(),
+                            externalContact.getCorpId(), externalContact.getUserId(),
+                            newRemark, e.getMessage());
+
+                    if (attempt < 3) {
+                        smartDelayByErrorCode(-1, attempt);
+                    }
                 }
 
                 // 若不是最后一次尝试,则等待3秒再试
@@ -411,4 +437,64 @@ public class FsCourseFinishTempServiceImpl implements IFsCourseFinishTempService
     public List<FsCourseFinishTempListVO> selectFsCourseFinishTempListVO(FsCourseFinishTemp fsCourseFinishTemp) {
         return fsCourseFinishTempMapper.selectFsCourseFinishTempListVO(fsCourseFinishTemp);
     }
+
+    /**
+     * 添加随机延迟,分散请求峰值
+     */
+    private void addRandomDelay() {
+        try {
+            // 随机延迟100-500ms,避免同时大量请求
+            Thread.sleep(100 + (long)(Math.random() * 400));
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("延迟被中断", e);
+        }
+    }
+
+    /**
+     * 根据错误码智能延迟
+     */
+    private void smartDelayByErrorCode(Integer errcode, int attempt) {
+        try {
+            long delayMs;
+            if (errcode == 45033) { // 并发限制
+                delayMs = 5000 + attempt * 2000L; // 递增延迟
+            } else if (errcode == 60020) { // 频率限制
+                delayMs = 3000 + attempt * 1000L;
+            } else if (errcode == -1) { // 系统繁忙
+                delayMs = 2000;
+            } else {
+                delayMs = 1000; // 默认延迟
+            }
+            log.info("因错误码 {} 延迟 {}ms", errcode, delayMs);
+            Thread.sleep(delayMs);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    /**
+     * 判断是否为频率限制类错误
+     */
+    private boolean isRateLimitError(Integer errcode) {
+        return errcode == 45033 || errcode == 60020 || errcode == -1;
+    }
+
+    /**
+     * 保存到重试表
+     */
+    private void saveToRetryTable(QwExternalContact externalContact, String remark) {
+        try {
+            QwCourseFinishRemarkRty remarkRty=new QwCourseFinishRemarkRty();
+            remarkRty.setQwUserId(externalContact.getUserId());
+            remarkRty.setCorpId(externalContact.getCorpId());
+            remarkRty.setExternalUserId(externalContact.getExternalUserId());
+            remarkRty.setExternalId(externalContact.getId());
+            remarkRty.setRemark(remark);
+            remarkRty.setCreateTime(new Date());
+            finishRemarkRtyService.insertOrUpdateQwCourseFinishRemarkRty(remarkRty);
+        } catch (Exception e) {
+            log.error("保存重试记录失败", e);
+        }
+    }
 }