Переглянути джерело

修改了AIEvaluationService的rewritePerSentence方法,
增加了CAS机制
修改了EvaluationController的evaluate方法,从假异步变成了真异步

Jiang Pengyu 6 місяців тому
батько
коміт
e0b96e6e98

+ 13 - 4
src/main/java/com/njuzr/eaibackend/controller/EvaluationController.java

@@ -4,6 +4,7 @@ import com.njuzr.eaibackend.service.AIEvaluationService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -27,13 +28,21 @@ public class EvaluationController {
      * @param studentId 学生ID
      * @return
      */
-    @PostMapping("/overall")
-    public MyResponse rewrite(
+      @PostMapping("/overall")
+    public CompletableFuture<MyResponse>  rewrite(
             @RequestParam Long assignmentId,
             @RequestParam Long studentId
     ) throws ExecutionException, InterruptedException {
-        return MyResponse.success(aiEvaluationService.evaluationAsync(assignmentId, studentId).get());
-    }
+        return aiEvaluationService.evaluationAsync(assignmentId, studentId)
+            .thenApply(result -> MyResponse.success(result))
+            .exceptionally(ex -> MyResponse.error(500, "AI请求失败:" + ex.getMessage()));
+    }  //真正的异步,之前的假异步
+    // public MyResponse rewrite(
+    //         @RequestParam Long assignmentId,
+    //         @RequestParam Long studentId
+    // ) throws ExecutionException, InterruptedException {
+    //     return MyResponse.success(aiEvaluationService.evaluationAsync(assignmentId, studentId).get());
+    // }
 
     /**
      * 对作文进行逐句批改

+ 130 - 55
src/main/java/com/njuzr/eaibackend/service/AIEvaluationService.java

@@ -251,9 +251,10 @@ public class AIEvaluationService {
         // 没有智能评价记录时,进行重试,等待整体智能批改将记录写入数据库
         // 使用编程式事务在新事务中查询,避免REPEATABLE READ隔离级别导致看不到其他事务提交的数据
         int retryTime = 0;
+        Evaluation evaluationJudge = null;
         while (retryTime < 3) {
             final int currentVersion = engagement.getVersion();
-            Evaluation evaluationJudge = requiresNewTransactionTemplate.execute(status ->
+            evaluationJudge = requiresNewTransactionTemplate.execute(status ->
                 evaluationMapperServiceImpl.getByVersion(assignmentId, studentId, currentVersion)
             );
             log.info("rewritePerSentence find evaluation record, assignmentId: {}, studentId: {}, version: {}", assignmentId, studentId, engagement.getVersion());
@@ -263,78 +264,152 @@ public class AIEvaluationService {
                     Thread.sleep(1500);
                     retryTime++;
                 } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
+                    Thread.currentThread().interrupt();
+                    throw MyException.create(HttpStatus.BAD_REQUEST, "处理被中断: " + e.getMessage());
                 }
             } else {
-                if(evaluationJudge.getSentence() != 0){
-                    throw MyException.create(HttpStatus.BAD_REQUEST, "作业已进行过逐句智能评价");
-                } else {
-                    break;
-                }
+                break;
             }
-        }
+        }   
         if(retryTime >= 3) {
             log.info("rewritePerSentence failed, assignmentId: {}, studentId: {}, reason: over max retry times(3)", assignmentId, studentId);
             throw MyException.create(HttpStatus.BAD_REQUEST, "逐句批改失败,超过重试次数,请重新发起请求");
         }
 
-        String content = engagement.getTextContent();
-        String[] sentences = getSentenceList(content);
-        log.info("rewritePerSentence 开始处理, assignmentId: {}, studentId: {}, 句子数量: {}", assignmentId, studentId, sentences.length);
-        
-        // 使用线程安全的集合,避免多线程并发写入导致数据丢失
-        List<SentenceEvaluation> sentenceEvaluationsList = Collections.synchronizedList(new ArrayList<>());
-        @SuppressWarnings("unchecked")
-        CompletableFuture<SentenceEvaluation>[] futures = new CompletableFuture[sentences.length];
+        try {
+            String content = engagement.getTextContent();
+            String[] sentences = getSentenceList(content);
+
+            log.info("rewritePerSentence start, assignmentId: {}, studentId: {}, sentenceCount: {}, version: {}",
+                    assignmentId, studentId, sentences.length, engagement.getVersion());
+
+            List<SentenceEvaluation> sentenceEvaluationsList = new ArrayList<>();
+            @SuppressWarnings("unchecked")
+            CompletableFuture<SentenceEvaluation>[] futures = new CompletableFuture[sentences.length];
+
+            for (int i = 0; i < sentences.length; i++) {
+                final int sentenceNo = i;
+                final String sentence = sentences[i];
+
+                futures[i] = CompletableFuture.supplyAsync(
+                        () -> processSingleSentenceWithRetry(
+                                assignmentId, studentId,
+                                content, assignment.getDescription(),
+                                sentence, sentenceNo,
+                                engagement.getVersion()
+                        ),
+                        aiPerSentenceExecutor
+                );
+            }
 
-        for (int i = 0; i < sentences.length; i++) {
-            final int sentenceNo = i;
-            final String sentence = sentences[i];
-            futures[i] = CompletableFuture.supplyAsync(() ->
-                    processSingleSentenceWithRetry(assignmentId, studentId, content, assignment.getDescription(), sentence, sentenceNo, engagement.getVersion()),
-                    aiPerSentenceExecutor);
-        }
+            try {
+                CompletableFuture.allOf(futures).get(80, TimeUnit.SECONDS);
 
-        // 等待所有任务完成
-        try {
-            CompletableFuture.allOf(futures).get(60, TimeUnit.SECONDS);
-            // 收集所有结果
-            for (CompletableFuture<SentenceEvaluation> future : futures) {
-                SentenceEvaluation result = future.get();
-                if (result != null) {
-                    sentenceEvaluationsList.add(result);
+                for (CompletableFuture<SentenceEvaluation> future : futures) {
+                    SentenceEvaluation result = future.get();
+                    if (result != null) {
+                        sentenceEvaluationsList.add(result);
+                    }
                 }
+            } catch (TimeoutException e) {
+                log.warn("rewritePerSentence timeout, assignmentId: {}, studentId: {}", assignmentId, studentId);
+                Arrays.stream(futures).forEach(f -> f.cancel(true));
+                throw MyException.create(HttpStatus.BAD_REQUEST, "处理超时,请重试");
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw MyException.create(HttpStatus.BAD_REQUEST, "处理被中断: " + e.getMessage());
+            } catch (Exception e) {
+                throw MyException.create(HttpStatus.BAD_REQUEST, "处理失败: " + e.getMessage());
             }
-            log.info("rewritePerSentence allOf success, assignmentId: {}, studentId: {}, version: {}, sentenceCount: {}, resultCount: {}", 
-                    assignmentId, studentId, engagement.getVersion(), sentences.length, sentenceEvaluationsList.size());
-        } catch (TimeoutException e) {
-            log.warn("rewritePerSentence failed, assignmentId: {}, studentId: {}, reason: timeout", assignmentId, studentId);
-            Arrays.stream(futures).forEach(f -> f.cancel(true));
-            throw MyException.create(HttpStatus.BAD_REQUEST, "处理超时,请重试");
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw MyException.create(HttpStatus.BAD_REQUEST, "处理被中断: " + e.getMessage());
-        } catch (Exception e){
-            throw MyException.create(HttpStatus.BAD_REQUEST, "处理失败: " + e.getMessage());
-        }
 
-        // 所有任务完成后批量插入数据库
-        log.info("rewritePerSentence准备保存, listSize: {}, list: {}", sentenceEvaluationsList.size(), sentenceEvaluationsList);
-        if(!sentenceEvaluationsList.isEmpty()) {
-            sentenceEvaluationMapperServiceImpl.saveBatch(sentenceEvaluationsList);
-            log.info("rewritePerSentence saveBatch完成, assignmentId: {}, studentId: {}", assignmentId, studentId);
-        } else {
-            log.warn("rewritePerSentence 列表为空,跳过保存! assignmentId: {}, studentId: {}", assignmentId, studentId);
-        }
+            // 3) 批量保存逐句结果
+            if (!sentenceEvaluationsList.isEmpty()) {
+                sentenceEvaluationMapperServiceImpl.saveBatch(sentenceEvaluationsList);
+            }
 
+            // 4) 完成态:2 -> 1(只允许从处理中改成完成,避免误覆盖)
+            evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
+                    .set("sentence", 1)
+                    .eq("student_id", studentId)
+                    .eq("assignment_id", assignmentId)
+                    .eq("version", engagement.getVersion())
+                    .eq("sentence", 2)
+            );
+
+            log.info("rewritePerSentence success, assignmentId: {}, studentId: {}, cost: {}",
+                    assignmentId, studentId, sw.formatTime());
+
+            return sentenceEvaluationsList;
+
+    } catch (RuntimeException ex) {
+        // 5) 出现任何异常:释放处理中状态(2 -> 0),允许重试,避免永久卡死
         evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
-                .set("sentence", 1)
+                .set("sentence", 0)
                 .eq("student_id", studentId)
                 .eq("assignment_id", assignmentId)
-                .eq("version", engagement.getVersion()));
+                .eq("version", engagement.getVersion())
+                .eq("sentence", 2)
+        );
+        throw ex;
+    }
+        // String content = engagement.getTextContent();
+        // String[] sentences = getSentenceList(content);
+        // log.info("rewritePerSentence 开始处理, assignmentId: {}, studentId: {}, 句子数量: {}", assignmentId, studentId, sentences.length);
+        
+        // // 使用线程安全的集合,避免多线程并发写入导致数据丢失
+        // List<SentenceEvaluation> sentenceEvaluationsList = Collections.synchronizedList(new ArrayList<>());
+        // @SuppressWarnings("unchecked")
+        // CompletableFuture<SentenceEvaluation>[] futures = new CompletableFuture[sentences.length];
+
+        // for (int i = 0; i < sentences.length; i++) {
+        //     final int sentenceNo = i;
+        //     final String sentence = sentences[i];
+        //     futures[i] = CompletableFuture.supplyAsync(() ->
+        //             processSingleSentenceWithRetry(assignmentId, studentId, content, assignment.getDescription(), sentence, sentenceNo, engagement.getVersion()),
+        //             aiPerSentenceExecutor);
+        // }
 
-        log.info("rewritePerSentence cost: {}", sw.formatTime());
-        return sentenceEvaluationsList;
+        // // 等待所有任务完成
+        // try {
+        //     CompletableFuture.allOf(futures).get(60, TimeUnit.SECONDS);
+        //     // 收集所有结果
+        //     for (CompletableFuture<SentenceEvaluation> future : futures) {
+        //         SentenceEvaluation result = future.get();
+        //         if (result != null) {
+        //             sentenceEvaluationsList.add(result);
+        //         }
+        //     }
+        //     log.info("rewritePerSentence allOf success, assignmentId: {}, studentId: {}, version: {}, sentenceCount: {}, resultCount: {}", 
+        //             assignmentId, studentId, engagement.getVersion(), sentences.length, sentenceEvaluationsList.size());
+        // } catch (TimeoutException e) {
+        //     log.warn("rewritePerSentence failed, assignmentId: {}, studentId: {}, reason: timeout", assignmentId, studentId);
+        //     Arrays.stream(futures).forEach(f -> f.cancel(true));
+        //     throw MyException.create(HttpStatus.BAD_REQUEST, "处理超时,请重试");
+        // } catch (InterruptedException e) {
+        //     Thread.currentThread().interrupt();
+        //     throw MyException.create(HttpStatus.BAD_REQUEST, "处理被中断: " + e.getMessage());
+        // } catch (Exception e){
+        //     throw MyException.create(HttpStatus.BAD_REQUEST, "处理失败: " + e.getMessage());
+        // }
+
+        // // 所有任务完成后批量插入数据库
+        // log.info("rewritePerSentence准备保存, listSize: {}, list: {}", sentenceEvaluationsList.size(), sentenceEvaluationsList);
+        // if(!sentenceEvaluationsList.isEmpty()) {
+        //     sentenceEvaluationMapperServiceImpl.saveBatch(sentenceEvaluationsList);
+        //     log.info("rewritePerSentence saveBatch完成, assignmentId: {}, studentId: {}", assignmentId, studentId);
+        // } else {
+        //     log.warn("rewritePerSentence 列表为空,跳过保存! assignmentId: {}, studentId: {}", assignmentId, studentId);
+        // }
+
+        // evaluationMapperServiceImpl.update(new UpdateWrapper<Evaluation>()
+        //         .set("sentence", 1)
+        //         .eq("student_id", studentId)
+        //         .eq("assignment_id", assignmentId)
+        //         .eq("version", engagement.getVersion()));
+
+        // log.info("rewritePerSentence cost: {}", sw.formatTime());
+        // return sentenceEvaluationsList;
+        
     }
     //原来的getSentenceList方法存在问题,无法正确处理多种标点符号和不同语言的句子分割,这里改用BreakIterator来实现更准确的句子分割
     private static String[] getSentenceList(String content) {