|
|
@@ -21,6 +21,7 @@ import org.springframework.data.mongodb.core.aggregation.*;
|
|
|
import org.springframework.data.mongodb.core.query.Criteria;
|
|
|
import org.springframework.data.mongodb.core.query.Query;
|
|
|
import org.springframework.data.mongodb.core.query.Update;
|
|
|
+import com.mongodb.client.result.UpdateResult;
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
|
|
@@ -85,6 +86,14 @@ public class AIDialogueService {
|
|
|
aiDialogueMapper.save(aiDialogue);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 幂等补建:若 (assignmentId, userId) 在 Mongo 中已有会话则直接返回,否则创建空会话。
|
|
|
+ * 用于参加作业、流式前纠偏等场景,避免「已参与但无 AIDialogue」或重复调用 createAIDialogue 抛错导致整体失败。
|
|
|
+ */
|
|
|
+ public void ensureAIDialogueExists(Long assignmentId, Long userId) {
|
|
|
+ findOrCreateByAssignmentAndUser(assignmentId, userId, false);
|
|
|
+ }
|
|
|
+
|
|
|
public void createAIRewriteRecords(Long assignmentId, Long userId) {
|
|
|
if (aiRewriteRecordMapper.findByAssignmentIdAndUserId(assignmentId, userId, PageRequest.of(0,1)).hasContent()) {
|
|
|
throw MyException.create(HttpStatus.BAD_REQUEST, "AI改写记录已存在");
|
|
|
@@ -115,7 +124,14 @@ public class AIDialogueService {
|
|
|
);
|
|
|
|
|
|
if (targetDialogue == null) {
|
|
|
- throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase()+":"+"找不到AI会话");
|
|
|
+ // 自愈合:如果历史上出现“能对话但无会话文档”的脏数据,补一条空会话,避免前端反复 400
|
|
|
+ log.warn("【AI会话】未找到对话文档,将自动补建: assignmentId={}, userId={}", assignmentId, userId);
|
|
|
+ AIDialogue created = new AIDialogue();
|
|
|
+ created.setAssignmentId(assignmentId);
|
|
|
+ created.setUserId(userId);
|
|
|
+ created.setDialogues(new ArrayList<>());
|
|
|
+ AIDialogue saved = mongoTemplate.save(created, "aiDialogues");
|
|
|
+ targetDialogue = saved;
|
|
|
}
|
|
|
|
|
|
Pageable pageable = PageRequest.of(page, size);
|
|
|
@@ -270,9 +286,14 @@ public class AIDialogueService {
|
|
|
assistantMsgCount++;
|
|
|
}
|
|
|
}
|
|
|
- log.info("【Service层】消息统计 - user消息={}, assistant消息={}, 总消息数={}",
|
|
|
+ log.info("【Service层】消息统计 - user消息={}, assistant消息={}, deepSeek入参消息数(含system)={}",
|
|
|
userMsgCount, assistantMsgCount, messages.size());
|
|
|
|
|
|
+ // 在发起外部模型请求前,确保 Mongo 中存在可用于 $push 的根文档;必要时按 assignmentId+userId 自动补建/纠偏 dialogueId
|
|
|
+ String effectiveDialogueId = resolveOrCreateDialogueId(dialogueId, aidto);
|
|
|
+ log.info("【Service层】resolved dialogueId: requested={}, effective={}, assignmentId={}, userId={}",
|
|
|
+ dialogueId, effectiveDialogueId, aidto.getAssignmentId(), aidto.getUserId());
|
|
|
+
|
|
|
SseEmitter emitter = new SseEmitter(0L);
|
|
|
StringBuilder fullResponse = new StringBuilder();
|
|
|
|
|
|
@@ -297,7 +318,7 @@ public class AIDialogueService {
|
|
|
fullResponse.toString(),
|
|
|
System.currentTimeMillis() / 1000
|
|
|
);
|
|
|
- addDialogueEntry(dialogueId, userEntry, aiEntry);
|
|
|
+ addDialogueEntry(effectiveDialogueId, userEntry, aiEntry);
|
|
|
emitter.complete();
|
|
|
log.info("【Service层】emitter.complete()已调用");
|
|
|
},
|
|
|
@@ -319,6 +340,61 @@ public class AIDialogueService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 解析/补建对话根文档 id:
|
|
|
+ * - 优先信任请求参数 dialogueId(若存在且能查到)
|
|
|
+ * - 否则按 assignmentId+userId 查找/创建
|
|
|
+ * - 若仅有 userId 缺失,尝试用 aidto.userId(若前端传入)
|
|
|
+ */
|
|
|
+ private String resolveOrCreateDialogueId(String requestedDialogueId, AIDTO aidto) {
|
|
|
+ if (requestedDialogueId != null && !requestedDialogueId.isBlank()) {
|
|
|
+ AIDialogue byId = mongoTemplate.findById(requestedDialogueId, AIDialogue.class);
|
|
|
+ if (byId != null) {
|
|
|
+ return byId.getId();
|
|
|
+ }
|
|
|
+ log.warn("【AI会话】request dialogueId 不存在,将尝试按 assignmentId+userId 纠偏/补建。requestedDialogueId={}", requestedDialogueId);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long assignmentId = aidto == null ? null : aidto.getAssignmentId();
|
|
|
+ Long userId = aidto == null ? null : aidto.getUserId();
|
|
|
+ if (assignmentId == null || userId == null) {
|
|
|
+ throw MyException.create(HttpStatus.BAD_REQUEST,
|
|
|
+ "AI会话不存在,且缺少补建参数(assignmentId/userId)。请刷新页面后重试,或在流式请求体携带 assignmentId,并由登录态或请求体提供 userId");
|
|
|
+ }
|
|
|
+
|
|
|
+ return findOrCreateByAssignmentAndUser(assignmentId, userId, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按 (assignmentId, userId) 查 Mongo 会话;不存在则补建。优先走 Repository 分页,避免与 MongoTemplate 各查各的导致误判。
|
|
|
+ *
|
|
|
+ * @param logRepair true 时仅在“本次新建了文档”打 warn 日志(流式纠偏场景);false 为参与作业等静默幂等
|
|
|
+ */
|
|
|
+ private String findOrCreateByAssignmentAndUser(Long assignmentId, Long userId, boolean logRepair) {
|
|
|
+ Page<AIDialogue> page = aiDialogueMapper.findByAssignmentIdAndUserId(assignmentId, userId, PageRequest.of(0, 1));
|
|
|
+ if (page.hasContent()) {
|
|
|
+ return page.getContent().get(0).getId();
|
|
|
+ }
|
|
|
+ AIDialogue byPair = mongoTemplate.findOne(
|
|
|
+ Query.query(Criteria.where("assignmentId").is(assignmentId).and("userId").is(userId)),
|
|
|
+ AIDialogue.class
|
|
|
+ );
|
|
|
+ if (byPair != null) {
|
|
|
+ return byPair.getId();
|
|
|
+ }
|
|
|
+ AIDialogue created = new AIDialogue();
|
|
|
+ created.setAssignmentId(assignmentId);
|
|
|
+ created.setUserId(userId);
|
|
|
+ created.setDialogues(new ArrayList<>());
|
|
|
+ AIDialogue saved = mongoTemplate.save(created, "aiDialogues");
|
|
|
+ if (logRepair) {
|
|
|
+ log.warn("【AI会话】已自动补建对话文档: id={}, assignmentId={}, userId={}", saved.getId(), assignmentId, userId);
|
|
|
+ } else {
|
|
|
+ log.info("【AI会话】ensure: 已补建空会话, id={}, assignmentId={}, userId={}", saved.getId(), assignmentId, userId);
|
|
|
+ }
|
|
|
+ return saved.getId();
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
public AIEntry rewrite(Long assignmentId, Long studentId) {
|
|
|
// 1 通过assignmentId和studentId找到engagement,获取当前文件链接
|
|
|
@@ -409,9 +485,18 @@ public class AIDialogueService {
|
|
|
try {
|
|
|
Query query = new Query(Criteria.where("id").is(dialogueId));
|
|
|
Update update = new Update().push("dialogues").each(userEntry, aiEntry);
|
|
|
- mongoTemplate.updateFirst(query, update, AIDialogue.class);
|
|
|
+ UpdateResult result = mongoTemplate.updateFirst(query, update, AIDialogue.class);
|
|
|
+ if (result == null || result.getMatchedCount() == 0) {
|
|
|
+ log.error("【AI会话】写库未匹配到对话文档: dialogueId={}, matched={}, modified={}",
|
|
|
+ dialogueId,
|
|
|
+ result == null ? "null" : result.getMatchedCount(),
|
|
|
+ result == null ? "null" : result.getModifiedCount());
|
|
|
+ throw new MyException(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + "AI会话不存在,无法保存对话");
|
|
|
+ }
|
|
|
+ } catch (MyException e) {
|
|
|
+ throw e;
|
|
|
} catch (Exception e) {
|
|
|
- log.error("MongoDB数据库更新出错");
|
|
|
+ log.error("MongoDB数据库更新出错: {}", e.getMessage(), e);
|
|
|
throw new MyException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()+":"+"数据库更新失败");
|
|
|
}
|
|
|
}
|