Browse Source

Merge branch 'lll_fix_deepseek' of FanYanPeng/EAI-Backend into refactor

JiangPengYu 4 months ago
parent
commit
29c4d18ea5

+ 1 - 1
application.yaml

@@ -62,7 +62,7 @@ jwt:
 
 # DeepSeek AI 配置
 deepseek:
-  api-key: ${DEEPSEEK_API_KEY:sk-994409a3b69648de8171be819274fc98}
+  api-key: sk-994409a3b69648de8171be819274fc98
   base-url: https://api.deepseek.com/v1
   model: deepseek-chat
   timeout: 60000

+ 9 - 0
src/main/java/com/njuzr/eaibackend/mapper/StudentAssignmentMapper.java

@@ -31,6 +31,15 @@ public interface StudentAssignmentMapper extends BaseMapper<Engagement> {
 
     void insertHistory(StudentAssignmentHistory history);
 
+    /**
+     * 将 student_assignment 当前行快照写入 student_assignment_history(若该 version 尚不存在)。
+     * 用于保证每次 version 变化后都能落一条历史记录(幂等)。
+     *
+     * @return 实际插入行数(0/1)
+     */
+    int insertHistorySnapshot(@Param("assignmentId") Long assignmentId,
+                              @Param("studentId") Long studentId);
+
     List<StudentAssignmentHistory> findHistoryByStudentIdAndAssignmentId(Long studentId, Long assignmentId);
 
     StudentAssignmentHistory findHistoryByAssignmentIdAndStudentIdAndVersion(

+ 13 - 2
src/main/java/com/njuzr/eaibackend/service/AIDialogueService.java

@@ -324,8 +324,19 @@ public class AIDialogueService {
                 },
                 error -> {
                     // 错误回调
-                    log.error("【Service层】DeepSeek 流式请求失败: {}", error.getMessage());
-                    emitter.completeWithError(error);
+                    log.error("【Service层】DeepSeek 流式请求失败: {}", error.getMessage(), error);
+                    // SSE 场景:completeWithError 可能触发容器异常派发,进入全局异常处理器并导致 406(No acceptable representation)
+                    // 改为发送可读错误事件后正常 complete,前端可稳定展示错误信息
+                    try {
+                        String msg = (error instanceof MyException)
+                                ? ((MyException) error).getMessage()
+                                : ("AI 请求失败:" + (error.getMessage() == null ? "unknown" : error.getMessage()));
+                        emitter.send(SseEmitter.event().name("error").data(msg));
+                    } catch (Exception sendErr) {
+                        log.warn("【Service层】发送 SSE error 事件失败: {}", sendErr.getMessage());
+                    } finally {
+                        emitter.complete();
+                    }
                 }
             );
             

+ 4 - 3
src/main/java/com/njuzr/eaibackend/service/impl/AssignmentServiceImpl.java

@@ -230,7 +230,7 @@ public class AssignmentServiceImpl implements AssignmentService {
         Engagement engagement = new Engagement();
         engagement.setStudentId(studentId);
         engagement.setAssignmentId(assignmentId);
-        engagement.setVersion(1); // 设置这是version
+        engagement.setVersion(0); // 初始版本从0开始,首次提交后变为1
         engagement.setStatus(AssignmentCompletionStatus.NOT_SUBMITTED); // 已经进入到页面编辑,但是还没有提交
 
         Assignment assignment = assignmentMapper.selectById(assignmentId);
@@ -396,7 +396,8 @@ public class AssignmentServiceImpl implements AssignmentService {
             if (code == 0) {
                 throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
             }
-            recordHistorySnapshot(engagement);
+            // 基于主表当前行落一条快照(幂等),避免漏写/并发导致 history 缺版本
+            studentAssignmentMapper.insertHistorySnapshot(assignmentId, studentId);
         } catch (Exception e) {
             log.error("更新异常: {}", e.getMessage(), e);
             throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
@@ -429,7 +430,7 @@ public class AssignmentServiceImpl implements AssignmentService {
             int code = studentAssignmentMapper.updateById(engagement);
             if (code == 0)
                 throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
-            recordHistorySnapshot(engagement);
+            studentAssignmentMapper.insertHistorySnapshot(assignmentId, studentId);
         } catch (Exception e) {
             log.error(e.getMessage());
             throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");

+ 12 - 2
src/main/java/com/njuzr/eaibackend/service/impl/DeepSeekServiceImpl.java

@@ -6,10 +6,12 @@ import com.njuzr.eaibackend.dto.deepseek.DeepSeekMessage;
 import com.njuzr.eaibackend.dto.deepseek.DeepSeekRequest;
 import com.njuzr.eaibackend.dto.deepseek.DeepSeekResponse;
 import com.njuzr.eaibackend.dto.deepseek.DeepSeekStreamResponse;
+import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.service.DeepSeekService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.client.SimpleClientHttpRequestFactory;
 import org.springframework.stereotype.Service;
@@ -56,7 +58,9 @@ public class DeepSeekServiceImpl implements DeepSeekService {
             RestTemplate restTemplate = createRestTemplate();
             org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
             headers.setContentType(MediaType.APPLICATION_JSON);
-            headers.setBearerAuth(deepSeekConfig.getApiKey());
+            String apiKey = deepSeekConfig.getApiKey();
+            if (apiKey != null) apiKey = apiKey.trim();
+            headers.setBearerAuth(apiKey);
 
             org.springframework.http.HttpEntity<DeepSeekRequest> entity = 
                     new org.springframework.http.HttpEntity<>(request, headers);
@@ -167,6 +171,12 @@ public class DeepSeekServiceImpl implements DeepSeekService {
         new Thread(() -> {
             HttpURLConnection connection = null;
             try {
+                String apiKey = deepSeekConfig.getApiKey();
+                if (apiKey != null) apiKey = apiKey.trim();
+                if (apiKey == null || apiKey.isBlank()) {
+                    throw MyException.create(HttpStatus.UNAUTHORIZED, "DeepSeek API key 未配置(deepseek.api-key / DEEPSEEK_API_KEY)");
+                }
+
                 DeepSeekRequest request = DeepSeekRequest.builder()
                         .model(deepSeekConfig.getModel())
                         .messages(messages)
@@ -180,7 +190,7 @@ public class DeepSeekServiceImpl implements DeepSeekService {
                 connection = (HttpURLConnection) new URL(url).openConnection();
                 connection.setRequestMethod("POST");
                 connection.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
-                connection.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + deepSeekConfig.getApiKey());
+                connection.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey);
                 connection.setDoOutput(true);
                 connection.setDoInput(true);
                 connection.setConnectTimeout(deepSeekConfig.getTimeout());

+ 2 - 25
src/main/java/com/njuzr/eaibackend/service/impl/ExamTimerServiceImpl.java

@@ -212,34 +212,11 @@ public class ExamTimerServiceImpl implements ExamTimerService {
                 log.error("自动提交失败:更新失败 - studentId: {}, assignmentId: {}", studentId, assignmentId);
                 return;
             }
-            recordHistorySnapshot(engagement);
+            // 基于主表当前行落一条快照(幂等),避免漏写/并发导致 history 缺版本
+            studentAssignmentMapper.insertHistorySnapshot(assignmentId, studentId);
             log.info("考试自动提交完成 - studentId: {}, assignmentId: {}", studentId, assignmentId);
         } catch (Exception e) {
             log.error("自动提交异常 - studentId: {}, assignmentId: {}, error: {}", studentId, assignmentId, e.getMessage());
         }
     }
-
-    private void recordHistorySnapshot(Engagement engagement) {
-        // 检查是否已存在相同版本的历史记录
-        StudentAssignmentHistory existingHistory = studentAssignmentMapper.findHistoryByAssignmentIdAndStudentIdAndVersion(
-                engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
-        if (existingHistory != null) {
-            log.info("历史记录已存在,跳过插入 - assignmentId: {}, studentId: {}, version: {}",
-                    engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
-            return;
-        }
-
-        StudentAssignmentHistory history = new StudentAssignmentHistory();
-        history.setAssignmentId(engagement.getAssignmentId());
-        history.setStudentId(engagement.getStudentId());
-        history.setVersion(engagement.getVersion());
-        history.setStatus(engagement.getStatus());
-        history.setQuillContent(engagement.getQuillContent());
-        history.setTextContent(engagement.getTextContent());
-        history.setHtmlContent(engagement.getHtmlContent());
-        history.setSubmitTime(new Date());
-        studentAssignmentMapper.insertHistory(history);
-        log.info("历史记录插入成功 - assignmentId: {}, studentId: {}, version: {}",
-                engagement.getAssignmentId(), engagement.getStudentId(), engagement.getVersion());
-    }
 }

+ 28 - 0
src/main/resources/mapper/StudentAssignmentMapper.xml

@@ -28,6 +28,34 @@
         (#{assignmentId}, #{studentId}, #{version}, #{status}, #{quillContent}, #{textContent}, #{htmlContent}, #{submitTime})
     </insert>
 
+    <!--
+      将 student_assignment 当前行写入 history(若该 version 尚不存在)。
+      这样即使业务层忘记组装 StudentAssignmentHistory,也能保证 history 与主表 version 对齐。
+    -->
+    <insert id="insertHistorySnapshot">
+        INSERT INTO student_assignment_history
+        (assignment_id, student_id, version, status, quill_content, text_content, html_content, submit_time)
+        SELECT
+            sa.assignment_id,
+            sa.student_id,
+            sa.version,
+            sa.status,
+            sa.quill_content,
+            sa.text_content,
+            sa.html_content,
+            NOW()
+        FROM student_assignment sa
+        WHERE sa.assignment_id = #{assignmentId}
+          AND sa.student_id = #{studentId}
+          AND NOT EXISTS (
+              SELECT 1
+              FROM student_assignment_history h
+              WHERE h.assignment_id = sa.assignment_id
+                AND h.student_id = sa.student_id
+                AND h.version = sa.version
+          )
+    </insert>
+
     <select id="findHistoryByStudentIdAndAssignmentId" resultType="com.njuzr.eaibackend.po.StudentAssignmentHistory">
         SELECT *
         FROM student_assignment_history