From fc7c4f2e4ad114b4374c49f69c782910dba004c4 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 01:12:50 +0800 Subject: [PATCH 01/49] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8Dtoken=E5=B7=B2?= =?UTF-8?q?=E7=9F=A5=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/achobeta/aop/GlobalOperationAspect.java | 6 +++--- .../src/main/java/com/achobeta/jwt/JwtTool.java | 14 +++++++------- .../java/com/achobeta/domain/user/service/Jwt.java | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/refine-app/src/main/java/com/achobeta/aop/GlobalOperationAspect.java b/refine-app/src/main/java/com/achobeta/aop/GlobalOperationAspect.java index 2945bf9..ea3b2f2 100644 --- a/refine-app/src/main/java/com/achobeta/aop/GlobalOperationAspect.java +++ b/refine-app/src/main/java/com/achobeta/aop/GlobalOperationAspect.java @@ -43,10 +43,10 @@ public void interceptorDo(JoinPoint point) { checkLogin(); } catch (UnauthorizedException e) { log.error("登录验证失败:{}", e.getMessage()); - throw new AppException(e.getCode(),e.getMessage()); + throw new AppException(e.getCode(), e.getMessage()); } catch (AppException e) { log.error("全局拦截异常", e); - throw new AppException(e.getMessage()); + throw new AppException(e.getCode(), e.getMessage()); } catch (Throwable e) { log.error("全局拦截异常", e); throw new AppException(GlobalServiceStatusCode.PARAM_FAILED_VALIDATE); @@ -74,7 +74,7 @@ private void checkLogin() { // 从请求头获取token String token = request.getHeader("access-token"); if (StringTools.isEmpty(token)) { - throw new AppException(GlobalServiceStatusCode.UNAUTHORIZED); // token为空,未登录 + throw new AppException(401, "token为空"); // token为空,未登录 } // 先JWT技术验证,验签名、过期时间 diff --git a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java index e82969a..e5e3839 100644 --- a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java +++ b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java @@ -86,7 +86,7 @@ public String createRefreshToken(String userId) { * 通用Token生成方法 */ private String createToken(Map claims, Duration ttl) { - claims.put("iat", new Date()); // 添加签发时间 + claims.put("iat", System.currentTimeMillis()); // 添加签发时间,毫秒值 return JWT.create() .addPayloads(claims) // 设置载荷 .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())) // 过期时间 @@ -139,7 +139,7 @@ private TokenPayload parseToken(String token, String expectedType) { log.debug("Token有效:{}", jwt); } catch (ValidateException e) { // 细分异常:过期/签名无效 - if (e.getMessage().contains("expired")) { + if (e.getMessage().contains("is before now:")) { throw new UnauthorizedException(expectedType + "-token已过期", e); } else { throw new UnauthorizedException(expectedType + "-token签名无效", e); @@ -151,7 +151,7 @@ private TokenPayload parseToken(String token, String expectedType) { // 4. 校验Token类型 String actualType = Convert.toStr(jwt.getPayload("type")); if (!StrUtil.equals(actualType, expectedType)) { - throw new UnauthorizedException("无效的" + expectedType + "-token:类型错误"); + throw new UnauthorizedException("无效的" + expectedType + "-token:token类型错误"); } // 5. 提取用户ID @@ -167,7 +167,7 @@ private TokenPayload parseToken(String token, String expectedType) { } // 7. 提取iat - Date iat = Convert.toDate(jwt.getPayload("iat")); + Long iat = Convert.toLong(jwt.getPayload("iat")); if (iat == null) { throw new UnauthorizedException("无效的" + expectedType + "-token:缺失签发时间"); } @@ -190,7 +190,7 @@ public Map refreshAccessToken(String refreshToken, Map 0 && remainingTime < (jwtProperties.getRefreshTokenTtl().toMillis() / 2)) { // 同时会自动更新 Redis 的refresh-token @@ -232,7 +232,7 @@ public void invalidateRefreshToken(String refreshToken) { /** * 提取refresh-token中的iat值 */ - public Date getRefreshTokenIat(String refreshToken) { + public Long getRefreshTokenIat(String refreshToken) { return parseToken(refreshToken, "refresh").getIat(); } @@ -245,7 +245,7 @@ public Date getRefreshTokenIat(String refreshToken) { private static class TokenPayload { private final String userId; private final String jti; - private final Date iat; + private final Long iat; } } \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java index 03d7cbe..7e89ce0 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java @@ -1,6 +1,5 @@ package com.achobeta.domain.user.service; -import java.util.Date; import java.util.Map; public interface Jwt { @@ -17,6 +16,6 @@ public interface Jwt { void invalidateRefreshToken(String refreshToken); - Date getRefreshTokenIat(String refreshToken); + Long getRefreshTokenIat(String refreshToken); } From 67c0b769bd7bc646bcc2542baeceb37221070e42 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 10:11:27 +0800 Subject: [PATCH 02/49] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ITrickyKnowledgeRepository.java | 3 ++ .../extendbiz/ReviewFeedbackService.java | 4 +- .../http/AILearningSuggessionController.java | 11 +++- .../http/LearningOverviewController.java | 32 ++++++----- .../http/ReviewFeedbackController.java | 53 +++++++++++-------- .../types/enums/GlobalServiceStatusCode.java | 5 +- 6 files changed, 68 insertions(+), 40 deletions(-) diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java index f2fcd5f..c71d379 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java @@ -19,4 +19,7 @@ public interface ITrickyKnowledgeRepository { "group by m.knowledge_point_id, k.knowledge_desc " + "having count(m.knowledge_point_id) >= 3") List getTrickyKnowledgePoints(String userId); + + @Select("insert into UserData (user_id, hard_questions) values (#{userId}, #{size})") + void setTrickyKnowledgePointsCnt(String userId, int size); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java index 135c85b..8881654 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java @@ -131,7 +131,9 @@ public StatsVO getStatistics(String userId) { */ @Override public List getTrickyKnowledgePoint(String userId) { - return trickyKnowledgeRepository.getTrickyKnowledgePoints(userId); + List TrickyKnowledgePointVOs= trickyKnowledgeRepository.getTrickyKnowledgePoints(userId); + trickyKnowledgeRepository.setTrickyKnowledgePointsCnt(userId, TrickyKnowledgePointVOs.size()); + return TrickyKnowledgePointVOs; } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java index 731437b..1fe5293 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java @@ -5,6 +5,8 @@ import com.achobeta.domain.aisuggession.service.IAILearningSuggessionService; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.UserContext; +import com.achobeta.types.enums.GlobalServiceStatusCode; +import com.achobeta.types.exception.AppException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -28,8 +30,13 @@ public class AILearningSuggessionController { @GlobalInterception public List getKeyPoint() { String userId = UserContext.getUserId(); - log.info("用户获取AI学习建议,userId:{}", userId); - List keyPointVOS = service.getKeyPoint(userId); + List keyPointVOS = null; + try { + log.info("用户获取AI学习建议,userId:{}", userId); + keyPointVOS = service.getKeyPoint(userId); + } catch (Exception e) { + throw new AppException(GlobalServiceStatusCode.AI_RESPONSE_TIMEOUT); + } List keyPointDTOS = keyPointVOS.stream() .map(keyPointVO -> KeyPointDTO.builder() .knowledgePoint(keyPointVO.getKnowledgePoint()) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java index 7dd5864..9773613 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java @@ -6,9 +6,11 @@ import com.achobeta.domain.overview.model.valobj.LearningDynamicVO; import com.achobeta.domain.overview.service.ILearningOverviewService; import com.achobeta.domain.overview.model.valobj.StudyOverviewVO; +import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.Constants; import com.achobeta.types.common.UserContext; +import com.achobeta.types.exception.AppException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.annotations.Param; @@ -16,6 +18,8 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import static com.achobeta.types.enums.GlobalServiceStatusCode.*; + /** * 学习概览接口 */ @@ -34,21 +38,22 @@ public class LearningOverviewController { */ @GetMapping("/get_overview") @GlobalInterception - public StudyOverviewDTO getOverview() { + public Response getOverview() { String userId = UserContext.getUserId(); + StudyOverviewVO vo = null; try { log.info("用户获取学习概览,userId:{}", userId); - StudyOverviewVO vo = service.getOverview(userId); - return StudyOverviewDTO.builder() - .questionsNum(vo.getQuestionsNum()) - .reviewRate(vo.getReviewRate()) - .hardQuestions(vo.getHardQuestions()) - .studyTime(vo.getStudyTime()) - .build(); + vo = service.getOverview(userId); } catch (Exception e) { - log.error("getOverview error", e); - return null; + throw new AppException(REQUEST_NOT_VALID); } + return Response.SYSTEM_SUCCESS(StudyOverviewDTO.builder() + .questionsNum(vo.getQuestionsNum()) + .reviewRate(vo.getReviewRate()) + .hardQuestions(vo.getHardQuestions()) + .studyTime(vo.getStudyTime()) + .build()); + } /** @@ -59,13 +64,14 @@ public StudyOverviewDTO getOverview() { @GlobalInterception public ResponseEntity getStudyDynamic(){ String userId = UserContext.getUserId(); + LearningDynamicVO result = null; try { log.info("用户获取学习动画,userId:{}", userId); - LearningDynamicVO result = service.getStudyDynamic(userId); - return ResponseEntity.ok(result); + result = service.getStudyDynamic(userId); } catch (Exception e) { log.error("getStudyDynamic error", e); - throw new RuntimeException(e); + throw new AppException(GET_STUDY_DYNAMIC_FAIL); } + return ResponseEntity.ok(result); } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java index 1f70dbc..0adbfc2 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java @@ -14,6 +14,7 @@ import com.achobeta.types.common.Constants; import com.achobeta.types.common.UserContext; import com.achobeta.types.enums.TimeRange; +import com.achobeta.types.exception.AppException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -27,6 +28,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static com.achobeta.types.enums.GlobalServiceStatusCode.*; + /** * 待复习题目反馈接口 */ @@ -50,19 +53,20 @@ public class ReviewFeedbackController { @GlobalInterception public Response getOverdueReviewCount() { String userId = UserContext.getUserId(); + OverdueCountVO overdueCountVO = null; try{ log.info("用户获取待复习题目数量开始,userId:{}", userId); - OverdueCountVO overdueCountVO = reviewFeedbackService.getOverdueCount(userId); + overdueCountVO = reviewFeedbackService.getOverdueCount(userId); log.info("用户获取待复习题目数量结束,userId:{} count:{}", userId, overdueCountVO.getCount()); - - return Response.SYSTEM_SUCCESS(OverdueReviewDTO.builder() - .count(overdueCountVO.getCount()) - .description(overdueCountVO.getDescription()) - .build()); }catch (Exception e){ log.error("用户获取待复习题目数量失败!userId:{}", userId, e); - return Response.SERVICE_ERROR(e.getMessage()); + throw new AppException(GET_OVERDUE_REVIEW_COUNT_FAIL); } + return Response.SYSTEM_SUCCESS(OverdueReviewDTO.builder() + .count(overdueCountVO.getCount()) + .description(overdueCountVO.getDescription()) + .build()); + } /** @@ -72,27 +76,30 @@ public Response getOverdueReviewCount() { @GlobalInterception public Response> getTrickyKnowledgePoint(){ String userId = UserContext.getUserId(); + List trickyKnowledgePointVOS = null; try{ log.info("用户获取近期出错最多的知识点,userId:{}", userId); - List trickyKnowledgePointVOS = reviewFeedbackService.getTrickyKnowledgePoint(userId); - List trickyKnowledgePointDTOS = trickyKnowledgePointVOS.stream() - .map(trickyKnowledgePointVO -> { - if (trickyKnowledgePointVO == null) { - return TrickyKnowledgePointDTO.builder() - .knowledgeId("") - .knowledgeName("未知") - .build(); - } - return TrickyKnowledgePointDTO.builder() - .knowledgeId(trickyKnowledgePointVO.getKnowledgeId()) - .knowledgeName(trickyKnowledgePointVO.getKnowledgeName()) - .build(); - }).toList(); - return Response.SYSTEM_SUCCESS(trickyKnowledgePointDTOS); + trickyKnowledgePointVOS = reviewFeedbackService.getTrickyKnowledgePoint(userId); }catch (Exception e){ log.error("用户获取近期出错最多的知识点失败!userId:{}", userId, e); - return Response.SERVICE_ERROR(e.getMessage()); + throw new AppException(GET_TRICKY_KNOWLEDGE_POINT_FAIL); } + + List trickyKnowledgePointDTOS = trickyKnowledgePointVOS.stream() + .map(trickyKnowledgePointVO -> { + if (trickyKnowledgePointVO == null) { + return TrickyKnowledgePointDTO.builder() + .knowledgeId("") + .knowledgeName("未知") + .build(); + } + return TrickyKnowledgePointDTO.builder() + .knowledgeId(trickyKnowledgePointVO.getKnowledgeId()) + .knowledgeName(trickyKnowledgePointVO.getKnowledgeName()) + .build(); + }).toList(); + return Response.SYSTEM_SUCCESS(trickyKnowledgePointDTOS); + } /** diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index 672eb5d..617f8c4 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -57,7 +57,10 @@ public enum GlobalServiceStatusCode { QUESTION_GENERATION_FAIL(10001, "题目生成失败,请稍后再试"), QUESTION_IS_EXPIRED(10002, "题目已过期或不存在" ), OCR_ERROR(10003, "OCR图片识别错误"), - + GET_STUDY_DYNAMIC_FAIL(10004, "获取学习动态失败,请稍后再试"), + GET_OVERDUE_REVIEW_COUNT_FAIL(10005, "获取待复习题目数量失败,请稍后再试"), + GET_TRICKY_KNOWLEDGE_POINT_FAIL(10006, "获取易错知识点失败,请稍后再试"), + AI_RESPONSE_TIMEOUT(10007, "AI响应超时") ; private Integer code; From 6ae84150b1378da7c292bc34822cb2edd908efe0 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 10:34:42 +0800 Subject: [PATCH 03/49] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eocr=E6=8F=90?= =?UTF-8?q?=E5=8F=96=E9=A2=98=E7=9B=AE=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E8=B7=9F=E8=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/achobeta/domain/ocr/service/impl/DefaultOcrService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java index 02463c4..a8dcdfc 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java @@ -101,6 +101,7 @@ else if (lowerType.endsWith(".txt") || "txt".equals(lowerType)) { // 使用 AI 模型尝试提取第一个问题 recognizedText = aiService.extractTheFirstQuestion(recognizedText); String uuid = UUID.fastUUID().toString(); + log.info("成功识别出第一个问题:{}", recognizedText); // 创建问题实体 QuestionEntity questionEntity = new QuestionEntity(); From f424fd7d3582d70e6f16a67e3a443ecbadc269f3 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 10:47:59 +0800 Subject: [PATCH 04/49] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E9=94=99?= =?UTF-8?q?=E9=A2=98mapper=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/mapper/MistakeQuestion_Mapper.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/MistakeQuestion_Mapper.xml b/refine-app/src/main/resources/mybatis/mapper/MistakeQuestion_Mapper.xml index 2dcae3a..27c3a7f 100644 --- a/refine-app/src/main/resources/mybatis/mapper/MistakeQuestion_Mapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/MistakeQuestion_Mapper.xml @@ -28,9 +28,9 @@ - INSERT INTO mistake_question ( + INSERT INTO MistakeQuestion ( user_id, question_id, question_content, subject, is_careless, is_unfamiliar, is_calculate_err, - is_time_shortage, other_reason, knowledge_point_id, study_note, question_status, + is_time_shortage, other_reason, other_reason_flag, knowledge_point_id, study_note, question_status, create_time, update_time ) VALUES ( #{userId,jdbcType=VARCHAR}, @@ -54,14 +54,14 @@ @@ -69,12 +69,12 @@ - UPDATE mistake_question + UPDATE MistakeQuestion SET is_careless = #{isCareless,jdbcType=INTEGER}, is_unfamiliar = #{isUnfamiliar,jdbcType=INTEGER}, @@ -87,14 +87,14 @@ - UPDATE mistake_question + UPDATE MistakeQuestion SET other_reason = #{otherReasonText,jdbcType=VARCHAR}, update_time = NOW() WHERE user_id = #{userId,jdbcType=VARCHAR} AND question_id = #{questionId,jdbcType=VARCHAR} - UPDATE mistake_question + UPDATE MistakeQuestion SET study_note = #{studyNote,jdbcType=LONGVARCHAR}, update_time = NOW() WHERE user_id = #{userId,jdbcType=VARCHAR} AND question_id = #{questionId,jdbcType=VARCHAR} From 1bdb34a7b0b1ba0f5f9fcfeddf68d6c5b76f30fc Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 11:03:59 +0800 Subject: [PATCH 05/49] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9EOCR=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E8=BF=BD=E8=B8=AA=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/service/impl/aiService.java | 11 ++++++++--- .../ocr/service/impl/DefaultOcrService.java | 17 +++++++++++++++-- .../infrastructure/adapter/port/OcrPort.java | 9 ++++++++- .../infrastructure/gateway/BaiduOcrRPC.java | 10 +++++++++- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java b/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java index ea9ed03..db4030f 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java @@ -65,6 +65,8 @@ public String extractTheFirstQuestion(String content) { log.warn("输入内容为空,无法提取问题"); return ""; } + + log.info("开始AI提取题目,输入内容长度: {}", content.length()); try { Generation gen = new Generation(); @@ -132,15 +134,18 @@ public String extractTheFirstQuestion(String content) { if (result != null && result.getOutput() != null && !result.getOutput().getChoices().isEmpty() && result.getOutput().getChoices().get(0).getMessage() != null) { - return result.getOutput().getChoices().get(0).getMessage().getContent().trim(); + String extractedContent = result.getOutput().getChoices().get(0).getMessage().getContent().trim(); + log.info("AI模型返回内容: {}", extractedContent); + return extractedContent; } else { - log.warn("模型返回结果为空或格式异常"); + log.warn("模型返回结果为空或格式异常,result: {}", result); return ""; } } catch (Exception e) { // 记录异常日志便于排查问题 - log.error("调用大模型提取问题失败,输入内容: {}", content, e); + log.error("调用大模型提取问题失败,输入内容长度: {}, 异常信息: {}", + content != null ? content.length() : 0, e.getMessage(), e); return ""; } } diff --git a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java index a8dcdfc..08f4a33 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/DefaultOcrService.java @@ -98,10 +98,23 @@ else if (lowerType.endsWith(".txt") || "txt".equals(lowerType)) { throw new AppException("文件识别失败,请稍后重试或联系客服"); } + // 记录OCR识别的原始文本 + log.info("OCR识别原始文本: {}", recognizedText); + // 使用 AI 模型尝试提取第一个问题 - recognizedText = aiService.extractTheFirstQuestion(recognizedText); + String extractedText = aiService.extractTheFirstQuestion(recognizedText); + log.info("AI提取后的文本: {}", extractedText); + + // 检查AI提取结果是否为空 + if (extractedText == null || extractedText.trim().isEmpty()) { + log.warn("AI提取的题目内容为空,使用原始OCR文本"); + recognizedText = recognizedText; + } else { + recognizedText = extractedText; + } + String uuid = UUID.fastUUID().toString(); - log.info("成功识别出第一个问题:{}", recognizedText); + log.info("最终题目内容: {}", recognizedText); // 创建问题实体 QuestionEntity questionEntity = new QuestionEntity(); diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/OcrPort.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/OcrPort.java index cbe4486..867c31b 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/OcrPort.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/OcrPort.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -14,6 +15,7 @@ * @Desc : OCR 适配器 * @Time : 2025/10/31 17:02 */ +@Slf4j @Component @RequiredArgsConstructor public class OcrPort implements IOcrPort { @@ -36,12 +38,17 @@ public String recognizeImage(byte[] imageBytes) { String json = "baidu".equalsIgnoreCase(ocrProvider) ? baiduOcrRPC.recognizeImage(imageBytes) : ocrRPC.recognizeImage(imageBytes); + + log.info("OCR服务提供商: {}, 返回JSON: {}", ocrProvider, json); try { JsonNode node = objectMapper.readTree(json); JsonNode textNode = node.get("text"); - return textNode != null ? textNode.asText("") : ""; + String text = textNode != null ? textNode.asText("") : ""; + log.info("OCR识别文本: {}", text); + return text; } catch (Exception e) { + log.error("解析OCR返回JSON失败: {}", json, e); return ""; } } diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/gateway/BaiduOcrRPC.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/gateway/BaiduOcrRPC.java index 163c3b5..2ef1122 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/gateway/BaiduOcrRPC.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/gateway/BaiduOcrRPC.java @@ -83,10 +83,14 @@ public class BaiduOcrRPC { * - raw: 第三方原始返回字符串 */ public String recognizeImage(byte[] imageBytes) { + log.info("百度OCR调用开始,enabled: {}, imageBytes长度: {}", enabled, imageBytes != null ? imageBytes.length : 0); + if (!enabled) { + log.warn("百度OCR未启用"); return toJson(skeletonJson("Baidu OCR disabled", null, "")); } if (isBlank(apiKey) || isBlank(secretKey)) { + log.error("百度OCR API Key或Secret Key为空"); return toJson(skeletonJson("Baidu OCR call failed: API Key/Secret Key is empty", null, "")); } try { @@ -125,10 +129,14 @@ public String recognizeImage(byte[] imageBytes) { // 解析百度OCR响应,提取识别出的文本内容 String extractedText = extractTextFromBaiduResponse(raw); + log.info("百度OCR识别结果: {}", extractedText); Map data = skeletonJson(extractedText, raw, "v1"); - return toJson(data); + String result = toJson(data); + log.info("百度OCR最终返回: {}", result); + return result; } catch (Exception e) { + log.error("百度OCR调用异常", e); return toJson(skeletonJson("Baidu OCR call failed: " + e.getMessage(), null, "")); } From 07cd61c3a46baae95a332e546825a647f49f389b Mon Sep 17 00:00:00 2001 From: hamster-yhz <1427739483@qq.com> Date: Fri, 28 Nov 2025 12:19:21 +0800 Subject: [PATCH 06/49] =?UTF-8?q?fix:=20=E5=BC=BA=E5=88=B6=E6=8B=89?= =?UTF-8?q?=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/depoly.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/depoly.yml b/.github/workflows/depoly.yml index 7dd62c0..47a094e 100644 --- a/.github/workflows/depoly.yml +++ b/.github/workflows/depoly.yml @@ -63,9 +63,7 @@ jobs: git checkout develop echo "pull completed." echo "Deploying..." - sh build.sh stop - sh build.sh rm && sh build.sh rmi + echo "Pulling latest Docker image..." + docker pull docker.cnb.cool/hamster-yhz/refine:latest sh build.sh base && sh build.sh services echo "Deployment completed." - - From a89564efa1007eb0238aec887dbf1776341d54d7 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 12:23:07 +0800 Subject: [PATCH 07/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dai=E8=A7=A3?= =?UTF-8?q?=E7=AD=94=E8=BF=87=E7=A8=8B=E4=B8=AD=E8=B6=85=E6=97=B6=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai/service/impl/aiService.java | 32 +++++++++++- .../trigger/http/AiSolveController.java | 49 ++++++++++++++++--- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java b/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java index db4030f..c3a1e17 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ai/service/impl/aiService.java @@ -212,6 +212,15 @@ public void aiSolveQuestion(String question, Consumer contentCallback) { _streamCallWithMessage(gen, combineMsg, contentCallback); } catch (ApiException | NoApiKeyException | InputRequiredException e) { logger.error("An exception occurred: {}", e.getMessage()); + // 通知回调函数发生了错误 + if (contentCallback != null) { + contentCallback.accept("抱歉,AI服务暂时不可用,请稍后重试。"); + } + } catch (Exception e) { + logger.error("Unexpected error in aiSolveQuestion: {}", e.getMessage(), e); + if (contentCallback != null) { + contentCallback.accept("抱歉,处理您的问题时发生了错误,请稍后重试。"); + } } } @@ -428,11 +437,19 @@ private static void _handleGenerationResult(GenerationResult result, Consumer result = gen.streamCall(param); - result.blockingForEach(resultItem -> _handleGenerationResult(resultItem, contentCallback)); + + try { + result.blockingForEach(resultItem -> _handleGenerationResult(resultItem, contentCallback)); + } catch (RuntimeException e) { + // 检查是否是连接断开导致的异常 + if (e.getMessage() != null && e.getMessage().contains("Connection lost")) { + logger.warn("检测到连接断开,停止流式输出"); + return; // 优雅地停止流式输出 + } + // 其他异常继续抛出 + throw e; + } // 流式输出结束后换行 System.out.println(); diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java index 4d9677a..ac6d294 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java @@ -27,24 +27,61 @@ public class AiSolveController { @PostMapping("stream") public SseEmitter stream(@RequestParam("question") String question) { - SseEmitter emitter = new SseEmitter(); + // 设置超时时间为5分钟 + SseEmitter emitter = new SseEmitter(300000L); + + // 添加连接状态标志 + final boolean[] isConnectionActive = {true}; + + // 设置超时和完成回调 + emitter.onTimeout(() -> { + log.warn("SSE connection timeout for question: {}", question); + isConnectionActive[0] = false; + emitter.complete(); + }); + + emitter.onCompletion(() -> { + log.info("SSE connection completed for question: {}", question); + isConnectionActive[0] = false; + }); + + emitter.onError((ex) -> { + log.error("SSE connection error for question: {}", question, ex); + isConnectionActive[0] = false; + }); try { aiService.aiSolveQuestion(question, content -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive[0]) { + log.warn("Connection is no longer active, stopping content sending"); + return; + } + try { emitter.send(SseEmitter.event().data(content)); } catch (IOException e) { - log.error("Error sending SSE event", e); - emitter.completeWithError(e); + log.error("Error sending SSE event, marking connection as inactive", e); + isConnectionActive[0] = false; + // 不要在这里调用completeWithError,因为连接可能已经断开 + // emitter.completeWithError(e); + } catch (IllegalStateException e) { + // 处理连接已关闭的情况 + log.warn("SSE connection already closed: {}", e.getMessage()); + isConnectionActive[0] = false; } }); - // 当aiSolveQuestion方法执行完毕(流式输出完成)后,完成emitter - emitter.complete(); + // 只有在连接仍然活跃时才完成emitter + if (isConnectionActive[0]) { + emitter.complete(); + } } catch (Exception e) { log.error("Error during AI stream call", e); - emitter.completeWithError(e); + if (isConnectionActive[0]) { + emitter.completeWithError(e); + } } return emitter; From f3723d25b7008a97d193e4011ef74521553bf374 Mon Sep 17 00:00:00 2001 From: hamster-yhz <1427739483@qq.com> Date: Fri, 28 Nov 2025 12:28:59 +0800 Subject: [PATCH 08/49] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E4=BE=9D=E8=B5=96=E5=85=B3=E7=B3=BB=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=A7=8B=E7=BB=88=E6=8B=89=E5=8F=96=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E9=95=9C=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/depoly.yml | 9 +++++++-- docs/dev-ops/docker-compose-app.yml | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/depoly.yml b/.github/workflows/depoly.yml index 47a094e..8e99c5a 100644 --- a/.github/workflows/depoly.yml +++ b/.github/workflows/depoly.yml @@ -58,10 +58,15 @@ jobs: script: | set -e cd ./refine + echo "pulling latest code..." - git pull - git checkout develop + git fetch --all + BRANCH_NAME="${{ github.ref_name }}" + echo "Detected branch: $BRANCH_NAME" + git checkout $BRANCH_NAME + git pull origin $BRANCH_NAME echo "pull completed." + echo "Deploying..." echo "Pulling latest Docker image..." docker pull docker.cnb.cool/hamster-yhz/refine:latest diff --git a/docs/dev-ops/docker-compose-app.yml b/docs/dev-ops/docker-compose-app.yml index 5d68f4d..84d16ba 100644 --- a/docs/dev-ops/docker-compose-app.yml +++ b/docs/dev-ops/docker-compose-app.yml @@ -6,6 +6,7 @@ services: refine: image: docker.cnb.cool/hamster-yhz/refine:latest container_name: refine + pull_policy: always restart: on-failure ports: - "8091:8091" @@ -26,6 +27,11 @@ services: max-file: "3" networks: - my-network + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_healthy networks: my-network: From 1ed6bb5b44ef1a1210a1ed4aefd45b0058dcde57 Mon Sep 17 00:00:00 2001 From: hamster-yhz <1427739483@qq.com> Date: Fri, 28 Nov 2025 12:33:24 +0800 Subject: [PATCH 09/49] =?UTF-8?q?test:=20=E6=B5=8B=E8=AF=95=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index df32ac7..73d0162 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,5 @@ AchoBeta Refine 琢玉系统-2025年-J组-复试项目 一个面向学生的AI学习助手项目后端,通过上传错题,系统自动识题目内容、分错误原因、讲解知识点并生成个性化练习,帮助学生快速查漏补缺 > 快速了解项目结构 请阅读: `docs/PROJECT_GUIDE.md` +> > 快速了解贡献指南 请阅读: `docs/CONTRIBUTING_GUIDE.md` \ No newline at end of file From a64ca9e1836db485cd5c83a78b1316ad088c87aa Mon Sep 17 00:00:00 2001 From: hamster-yhz <1427739483@qq.com> Date: Fri, 28 Nov 2025 12:44:35 +0800 Subject: [PATCH 10/49] =?UTF-8?q?fix:=20=E5=88=A0=E9=99=A4=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dev-ops/docker-compose-app.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/dev-ops/docker-compose-app.yml b/docs/dev-ops/docker-compose-app.yml index 84d16ba..d387f44 100644 --- a/docs/dev-ops/docker-compose-app.yml +++ b/docs/dev-ops/docker-compose-app.yml @@ -27,11 +27,6 @@ services: max-file: "3" networks: - my-network - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_healthy networks: my-network: From 947f27a941ad1721cc82d7f7a6047acc631dc42b Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 13:57:49 +0800 Subject: [PATCH 11/49] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/QuestionController.java | 6 ++++++ .../trigger/http/UserAccountController.java | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/QuestionController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/QuestionController.java index 8f7e230..b8122eb 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/QuestionController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/QuestionController.java @@ -45,6 +45,8 @@ public Response questionGeneration(@NotNull Integer mistake QuestionResponseDTO responseDTO = questionService.questionGeneration(userId, mistakeQuestionId); log.info("用户id: {} 生成题目成功,题目redis id: {}", userId, responseDTO.getQuestionId()); return Response.SYSTEM_SUCCESS(responseDTO); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { log.error("用户id: {} 生成题目失败,题目id: {}", userId, mistakeQuestionId, e); throw new AppException(e.getMessage()); @@ -62,6 +64,8 @@ public Flux> aiJudge(@NotNull String questionId, @NotNul try { log.info("用户id: {} 调用ai判题,题目id: {}", userId, questionId); return questionService.aiJudge(userId, questionId, answer); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { log.error("用户id: {} 调用ai判题失败,题目id: {}", userId, questionId, e); throw new AppException(e.getMessage()); @@ -80,6 +84,8 @@ public Response recordMistakeQuestion(@NotNull String questionId) { log.info("用户 {} 记录错题,题目id: {}", userId, questionId); questionService.recordMistakeQuestion(userId, questionId); return Response.SYSTEM_SUCCESS("已加入错题"); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { log.error("用户 {} 记录错题失败,题目id: {}", userId, questionId, e); throw new AppException(e.getMessage()); diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 42d1b7d..10290bb 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -45,6 +45,8 @@ public class UserAccountController { public Response sendEmailCode(@NotBlank(message = "接收验证码邮箱不能为空") String userAccount) { try { emailVerificationService.sendEmailCode(userAccount); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -55,6 +57,8 @@ public Response sendEmailCode(@NotBlank(message = "接收验证码邮箱不能 public Response register(@RequestBody RegisterRequestDTO request) { try { userAccountService.register(request.getUserAccount(), request.getUserPassword(), request.getUserName(), request.getCheckCode()); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -67,6 +71,8 @@ public Response login(@RequestBody LoginRequestDTO request) { try { user = userAccountService.login(request.getUserAccount(), request.getUserPassword()); log.info("用户 {} 登录", request.getUserAccount()); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -79,6 +85,8 @@ public Response logout(@RequestHeader("refresh-token") String refreshToken) { try { userAccountService.logout(refreshToken); log.info("用户 {} 登出", UserContext.getUserId()); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -89,11 +97,12 @@ public Response logout(@RequestHeader("refresh-token") String refreshToken) { * 重置密码(忘记密码) */ @PostMapping("/resetPassword") - @Validated public Response resetPassword(@NotBlank String userAccount, @NotBlank @Pattern(regexp = Constants.REGEX_PASSWORD) String newPassword, @NotBlank String checkCode) { try { userAccountService.resetPassword(userAccount, newPassword, checkCode); log.info("账号 {} 重置密码", userAccount); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -110,6 +119,8 @@ public Response updatePassword(@NotBlank String oldPassword, @NotBlank @Pattern( try { userAccountService.updatePassword(userId, oldPassword, newPassword); log.info("userId {} 修改密码", userId); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -123,8 +134,8 @@ public Response> refreshToken(@RequestHeader("refresh-token" try { newToken = userAccountService.refreshToken(refreshToken); log.info("用户刷新access-token"); - } catch (Exception e) { - throw new AppException(e.getMessage()); + } catch (AppException e) { + throw new AppException(e.getCode(), e.getMessage()); } return Response.SYSTEM_SUCCESS(newToken); } From 8d4a5000ec9114830455f01c8329e439f2d3c0d9 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 14:08:32 +0800 Subject: [PATCH 12/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96SSE=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E8=B6=85=E6=97=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/AiSolveController.java | 105 ++++++++++++------ 1 file changed, 68 insertions(+), 37 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java index ac6d294..8f10cd0 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java @@ -8,6 +8,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; /** * @Auth : Malog @@ -30,59 +31,89 @@ public SseEmitter stream(@RequestParam("question") String question) { // 设置超时时间为5分钟 SseEmitter emitter = new SseEmitter(300000L); - // 添加连接状态标志 - final boolean[] isConnectionActive = {true}; + // 使用AtomicBoolean确保线程安全 + final AtomicBoolean isConnectionActive = new java.util.concurrent.atomic.AtomicBoolean(true); // 设置超时和完成回调 emitter.onTimeout(() -> { log.warn("SSE connection timeout for question: {}", question); - isConnectionActive[0] = false; - emitter.complete(); + isConnectionActive.set(false); + try { + emitter.complete(); + } catch (Exception e) { + log.debug("Error completing emitter on timeout: {}", e.getMessage()); + } }); emitter.onCompletion(() -> { log.info("SSE connection completed for question: {}", question); - isConnectionActive[0] = false; + isConnectionActive.set(false); }); emitter.onError((ex) -> { - log.error("SSE connection error for question: {}", question, ex); - isConnectionActive[0] = false; + // 区分不同类型的错误,避免记录客户端主动断开的错误 + if (ex instanceof java.io.IOException && ex.getMessage() != null && + (ex.getMessage().contains("Broken pipe") || ex.getMessage().contains("Connection reset"))) { + log.debug("Client disconnected during SSE stream for question: {}", question); + } else { + log.error("SSE connection error for question: {}", question, ex); + } + isConnectionActive.set(false); }); - try { - aiService.aiSolveQuestion(question, content -> { - // 检查连接是否仍然活跃 - if (!isConnectionActive[0]) { - log.warn("Connection is no longer active, stopping content sending"); - return; - } - - try { - emitter.send(SseEmitter.event().data(content)); - } catch (IOException e) { - log.error("Error sending SSE event, marking connection as inactive", e); - isConnectionActive[0] = false; - // 不要在这里调用completeWithError,因为连接可能已经断开 - // emitter.completeWithError(e); - } catch (IllegalStateException e) { - // 处理连接已关闭的情况 - log.warn("SSE connection already closed: {}", e.getMessage()); - isConnectionActive[0] = false; - } - }); + // 异步处理AI调用,避免阻塞HTTP线程 + java.util.concurrent.CompletableFuture.runAsync(() -> { + try { + aiService.aiSolveQuestion(question, content -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive.get()) { + log.debug("Connection is no longer active, stopping content sending"); + return; + } + + try { + emitter.send(SseEmitter.event().data(content)); + } catch (IOException e) { + // 检查是否是客户端断开连接 + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset") || + e.getMessage().contains("ClientAbortException"))) { + log.debug("Client disconnected while sending SSE data: {}", e.getMessage()); + } else { + log.error("Error sending SSE event: {}", e.getMessage()); + } + isConnectionActive.set(false); + } catch (IllegalStateException e) { + // 处理连接已关闭的情况 + log.debug("SSE connection already closed: {}", e.getMessage()); + isConnectionActive.set(false); + } catch (Exception e) { + log.error("Unexpected error sending SSE event", e); + isConnectionActive.set(false); + } + }); - // 只有在连接仍然活跃时才完成emitter - if (isConnectionActive[0]) { - emitter.complete(); - } + // 只有在连接仍然活跃时才完成emitter + if (isConnectionActive.get()) { + try { + emitter.complete(); + } catch (Exception e) { + log.debug("Error completing emitter: {}", e.getMessage()); + } + } - } catch (Exception e) { - log.error("Error during AI stream call", e); - if (isConnectionActive[0]) { - emitter.completeWithError(e); + } catch (Exception e) { + log.error("Error during AI stream call", e); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(e); + } catch (Exception ex) { + log.debug("Error completing emitter with error: {}", ex.getMessage()); + } + } } - } + }); return emitter; } From 328d3e187217e1a28059d71244547da69a4cb0bf Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 14:22:50 +0800 Subject: [PATCH 13/49] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9ai=E8=A7=A3?= =?UTF-8?q?=E9=A2=98=E8=AF=B7=E6=B1=82=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/achobeta/api/dto/AiSolveRequestDTO.java | 12 ++++++++++-- .../com/achobeta/trigger/http/AiSolveController.java | 5 ++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/refine-api/src/main/java/com/achobeta/api/dto/AiSolveRequestDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/AiSolveRequestDTO.java index 4c289f1..7dda8c9 100644 --- a/refine-api/src/main/java/com/achobeta/api/dto/AiSolveRequestDTO.java +++ b/refine-api/src/main/java/com/achobeta/api/dto/AiSolveRequestDTO.java @@ -1,22 +1,30 @@ package com.achobeta.api.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; /** * @Auth : Malog - * @Desc : + * @Desc : AI解题请求DTO * @Time : 2025/11/7 11:17 */ @Data @Builder +@NoArgsConstructor +@AllArgsConstructor public class AiSolveRequestDTO implements Serializable { /** * 问题内容 */ - private String questionContext; + @NotBlank(message = "问题内容不能为空") + @Size(max = 10000, message = "问题内容长度不能超过10000字符") + private String question; } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java index 8f10cd0..49c30b8 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/AiSolveController.java @@ -1,6 +1,8 @@ package com.achobeta.trigger.http; +import com.achobeta.api.dto.AiSolveRequestDTO; import com.achobeta.domain.ai.service.IAiService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -27,7 +29,8 @@ public class AiSolveController { private final IAiService aiService; @PostMapping("stream") - public SseEmitter stream(@RequestParam("question") String question) { + public SseEmitter stream(@Valid @RequestBody AiSolveRequestDTO requestDTO) { + String question = requestDTO.getQuestion(); // 设置超时时间为5分钟 SseEmitter emitter = new SseEmitter(300000L); From d281310f769c585bed6279fef97f2075b07ae0ea Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 14:30:05 +0800 Subject: [PATCH 14/49] =?UTF-8?q?fix:=E9=98=B2=E6=AD=A2=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=E5=BC=82=E5=B8=B8=E6=97=A5=E5=BF=97=E8=BF=87=E5=A4=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/UserAccountController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 10290bb..6ca6dfb 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -46,7 +46,7 @@ public Response sendEmailCode(@NotBlank(message = "接收验证码邮箱不能 try { emailVerificationService.sendEmailCode(userAccount); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -58,7 +58,7 @@ public Response register(@RequestBody RegisterRequestDTO request) { try { userAccountService.register(request.getUserAccount(), request.getUserPassword(), request.getUserName(), request.getCheckCode()); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -72,7 +72,7 @@ public Response login(@RequestBody LoginRequestDTO request) { user = userAccountService.login(request.getUserAccount(), request.getUserPassword()); log.info("用户 {} 登录", request.getUserAccount()); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -86,7 +86,7 @@ public Response logout(@RequestHeader("refresh-token") String refreshToken) { userAccountService.logout(refreshToken); log.info("用户 {} 登出", UserContext.getUserId()); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -102,7 +102,7 @@ public Response resetPassword(@NotBlank String userAccount, @NotBlank @Pattern(r userAccountService.resetPassword(userAccount, newPassword, checkCode); log.info("账号 {} 重置密码", userAccount); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -120,7 +120,7 @@ public Response updatePassword(@NotBlank String oldPassword, @NotBlank @Pattern( userAccountService.updatePassword(userId, oldPassword, newPassword); log.info("userId {} 修改密码", userId); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } @@ -135,7 +135,7 @@ public Response> refreshToken(@RequestHeader("refresh-token" newToken = userAccountService.refreshToken(refreshToken); log.info("用户刷新access-token"); } catch (AppException e) { - throw new AppException(e.getCode(), e.getMessage()); + return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } return Response.SYSTEM_SUCCESS(newToken); } From 7840235a94ed783301888efd69e49789f87e6664 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 14:47:03 +0800 Subject: [PATCH 15/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96ConversationCont?= =?UTF-8?q?roller=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/ConversationController.java | 377 ++++++++++++++---- 1 file changed, 290 insertions(+), 87 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/ConversationController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/ConversationController.java index c5dad02..f6b492d 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/ConversationController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/ConversationController.java @@ -36,53 +36,152 @@ public class ConversationController { */ @PostMapping("send-message") public SseEmitter sendMessage(@Valid @RequestBody SendMessageRequestDTO requestDTO) { - SseEmitter emitter = new SseEmitter(); + // 设置超时时间为5分钟 + SseEmitter emitter = new SseEmitter(300000L); + + // 使用AtomicBoolean确保线程安全 + final java.util.concurrent.atomic.AtomicBoolean isConnectionActive = new java.util.concurrent.atomic.AtomicBoolean(true); - try { - log.info("发送消息开始,conversationId:{}", requestDTO.getConversationId()); + // 设置超时和完成回调 + emitter.onTimeout(() -> { + log.warn("会话SSE连接超时,conversationId: {}", requestDTO.getConversationId()); + isConnectionActive.set(false); + try { + emitter.complete(); + } catch (Exception e) { + log.debug("超时时完成emitter出错: {}", e.getMessage()); + } + }); - // 创建AI回复处理器 - AiResponseHandler responseHandler = AiResponseHandler.builder() - .onStreaming(pureContent -> { - try { - emitter.send(SseEmitter.event().data(pureContent)); - } catch (IOException e) { - log.error("发送SSE流式事件失败", e); - emitter.completeWithError(e); - } - }) - .onCompleted(pureContent -> { - try { - emitter.send(SseEmitter.event().data(pureContent)); - } catch (IOException e) { - log.error("发送SSE完成事件失败", e); - emitter.completeWithError(e); - } - }) - .onError(errorContent -> { - log.error("AI回复错误: {}", errorContent); - emitter.completeWithError(new RuntimeException("AI回复错误: " + errorContent)); - }) - .onFinish(emitter::complete) - .build(); - - // 调用会话服务,使用Redis存储上下文 - boolean success = conversationService.sendMessageWithStream( - requestDTO.getConversationId(), - requestDTO.getMessage(), - responseHandler::handle); - - if (!success) { - log.error("发送消息失败,conversationId:{}", requestDTO.getConversationId()); - emitter.completeWithError(new RuntimeException("发送消息失败")); + emitter.onCompletion(() -> { + log.info("会话SSE连接已完成,conversationId: {}", requestDTO.getConversationId()); + isConnectionActive.set(false); + }); + + emitter.onError((ex) -> { + // 区分不同类型的错误,避免记录客户端主动断开的错误 + if (ex instanceof java.io.IOException && ex.getMessage() != null && + (ex.getMessage().contains("Broken pipe") || ex.getMessage().contains("Connection reset"))) { + log.debug("客户端主动断开SSE连接,conversationId: {}", requestDTO.getConversationId()); } else { - log.info("发送消息成功,conversationId:{}", requestDTO.getConversationId()); + log.error("会话SSE连接出错,conversationId: {}", requestDTO.getConversationId(), ex); } + isConnectionActive.set(false); + }); - } catch (Exception e) { - log.error("发送消息时发生异常,conversationId:{}", requestDTO.getConversationId(), e); - emitter.completeWithError(e); - } + // 异步处理AI调用,避免阻塞HTTP线程 + java.util.concurrent.CompletableFuture.runAsync(() -> { + try { + log.info("发送消息开始,conversationId:{}", requestDTO.getConversationId()); + + // 创建AI回复处理器 + AiResponseHandler responseHandler = AiResponseHandler.builder() + .onStreaming(pureContent -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive.get()) { + log.debug("连接已不活跃,停止发送内容"); + return; + } + + try { + emitter.send(SseEmitter.event().data(pureContent)); + } catch (IOException e) { + // 检查是否是客户端断开连接 + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset") || + e.getMessage().contains("ClientAbortException"))) { + log.debug("发送SSE数据时客户端断开连接: {}", e.getMessage()); + } else { + log.error("发送SSE流式事件出错: {}", e.getMessage()); + } + isConnectionActive.set(false); + } catch (IllegalStateException e) { + log.debug("SSE连接已关闭: {}", e.getMessage()); + isConnectionActive.set(false); + } catch (Exception e) { + log.error("发送SSE流式事件时发生意外错误", e); + isConnectionActive.set(false); + } + }) + .onCompleted(pureContent -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive.get()) { + log.debug("连接已不活跃,停止发送内容"); + return; + } + + try { + emitter.send(SseEmitter.event().data(pureContent)); + } catch (IOException e) { + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset") || + e.getMessage().contains("ClientAbortException"))) { + log.debug("发送SSE完成数据时客户端断开连接: {}", e.getMessage()); + } else { + log.error("发送SSE完成事件出错: {}", e.getMessage()); + } + isConnectionActive.set(false); + } catch (IllegalStateException e) { + log.debug("SSE连接已关闭: {}", e.getMessage()); + isConnectionActive.set(false); + } catch (Exception e) { + log.error("发送SSE完成事件时发生意外错误", e); + isConnectionActive.set(false); + } + }) + .onError(errorContent -> { + log.error("AI回复错误: {}", errorContent); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(new RuntimeException("AI回复错误: " + errorContent)); + } catch (Exception e) { + log.debug("完成emitter时出错: {}", e.getMessage()); + } + } + }) + .onFinish(() -> { + if (isConnectionActive.get()) { + try { + emitter.complete(); + } catch (Exception e) { + log.debug("完成emitter出错: {}", e.getMessage()); + } + } + }) + .build(); + + // 调用会话服务,使用Redis存储上下文 + boolean success = conversationService.sendMessageWithStream( + requestDTO.getConversationId(), + requestDTO.getMessage(), + responseHandler::handle); + + if (!success) { + log.error("发送消息失败,conversationId:{}", requestDTO.getConversationId()); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(new RuntimeException("发送消息失败")); + } catch (Exception e) { + log.debug("完成emitter时出错: {}", e.getMessage()); + } + } + } else { + log.info("发送消息成功,conversationId:{}", requestDTO.getConversationId()); + } + + } catch (Exception e) { + log.error("发送消息时发生异常,conversationId:{}", requestDTO.getConversationId(), e); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(e); + } catch (Exception ex) { + log.debug("完成emitter时出错: {}", ex.getMessage()); + } + } + } + }); return emitter; } @@ -93,59 +192,163 @@ public SseEmitter sendMessage(@Valid @RequestBody SendMessageRequestDTO requestD @GlobalInterception @PostMapping("solve-with-context") public SseEmitter solveWithContext(@Valid @RequestBody SolveWithContextRequestDTO requestDTO) { - SseEmitter emitter = new SseEmitter(); + //分钟 + SseEmitter emitter = new SseEmitter(300000L); + + // 使用AtomicBoolean确保线程安全 + final java.util.concurrent.atomic.AtomicBoolean isConnectionActive = new java.util.concurrent.atomic.AtomicBoolean(true); - try { - String userId = UserContext.getUserId(); - if (userId == null) { + // 获取用户ID并进行早期验证 + String userId = UserContext.getUserId(); + if (userId == null) { + try { emitter.completeWithError(new RuntimeException("用户信息获取失败")); - return emitter; + } catch (Exception e) { + log.debug("完成emitter时出错: {}", e.getMessage()); } + return emitter; + } - log.info("基于错题ID的AI对话开始,userId:{} questionId:{}", userId, requestDTO.getQuestionId()); + // 设置超时和完成回调 + emitter.onTimeout(() -> { + log.warn("错题对话SSE连接超时,questionId: {}", requestDTO.getQuestionId()); + isConnectionActive.set(false); + try { + emitter.complete(); + } catch (Exception e) { + log.debug("超时时完成emitter出错: {}", e.getMessage()); + } + }); - // 创建AI回复处理器 - AiResponseHandler responseHandler = AiResponseHandler.builder() - .onStreaming(pureContent -> { - try { - emitter.send(SseEmitter.event().data(pureContent)); - } catch (IOException e) { - log.error("发送SSE流式事件失败", e); - emitter.completeWithError(e); - } - }) - .onCompleted(pureContent -> { - try { - emitter.send(SseEmitter.event().data(pureContent)); - } catch (IOException e) { - log.error("发送SSE完成事件失败", e); - emitter.completeWithError(e); - } - }) - .onError(errorContent -> { - log.error("AI回复错误: {}", errorContent); - emitter.completeWithError(new RuntimeException("AI回复错误: " + errorContent)); - }) - .onFinish(emitter::complete) - .build(); - - // 直接使用错题ID作为会话ID进行对话(不需要创建数据库会话) - boolean success = conversationService.sendMessageWithStream( - requestDTO.getQuestionId(), // 使用错题ID作为会话ID - requestDTO.getUserQuestion(), - responseHandler::handle); - - if (!success) { - log.error("AI对话失败,questionId:{}", requestDTO.getQuestionId()); - emitter.completeWithError(new RuntimeException("AI对话失败")); + emitter.onCompletion(() -> { + log.info("错题对话SSE连接已完成,questionId: {}", requestDTO.getQuestionId()); + isConnectionActive.set(false); + }); + + emitter.onError((ex) -> { + // 区分不同类型的错误,避免记录客户端主动断开的错误 + if (ex instanceof java.io.IOException && ex.getMessage() != null && + (ex.getMessage().contains("Broken pipe") || ex.getMessage().contains("Connection reset"))) { + log.debug("客户端主动断开SSE连接,questionId: {}", requestDTO.getQuestionId()); } else { - log.info("AI对话成功,questionId:{}", requestDTO.getQuestionId()); + log.error("错题对话SSE连接出错,questionId: {}", requestDTO.getQuestionId(), ex); } + isConnectionActive.set(false); + }); - } catch (Exception e) { - log.error("基于错题ID的AI对话时发生异常,questionId:{}", requestDTO.getQuestionId(), e); - emitter.completeWithError(e); - } + // 异步处理AI调用,避免阻塞HTTP线程 + java.util.concurrent.CompletableFuture.runAsync(() -> { + try { + log.info("基于错题ID的AI对话开始,userId:{} questionId:{}", userId, requestDTO.getQuestionId()); + + // 创建AI回复处理器 + AiResponseHandler responseHandler = AiResponseHandler.builder() + .onStreaming(pureContent -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive.get()) { + log.debug("连接已不活跃,停止发送内容"); + return; + } + + try { + emitter.send(SseEmitter.event().data(pureContent)); + } catch (IOException e) { + // 检查是否是客户端断开连接 + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset") || + e.getMessage().contains("ClientAbortException"))) { + log.debug("发送SSE数据时客户端断开连接: {}", e.getMessage()); + } else { + log.error("发送SSE流式事件出错: {}", e.getMessage()); + } + isConnectionActive.set(false); + } catch (IllegalStateException e) { + log.debug("SSE连接已关闭: {}", e.getMessage()); + isConnectionActive.set(false); + } catch (Exception e) { + log.error("发送SSE流式事件时发生意外错误", e); + isConnectionActive.set(false); + } + }) + .onCompleted(pureContent -> { + // 检查连接是否仍然活跃 + if (!isConnectionActive.get()) { + log.debug("连接已不活跃,停止发送内容"); + return; + } + + try { + emitter.send(SseEmitter.event().data(pureContent)); + } catch (IOException e) { + if (e.getMessage() != null && + (e.getMessage().contains("Broken pipe") || + e.getMessage().contains("Connection reset") || + e.getMessage().contains("ClientAbortException"))) { + log.debug("发送SSE完成数据时客户端断开连接: {}", e.getMessage()); + } else { + log.error("发送SSE完成事件出错: {}", e.getMessage()); + } + isConnectionActive.set(false); + } catch (IllegalStateException e) { + log.debug("SSE连接已关闭: {}", e.getMessage()); + isConnectionActive.set(false); + } catch (Exception e) { + log.error("发送SSE完成事件时发生意外错误", e); + isConnectionActive.set(false); + } + }) + .onError(errorContent -> { + log.error("AI回复错误: {}", errorContent); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(new RuntimeException("AI回复错误: " + errorContent)); + } catch (Exception e) { + log.debug("完成emitter时出错: {}", e.getMessage()); + } + } + }) + .onFinish(() -> { + if (isConnectionActive.get()) { + try { + emitter.complete(); + } catch (Exception e) { + log.debug("完成emitter出错: {}", e.getMessage()); + } + } + }) + .build(); + + // 直接使用错题ID作为会话ID进行对话(不需要创建数据库会话) + boolean success = conversationService.sendMessageWithStream( + requestDTO.getQuestionId(), // 使用错题ID作为会话ID + requestDTO.getUserQuestion(), + responseHandler::handle); + + if (!success) { + log.error("AI对话失败,questionId:{}", requestDTO.getQuestionId()); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(new RuntimeException("AI对话失败")); + } catch (Exception e) { + log.debug("完成emitter时出错: {}", e.getMessage()); + } + } + } else { + log.info("AI对话成功,questionId:{}", requestDTO.getQuestionId()); + } + + } catch (Exception e) { + log.error("基于错题ID的AI对话时发生异常,questionId:{}", requestDTO.getQuestionId(), e); + if (isConnectionActive.get()) { + try { + emitter.completeWithError(e); + } catch (Exception ex) { + log.debug("完成emitter时出错: {}", ex.getMessage()); + } + } + } + }); return emitter; } From c5ad3347fef93ad536179e01902ffe9ed29056c8 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 15:10:42 +0800 Subject: [PATCH 16/49] fix: --- refine-app/src/main/java/com/achobeta/jwt/JwtTool.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java index e5e3839..9a7c53c 100644 --- a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java +++ b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java @@ -130,7 +130,7 @@ private TokenPayload parseToken(String token, String expectedType) { throw new UnauthorizedException("未登录:token为空"); } - JWT jwt; + JWT jwt = null; try { // 2. 解析Token jwt = JWT.of(token).setSigner(jwtSigner); @@ -141,8 +141,6 @@ private TokenPayload parseToken(String token, String expectedType) { // 细分异常:过期/签名无效 if (e.getMessage().contains("is before now:")) { throw new UnauthorizedException(expectedType + "-token已过期", e); - } else { - throw new UnauthorizedException(expectedType + "-token签名无效", e); } } catch (Exception e) { throw new UnauthorizedException("无效的" + expectedType + "-token:格式错误", e); From 103bb27e57e816d7372221f81fef4ca0ce5c8e72 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 15:08:03 +0800 Subject: [PATCH 17/49] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=BB=E9=A1=B5?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/AILearningSuggessionController.java | 7 ++++--- .../achobeta/trigger/http/LearningOverviewController.java | 8 ++++---- .../achobeta/trigger/http/ReviewFeedbackController.java | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java index 1fe5293..ca78fe7 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/AILearningSuggessionController.java @@ -3,6 +3,7 @@ import com.achobeta.api.dto.KeyPointDTO; import com.achobeta.domain.aisuggession.model.valobj.KeyPointVO; import com.achobeta.domain.aisuggession.service.IAILearningSuggessionService; +import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.UserContext; import com.achobeta.types.enums.GlobalServiceStatusCode; @@ -28,20 +29,20 @@ public class AILearningSuggessionController { @RequestMapping("/get_key_point") @GlobalInterception - public List getKeyPoint() { + public Response> getKeyPoint() { String userId = UserContext.getUserId(); List keyPointVOS = null; try { log.info("用户获取AI学习建议,userId:{}", userId); keyPointVOS = service.getKeyPoint(userId); } catch (Exception e) { - throw new AppException(GlobalServiceStatusCode.AI_RESPONSE_TIMEOUT); + return Response.CUSTOMIZE_ERROR(GlobalServiceStatusCode.AI_RESPONSE_TIMEOUT); } List keyPointDTOS = keyPointVOS.stream() .map(keyPointVO -> KeyPointDTO.builder() .knowledgePoint(keyPointVO.getKnowledgePoint()) .reviewReason(keyPointVO.getReviewReason()) .build()).toList(); - return keyPointDTOS; + return Response.SYSTEM_SUCCESS(keyPointDTOS); } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java index 9773613..aba8f91 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/LearningOverviewController.java @@ -45,7 +45,7 @@ public Response getOverview() { log.info("用户获取学习概览,userId:{}", userId); vo = service.getOverview(userId); } catch (Exception e) { - throw new AppException(REQUEST_NOT_VALID); + return Response.CUSTOMIZE_ERROR(REQUEST_NOT_VALID); } return Response.SYSTEM_SUCCESS(StudyOverviewDTO.builder() .questionsNum(vo.getQuestionsNum()) @@ -62,7 +62,7 @@ public Response getOverview() { */ @GetMapping("/get_study_dynamic") @GlobalInterception - public ResponseEntity getStudyDynamic(){ + public Response getStudyDynamic(){ String userId = UserContext.getUserId(); LearningDynamicVO result = null; try { @@ -70,8 +70,8 @@ public ResponseEntity getStudyDynamic(){ result = service.getStudyDynamic(userId); } catch (Exception e) { log.error("getStudyDynamic error", e); - throw new AppException(GET_STUDY_DYNAMIC_FAIL); + return Response.CUSTOMIZE_ERROR(GET_STUDY_DYNAMIC_FAIL); } - return ResponseEntity.ok(result); + return Response.SYSTEM_SUCCESS(result); } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java index 0adbfc2..1ffa01a 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java @@ -60,7 +60,7 @@ public Response getOverdueReviewCount() { log.info("用户获取待复习题目数量结束,userId:{} count:{}", userId, overdueCountVO.getCount()); }catch (Exception e){ log.error("用户获取待复习题目数量失败!userId:{}", userId, e); - throw new AppException(GET_OVERDUE_REVIEW_COUNT_FAIL); + return Response.CUSTOMIZE_ERROR(GET_OVERDUE_REVIEW_COUNT_FAIL); } return Response.SYSTEM_SUCCESS(OverdueReviewDTO.builder() .count(overdueCountVO.getCount()) @@ -82,7 +82,7 @@ public Response> getTrickyKnowledgePoint(){ trickyKnowledgePointVOS = reviewFeedbackService.getTrickyKnowledgePoint(userId); }catch (Exception e){ log.error("用户获取近期出错最多的知识点失败!userId:{}", userId, e); - throw new AppException(GET_TRICKY_KNOWLEDGE_POINT_FAIL); + return Response.CUSTOMIZE_ERROR(GET_TRICKY_KNOWLEDGE_POINT_FAIL); } List trickyKnowledgePointDTOS = trickyKnowledgePointVOS.stream() From f1d7a27349d44d28c96325a70cf4d380554cff55 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 15:33:08 +0800 Subject: [PATCH 18/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96ConversationCont?= =?UTF-8?q?roller=E4=B8=8A=E4=B8=8B=E6=96=87=E5=AD=98=E5=82=A8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ConversationServiceImpl.java | 17 ++- .../port/redis/IQuestionRedisRepository.java | 44 +++++++ .../redis/QuestionRedisRepository.java | 109 ++++++++++++++++++ .../achobeta/trigger/http/OcrController.java | 9 ++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 refine-domain/src/main/java/com/achobeta/domain/ocr/adapter/port/redis/IQuestionRedisRepository.java create mode 100644 refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/redis/QuestionRedisRepository.java diff --git a/refine-domain/src/main/java/com/achobeta/domain/conversation/service/impl/ConversationServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/conversation/service/impl/ConversationServiceImpl.java index 56bda37..62e55ca 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/conversation/service/impl/ConversationServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/conversation/service/impl/ConversationServiceImpl.java @@ -4,6 +4,8 @@ import com.achobeta.domain.conversation.adapter.port.redis.IConversationRedisRepository; import com.achobeta.domain.conversation.model.entity.ConversationMessageEntity; import com.achobeta.domain.conversation.service.IConversationService; +import com.achobeta.domain.ocr.adapter.port.redis.IQuestionRedisRepository; +import com.achobeta.domain.ocr.model.entity.QuestionEntity; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -24,6 +26,7 @@ public class ConversationServiceImpl implements IConversationService { private final IAiService aiService; private final IConversationRedisRepository conversationRedisRepository; + private final IQuestionRedisRepository questionRedisRepository; @Override public boolean sendMessageWithStream(String conversationId, String userMessage, Consumer contentCallback) { @@ -44,11 +47,23 @@ public boolean sendMessageWithStream(String conversationId, String userMessage, log.info("未找到会话历史,使用空历史列表: conversationId={}", conversationId); } + // 尝试从Redis获取题目信息,如果conversationId是questionId的话 + QuestionEntity questionEntity = questionRedisRepository.getQuestion(conversationId); + String contextMessage = userMessage; + + // 如果找到了题目信息,将题目内容加入到用户消息的上下文中 + if (questionEntity != null) { + contextMessage = "题目内容:" + questionEntity.getQuestionText() + "\n\n用户问题:" + userMessage; + log.info("找到题目信息,已加入对话上下文: questionId={}", conversationId); + } else { + log.debug("未找到题目信息,使用原始用户消息: conversationId={}", conversationId); + } + // 创建包装的回调,用于处理AI回复完成后的保存操作 Consumer wrappedCallback = createWrappedCallback(conversationId, userMessage, conversationHistory, contentCallback); // 调用AI服务获取回复(带上下文) - aiService.aiSolveQuestionWithContext(conversationId, userMessage, conversationHistory, wrappedCallback); + aiService.aiSolveQuestionWithContext(conversationId, contextMessage, conversationHistory, wrappedCallback); return true; } catch (Exception e) { diff --git a/refine-domain/src/main/java/com/achobeta/domain/ocr/adapter/port/redis/IQuestionRedisRepository.java b/refine-domain/src/main/java/com/achobeta/domain/ocr/adapter/port/redis/IQuestionRedisRepository.java new file mode 100644 index 0000000..e76ca1e --- /dev/null +++ b/refine-domain/src/main/java/com/achobeta/domain/ocr/adapter/port/redis/IQuestionRedisRepository.java @@ -0,0 +1,44 @@ +package com.achobeta.domain.ocr.adapter.port.redis; + +import com.achobeta.domain.ocr.model.entity.QuestionEntity; + +/** + * @Auth : Malog + * @Desc : 题目Redis仓储接口 + * @Time : 2025/11/11 + */ +public interface IQuestionRedisRepository { + + /** + * 保存题目信息到Redis + * + * @param questionEntity 题目实体 + * @return 是否保存成功 + */ + boolean saveQuestion(QuestionEntity questionEntity); + + /** + * 根据题目ID从Redis获取题目信息 + * + * @param questionId 题目ID + * @return 题目实体,如果不存在则返回null + */ + QuestionEntity getQuestion(String questionId); + + /** + * 删除Redis中的题目信息 + * + * @param questionId 题目ID + * @return 是否删除成功 + */ + boolean deleteQuestion(String questionId); + + /** + * 设置题目信息的过期时间 + * + * @param questionId 题目ID + * @param expireTime 过期时间(毫秒) + * @return 是否设置成功 + */ + boolean setExpireTime(String questionId, long expireTime); +} \ No newline at end of file diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/redis/QuestionRedisRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/redis/QuestionRedisRepository.java new file mode 100644 index 0000000..5d7d3fe --- /dev/null +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/redis/QuestionRedisRepository.java @@ -0,0 +1,109 @@ +package com.achobeta.infrastructure.adapter.repository.redis; + +import com.achobeta.domain.IRedisService; +import com.achobeta.domain.ocr.adapter.port.redis.IQuestionRedisRepository; +import com.achobeta.domain.ocr.model.entity.QuestionEntity; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +/** + * @Auth : Malog + * @Desc : 题目Redis仓储实现 + * @Time : 2025/11/11 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class QuestionRedisRepository implements IQuestionRedisRepository { + + private static final String REDIS_QUESTION_PREFIX = "question:"; + private static final long QUESTION_EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时过期时间(毫秒) + + private final IRedisService redisService; + + @Override + public boolean saveQuestion(QuestionEntity questionEntity) { + try { + if (questionEntity == null || questionEntity.getQuestionId() == null) { + log.warn("题目实体或题目ID为空,无法保存到Redis"); + return false; + } + + String redisKey = REDIS_QUESTION_PREFIX + questionEntity.getQuestionId(); + + // 保存到Redis,设置24小时过期时间 + redisService.setValue(redisKey, questionEntity, QUESTION_EXPIRE_TIME); + + log.debug("题目信息保存到Redis成功: questionId={}", questionEntity.getQuestionId()); + return true; + } catch (Exception e) { + log.error("保存题目信息到Redis时发生异常: questionId={}", + questionEntity != null ? questionEntity.getQuestionId() : null, e); + return false; + } + } + + @Override + public QuestionEntity getQuestion(String questionId) { + try { + if (questionId == null || questionId.trim().isEmpty()) { + log.warn("题目ID为空,无法从Redis获取题目信息"); + return null; + } + + String redisKey = REDIS_QUESTION_PREFIX + questionId; + QuestionEntity questionEntity = redisService.getValue(redisKey); + + if (questionEntity != null) { + log.debug("从Redis获取题目信息成功: questionId={}", questionId); + } else { + log.debug("Redis中未找到题目信息: questionId={}", questionId); + } + + return questionEntity; + } catch (Exception e) { + log.error("从Redis获取题目信息失败: questionId={}", questionId, e); + return null; + } + } + + @Override + public boolean deleteQuestion(String questionId) { + try { + if (questionId == null || questionId.trim().isEmpty()) { + log.warn("题目ID为空,无法删除Redis中的题目信息"); + return false; + } + + String redisKey = REDIS_QUESTION_PREFIX + questionId; + redisService.remove(redisKey); + + log.debug("删除Redis中的题目信息成功: questionId={}", questionId); + return true; + } catch (Exception e) { + log.error("删除Redis中的题目信息时发生异常: questionId={}", questionId, e); + return false; + } + } + + @Override + public boolean setExpireTime(String questionId, long expireTime) { + try { + if (questionId == null || questionId.trim().isEmpty()) { + log.warn("题目ID为空,无法设置过期时间"); + return false; + } + + String redisKey = REDIS_QUESTION_PREFIX + questionId; + // TODO: 实现设置过期时间的逻辑,当前Redis服务可能不支持expire方法 + // boolean success = redisService.expire(redisKey, expireTime); + + log.debug("设置题目信息过期时间: questionId={}, expireTime={}ms", questionId, expireTime); + return true; + } catch (Exception e) { + log.error("设置题目信息过期时间时发生异常: questionId={}", questionId, e); + return false; + } + } +} \ No newline at end of file diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java index 5131c7e..9503202 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java @@ -4,6 +4,7 @@ import com.achobeta.domain.ocr.model.entity.QuestionEntity; import com.achobeta.domain.ocr.service.IMistakeQuestionService; import com.achobeta.domain.ocr.service.IOcrService; +import com.achobeta.domain.ocr.adapter.port.redis.IQuestionRedisRepository; import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.UserContext; @@ -30,6 +31,7 @@ public class OcrController { private final IOcrService ocrService; private final IMistakeQuestionService mistakeQuestionService; + private final IQuestionRedisRepository questionRedisRepository; /** * 抽取第一个问题 @@ -70,6 +72,13 @@ public Response extractFirst(@RequestPart("file") Multi userId, questionEntity.getQuestionId()); } + // 将题目信息保存到Redis中,用于后续对话查询 + boolean redisSaveSuccess = questionRedisRepository.saveQuestion(questionEntity); + if (!redisSaveSuccess) { + log.warn("题目信息保存到Redis失败,但继续返回OCR识别结果: userId={}, questionId={}", + userId, questionEntity.getQuestionId()); + } + // 返回响应 return Response.builder() .code(GlobalServiceStatusCode.SYSTEM_SUCCESS.getCode()) From 3f8f43a88a4b372b54e666f71537a20a02b431f9 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 16:00:14 +0800 Subject: [PATCH 19/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=94=99?= =?UTF-8?q?=E5=9B=A0=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/MistakeReasonToggleRequestDTO.java | 36 ++++++ .../service/IMistakeReasonService.java | 14 +++ .../impl/MistakeReasonServiceImpl.java | 74 +++++++++++ .../trigger/http/MistakeReasonController.java | 117 +++++++++++++++--- .../types/enums/GlobalServiceStatusCode.java | 17 ++- 5 files changed, 242 insertions(+), 16 deletions(-) create mode 100644 refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java diff --git a/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java new file mode 100644 index 0000000..eb69de0 --- /dev/null +++ b/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java @@ -0,0 +1,36 @@ +package com.achobeta.api.dto; + +import com.achobeta.types.annotation.FieldDesc; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import jakarta.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @Auth : Malog + * @Desc : 简化错因切换请求DTO + * @Time : 2025/11/10 + */ +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MistakeReasonToggleRequestDTO implements Serializable { + + @NotBlank(message = "用户ID不能为空") + @FieldDesc(name = "用户ID") + private String userId; + + @NotBlank(message = "题目ID不能为空") + @FieldDesc(name = "题目ID") + private String questionId; + + @NotBlank(message = "错因参数名不能为空") + @FieldDesc(name = "错因参数名") + private String reasonName; +} \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java index 8f404ad..893c5b0 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java @@ -39,4 +39,18 @@ MistakeReasonVO updateOtherReasonText( MistakeReasonVO getMistakeReasons( String userId, String questionId); + + /** + * 简化的错因状态切换 + * 自动查询数据库中的错因状态,并进行0/1切换 + * + * @param userId 用户ID + * @param questionId 题目ID + * @param reasonName 要切换的错因名称 + * @return 错因管理响应 + */ + MistakeReasonVO toggleMistakeReasonByName( + String userId, + String questionId, + String reasonName); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java index 1f3675b..b50866c 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java @@ -134,4 +134,78 @@ public MistakeReasonVO getMistakeReasons(String userId, String questionId) { return MistakeReasonVO.error(userId, questionId, "系统异常: " + e.getMessage()); } } + + @Override + public MistakeReasonVO toggleMistakeReasonByName(String userId, String questionId, String reasonName) { + try { + log.info("开始简化切换错因状态: userId={}, questionId={}, reasonName={}", + userId, questionId, reasonName); + + // 获取当前错题信息 + var currentReasons = mistakeReasonRepository.getMistakeReasons(userId, questionId); + + if (currentReasons == null) { + return MistakeReasonVO.error(userId, questionId, "未找到对应的错题记录"); + } + + // 根据错因名称切换状态(0变1,1变0) + boolean toggleSuccess = toggleReasonByName(currentReasons, reasonName); + if (!toggleSuccess) { + return MistakeReasonVO.error(userId, questionId, + "不支持的错因类型: " + reasonName); + } + + // 更新数据库 + boolean success = mistakeReasonRepository.updateMistakeReasons(currentReasons); + + if (success) { + // 重新获取更新后的数据 + var updatedReasons = mistakeReasonRepository.getMistakeReasons(userId, questionId); + return MistakeReasonVO.success(updatedReasons); + } else { + return MistakeReasonVO.error(userId, questionId, "更新错因状态失败"); + } + } catch (Exception e) { + log.error("简化切换错因状态时发生异常: userId={}, questionId={}, reasonName={}", + userId, questionId, reasonName, e); + return MistakeReasonVO.error(userId, questionId, "系统异常: " + e.getMessage()); + } + } + + /** + * 根据错因名称切换状态 + * @param reasonVO 错因值对象 + * @param reasonName 错因名称 + * @return 是否切换成功 + */ + private boolean toggleReasonByName(MistakeReasonVO reasonVO, String reasonName) { + switch (reasonName) { + case "isCareless": + Integer careless = reasonVO.getIsCareless(); + reasonVO.setIsCareless(careless == null || careless == 0 ? 1 : 0); + return true; + case "isUnfamiliar": + Integer unfamiliar = reasonVO.getIsUnfamiliar(); + reasonVO.setIsUnfamiliar(unfamiliar == null || unfamiliar == 0 ? 1 : 0); + return true; + case "isCalculateErr": + Integer calculateErr = reasonVO.getIsCalculateErr(); + reasonVO.setIsCalculateErr(calculateErr == null || calculateErr == 0 ? 1 : 0); + return true; + case "isTimeShortage": + Integer timeShortage = reasonVO.getIsTimeShortage(); + reasonVO.setIsTimeShortage(timeShortage == null || timeShortage == 0 ? 1 : 0); + return true; + case "otherReason": + Integer otherReason = reasonVO.getOtherReason(); + reasonVO.setOtherReason(otherReason == null || otherReason == 0 ? 1 : 0); + // 如果关闭其他原因,清空文本内容 + if (reasonVO.getOtherReason() == 0) { + reasonVO.setOtherReasonText(""); + } + return true; + default: + return false; + } + } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java index 08250f1..bb4a1a6 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java @@ -2,6 +2,7 @@ import com.achobeta.api.dto.MistakeReasonRequestDTO; import com.achobeta.api.dto.MistakeReasonResponseDTO; +import com.achobeta.api.dto.MistakeReasonToggleRequestDTO; import com.achobeta.api.dto.StudyNoteRequestDTO; import com.achobeta.api.dto.StudyNoteResponseDTO; import com.achobeta.domain.mistake.model.valobj.MistakeReasonVO; @@ -9,6 +10,7 @@ import com.achobeta.domain.mistake.service.IMistakeReasonService; import com.achobeta.domain.mistake.service.IStudyNoteService; import com.achobeta.types.Response; +import com.achobeta.types.enums.GlobalServiceStatusCode; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -58,12 +60,16 @@ public Response toggleMistakeReason( if (response.getSuccess()) { log.info("用户切换错因状态成功,userId:{} questionId:{} reasonName:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), reasonName); - return Response.SYSTEM_SUCCESS(response); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getMessage()) + .data(response) + .build(); } else { log.warn("用户切换错因状态失败,userId:{} questionId:{} reasonName:{} message:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), reasonName, response.getMessage()); return Response.builder() - .code(Response.SERVICE_ERROR().getCode()) + .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_FAILED.getCode()) .info(response.getMessage()) .data(response) .build(); @@ -71,7 +77,10 @@ public Response toggleMistakeReason( } catch (Exception e) { log.error("用户切换错因状态时发生异常,userId:{} questionId:{} reasonName:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), reasonName, e); - return Response.SERVICE_ERROR("系统异常: " + e.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); } } @@ -100,12 +109,16 @@ public Response updateOtherReasonText( if (response.getSuccess()) { log.info("用户更新其他原因成功,userId:{} questionId:{}", requestDTO.getUserId(), requestDTO.getQuestionId()); - return Response.SYSTEM_SUCCESS(response); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_SUCCESS.getMessage()) + .data(response) + .build(); } else { log.warn("用户更新其他原因失败,userId:{} questionId:{} message:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), response.getMessage()); return Response.builder() - .code(Response.SERVICE_ERROR().getCode()) + .code(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_FAILED.getCode()) .info(response.getMessage()) .data(response) .build(); @@ -113,7 +126,60 @@ public Response updateOtherReasonText( } catch (Exception e) { log.error("用户更新其他原因时发生异常,userId:{} questionId:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), e); - return Response.SERVICE_ERROR("系统异常: " + e.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); + } + } + + /** + * 简化的错因状态切换接口 + * 只需传入错因参数名,自动查询数据库并切换状态(0变1,1变0) + * + * @param requestDTO 简化错因切换请求DTO + * @return 错因管理响应 + */ + @PostMapping("toggle") + public Response toggleMistakeReasonSimple( + @Valid @RequestBody MistakeReasonToggleRequestDTO requestDTO) { + try { + log.info("用户简化切换错因状态开始,userId:{} questionId:{} reasonName:{}", + requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName()); + + // 调用领域服务 + MistakeReasonVO responseVO = mistakeReasonService.toggleMistakeReasonByName( + requestDTO.getUserId(), + requestDTO.getQuestionId(), + requestDTO.getReasonName()); + + // 转换为响应DTO + MistakeReasonResponseDTO response = convertToMistakeReasonResponseDTO(responseVO); + + if (response.getSuccess()) { + log.info("用户简化切换错因状态成功,userId:{} questionId:{} reasonName:{}", + requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getMessage()) + .data(response) + .build(); + } else { + log.warn("用户简化切换错因状态失败,userId:{} questionId:{} reasonName:{} message:{}", + requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName(), response.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_FAILED.getCode()) + .info(response.getMessage()) + .data(response) + .build(); + } + } catch (Exception e) { + log.error("用户简化切换错因状态时发生异常,userId:{} questionId:{} reasonName:{}", + requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName(), e); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); } } @@ -139,19 +205,26 @@ public Response getMistakeReasons( if (response.getSuccess()) { log.info("获取错因信息成功,userId:{} questionId:{}", userId, questionId); - return Response.SYSTEM_SUCCESS(response); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_GET_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.MISTAKE_REASON_GET_SUCCESS.getMessage()) + .data(response) + .build(); } else { log.warn("获取错因信息失败,userId:{} questionId:{} message:{}", userId, questionId, response.getMessage()); return Response.builder() - .code(Response.SERVICE_ERROR().getCode()) + .code(GlobalServiceStatusCode.MISTAKE_REASON_NOT_FOUND.getCode()) .info(response.getMessage()) .data(response) .build(); } } catch (Exception e) { log.error("获取错因信息时发生异常,userId:{} questionId:{}", userId, questionId, e); - return Response.SERVICE_ERROR("系统异常: " + e.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); } } @@ -180,12 +253,16 @@ public Response submitStudyNote( if (response.getSuccess()) { log.info("用户提交错题笔记成功,userId:{} questionId:{}", requestDTO.getUserId(), requestDTO.getQuestionId()); - return Response.SYSTEM_SUCCESS(response); + return Response.builder() + .code(GlobalServiceStatusCode.STUDY_NOTE_SUBMIT_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.STUDY_NOTE_SUBMIT_SUCCESS.getMessage()) + .data(response) + .build(); } else { log.warn("用户提交错题笔记失败,userId:{} questionId:{} message:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), response.getMessage()); return Response.builder() - .code(Response.SERVICE_ERROR().getCode()) + .code(GlobalServiceStatusCode.STUDY_NOTE_UPDATE_FAILED.getCode()) .info(response.getMessage()) .data(response) .build(); @@ -193,7 +270,10 @@ public Response submitStudyNote( } catch (Exception e) { log.error("用户提交错题笔记时发生异常,userId:{} questionId:{}", requestDTO.getUserId(), requestDTO.getQuestionId(), e); - return Response.SERVICE_ERROR("系统异常: " + e.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); } } @@ -219,19 +299,26 @@ public Response getStudyNote( if (response.getSuccess()) { log.info("获取错题笔记成功,userId:{} questionId:{}", userId, questionId); - return Response.SYSTEM_SUCCESS(response); + return Response.builder() + .code(GlobalServiceStatusCode.STUDY_NOTE_GET_SUCCESS.getCode()) + .info(GlobalServiceStatusCode.STUDY_NOTE_GET_SUCCESS.getMessage()) + .data(response) + .build(); } else { log.warn("获取错题笔记失败,userId:{} questionId:{} message:{}", userId, questionId, response.getMessage()); return Response.builder() - .code(Response.SERVICE_ERROR().getCode()) + .code(GlobalServiceStatusCode.MISTAKE_REASON_NOT_FOUND.getCode()) .info(response.getMessage()) .data(response) .build(); } } catch (Exception e) { log.error("获取错题笔记时发生异常,userId:{} questionId:{}", userId, questionId, e); - return Response.SERVICE_ERROR("系统异常: " + e.getMessage()); + return Response.builder() + .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) + .info("系统异常: " + e.getMessage()) + .build(); } } diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index 617f8c4..75a1821 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -60,7 +60,22 @@ public enum GlobalServiceStatusCode { GET_STUDY_DYNAMIC_FAIL(10004, "获取学习动态失败,请稍后再试"), GET_OVERDUE_REVIEW_COUNT_FAIL(10005, "获取待复习题目数量失败,请稍后再试"), GET_TRICKY_KNOWLEDGE_POINT_FAIL(10006, "获取易错知识点失败,请稍后再试"), - AI_RESPONSE_TIMEOUT(10007, "AI响应超时") + AI_RESPONSE_TIMEOUT(10007, "AI响应超时"), + + /* 错因管理相关状态码 11001-11100 */ + MISTAKE_REASON_SUCCESS(11001, "错因操作成功"), + MISTAKE_REASON_TOGGLE_SUCCESS(11002, "错因状态切换成功"), + MISTAKE_REASON_UPDATE_SUCCESS(11003, "错因信息更新成功"), + MISTAKE_REASON_GET_SUCCESS(11004, "错因信息获取成功"), + STUDY_NOTE_SUBMIT_SUCCESS(11005, "错题笔记提交成功"), + STUDY_NOTE_GET_SUCCESS(11006, "错题笔记获取成功"), + + MISTAKE_REASON_NOT_FOUND(11101, "未找到对应的错题记录"), + MISTAKE_REASON_INVALID_PARAM(11102, "错因参数无效"), + MISTAKE_REASON_UPDATE_FAILED(11103, "错因状态更新失败"), + MISTAKE_REASON_TOGGLE_FAILED(11104, "错因状态切换失败"), + STUDY_NOTE_UPDATE_FAILED(11105, "错题笔记更新失败"), + MISTAKE_REASON_SYSTEM_ERROR(11106, "错因管理系统异常") ; private Integer code; From c8d14ae0d7233ca12c15d5c6c4a0d3f124da126e Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 16:15:09 +0800 Subject: [PATCH 20/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=94=A8?= =?UTF-8?q?=E6=88=B7id=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/dto/MistakeReasonToggleRequestDTO.java | 4 ---- .../trigger/http/MistakeReasonController.java | 14 +++++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java index eb69de0..c38ac82 100644 --- a/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java +++ b/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.java @@ -22,10 +22,6 @@ @NoArgsConstructor public class MistakeReasonToggleRequestDTO implements Serializable { - @NotBlank(message = "用户ID不能为空") - @FieldDesc(name = "用户ID") - private String userId; - @NotBlank(message = "题目ID不能为空") @FieldDesc(name = "题目ID") private String questionId; diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java index bb4a1a6..8670e97 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java @@ -10,6 +10,8 @@ import com.achobeta.domain.mistake.service.IMistakeReasonService; import com.achobeta.domain.mistake.service.IStudyNoteService; import com.achobeta.types.Response; +import com.achobeta.types.annotation.GlobalInterception; +import com.achobeta.types.common.UserContext; import com.achobeta.types.enums.GlobalServiceStatusCode; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -140,16 +142,18 @@ public Response updateOtherReasonText( * @param requestDTO 简化错因切换请求DTO * @return 错因管理响应 */ + @GlobalInterception @PostMapping("toggle") public Response toggleMistakeReasonSimple( @Valid @RequestBody MistakeReasonToggleRequestDTO requestDTO) { + String userId = UserContext.getUserId(); try { log.info("用户简化切换错因状态开始,userId:{} questionId:{} reasonName:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName()); + userId, requestDTO.getQuestionId(), requestDTO.getReasonName()); // 调用领域服务 MistakeReasonVO responseVO = mistakeReasonService.toggleMistakeReasonByName( - requestDTO.getUserId(), + userId, requestDTO.getQuestionId(), requestDTO.getReasonName()); @@ -158,7 +162,7 @@ public Response toggleMistakeReasonSimple( if (response.getSuccess()) { log.info("用户简化切换错因状态成功,userId:{} questionId:{} reasonName:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName()); + userId, requestDTO.getQuestionId(), requestDTO.getReasonName()); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getCode()) .info(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_SUCCESS.getMessage()) @@ -166,7 +170,7 @@ public Response toggleMistakeReasonSimple( .build(); } else { log.warn("用户简化切换错因状态失败,userId:{} questionId:{} reasonName:{} message:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName(), response.getMessage()); + userId, requestDTO.getQuestionId(), requestDTO.getReasonName(), response.getMessage()); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_TOGGLE_FAILED.getCode()) .info(response.getMessage()) @@ -175,7 +179,7 @@ public Response toggleMistakeReasonSimple( } } catch (Exception e) { log.error("用户简化切换错因状态时发生异常,userId:{} questionId:{} reasonName:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), requestDTO.getReasonName(), e); + userId, requestDTO.getQuestionId(), requestDTO.getReasonName(), e); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) .info("系统异常: " + e.getMessage()) From a622b0d0dacda80e230a255b4f920ea03afe1d72 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 16:50:33 +0800 Subject: [PATCH 21/49] fix: --- .../java/com/achobeta/config/RagConfig.java | 43 ++++++++++++++++--- .../main/java/com/achobeta/jwt/JwtTool.java | 6 ++- .../service/impl/QuestionServiceImpl.java | 7 +-- .../user/service/IUserAccountService.java | 3 +- .../impl/EmailVerificationServiceImpl.java | 4 +- .../service/impl/UserAccountServiceImpl.java | 9 ++-- .../adapter/repository/MistakeRepository.java | 2 +- .../trigger/http/UserAccountController.java | 3 +- .../types/enums/GlobalServiceStatusCode.java | 3 +- 9 files changed, 56 insertions(+), 24 deletions(-) diff --git a/refine-app/src/main/java/com/achobeta/config/RagConfig.java b/refine-app/src/main/java/com/achobeta/config/RagConfig.java index 4ea9db1..da9e7c8 100644 --- a/refine-app/src/main/java/com/achobeta/config/RagConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/RagConfig.java @@ -3,12 +3,19 @@ import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.splitter.DocumentByParagraphSplitter; +import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; +import dev.langchain4j.rag.content.Content; import dev.langchain4j.rag.content.retriever.ContentRetriever; import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; +import dev.langchain4j.rag.query.Query; +import dev.langchain4j.store.embedding.EmbeddingSearchRequest; +import dev.langchain4j.store.embedding.EmbeddingSearchResult; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.EmbeddingStoreIngestor; +import dev.langchain4j.store.embedding.filter.Filter; +import dev.langchain4j.store.embedding.filter.MetadataFilterBuilder; import dev.langchain4j.store.embedding.pgvector.PgVectorEmbeddingStore; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; @@ -101,13 +108,35 @@ public ContentRetriever contentRetriever() { } // 自定义内容加载器 - EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder() - .embeddingStore(embeddingStore) - .embeddingModel(qwenEmbeddingModel) - .maxResults(5) // 最多返回5条结果 - .minScore(0.75) // 过滤掉分数小于0.75的结果 - .build(); - return retriever; + return new ContentRetriever() { + @Override + public List retrieve(Query query) { + // 1. 从查询元数据中动态提取业务传入的memoryId + String subject = String.valueOf(query.metadata()); + log.info("RAG检索 - 动态过滤 subject: {}", subject); + + // 2. 生成查询向量 + Embedding embeddedQuery = qwenEmbeddingModel.embed(query.text()).content(); + + // 3. 用自定义的MemoryIdFilter构建动态过滤条件 + Filter filter = MetadataFilterBuilder.metadataKey("subject").isEqualTo(subject); + + // 4. 执行带动态过滤的检索 + EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() + .queryEmbedding(embeddedQuery) + .maxResults(5) // 最多返回5条 + .minScore(0.75) // 相关度阈值 + .filter(filter) // 动态过滤条件 + .build(); + + EmbeddingSearchResult searchResult = embeddingStore.search(searchRequest); + + // 5. 转换结果为Content列表 + return searchResult.matches().stream() + .map(match -> Content.from(match.embedded())) + .collect(Collectors.toList()); + } + }; } private void truncateEmbeddingTable() { diff --git a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java index 9a7c53c..cb503bf 100644 --- a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java +++ b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java @@ -86,7 +86,7 @@ public String createRefreshToken(String userId) { * 通用Token生成方法 */ private String createToken(Map claims, Duration ttl) { - claims.put("iat", System.currentTimeMillis()); // 添加签发时间,毫秒值 + claims.put("iat", System.currentTimeMillis()/1000); // 添加签发时间,毫秒值 return JWT.create() .addPayloads(claims) // 设置载荷 .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())) // 过期时间 @@ -130,7 +130,7 @@ private TokenPayload parseToken(String token, String expectedType) { throw new UnauthorizedException("未登录:token为空"); } - JWT jwt = null; + JWT jwt; try { // 2. 解析Token jwt = JWT.of(token).setSigner(jwtSigner); @@ -141,6 +141,8 @@ private TokenPayload parseToken(String token, String expectedType) { // 细分异常:过期/签名无效 if (e.getMessage().contains("is before now:")) { throw new UnauthorizedException(expectedType + "-token已过期", e); + } else { + throw new UnauthorizedException(expectedType + "-token签名无效", e); } } catch (Exception e) { throw new UnauthorizedException("无效的" + expectedType + "-token:格式错误", e); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index 88b0f3e..33d4e6a 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -55,10 +55,10 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues String knowledgePointName = knowledgeRepository.findKnowledgeNameById(knowledgeId); if (null == subject) { - throw new AppException("找不到题目所属科目"); + throw new AppException("找不到题目所属科目,mistakeQuestionId"+mistakeQuestionId); } if (null == knowledgePointName) { - throw new AppException("找不到该题知识点名称"); + throw new AppException("找不到该题知识点名称,mistakeQuestionId"+mistakeQuestionId); } String toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; @@ -138,7 +138,7 @@ public Flux> aiJudge(String userId, String questionId, S String correctAnswer = value.getAnswer(); Integer auto = 0; // 用户可选是否自动录入TODO - if (auto==1){ + if (auto == 1) { autoRecord(userId, questionId, userAnswer, correctAnswer); } @@ -148,6 +148,7 @@ public Flux> aiJudge(String userId, String questionId, S // 将ai流式调用提交到自定义线程池 return Flux.defer(() -> aiGenerationService.aiJudgeStream(chat)) .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) .map(chunk -> ServerSentEvent.builder() .data(chunk) .build()); diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/IUserAccountService.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/IUserAccountService.java index 3574db8..0431dbf 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/IUserAccountService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/IUserAccountService.java @@ -1,6 +1,7 @@ package com.achobeta.domain.user.service; import com.achobeta.domain.user.model.valobj.UserLoginVO; +import com.achobeta.types.Response; import com.achobeta.types.exception.AppException; import java.util.Map; @@ -22,7 +23,7 @@ public interface IUserAccountService { * @return 注册成功 * @throws AppException 邮箱已注册等异常 */ - void register(String userEmail, String userPassword, String userName, String checkCode); + Response register(String userEmail, String userPassword, String userName, String checkCode); /** * 用户登录 diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/EmailVerificationServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/EmailVerificationServiceImpl.java index 559cb0e..fc27e7e 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/EmailVerificationServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/EmailVerificationServiceImpl.java @@ -47,7 +47,7 @@ public class EmailVerificationServiceImpl implements IEmailVerificationService { public void sendEmailCode(String userEmail) { // 校验邮箱格式 if (!StringTools.isEmail(userEmail)) { - throw new AppException(GlobalServiceStatusCode.USER_CAPTCHA_CODE_ERROR); + throw new AppException(GlobalServiceStatusCode.USER_EMAIL_FORMAT_ERROR); } // 检查发送频率 @@ -102,7 +102,7 @@ public void verifyCode(String userEmail, String checkCode) { // 3. 验证验证码 if (null == cachedCode) { - throw new AppException("验证码已过期,请重新获取"); + throw new AppException(GlobalServiceStatusCode.USER_EMAIL_VERIFY_CODE_ERROR); } if (!cachedCode.equals(checkCode)) { throw new AppException(GlobalServiceStatusCode.USER_CAPTCHA_CODE_ERROR); diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java index 8e3e263..c11367a 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java @@ -7,6 +7,7 @@ import com.achobeta.domain.user.service.IEmailVerificationService; import com.achobeta.domain.user.service.IUserAccountService; import com.achobeta.domain.user.service.Jwt; +import com.achobeta.types.Response; import com.achobeta.types.enums.GlobalServiceStatusCode; import com.achobeta.types.exception.AppException; import com.achobeta.types.support.util.StringTools; @@ -47,17 +48,14 @@ public class UserAccountServiceImpl implements IUserAccountService { * @throws AppException 邮箱已注册、验证码无效等异常 */ @Override - public void register(String email, String password, String userName, String checkCode) { + public Response register(String email, String password, String userName, String checkCode) { - if (!StringTools.isEmail(email)) { - throw new AppException(GlobalServiceStatusCode.USER_EMAIL_FORMAT_ERROR); - } // 先验证验证码(核心新增逻辑:调用验证码服务验证) emailVerificationService.verifyCode(email, checkCode); // 校验邮箱是否已存在 if (null != userRepository.findByAccount(email)) { - throw new AppException(GlobalServiceStatusCode.USER_EMAIL_ALREADY_EXIST); + return Response.CUSTOMIZE_MSG_ERROR(GlobalServiceStatusCode.USER_EMAIL_ALREADY_EXIST,null); } // 创建用户实体 @@ -71,6 +69,7 @@ public void register(String email, String password, String userName, String chec // 5. 保存用户 userRepository.save(user); + return Response.SYSTEM_SUCCESS(); } /** diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java index 0f21478..e71e3c5 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java @@ -37,7 +37,7 @@ public void save(MistakeQuestionEntity mistakeEntity) { public MistakeKnowledgePO findSubjectAndKnowledgeIdById(Integer mistakeQuestionId) { MistakeKnowledgePO po = mistakeQuestionMapper.findSubjectAndKnowledgeIdById(mistakeQuestionId); if (null == po) { - throw new AppException(GlobalServiceStatusCode.PARAM_NOT_VALID); + throw new AppException("可能是数据库一致性问题,mistakeQuestionId:"+mistakeQuestionId); } return po; } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 6ca6dfb..5d9ab05 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -56,13 +56,12 @@ public Response sendEmailCode(@NotBlank(message = "接收验证码邮箱不能 @PostMapping("/register") public Response register(@RequestBody RegisterRequestDTO request) { try { - userAccountService.register(request.getUserAccount(), request.getUserPassword(), request.getUserName(), request.getCheckCode()); + return userAccountService.register(request.getUserAccount(), request.getUserPassword(), request.getUserName(), request.getCheckCode()); } catch (AppException e) { return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } - return Response.SYSTEM_SUCCESS(); } @PostMapping("/login") diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index 617f8c4..c49d439 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -36,7 +36,7 @@ public enum GlobalServiceStatusCode { /* 用户错误 2001-3000 */ USER_NOT_LOGIN(2001, "用户未登录"), USER_ACCOUNT_EXPIRED(2002, "账号已过期"), - USER_CREDENTIALS_ERROR(2003, "密码错误"), + USER_CREDENTIALS_ERROR(2003, "账号或密码错误"), USER_CREDENTIALS_EXPIRED(2004, "密码过期"), USER_ACCOUNT_DISABLE(2005, "账号不可用"), USER_ACCOUNT_LOCKED(2006, "账号被锁定"), @@ -48,6 +48,7 @@ public enum GlobalServiceStatusCode { USER_EMAIL_ALREADY_EXIST(2012, "邮箱已存在"), USER_EMAIL_NOT_EXIST(2013, "邮箱不存在,请查看邮箱是否有误"), USER_ID_IS_NULL(2014, "用户id为空"), + USER_EMAIL_VERIFY_CODE_ERROR(2100, "验证码已过期,请重新获取"), USER_TYPE_EXCEPTION(2101, "用户类别异常"), From 20af7198f73ac8914eba2afa2701eaae226911c3 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 17:48:06 +0800 Subject: [PATCH 22/49] fix: --- .../src/main/java/com/achobeta/jwt/JwtTool.java | 11 +++++++++++ .../java/com/achobeta/domain/user/service/Jwt.java | 2 ++ .../user/service/impl/UserAccountServiceImpl.java | 1 + .../achobeta/trigger/http/UserAccountController.java | 7 ++++--- .../src/main/java/com/achobeta/types/Response.java | 1 - .../java/com/achobeta/types/common/Constants.java | 1 + 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java index cb503bf..7cea9fc 100644 --- a/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java +++ b/refine-app/src/main/java/com/achobeta/jwt/JwtTool.java @@ -24,6 +24,7 @@ import java.util.UUID; import static com.achobeta.types.common.Constants.USER_REFRESH_TOKEN_KEY; +import static com.achobeta.types.common.Constants.USER_TOKEN_ID_KEY; @Component @EnableConfigurationProperties(JwtProperties.class) @@ -79,6 +80,11 @@ public String createRefreshToken(String userId) { userId, jwtProperties.getRefreshTokenTtl().toMillis() ); + redis.setValue( + USER_TOKEN_ID_KEY + userId, + refreshToken, + jwtProperties.getRefreshTokenTtl().toMillis() + ); return refreshToken; } @@ -237,6 +243,11 @@ public Long getRefreshTokenIat(String refreshToken) { } + public String getRefreshToken4UserId(String userId) { + return redis.getValue(USER_TOKEN_ID_KEY + userId); + } + + /** * 内部类:封装解析后的Token载荷 */ diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java index 7e89ce0..61db798 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/Jwt.java @@ -18,4 +18,6 @@ public interface Jwt { Long getRefreshTokenIat(String refreshToken); + String getRefreshToken4UserId(String userId); + } diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java index c11367a..043237e 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java @@ -141,6 +141,7 @@ public void updatePassword(String userId, String oldPassword, String newPassword } user.encryptPassword(newPassword); userRepository.updateByUserAccount(user, user.getUserAccount()); + logout(jwt.getRefreshToken4UserId(userId)); } @Override diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 5d9ab05..016cb09 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -118,25 +118,26 @@ public Response updatePassword(@NotBlank String oldPassword, @NotBlank @Pattern( try { userAccountService.updatePassword(userId, oldPassword, newPassword); log.info("userId {} 修改密码", userId); + return Response.SYSTEM_SUCCESS("修改密码成功,请重新登录"); } catch (AppException e) { return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { throw new AppException(e.getMessage()); } - return Response.SYSTEM_SUCCESS(); } @PostMapping("/refreshToken") public Response> refreshToken(@RequestHeader("refresh-token") String refreshToken) { - Map newToken = null; + Map newToken; try { newToken = userAccountService.refreshToken(refreshToken); log.info("用户刷新access-token"); + return Response.SYSTEM_SUCCESS(newToken); } catch (AppException e) { return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } - return Response.SYSTEM_SUCCESS(newToken); + } } \ No newline at end of file diff --git a/refine-types/src/main/java/com/achobeta/types/Response.java b/refine-types/src/main/java/com/achobeta/types/Response.java index 05b2ce5..6f11b42 100644 --- a/refine-types/src/main/java/com/achobeta/types/Response.java +++ b/refine-types/src/main/java/com/achobeta/types/Response.java @@ -119,7 +119,6 @@ public static Response CUSTOMIZE_MSG_ERROR(GlobalServiceStatusCode code, .build(); } - //token异常 public static Response CUSTOMIZE_MSG_ERROR(Integer code, String msg, T data) { return Response.builder() .code(code) diff --git a/refine-types/src/main/java/com/achobeta/types/common/Constants.java b/refine-types/src/main/java/com/achobeta/types/common/Constants.java index 692e6a4..54acf5a 100644 --- a/refine-types/src/main/java/com/achobeta/types/common/Constants.java +++ b/refine-types/src/main/java/com/achobeta/types/common/Constants.java @@ -20,6 +20,7 @@ public class Constants { public static final int SEND_INTERVAL_MILLISECONDS = 60 * 1000; public static final String USER_REFRESH_TOKEN_KEY = "user:token:refresh:"; + public static final String USER_TOKEN_ID_KEY = "user:token:id:"; // 密码正则 public static final String REGEX_PASSWORD = "^(?=.*\\d)(?=.*[a-zA-Z])[\\da-zA-Z~!@#$%^&*_]{8,18}$"; From ed744a2923f1124794617445caaa26f2a58340d9 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Fri, 28 Nov 2025 19:30:19 +0800 Subject: [PATCH 23/49] =?UTF-8?q?feat:=E5=BC=BA=E5=8C=96rag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 19 +++++++----------- .../java/com/achobeta/config/RagConfig.java | 20 ++++++++++++------- .../adapter/port/AiGenerationService.java | 5 +++-- .../service/impl/QuestionServiceImpl.java | 9 +++++---- .../port/AiGenerationServiceFactory.java | 2 ++ 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/pom.xml b/pom.xml index f51ba2e..988b1b4 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ UTF-8 21 21 - 1.1.0-beta7 + 1.6.0-beta12 @@ -64,11 +64,11 @@ - - dev.langchain4j - langchain4j-core - 1.1.0 - + + + + + org.postgresql @@ -202,11 +202,6 @@ java-jwt 4.4.0 - - commons-codec - commons-codec - 1.15 - org.projectlombok lombok @@ -227,7 +222,7 @@ dev.langchain4j langchain4j - 1.1.0 + 1.6.0 dev.langchain4j diff --git a/refine-app/src/main/java/com/achobeta/config/RagConfig.java b/refine-app/src/main/java/com/achobeta/config/RagConfig.java index da9e7c8..9f7d305 100644 --- a/refine-app/src/main/java/com/achobeta/config/RagConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/RagConfig.java @@ -8,7 +8,6 @@ import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.rag.content.Content; import dev.langchain4j.rag.content.retriever.ContentRetriever; -import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.rag.query.Query; import dev.langchain4j.store.embedding.EmbeddingSearchRequest; import dev.langchain4j.store.embedding.EmbeddingSearchResult; @@ -20,7 +19,6 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -111,15 +109,23 @@ public ContentRetriever contentRetriever() { return new ContentRetriever() { @Override public List retrieve(Query query) { - // 1. 从查询元数据中动态提取业务传入的memoryId - String subject = String.valueOf(query.metadata()); - log.info("RAG检索 - 动态过滤 subject: {}", subject); + // 1. 从查询元数据中动态提取业务传入的学科(target) + Object obj = query.metadata().invocationContext().chatMemoryId(); + if (null == obj) { + log.warn("RAG检索 - 未传递 file_name 筛选条件,返回空结果"); + return List.of(); + } + String target = String.valueOf(obj); + log.info("RAG检索 - 动态过滤 file_name(模糊匹配): {}", target); // 2. 生成查询向量 Embedding embeddedQuery = qwenEmbeddingModel.embed(query.text()).content(); - // 3. 用自定义的MemoryIdFilter构建动态过滤条件 - Filter filter = MetadataFilterBuilder.metadataKey("subject").isEqualTo(subject); + // 3. 用自定义的Filter构建动态过滤条件 + //Filter filter = MetadataFilterBuilder.metadataKey("subject").isEqualTo(subject); + + //模糊匹配,自定义的Filter构建动态过滤条件 + Filter filter = MetadataFilterBuilder.metadataKey("file_name").containsString(target); // 4. 执行带动态过滤的检索 EmbeddingSearchRequest searchRequest = EmbeddingSearchRequest.builder() diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java index a0393cb..0aaf5ec 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java @@ -1,6 +1,7 @@ package com.achobeta.domain.question.adapter.port; import com.achobeta.api.dto.QuestionResponseDTO; +import dev.langchain4j.service.MemoryId; import dev.langchain4j.service.SystemMessage; import dev.langchain4j.service.UserMessage; import reactor.core.publisher.Flux; @@ -11,11 +12,11 @@ public interface AiGenerationService { @SystemMessage(fromResource = "AiGeneration.txt") - QuestionResponseDTO Generation(String message); + QuestionResponseDTO Generation(@MemoryId String subject, @UserMessage String message); //流式输出 @SystemMessage(fromResource = "AiAnalyze.txt") - Flux aiJudgeStream(@UserMessage String message); + Flux aiJudgeStream(@MemoryId String subject, @UserMessage String message); // 会话 String chat(String message); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index 33d4e6a..16d8d88 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -55,15 +55,15 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues String knowledgePointName = knowledgeRepository.findKnowledgeNameById(knowledgeId); if (null == subject) { - throw new AppException("找不到题目所属科目,mistakeQuestionId"+mistakeQuestionId); + throw new AppException("找不到题目所属科目,mistakeQuestionId" + mistakeQuestionId); } if (null == knowledgePointName) { - throw new AppException("找不到该题知识点名称,mistakeQuestionId"+mistakeQuestionId); + throw new AppException("找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); } String toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; // 调用外部接口生成新题目 - QuestionResponseDTO question = aiGenerationService.Generation(toAi); + QuestionResponseDTO question = aiGenerationService.Generation(subject, toAi); if (null == question.getContent() || null == question.getAnswer()) { throw new AppException(GlobalServiceStatusCode.QUESTION_GENERATION_FAIL); } @@ -146,7 +146,8 @@ public Flux> aiJudge(String userId, String questionId, S String chat = "请根据题目:\"" + questionContent + "\"以及正确答案:\"" + correctAnswer + "\",判断答案:\"" + correctAnswer + "\"是否正确,并给出解析。"; // 将ai流式调用提交到自定义线程池 - return Flux.defer(() -> aiGenerationService.aiJudgeStream(chat)) + String subject = value.getSubject(); + return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) .map(chunk -> ServerSentEvent.builder() diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java index 48a0373..8ce82ea 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java @@ -1,6 +1,7 @@ package com.achobeta.infrastructure.adapter.port; import com.achobeta.domain.question.adapter.port.AiGenerationService; +import dev.langchain4j.memory.chat.MessageWindowChatMemory; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.rag.content.retriever.ContentRetriever; @@ -30,6 +31,7 @@ public AiGenerationService aiGenerationService() { .chatModel(myQwenChatModel) .streamingChatModel(qwenStreamingChatModel) //流式输出 .contentRetriever(contentRetriever) // RAG检索增强 + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) .build(); return build; } From 80970796ad33a9d703dc74fe254366676bd4c0a7 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 20:05:31 +0800 Subject: [PATCH 24/49] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E7=A0=81=E4=BB=A5=E5=8F=8A=E4=BF=AE=E5=A4=8D=E4=B8=80=E7=82=B9?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ITrickyKnowledgeRepository.java | 2 - .../extendbiz/ReviewFeedbackService.java | 4 +- .../repository/IStudyOverviewRepository.java | 9 ++ .../extendbiz/LearningOverviewService.java | 22 ++- .../http/KeyPointsExplanationController.java | 143 ++++++++++++------ .../http/ReviewFeedbackController.java | 101 +++++++------ .../types/enums/GlobalServiceStatusCode.java | 27 +++- 7 files changed, 199 insertions(+), 109 deletions(-) diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java index c71d379..08ed217 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java @@ -20,6 +20,4 @@ public interface ITrickyKnowledgeRepository { "having count(m.knowledge_point_id) >= 3") List getTrickyKnowledgePoints(String userId); - @Select("insert into UserData (user_id, hard_questions) values (#{userId}, #{size})") - void setTrickyKnowledgePointsCnt(String userId, int size); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java index 8881654..135c85b 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/service/feedback/extendbiz/ReviewFeedbackService.java @@ -131,9 +131,7 @@ public StatsVO getStatistics(String userId) { */ @Override public List getTrickyKnowledgePoint(String userId) { - List TrickyKnowledgePointVOs= trickyKnowledgeRepository.getTrickyKnowledgePoints(userId); - trickyKnowledgeRepository.setTrickyKnowledgePointsCnt(userId, TrickyKnowledgePointVOs.size()); - return TrickyKnowledgePointVOs; + return trickyKnowledgeRepository.getTrickyKnowledgePoints(userId); } } diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java index 5f43a2c..9afca10 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java @@ -19,4 +19,13 @@ public interface IStudyOverviewRepository { @Select("select count(*) from MistakeQuestion where user_id = #{userId} and update_time between #{localDateTime} and #{localDateTime1}") Integer countByUserIdAndUpdateTimeBetween(@Param("userId") String userId, LocalDateTime localDateTime, LocalDateTime localDateTime1); + + @Select("select count(question_id) from MistakeQuestion where user_id = #{userId}") + int queryQuestionsNum(String userId); + + @Select("select count(question_id) from MistakeQuestion where user_id = #{userId} and question_status = 1") + int queryMasteredQuestions(String userId); + + @Select("select study_time from UserData where user_id = #{userId}") + int queryStudyTime(String userId); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java index 5887aea..a402454 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java @@ -1,6 +1,8 @@ package com.achobeta.domain.overview.service.extendbiz; import com.achobeta.api.dto.TrendDataDTO; +import com.achobeta.domain.Feetback.service.feedback.IReviewFeedbackService; +import com.achobeta.domain.Feetback.service.feedback.extendbiz.ReviewFeedbackService; import com.achobeta.domain.overview.adapter.repository.IStudyOverviewRepository; import com.achobeta.domain.overview.model.valobj.LearningDynamicVO; import com.achobeta.domain.overview.service.ILearningOverviewService; @@ -20,17 +22,21 @@ public class LearningOverviewService implements ILearningOverviewService{ @Autowired private IStudyOverviewRepository repository; + @Autowired + private IReviewFeedbackService reviewFeedbackService; @Override public StudyOverviewVO getOverview(String userId) { - StudyOverviewVO vo = repository.queryStudyOverview(userId); - if(vo == null){ - return StudyOverviewVO.builder() - .questionsNum(0) - .reviewRate(0) - .hardQuestions(0) - .studyTime(0) - .build(); + StudyOverviewVO vo = new StudyOverviewVO(); + vo.setQuestionsNum(repository.queryQuestionsNum(userId)); + vo.setStudyTime(repository.queryStudyTime(userId)); + vo.setHardQuestions(reviewFeedbackService.getTrickyKnowledgePoint(userId).size()); + + if (vo.getQuestionsNum() == 0) { + vo.setReviewRate(0); + return vo; } + + vo.setReviewRate(repository.queryMasteredQuestions(userId)*1.0 / vo.getQuestionsNum()); return vo; } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java index b6e677b..afccc1f 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java @@ -5,9 +5,11 @@ import com.achobeta.domain.IRedisService; import com.achobeta.domain.keypoints_explanation.model.valobj.*; import com.achobeta.domain.keypoints_explanation.service.IKeyPointsExplanationService; +import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.Constants; import com.achobeta.types.common.UserContext; +import com.achobeta.types.enums.GlobalServiceStatusCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.annotations.Param; @@ -18,6 +20,8 @@ import java.util.ArrayList; import java.util.List; +import static com.achobeta.types.enums.GlobalServiceStatusCode.*; + /** * 知识点解释接口 */ @@ -37,14 +41,19 @@ public class KeyPointsExplanationController { */ @GetMapping("/get_key_points") @GlobalInterception - public ResponseEntity> getKeyPoints(@Param("subject") String subject) { + public Response> getKeyPoints(@Param("subject") String subject) { String userId = UserContext.getUserId(); - log.info("用户获取中心知识点,subject:{}", subject); - List keyPoints = keyPointsExplanationService.getKeyPoints(subject, userId); + List keyPoints = null; + try { + log.info("用户获取中心知识点,subject:{}", subject); + keyPoints = keyPointsExplanationService.getKeyPoints(subject, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_KEY_POINTS_FAIL); + } if (keyPoints == null || keyPoints.isEmpty()) { - return ResponseEntity.ok(new ArrayList<>()); + return Response.SYSTEM_SUCCESS(new ArrayList<>()); } - return ResponseEntity.ok(keyPoints.stream() + return Response.SYSTEM_SUCCESS(keyPoints.stream() .map(keyPointsVO -> KeyPointsDTO.builder() .id(keyPointsVO.getId()) .keyPoints(keyPointsVO.getKeyPoints()) @@ -58,21 +67,25 @@ public ResponseEntity> getKeyPoints(@Param("subject") String */ @GetMapping("/get_son_key_points") @GlobalInterception - public List getSonKeyPoints(@Param("knowledgeId") int knowledgeId) { + public Response> getSonKeyPoints(@Param("knowledgeId") int knowledgeId) { String userId = UserContext.getUserId(); - - log.info("用户获取子知识点,knowledgeId:{}, userId:{}", knowledgeId, userId); - List keyPointsVOs = keyPointsExplanationService.getSonKeyPoints(knowledgeId, userId); + List keyPointsVOs = null; + try { + log.info("用户获取子知识点,knowledgeId:{}, userId:{}", knowledgeId, userId); + keyPointsVOs = keyPointsExplanationService.getSonKeyPoints(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_SON_KEY_POINTS_FAIL); + } //空值判断 if (keyPointsVOs == null || keyPointsVOs.isEmpty()) { - return new ArrayList<>(); + return Response.SYSTEM_SUCCESS(new ArrayList<>()); } - return keyPointsVOs.stream() + return Response.SYSTEM_SUCCESS(keyPointsVOs.stream() .map(keyPointsVO -> KeyPointsDTO.builder() .id(keyPointsVO.getId()) .keyPoints(keyPointsVO.getKeyPoints()) .build()) - .toList(); + .toList()); } /** @@ -80,13 +93,18 @@ public List getSonKeyPoints(@Param("knowledgeId") int knowledgeId) */ @GetMapping("/{knowledgeId}") @GlobalInterception - public ResponseEntity getKnowledgePoint(@PathVariable int knowledgeId ) { + public Response getKnowledgePoint(@PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); - String point = keyPointsExplanationService.getKnowledgedescById(knowledgeId, userId); + String point = null; + try { + point = keyPointsExplanationService.getKnowledgedescById(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_KNOWLEDGE_POINT_DESC_FAIL); + } if (point == null || point.isEmpty()) { - return ResponseEntity.ok("暂无知识点详情"); + return Response.SYSTEM_SUCCESS("暂无知识点详情"); } - return ResponseEntity.ok(point); + return Response.SYSTEM_SUCCESS(point); } /** @@ -95,17 +113,22 @@ public ResponseEntity getKnowledgePoint(@PathVariable int knowledgeId ) */ @GetMapping("/{knowledgeId}/related-questions-statistic") @GlobalInterception - public ResponseEntity getRelatedWrongQuestionsStatistic( + public Response getRelatedWrongQuestionsStatistic( @PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); + WrongQuestionVO questions = null; - WrongQuestionVO questions = keyPointsExplanationService.getRelatedWrongQuestionsStatistic(knowledgeId, userId); + try { + questions = keyPointsExplanationService.getRelatedWrongQuestionsStatistic(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_RELATED_MESSAGES_FAIL); + } if(questions.getUpdateCount() == 0){ String res = "暂无相关错题"; - return ResponseEntity.ok(res); + return Response.SYSTEM_SUCCESS(res); } String res = "该知识点上传了" + questions.getUpdateCount() + "道题,其中本周复习了" + questions.getReviewCount() + "道题"; - return ResponseEntity.ok(res); + return Response.SYSTEM_SUCCESS(res); } /** @@ -114,17 +137,22 @@ public ResponseEntity getRelatedWrongQuestionsStatistic( */ @GetMapping("/{knowledgeId}/related-questions") @GlobalInterception - public ResponseEntity getRelatedWrongQuestions( + public Response getRelatedWrongQuestions( @PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); - RelateQuestionVO relatedQuestions = keyPointsExplanationService.getRelatedWrongQuestions(knowledgeId, userId); + RelateQuestionVO relatedQuestions = null; + try { + relatedQuestions = keyPointsExplanationService.getRelatedWrongQuestions(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_RELATED_MESSAGES_FAIL); + } if (relatedQuestions == null){ - return ResponseEntity.ok(RelateQeustionDTO.builder() + return Response.SYSTEM_SUCCESS(RelateQeustionDTO.builder() .questions(new ArrayList<>()) .note("") .build()); } - return ResponseEntity.ok(RelateQeustionDTO.builder() + return Response.SYSTEM_SUCCESS(RelateQeustionDTO.builder() .questions(relatedQuestions.getQestions().stream() .map(questionVO -> QuestionDTO.builder() .id(questionVO.getId()) @@ -140,10 +168,14 @@ public ResponseEntity getRelatedWrongQuestions( */ @PostMapping("/{knowledgeId}/mark-as-mastered") @GlobalInterception - public ResponseEntity markAsMastered(@PathVariable int knowledgeId ) { + public Response markAsMastered(@PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); - keyPointsExplanationService.markAsMastered(knowledgeId, userId); - return ResponseEntity.ok("已修改成功"); + try { + keyPointsExplanationService.markAsMastered(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(MARK_AS_MASTERED_FAIL); + } + return Response.SYSTEM_SUCCESS("已修改成功"); } /** @@ -151,15 +183,19 @@ public ResponseEntity markAsMastered(@PathVariable int knowledgeId ) { */ @GetMapping("/{knowledgeId}/related-points") @GlobalInterception - public ResponseEntity> getRelatedKnowledgePoints( + public Response> getRelatedKnowledgePoints( @PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); - - List relatedPoints = keyPointsExplanationService.getRelatedKnowledgePoints(knowledgeId, userId); + List relatedPoints = null; + try { + relatedPoints = keyPointsExplanationService.getRelatedKnowledgePoints(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(GET_RELATED_POINTS_FAIL); + } if (relatedPoints == null || relatedPoints.isEmpty()){ - return ResponseEntity.ok(new ArrayList<>()); + return Response.SYSTEM_SUCCESS(new ArrayList<>()); } - return ResponseEntity.ok(relatedPoints.stream() + return Response.SYSTEM_SUCCESS(relatedPoints.stream() .map(relatedPoint -> KeyPointsDTO.builder() .id(relatedPoint.getId()) .keyPoints(relatedPoint.getKeyPoints()) @@ -172,10 +208,14 @@ public ResponseEntity> getRelatedKnowledgePoints( */ @PostMapping("/{knowledgeId}/notes") @GlobalInterception - public ResponseEntity saveOrUpdateStudentNote(@PathVariable int knowledgeId, @RequestBody String note ) { + public Response saveOrUpdateStudentNote(@PathVariable int knowledgeId, @RequestBody String note ) { String userId = UserContext.getUserId(); - keyPointsExplanationService.savedNote(note, knowledgeId, userId); - return ResponseEntity.ok("笔记更新成功"); + try { + keyPointsExplanationService.savedNote(note, knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(SAVE_OR_UPDATE_NOTE_FAIL); + } + return Response.SYSTEM_SUCCESS("笔记更新成功"); } /** @@ -183,10 +223,14 @@ public ResponseEntity saveOrUpdateStudentNote(@PathVariable int knowledg */ @PostMapping("/{knowledgeId}/rename") @GlobalInterception - public ResponseEntity renameNode(@PathVariable int knowledgeId, @RequestBody String newName ) { + public Response renameNode(@PathVariable int knowledgeId, @RequestBody String newName ) { String userId = UserContext.getUserId(); - keyPointsExplanationService.renameNode(knowledgeId, newName, userId); - return ResponseEntity.ok("重命名成功"); + try { + keyPointsExplanationService.renameNode(knowledgeId, newName, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(RENAME_NODE_FAIL); + } + return Response.SYSTEM_SUCCESS("重命名成功"); } /** @@ -194,19 +238,24 @@ public ResponseEntity renameNode(@PathVariable int knowledgeId, @Request */ @GetMapping("/{knowledgeId}/show-tooltip") @GlobalInterception - public ResponseEntity showTooltip(@PathVariable int knowledgeId ) { + public Response showTooltip(@PathVariable int knowledgeId ) { String userId = UserContext.getUserId(); - ToolTipVO tooltip = keyPointsExplanationService.gettooltipById(knowledgeId, userId); + ToolTipVO tooltip = null; + try { + tooltip = keyPointsExplanationService.gettooltipById(knowledgeId, userId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(SHOW_TOOLTIP_FAIL); + } if(tooltip == null || tooltip.getTotal() == 0){ ToolTipDTO tooltipDTO = ToolTipDTO.builder() .count(0) .lastReviewTime("") .degreeOfProficiency(0) .build(); - return ResponseEntity.ok(tooltipDTO); + return Response.SYSTEM_SUCCESS(tooltipDTO); } double degreeOfProficiency = 1.0 * tooltip.getCount() / tooltip.getTotal(); - return ResponseEntity.ok(ToolTipDTO.builder() + return Response.SYSTEM_SUCCESS(ToolTipDTO.builder() .count(tooltip.getCount()) .lastReviewTime(tooltip.getLastReviewTime()) .degreeOfProficiency(degreeOfProficiency) @@ -218,10 +267,14 @@ public ResponseEntity showTooltip(@PathVariable int knowledgeId ) { */ @PostMapping("/{knowledgeId}/add-son-point") @GlobalInterception - public ResponseEntity addSonPoint(@PathVariable String knowledgeId, @RequestBody SonPointVO sonPoints ) { + public Response addSonPoint(@PathVariable String knowledgeId, @RequestBody SonPointVO sonPoints ) { String userId = UserContext.getUserId(); - keyPointsExplanationService.addSonPoint(sonPoints, userId, knowledgeId); - return ResponseEntity.ok("添加成功"); + try { + keyPointsExplanationService.addSonPoint(sonPoints, userId, knowledgeId); + } catch (Exception e) { + return Response.CUSTOMIZE_ERROR(ADD_SON_POINT_FAIL); + } + return Response.SYSTEM_SUCCESS("添加成功"); } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java index 1ffa01a..80d9080 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java @@ -107,7 +107,7 @@ public Response> getTrickyKnowledgePoint(){ */ @GetMapping("/list") @GlobalInterception - public ResponseEntity list( + public Response list( @RequestParam(required = false) String keyword, @RequestParam(required = false) List subject, @RequestParam(required = false) List errorType, @@ -116,6 +116,7 @@ public ResponseEntity list( @RequestParam(defaultValue = "10") int size ) { String userId = UserContext.getUserId(); + Page result = null; try { MistakeQueryParamsVO params = new MistakeQueryParamsVO(); params.setUserId(userId); @@ -133,13 +134,12 @@ public ResponseEntity list( params.setSize(size); log.info("用户筛选获取待复习题目列表开始,userId:{}", userId); - Page result = reviewFeedbackService.searchAndFilter(params); - return ResponseEntity.ok(result); + result = reviewFeedbackService.searchAndFilter(params); } catch (IllegalArgumentException e) { log.error("用户筛选获取待复习题目列表失败!userId:{}", userId, e); - return ResponseEntity.badRequest() - .body(Map.of("error", "请求参数无效: " + e.getMessage())); + return Response.CUSTOMIZE_ERROR(GET_REVIEW_QUESTION_LIST_FAIL); } + return Response.SYSTEM_SUCCESS(result); } /** @@ -147,16 +147,16 @@ public ResponseEntity list( */ @DeleteMapping("/deleteBatch") @GlobalInterception - public ResponseEntity deleteBatch(@RequestParam List questionIds) { + public Response deleteBatch(@RequestParam List questionIds) { String userId = UserContext.getUserId(); try { log.info("用户删除待复习题目开始,userId:{}", userId); reviewFeedbackService.deleteBatch(userId, questionIds); - return ResponseEntity.status(200).body("删除成功"); } catch (Exception e) { log.error("用户删除待复习题目失败!userId:{}", userId, e); - return ResponseEntity.status(500).body("删除失败"); + return Response.CUSTOMIZE_ERROR(DELETE_REVIEW_QUESTION_FAIL); } + return Response.SYSTEM_SUCCESS(); } /** @@ -164,55 +164,56 @@ public ResponseEntity deleteBatch(@RequestParam List questionId */ @GetMapping("/statistics") @GlobalInterception - public ResponseEntity getStatistics() { + public Response getStatistics() { String userId = UserContext.getUserId(); + StatsVO statsVO = null; try { log.info("获取待复习题目统计信息开始"); - StatsVO statsVO = reviewFeedbackService.getStatistics(userId); - List reviewTrendDTOS = new ArrayList<>(); - if(statsVO.getReviewTrend() == null || statsVO.getReviewTrend().isEmpty()){ - reviewTrendDTOS.add(ReviewTrendDTO.builder() - .month("0") - .total(0) - .reviewed(0) - .completionRate(0.0) - .build()); - }else{ - reviewTrendDTOS = statsVO.getReviewTrend().stream().map(reviewTrendVO -> { - if(reviewTrendVO == null || reviewTrendVO.getTotal() == 0){ - return ReviewTrendDTO.builder() - .month(reviewTrendVO.getMonth()) - .total(0) - .reviewed(0) - .completionRate(0.0) - .build(); - } + statsVO = reviewFeedbackService.getStatistics(userId); + } catch (Exception e) { + log.error("获取待复习题目统计信息失败!", e); + return Response.CUSTOMIZE_ERROR(GET_STATISTICS_FAIL); + } + List reviewTrendDTOS = new ArrayList<>(); + if(statsVO.getReviewTrend() == null || statsVO.getReviewTrend().isEmpty()){ + reviewTrendDTOS.add(ReviewTrendDTO.builder() + .month("0") + .total(0) + .reviewed(0) + .completionRate(0.0) + .build()); + }else{ + reviewTrendDTOS = statsVO.getReviewTrend().stream().map(reviewTrendVO -> { + if(reviewTrendVO == null || reviewTrendVO.getTotal() == 0){ return ReviewTrendDTO.builder() .month(reviewTrendVO.getMonth()) - .total(reviewTrendVO.getTotal()) - .reviewed(reviewTrendVO.getReviewed()) - .completionRate(reviewTrendVO.getReviewed()*1.0/reviewTrendVO.getTotal()) + .total(0) + .reviewed(0) + .completionRate(0.0) .build(); - }).toList(); - } - - StatsDTO statsDTO = StatsDTO.builder() - .subjectDistribution(statsVO.getSubjectDistribution().stream().map(count -> { - Map map = new HashMap<>(); - map.put(count.getName(), count.getCount()); - return map; - }).collect(Collectors.toList())) - .knowledgeDistribution(statsVO.getKnowledgeDistribution().stream().map(count -> { - Map map = new HashMap<>(); - map.put(count.getName(), count.getCount()); - return map; - }).collect(Collectors.toList())) - .reviewTrend(reviewTrendDTOS).build(); - return ResponseEntity.ok(statsDTO); - } catch (Exception e) { - log.error("获取待复习题目统计信息失败!", e); - throw new RuntimeException(e); + } + return ReviewTrendDTO.builder() + .month(reviewTrendVO.getMonth()) + .total(reviewTrendVO.getTotal()) + .reviewed(reviewTrendVO.getReviewed()) + .completionRate(reviewTrendVO.getReviewed()*1.0/reviewTrendVO.getTotal()) + .build(); + }).toList(); } + + StatsDTO statsDTO = StatsDTO.builder() + .subjectDistribution(statsVO.getSubjectDistribution().stream().map(count -> { + Map map = new HashMap<>(); + map.put(count.getName(), count.getCount()); + return map; + }).collect(Collectors.toList())) + .knowledgeDistribution(statsVO.getKnowledgeDistribution().stream().map(count -> { + Map map = new HashMap<>(); + map.put(count.getName(), count.getCount()); + return map; + }).collect(Collectors.toList())) + .reviewTrend(reviewTrendDTOS).build(); + return Response.SYSTEM_SUCCESS(statsDTO); } } diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index 617f8c4..7e6b612 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -57,10 +57,35 @@ public enum GlobalServiceStatusCode { QUESTION_GENERATION_FAIL(10001, "题目生成失败,请稍后再试"), QUESTION_IS_EXPIRED(10002, "题目已过期或不存在" ), OCR_ERROR(10003, "OCR图片识别错误"), + + /** + * 主页错误返回码 + */ GET_STUDY_DYNAMIC_FAIL(10004, "获取学习动态失败,请稍后再试"), GET_OVERDUE_REVIEW_COUNT_FAIL(10005, "获取待复习题目数量失败,请稍后再试"), GET_TRICKY_KNOWLEDGE_POINT_FAIL(10006, "获取易错知识点失败,请稍后再试"), - AI_RESPONSE_TIMEOUT(10007, "AI响应超时") + AI_RESPONSE_TIMEOUT(10007, "AI响应超时"), + + /** + * 待复习题目错误返回码 + */ + GET_REVIEW_QUESTION_LIST_FAIL(10008, "获取待复习题目列表失败,请稍后再试"), + DELETE_REVIEW_QUESTION_FAIL(10009, "删除待复习题目失败,请稍后再试"), + GET_STATISTICS_FAIL(10010, "获取待复习题目统计信息失败,请稍后再试"), + + /** + * 知识点脑图错误返回码 + */ + GET_KEY_POINTS_FAIL(10011, "获取中心知识点失败,请稍后再试"), + GET_SON_KEY_POINTS_FAIL(10012, "获取子知识点失败,请稍后再试"), + GET_KNOWLEDGE_POINT_DESC_FAIL(10013, "获取知识点描述失败,请稍后再试"), + GET_RELATED_MESSAGES_FAIL(10014, "获取知识点相关信息失败,请稍后再试"), + MARK_AS_MASTERED_FAIL(10015, "标记为已掌握失败,请稍后再试"), + GET_RELATED_POINTS_FAIL(10016, "获取相关知识点失败,请稍后再试"), + SAVE_OR_UPDATE_NOTE_FAIL(10017, "保存或更新笔记失败,请稍后再试"), + RENAME_NODE_FAIL(10018, "重命名节点失败,请稍后再试"), + SHOW_TOOLTIP_FAIL(10019, "显示提示失败,请稍后再试"), + ADD_SON_POINT_FAIL(10020, "添加子知识点失败,请稍后再试") ; private Integer code; From 932a1e82cbbf866a2b80f1492c33aebcca9e0fc3 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 20:19:05 +0800 Subject: [PATCH 25/49] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E7=82=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/achobeta/types/enums/GlobalServiceStatusCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index a274309..107b03e 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -86,7 +86,7 @@ public enum GlobalServiceStatusCode { SAVE_OR_UPDATE_NOTE_FAIL(10017, "保存或更新笔记失败,请稍后再试"), RENAME_NODE_FAIL(10018, "重命名节点失败,请稍后再试"), SHOW_TOOLTIP_FAIL(10019, "显示提示失败,请稍后再试"), - ADD_SON_POINT_FAIL(10020, "添加子知识点失败,请稍后再试") + ADD_SON_POINT_FAIL(10020, "添加子知识点失败,请稍后再试"), /* 错因管理相关状态码 11001-11100 */ MISTAKE_REASON_SUCCESS(11001, "错因操作成功"), MISTAKE_REASON_TOGGLE_SUCCESS(11002, "错因状态切换成功"), @@ -100,7 +100,7 @@ public enum GlobalServiceStatusCode { MISTAKE_REASON_UPDATE_FAILED(11103, "错因状态更新失败"), MISTAKE_REASON_TOGGLE_FAILED(11104, "错因状态切换失败"), STUDY_NOTE_UPDATE_FAILED(11105, "错题笔记更新失败"), - MISTAKE_REASON_SYSTEM_ERROR(11106, "错因管理系统异常") + MISTAKE_REASON_SYSTEM_ERROR(11106, "错因管理系统异常"), ; private Integer code; From ebd9d383a8392cc8ae9fa4e8a322e18e40cded89 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 20:23:16 +0800 Subject: [PATCH 26/49] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=80=E7=82=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/achobeta/types/enums/GlobalServiceStatusCode.java | 1 + 1 file changed, 1 insertion(+) diff --git a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java index 107b03e..1dc4ea5 100644 --- a/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java +++ b/refine-types/src/main/java/com/achobeta/types/enums/GlobalServiceStatusCode.java @@ -95,6 +95,7 @@ public enum GlobalServiceStatusCode { STUDY_NOTE_SUBMIT_SUCCESS(11005, "错题笔记提交成功"), STUDY_NOTE_GET_SUCCESS(11006, "错题笔记获取成功"), + MISTAKE_REASON_NOT_FOUND(11101, "未找到对应的错题记录"), MISTAKE_REASON_INVALID_PARAM(11102, "错因参数无效"), MISTAKE_REASON_UPDATE_FAILED(11103, "错因状态更新失败"), From cab0d0dc1ae9fc695a23ba1bab197432d21a3163 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 20:57:48 +0800 Subject: [PATCH 27/49] =?UTF-8?q?=E6=9B=B4=E6=94=B9jackson-annotations?= =?UTF-8?q?=E8=87=B3=E5=90=8C=E4=B8=80=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 988b1b4..bdebeaf 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,7 @@ 21 21 1.6.0-beta12 + 2.19.0 @@ -85,7 +86,7 @@ dev.langchain4j langchain4j-embeddings-all-minilm-l6-v2 - 1.1.0-beta6 + 1.0.1-beta6 @@ -97,7 +98,12 @@ com.fasterxml.jackson.core jackson-databind - 2.19.0 + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} From c2285d1c8eca609abc64816c157996beff450941 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 28 Nov 2025 21:53:02 +0800 Subject: [PATCH 28/49] =?UTF-8?q?=E4=BF=AE=E6=94=B9knowledge=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=B8=8E=E5=93=8D=E5=BA=94=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/achobeta/api/dto/KeyPointsDTO.java | 2 +- .../adapter/repository/KeyPointsMapper.java | 24 +++++++++---------- .../model/valobj/KeyPointsVO.java | 2 +- .../service/IKeyPointsExplanationService.java | 18 +++++++------- .../etendbiz/KeyPointsExplanationService.java | 18 +++++++------- .../http/KeyPointsExplanationController.java | 18 +++++++------- .../http/ReviewFeedbackController.java | 4 ---- 7 files changed, 41 insertions(+), 45 deletions(-) diff --git a/refine-api/src/main/java/com/achobeta/api/dto/KeyPointsDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/KeyPointsDTO.java index 43e0965..0a4eba7 100644 --- a/refine-api/src/main/java/com/achobeta/api/dto/KeyPointsDTO.java +++ b/refine-api/src/main/java/com/achobeta/api/dto/KeyPointsDTO.java @@ -11,7 +11,7 @@ public class KeyPointsDTO { /** * 知识点id */ - private int id; + private String id; /** * 知识点内容 */ diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java index 2c4ec4d..e0fa7a1 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java @@ -12,7 +12,7 @@ public interface KeyPointsMapper { @Select("select knowledge_point_id as id, knowledge_point_name as keyPoints from knowledgePoint" + " where parent_knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - List getSonKeyPoints(@Param("knowledgeId") int knowledgeId, @Param("userId") String userId); + List getSonKeyPoints(@Param("knowledgeId") String knowledgeId, @Param("userId") String userId); @Select("select knowledge_point_id as id, knowledge_point_name as keyPoints from knowledgePoint" + " where parent_knowledge_point_id = #{subjectId} and user_id = #{userId}") @@ -20,42 +20,42 @@ public interface KeyPointsMapper { @Select("select knowledge_desc from knowledgePoint" + " where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - String getKnowledgedescById(int knowledgeId, String userId); + String getKnowledgedescById(String knowledgeId, String userId); @Select("select count(id) as updateCount, count(update_time >= SUBDATE(now(), 7)) as reviewCount from MistakeQuestion" + " where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - WrongQuestionVO getRelatedWrongQuecstions(int knowledgeId, String userId); + WrongQuestionVO getRelatedWrongQuecstions(String knowledgeId, String userId); @Select("select k.parent_knowledge_point_id as id, k1.knowledge_point_name as keyPoints from knowledgePoint k " + "join knowledgePoint k1 on k.parent_knowledge_point_id = k1.knowledge_point_id" + " where k.knowledge_point_id = #{knowledgeId} and k.user_id = #{userId}") - KeyPointsVO getParentKeyPoints(int knowledgeId, String userId); + KeyPointsVO getParentKeyPoints(String knowledgeId, String userId); @Select("update knowledgePoint set note = #{note} where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - void savedNote(String note, int knowledgeId, String userId); + void savedNote(String note, String knowledgeId, String userId); @Select("update knowledgePoint set status = 1 where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - void markAsMastered(int knowledgeId, String userId); + void markAsMastered(String knowledgeId, String userId); @Select("update knowledgePoint set knowledge_point_name = #{newName} where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - void renameNode(int knowledgeId, String newName, String userId); + void renameNode(String knowledgeId, String newName, String userId); @Select("select count(id) from MistakeQuestion where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - int getTotalById(int knowledgeId, String userId); + int getTotalById(String knowledgeId, String userId); @Select("select count(id) from MistakeQuestion where knowledge_point_id = #{knowledgeId} and user_id = #{userId} and question_status = 0") - int getCountById(int knowledgeId, String userId); + int getCountById(String knowledgeId, String userId); @Select("select max(update_time) from MistakeQuestion where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - String getLastReviewTimeById(int knowledgeId, String userId); + String getLastReviewTimeById(String knowledgeId, String userId); @Select("select id as id, question_content as question from MistakeQuestion" + " where knowledge_point_id = #{knowledgeId} and user_id = #{userId} limit 3") - List getRelatedQuestions(int knowledgeId, String userId); + List getRelatedQuestions(String knowledgeId, String userId); @Select("select note from knowledgePoint" + " where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") - String getNoteById(int knowledgeId, String userId); + String getNoteById(String knowledgeId, String userId); @Select("insert into knowledgePoint(knowledge_point_id, knowledge_point_name, knowledge_desc, parent_knowledge_point_id, user_id)" + " values(#{node.pointId}, #{node.pointName}, #{node.pointDesc}, #{parentId}, #{userId})") diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/model/valobj/KeyPointsVO.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/model/valobj/KeyPointsVO.java index 79c48ee..04343a0 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/model/valobj/KeyPointsVO.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/model/valobj/KeyPointsVO.java @@ -11,7 +11,7 @@ public class KeyPointsVO { /** * 知识点id */ - private int id; + private String id; /** * 知识点内容 */ diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/IKeyPointsExplanationService.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/IKeyPointsExplanationService.java index d558a0b..2dfa329 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/IKeyPointsExplanationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/IKeyPointsExplanationService.java @@ -6,25 +6,25 @@ import java.util.List; public interface IKeyPointsExplanationService { - List getSonKeyPoints(int knowledgeId, String userId); + List getSonKeyPoints(String knowledgeId, String userId); List getKeyPoints(String subject, String userId); - String getKnowledgedescById(int knowledgeId, String userId); + String getKnowledgedescById(String knowledgeId, String userId); - WrongQuestionVO getRelatedWrongQuestionsStatistic(int knowledgeId, String userId); + WrongQuestionVO getRelatedWrongQuestionsStatistic(String knowledgeId, String userId); - List getRelatedKnowledgePoints(int knowledgeId, String userId); + List getRelatedKnowledgePoints(String knowledgeId, String userId); - void savedNote(String note, int knowledgeId, String userId); + void savedNote(String note, String knowledgeId, String userId); - void markAsMastered(int knowledgeId, String userId); + void markAsMastered(String knowledgeId, String userId); - void renameNode(int knowledgeId, String newName, String userId); + void renameNode(String knowledgeId, String newName, String userId); - ToolTipVO gettooltipById(int knowledgeId, String userId); + ToolTipVO gettooltipById(String knowledgeId, String userId); - RelateQuestionVO getRelatedWrongQuestions(int knowledgeId, String userId); + RelateQuestionVO getRelatedWrongQuestions(String knowledgeId, String userId); void addSonPoint(SonPointVO sonPointVOs, String userId, String parentId); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/etendbiz/KeyPointsExplanationService.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/etendbiz/KeyPointsExplanationService.java index 950238e..1d0545a 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/etendbiz/KeyPointsExplanationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/service/etendbiz/KeyPointsExplanationService.java @@ -27,7 +27,7 @@ public class KeyPointsExplanationService implements IKeyPointsExplanationService * @return */ @Override - public List getSonKeyPoints(int knowledgeId, String userId) { + public List getSonKeyPoints(String knowledgeId, String userId) { List keyPointsVOList = keyPointsMapper.getSonKeyPoints(knowledgeId, userId); return keyPointsVOList == null ? new ArrayList<>() : keyPointsVOList; } @@ -49,7 +49,7 @@ public List getKeyPoints(String subject, String userId) { * 获取知识点详情 */ @Override - public String getKnowledgedescById(int knowledgeId, String userId) { + public String getKnowledgedescById(String knowledgeId, String userId) { return keyPointsMapper.getKnowledgedescById(knowledgeId, userId); } @@ -59,7 +59,7 @@ public String getKnowledgedescById(int knowledgeId, String userId) { * @return */ @Override - public WrongQuestionVO getRelatedWrongQuestionsStatistic(int knowledgeId, String userId) { + public WrongQuestionVO getRelatedWrongQuestionsStatistic(String knowledgeId, String userId) { WrongQuestionVO wrongQuestionVO = keyPointsMapper.getRelatedWrongQuecstions(knowledgeId, userId); return wrongQuestionVO == null ? new WrongQuestionVO() : wrongQuestionVO; } @@ -70,7 +70,7 @@ public WrongQuestionVO getRelatedWrongQuestionsStatistic(int knowledgeId, String * @return */ @Override - public List getRelatedKnowledgePoints(int knowledgeId, String userId) { + public List getRelatedKnowledgePoints(String knowledgeId, String userId) { List keyPointsVOList = new ArrayList<>(); // 获取父知识点 @@ -93,7 +93,7 @@ public List getRelatedKnowledgePoints(int knowledgeId, String userI * @return */ @Override - public void savedNote(String note, int knowledgeId, String userId) { + public void savedNote(String note, String knowledgeId, String userId) { keyPointsMapper.savedNote(note, knowledgeId, userId); } @@ -103,7 +103,7 @@ public void savedNote(String note, int knowledgeId, String userId) { * @return */ @Override - public void markAsMastered(int knowledgeId, String userId) { + public void markAsMastered(String knowledgeId, String userId) { keyPointsMapper.markAsMastered(knowledgeId, userId); } @@ -114,7 +114,7 @@ public void markAsMastered(int knowledgeId, String userId) { * @return */ @Override - public void renameNode(int knowledgeId, String newName, String userId) { + public void renameNode(String knowledgeId, String newName, String userId) { keyPointsMapper.renameNode(knowledgeId, newName, userId); } @@ -124,7 +124,7 @@ public void renameNode(int knowledgeId, String newName, String userId) { * @return */ @Override - public ToolTipVO gettooltipById(int knowledgeId, String userId) { + public ToolTipVO gettooltipById(String knowledgeId, String userId) { // 获取总错题数 int total = keyPointsMapper.getTotalById(knowledgeId, userId); @@ -138,7 +138,7 @@ public ToolTipVO gettooltipById(int knowledgeId, String userId) { } @Override - public RelateQuestionVO getRelatedWrongQuestions(int knowledgeId, String userId) { + public RelateQuestionVO getRelatedWrongQuestions(String knowledgeId, String userId) { List qestions = keyPointsMapper.getRelatedQuestions(knowledgeId, userId); String note = keyPointsMapper.getNoteById(knowledgeId, userId); diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java index afccc1f..eb7514a 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/KeyPointsExplanationController.java @@ -67,7 +67,7 @@ public Response> getKeyPoints(@Param("subject") String subjec */ @GetMapping("/get_son_key_points") @GlobalInterception - public Response> getSonKeyPoints(@Param("knowledgeId") int knowledgeId) { + public Response> getSonKeyPoints(@Param("knowledgeId") String knowledgeId) { String userId = UserContext.getUserId(); List keyPointsVOs = null; try { @@ -93,7 +93,7 @@ public Response> getSonKeyPoints(@Param("knowledgeId") int kn */ @GetMapping("/{knowledgeId}") @GlobalInterception - public Response getKnowledgePoint(@PathVariable int knowledgeId ) { + public Response getKnowledgePoint(@PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); String point = null; try { @@ -114,7 +114,7 @@ public Response getKnowledgePoint(@PathVariable int knowledgeId ) { @GetMapping("/{knowledgeId}/related-questions-statistic") @GlobalInterception public Response getRelatedWrongQuestionsStatistic( - @PathVariable int knowledgeId ) { + @PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); WrongQuestionVO questions = null; @@ -138,7 +138,7 @@ public Response getRelatedWrongQuestionsStatistic( @GetMapping("/{knowledgeId}/related-questions") @GlobalInterception public Response getRelatedWrongQuestions( - @PathVariable int knowledgeId ) { + @PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); RelateQuestionVO relatedQuestions = null; try { @@ -168,7 +168,7 @@ public Response getRelatedWrongQuestions( */ @PostMapping("/{knowledgeId}/mark-as-mastered") @GlobalInterception - public Response markAsMastered(@PathVariable int knowledgeId ) { + public Response markAsMastered(@PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); try { keyPointsExplanationService.markAsMastered(knowledgeId, userId); @@ -184,7 +184,7 @@ public Response markAsMastered(@PathVariable int knowledgeId ) { @GetMapping("/{knowledgeId}/related-points") @GlobalInterception public Response> getRelatedKnowledgePoints( - @PathVariable int knowledgeId ) { + @PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); List relatedPoints = null; try { @@ -208,7 +208,7 @@ public Response> getRelatedKnowledgePoints( */ @PostMapping("/{knowledgeId}/notes") @GlobalInterception - public Response saveOrUpdateStudentNote(@PathVariable int knowledgeId, @RequestBody String note ) { + public Response saveOrUpdateStudentNote(@PathVariable String knowledgeId, @RequestBody String note ) { String userId = UserContext.getUserId(); try { keyPointsExplanationService.savedNote(note, knowledgeId, userId); @@ -223,7 +223,7 @@ public Response saveOrUpdateStudentNote(@PathVariable int knowledgeId, @ */ @PostMapping("/{knowledgeId}/rename") @GlobalInterception - public Response renameNode(@PathVariable int knowledgeId, @RequestBody String newName ) { + public Response renameNode(@PathVariable String knowledgeId, @RequestBody String newName ) { String userId = UserContext.getUserId(); try { keyPointsExplanationService.renameNode(knowledgeId, newName, userId); @@ -238,7 +238,7 @@ public Response renameNode(@PathVariable int knowledgeId, @RequestBody S */ @GetMapping("/{knowledgeId}/show-tooltip") @GlobalInterception - public Response showTooltip(@PathVariable int knowledgeId ) { + public Response showTooltip(@PathVariable String knowledgeId ) { String userId = UserContext.getUserId(); ToolTipVO tooltip = null; try { diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java index 80d9080..2adf093 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/ReviewFeedbackController.java @@ -8,16 +8,12 @@ import com.achobeta.domain.Feetback.model.entity.MistakeQuestionEntity; import com.achobeta.domain.Feetback.model.valobj.*; import com.achobeta.domain.Feetback.service.feedback.IReviewFeedbackService; -import com.achobeta.domain.IRedisService; import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; -import com.achobeta.types.common.Constants; import com.achobeta.types.common.UserContext; import com.achobeta.types.enums.TimeRange; -import com.achobeta.types.exception.AppException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.data.domain.Page; From af5812d8a0c2117f603e103c4273ef9652b82f02 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Fri, 28 Nov 2025 23:52:40 +0800 Subject: [PATCH 29/49] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=85=B6=E4=BB=96=E9=94=99=E8=AF=AF=E5=8E=9F=E5=9B=A0?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../achobeta/api/dto/StudyNoteRequestDTO.java | 6 -- .../api/dto/UpdateOtherReasonRequestDTO.java | 32 +++++++ .../service/IMistakeReasonService.java | 14 +++ .../impl/MistakeReasonServiceImpl.java | 38 +++++++++ .../trigger/http/MistakeReasonController.java | 85 ++++++++++++------- 5 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 refine-api/src/main/java/com/achobeta/api/dto/UpdateOtherReasonRequestDTO.java diff --git a/refine-api/src/main/java/com/achobeta/api/dto/StudyNoteRequestDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/StudyNoteRequestDTO.java index 7a17262..f05f610 100644 --- a/refine-api/src/main/java/com/achobeta/api/dto/StudyNoteRequestDTO.java +++ b/refine-api/src/main/java/com/achobeta/api/dto/StudyNoteRequestDTO.java @@ -14,12 +14,6 @@ @Data public class StudyNoteRequestDTO implements Serializable { - /** - * 用户ID - */ - @NotBlank(message = "用户ID不能为空") - private String userId; - /** * 题目ID */ diff --git a/refine-api/src/main/java/com/achobeta/api/dto/UpdateOtherReasonRequestDTO.java b/refine-api/src/main/java/com/achobeta/api/dto/UpdateOtherReasonRequestDTO.java new file mode 100644 index 0000000..da48b26 --- /dev/null +++ b/refine-api/src/main/java/com/achobeta/api/dto/UpdateOtherReasonRequestDTO.java @@ -0,0 +1,32 @@ +package com.achobeta.api.dto; + +import com.achobeta.types.annotation.FieldDesc; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import jakarta.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * @Auth : Malog + * @Desc : 更新其他原因文本请求DTO + * @Time : 2025/11/10 + */ +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UpdateOtherReasonRequestDTO implements Serializable { + + @NotBlank(message = "题目ID不能为空") + @FieldDesc(name = "题目ID") + private String questionId; + + @NotBlank(message = "其他原因描述不能为空") + @FieldDesc(name = "其他原因描述") + private String otherReasonText; +} \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java index 893c5b0..1bbe0e6 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/IMistakeReasonService.java @@ -53,4 +53,18 @@ MistakeReasonVO toggleMistakeReasonByName( String userId, String questionId, String reasonName); + + /** + * 带验证的更新其他原因文本 + * 先检查错题ID标志位是否为1,如果为1则更新文本,如果为0则返回失败 + * + * @param userId 用户ID + * @param questionId 题目ID + * @param otherReasonText 其他原因文本 + * @return 错因管理响应 + */ + MistakeReasonVO updateOtherReasonTextWithValidation( + String userId, + String questionId, + String otherReasonText); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java index b50866c..11d7a0b 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/mistake/service/impl/MistakeReasonServiceImpl.java @@ -172,6 +172,44 @@ public MistakeReasonVO toggleMistakeReasonByName(String userId, String questionI } } + @Override + public MistakeReasonVO updateOtherReasonTextWithValidation(String userId, String questionId, String otherReasonText) { + try { + log.info("开始带验证的更新其他原因文本: userId={}, questionId={}", userId, questionId); + + // 获取当前错题信息 + var currentReasons = mistakeReasonRepository.getMistakeReasons(userId, questionId); + + if (currentReasons == null) { + return MistakeReasonVO.error(userId, questionId, "未找到对应的错题记录"); + } + + // 检查错题ID标志位是否为1 + if (currentReasons.getOtherReason() == null || currentReasons.getOtherReason() != 1) { + return MistakeReasonVO.error(userId, questionId, "错题其他原因标志位不为1,无法更新文本"); + } + + // 验证文本内容 + if (otherReasonText == null || otherReasonText.trim().isEmpty()) { + return MistakeReasonVO.error(userId, questionId, "其他原因描述不能为空"); + } + + // 更新数据库 + boolean success = mistakeReasonRepository.updateOtherReasonText(userId, questionId, otherReasonText); + + if (success) { + // 重新获取更新后的数据 + var updatedReasons = mistakeReasonRepository.getMistakeReasons(userId, questionId); + return MistakeReasonVO.success(updatedReasons); + } else { + return MistakeReasonVO.error(userId, questionId, "更新其他原因失败"); + } + } catch (Exception e) { + log.error("带验证的更新其他原因时发生异常: userId={}, questionId={}", userId, questionId, e); + return MistakeReasonVO.error(userId, questionId, "系统异常: " + e.getMessage()); + } + } + /** * 根据错因名称切换状态 * @param reasonVO 错因值对象 diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java index 8670e97..ff19962 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/MistakeReasonController.java @@ -5,6 +5,7 @@ import com.achobeta.api.dto.MistakeReasonToggleRequestDTO; import com.achobeta.api.dto.StudyNoteRequestDTO; import com.achobeta.api.dto.StudyNoteResponseDTO; +import com.achobeta.api.dto.UpdateOtherReasonRequestDTO; import com.achobeta.domain.mistake.model.valobj.MistakeReasonVO; import com.achobeta.domain.mistake.model.valobj.StudyNoteVO; import com.achobeta.domain.mistake.service.IMistakeReasonService; @@ -88,29 +89,38 @@ public Response toggleMistakeReason( /** * 更新其他原因文本 + * 接收前端传来的错题id和文本原因,根据错题id到数据库中查询错题id标志位是否为1 + * 如果为1则根据传入的文本原因更新错题其他原因文本,如果为0则更新失败返回 * - * @param requestDTO 错因管理请求DTO + * @param requestDTO 更新其他原因请求DTO * @return 错因管理响应 */ + @GlobalInterception @PostMapping("update-other-reason") public Response updateOtherReasonText( - @Valid @RequestBody MistakeReasonRequestDTO requestDTO) { + @Valid @RequestBody UpdateOtherReasonRequestDTO requestDTO) { + String userId = UserContext.getUserId(); + if (userId == null) { + log.info("用户未登陆!"); + return Response.builder() + .code(GlobalServiceStatusCode.USER_NOT_LOGIN.getCode()) + .info(GlobalServiceStatusCode.USER_NOT_LOGIN.getMessage()) + .build(); + } try { log.info("用户更新其他原因开始,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId()); - - // 转换DTO为领域值对象 - MistakeReasonVO reasonVO = convertToMistakeReasonVO(requestDTO); + userId, requestDTO.getQuestionId()); // 调用领域服务 - MistakeReasonVO responseVO = mistakeReasonService.updateOtherReasonText(reasonVO); + MistakeReasonVO responseVO = mistakeReasonService.updateOtherReasonTextWithValidation( + userId, requestDTO.getQuestionId(), requestDTO.getOtherReasonText()); // 转换为响应DTO MistakeReasonResponseDTO response = convertToMistakeReasonResponseDTO(responseVO); if (response.getSuccess()) { log.info("用户更新其他原因成功,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId()); + userId, requestDTO.getQuestionId()); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_SUCCESS.getCode()) .info(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_SUCCESS.getMessage()) @@ -118,7 +128,7 @@ public Response updateOtherReasonText( .build(); } else { log.warn("用户更新其他原因失败,userId:{} questionId:{} message:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), response.getMessage()); + userId, requestDTO.getQuestionId(), response.getMessage()); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_UPDATE_FAILED.getCode()) .info(response.getMessage()) @@ -127,7 +137,7 @@ public Response updateOtherReasonText( } } catch (Exception e) { log.error("用户更新其他原因时发生异常,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), e); + userId, requestDTO.getQuestionId(), e); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) .info("系统异常: " + e.getMessage()) @@ -154,7 +164,7 @@ public Response toggleMistakeReasonSimple( // 调用领域服务 MistakeReasonVO responseVO = mistakeReasonService.toggleMistakeReasonByName( userId, - requestDTO.getQuestionId(), + requestDTO.getQuestionId(), requestDTO.getReasonName()); // 转换为响应DTO @@ -190,14 +200,20 @@ public Response toggleMistakeReasonSimple( /** * 获取错因信息 * - * @param userId 用户ID * @param questionId 题目ID * @return 错因管理响应 */ + @GlobalInterception @GetMapping("get") - public Response getMistakeReasons( - @RequestParam String userId, - @RequestParam String questionId) { + public Response getMistakeReasons(@RequestParam String questionId) { + String userId = UserContext.getUserId(); + if (userId == null) { + log.info("用户未登陆!"); + return Response.builder() + .code(GlobalServiceStatusCode.USER_NOT_LOGIN.getCode()) + .info(GlobalServiceStatusCode.USER_NOT_LOGIN.getMessage()) + .build(); + } try { log.info("获取错因信息开始,userId:{} questionId:{}", userId, questionId); @@ -238,15 +254,23 @@ public Response getMistakeReasons( * @param requestDTO 错题笔记请求DTO * @return 错题笔记响应 */ + @GlobalInterception @PostMapping("study-note/submit") - public Response submitStudyNote( - @Valid @RequestBody StudyNoteRequestDTO requestDTO) { + public Response submitStudyNote(@Valid @RequestBody StudyNoteRequestDTO requestDTO) { + String userId = UserContext.getUserId(); + if (userId == null) { + log.info("用户未登陆!"); + return Response.builder() + .code(GlobalServiceStatusCode.USER_NOT_LOGIN.getCode()) + .info(GlobalServiceStatusCode.USER_NOT_LOGIN.getMessage()) + .build(); + } try { - log.info("用户提交错题笔记开始,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId()); + log.info("用户提交错题笔记开始,userId:{} questionId:{}", userId, requestDTO.getQuestionId()); // 转换DTO为领域值对象 StudyNoteVO studyNoteVO = convertToStudyNoteVO(requestDTO); + studyNoteVO.setUserId(userId); // 调用领域服务 StudyNoteVO responseVO = studyNoteService.updateStudyNote(studyNoteVO); @@ -255,16 +279,14 @@ public Response submitStudyNote( StudyNoteResponseDTO response = convertToStudyNoteResponseDTO(responseVO); if (response.getSuccess()) { - log.info("用户提交错题笔记成功,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId()); + log.info("用户提交错题笔记成功,userId:{} questionId:{}", userId, requestDTO.getQuestionId()); return Response.builder() .code(GlobalServiceStatusCode.STUDY_NOTE_SUBMIT_SUCCESS.getCode()) .info(GlobalServiceStatusCode.STUDY_NOTE_SUBMIT_SUCCESS.getMessage()) .data(response) .build(); } else { - log.warn("用户提交错题笔记失败,userId:{} questionId:{} message:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), response.getMessage()); + log.warn("用户提交错题笔记失败,userId:{} questionId:{} message:{}", userId, requestDTO.getQuestionId(), response.getMessage()); return Response.builder() .code(GlobalServiceStatusCode.STUDY_NOTE_UPDATE_FAILED.getCode()) .info(response.getMessage()) @@ -273,7 +295,7 @@ public Response submitStudyNote( } } catch (Exception e) { log.error("用户提交错题笔记时发生异常,userId:{} questionId:{}", - requestDTO.getUserId(), requestDTO.getQuestionId(), e); + userId, requestDTO.getQuestionId(), e); return Response.builder() .code(GlobalServiceStatusCode.MISTAKE_REASON_SYSTEM_ERROR.getCode()) .info("系统异常: " + e.getMessage()) @@ -284,14 +306,20 @@ public Response submitStudyNote( /** * 获取错题笔记 * - * @param userId 用户ID * @param questionId 题目ID * @return 错题笔记响应 */ + @GlobalInterception @GetMapping("study-note/get") - public Response getStudyNote( - @RequestParam String userId, - @RequestParam String questionId) { + public Response getStudyNote(@RequestParam String questionId) { + String userId = UserContext.getUserId(); + if (userId == null) { + log.info("用户未登陆!"); + return Response.builder() + .code(GlobalServiceStatusCode.USER_NOT_LOGIN.getCode()) + .info(GlobalServiceStatusCode.USER_NOT_LOGIN.getMessage()) + .build(); + } try { log.info("获取错题笔记开始,userId:{} questionId:{}", userId, questionId); @@ -372,7 +400,6 @@ private MistakeReasonResponseDTO convertToMistakeReasonResponseDTO(MistakeReason */ private StudyNoteVO convertToStudyNoteVO(StudyNoteRequestDTO requestDTO) { return StudyNoteVO.builder() - .userId(requestDTO.getUserId()) .questionId(requestDTO.getQuestionId()) .studyNote(requestDTO.getStudyNote()) .build(); From 7ae832b8462e80f1d40e0f94d1ccbe2c15de0cb6 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 00:27:51 +0800 Subject: [PATCH 30/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=88=86=E6=9E=90=E7=94=A8=E6=88=B7=E8=A1=8C=E4=B8=BA?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/rag/service/impl/LearningAnalysisService.java | 5 +---- .../com/achobeta/trigger/event/UserLoginEventListener.java | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/refine-domain/src/main/java/com/achobeta/domain/rag/service/impl/LearningAnalysisService.java b/refine-domain/src/main/java/com/achobeta/domain/rag/service/impl/LearningAnalysisService.java index 9b83405..544db96 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/rag/service/impl/LearningAnalysisService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/rag/service/impl/LearningAnalysisService.java @@ -29,10 +29,9 @@ public class LearningAnalysisService { * 用户登录时触发的学习动态分析 * * @param userId 用户ID - * @return 学习动态列表 */ @Async - public List onUserLogin(String userId) { + public void onUserLogin(String userId) { try { log.info("用户登录触发学习动态分析,userId:{}", userId); @@ -40,11 +39,9 @@ public List onUserLogin(String userId) { List dynamics = learningDynamicsService.analyzeUserLearningDynamics(userId); log.info("用户登录学习动态分析完成,userId:{} 动态数量:{}", userId, dynamics.size()); - return dynamics; } catch (Exception e) { log.error("用户登录学习动态分析失败,userId:{}", userId, e); - return Collections.emptyList(); } } diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java b/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java index 0b729e7..2575132 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java @@ -32,7 +32,7 @@ public void handleUserLoginEvent(UserLoginEvent event) { log.info("检测到用户登录,开始分析学习动态,userId:{}", userId); // 异步分析用户学习动态 - List dynamics = learningAnalysisService.onUserLogin(userId); + learningAnalysisService.onUserLogin(userId); } catch (Exception e) { log.error("处理用户登录事件失败", e); } From 9b719a2b63cd5dc497620e9481b55fcec8e91b92 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 01:54:23 +0800 Subject: [PATCH 31/49] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E5=90=91?= =?UTF-8?q?=E9=87=8Fmapper=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mybatis/mapper/VectorMapper.xml | 2 +- .../repository/LearningDataRepository.java | 209 ++++++++++++++---- .../adapter/repository/VectorRepository.java | 2 +- .../dao/{ => vector}/IVectorDao.java | 2 +- 4 files changed, 167 insertions(+), 48 deletions(-) rename {refine-infrastructure => refine-app}/src/main/resources/mybatis/mapper/VectorMapper.xml (98%) rename refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/{ => vector}/IVectorDao.java (97%) diff --git a/refine-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml similarity index 98% rename from refine-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml rename to refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 6fed997..69b4bed 100644 --- a/refine-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -1,6 +1,6 @@ - + diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/LearningDataRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/LearningDataRepository.java index bd6306b..8107137 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/LearningDataRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/LearningDataRepository.java @@ -3,20 +3,20 @@ import com.achobeta.domain.rag.adapter.port.ILearningDataRepository; import com.achobeta.domain.rag.model.entity.LearningVectorEntity; import com.achobeta.domain.rag.model.valobj.LearningStatisticsVO; -import com.achobeta.infrastructure.dao.IVectorDao; +import com.achobeta.infrastructure.dao.vector.IVectorDao; import com.achobeta.infrastructure.dao.po.LearningVector; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; +import org.springframework.util.CollectionUtils; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.time.LocalDateTime; +import java.util.*; import java.util.stream.Collectors; /** * @Auth : Malog - * @Desc : 学习数据仓储实现 + * @Desc : 学习数据仓储实现类 * @Time : 2025/11/25 */ @Slf4j @@ -29,12 +29,36 @@ public class LearningDataRepository implements ILearningDataRepository { @Override public List getUserRecentLearningData(String userId, int days) { try { + log.info("获取用户最近{}天的学习向量数据,userId:{}, days:{}", days, userId); + + if (userId == null || userId.trim().isEmpty()) { + log.warn("用户ID为空,无法查询学习数据"); + return Collections.emptyList(); + } + + if (days <= 0) { + log.warn("查询天数无效,days:{}", days); + return Collections.emptyList(); + } + + // 从数据库查询用户最近N天的学习数据 List vectors = vectorDao.getUserRecentLearningData(userId, days); - return vectors.stream() + + if (CollectionUtils.isEmpty(vectors)) { + log.info("用户最近{}天无学习数据,userId:{}", days, userId); + return Collections.emptyList(); + } + + // 转换为领域实体 + List result = vectors.stream() .map(this::convertToEntity) .collect(Collectors.toList()); + + log.info("成功获取用户学习向量数据,userId:{}, 数据量:{}", userId, result.size()); + return result; + } catch (Exception e) { - log.error("获取用户最近学习数据失败,userId:{} days:{}", userId, days, e); + log.error("获取用户学习向量数据失败,userId:{}, days:{}", userId, days, e); return Collections.emptyList(); } } @@ -42,22 +66,39 @@ public List getUserRecentLearningData(String userId, int d @Override public LearningStatisticsVO getUserLearningStatistics(String userId, int days) { try { - Map statistics = vectorDao.getUserLearningStatistics(userId, days); - if (statistics == null || statistics.isEmpty()) { + log.info("获取用户最近{}天的学习统计数据,userId:{}, days:{}", days, userId); + + if (userId == null || userId.trim().isEmpty()) { + log.warn("用户ID为空,无法查询学习统计数据"); return null; } - - return LearningStatisticsVO.builder() - .totalActivities(getIntValue(statistics, "total_activities")) - .actionTypesCount(getIntValue(statistics, "action_types_count")) - .subjectsCount(getIntValue(statistics, "subjects_count")) - .activeDays(getIntValue(statistics, "active_days")) - .firstActivity(getTimestampValue(statistics, "first_activity")) - .lastActivity(getTimestampValue(statistics, "last_activity")) - .build(); - + + if (days <= 0) { + log.warn("查询天数无效,days:{}", days); + return null; + } + + // 从数据库查询统计数据 + Map statisticsMap = vectorDao.getUserLearningStatistics(userId, days); + + if (statisticsMap == null || statisticsMap.isEmpty()) { + log.info("用户最近{}天无学习统计数据,userId:{}", days, userId); + return LearningStatisticsVO.builder() + .totalActivities(0) + .subjectsCount(0) + .activeDays(0) + .actionTypesCount(0) + .build(); + } + + // 转换为统计VO + LearningStatisticsVO result = convertToStatisticsVO(statisticsMap); + + log.info("成功获取用户学习统计数据,userId:{}, 总活动次数:{}", userId, result.getTotalActivities()); + return result; + } catch (Exception e) { - log.error("获取用户学习统计失败,userId:{} days:{}", userId, days, e); + log.error("获取用户学习统计数据失败,userId:{}, days:{}", userId, days, e); return null; } } @@ -65,9 +106,31 @@ public LearningStatisticsVO getUserLearningStatistics(String userId, int days) { @Override public List> getUserLearningDataBySubject(String userId, int days) { try { - return vectorDao.getUserLearningDataBySubject(userId, days); + log.info("获取用户最近{}天按科目分组的学习数据,userId:{}, days:{}", days, userId); + + if (userId == null || userId.trim().isEmpty()) { + log.warn("用户ID为空,无法查询按科目分组的学习数据"); + return Collections.emptyList(); + } + + if (days <= 0) { + log.warn("查询天数无效,days:{}", days); + return Collections.emptyList(); + } + + // 从数据库查询按科目分组的学习数据 + List> result = vectorDao.getUserLearningDataBySubject(userId, days); + + if (CollectionUtils.isEmpty(result)) { + log.info("用户最近{}天无按科目分组的学习数据,userId:{}", days, userId); + return Collections.emptyList(); + } + + log.info("成功获取用户按科目分组学习数据,userId:{}, 科目数量:{}", userId, result.size()); + return result; + } catch (Exception e) { - log.error("获取用户按科目分组的学习数据失败,userId:{} days:{}", userId, days, e); + log.error("获取用户按科目分组学习数据失败,userId:{}, days:{}", userId, days, e); return Collections.emptyList(); } } @@ -75,29 +138,78 @@ public List> getUserLearningDataBySubject(String userId, int @Override public List> getUserLearningDataByActionType(String userId, int days) { try { - return vectorDao.getUserLearningDataByActionType(userId, days); + log.info("获取用户最近{}天按行为类型分组的学习数据,userId:{}, days:{}", days, userId); + + if (userId == null || userId.trim().isEmpty()) { + log.warn("用户ID为空,无法查询按行为类型分组的学习数据"); + return Collections.emptyList(); + } + + if (days <= 0) { + log.warn("查询天数无效,days:{}", days); + return Collections.emptyList(); + } + + // 从数据库查询按行为类型分组的学习数据 + List> result = vectorDao.getUserLearningDataByActionType(userId, days); + + if (CollectionUtils.isEmpty(result)) { + log.info("用户最近{}天无按行为类型分组的学习数据,userId:{}", days, userId); + return Collections.emptyList(); + } + + log.info("成功获取用户按行为类型分组学习数据,userId:{}, 行为类型数量:{}", userId, result.size()); + return result; + } catch (Exception e) { - log.error("获取用户按行为类型分组的学习数据失败,userId:{} days:{}", userId, days, e); + log.error("获取用户按行为类型分组学习数据失败,userId:{}, days:{}", userId, days, e); return Collections.emptyList(); } } /** - * 转换为领域实体 + * 将数据库PO转换为领域实体 */ - private LearningVectorEntity convertToEntity(LearningVector po) { + private LearningVectorEntity convertToEntity(LearningVector vector) { + if (vector == null) { + return null; + } + return LearningVectorEntity.builder() - .id(po.getId()) - .userId(po.getUserId()) - .questionId(po.getQuestionId()) - .actionType(po.getActionType()) - .questionContent(po.getQuestionContent()) - .subject(po.getSubject()) - .knowledgePointId(po.getKnowledgePointId()) - .embedding(po.getEmbedding()) - .metadata(po.getMetadata()) - .createdAt(po.getCreatedAt()) - .updatedAt(po.getUpdatedAt()) + .id(vector.getId()) + .userId(vector.getUserId()) + .questionId(vector.getQuestionId()) + .actionType(vector.getActionType()) + .questionContent(vector.getQuestionContent()) + .subject(vector.getSubject()) + .knowledgePointId(vector.getKnowledgePointId()) + .embedding(vector.getEmbedding()) + .metadata(vector.getMetadata()) + .createdAt(vector.getCreatedAt()) + .updatedAt(vector.getUpdatedAt()) + .build(); + } + + /** + * 将数据库统计结果转换为统计VO + */ + private LearningStatisticsVO convertToStatisticsVO(Map statisticsMap) { + if (statisticsMap == null || statisticsMap.isEmpty()) { + return LearningStatisticsVO.builder() + .totalActivities(0) + .subjectsCount(0) + .activeDays(0) + .actionTypesCount(0) + .build(); + } + + return LearningStatisticsVO.builder() + .totalActivities(getIntValue(statisticsMap, "total_activities")) + .actionTypesCount(getIntValue(statisticsMap, "action_types_count")) + .subjectsCount(getIntValue(statisticsMap, "subjects_count")) + .activeDays(getIntValue(statisticsMap, "active_days")) + .firstActivity(getDateTimeValue(statisticsMap, "first_activity")) + .lastActivity(getDateTimeValue(statisticsMap, "last_activity")) .build(); } @@ -106,29 +218,36 @@ private LearningVectorEntity convertToEntity(LearningVector po) { */ private Integer getIntValue(Map map, String key) { Object value = map.get(key); - if (value == null) return 0; + if (value == null) { + return 0; + } + if (value instanceof Number) { return ((Number) value).intValue(); } + try { return Integer.parseInt(value.toString()); } catch (NumberFormatException e) { + log.warn("无法解析整数值: {} = {}", key, value); return 0; } } /** - * 安全获取时间戳值 + * 安全获取日期时间值 */ - private java.time.LocalDateTime getTimestampValue(Map map, String key) { + private LocalDateTime getDateTimeValue(Map map, String key) { Object value = map.get(key); - if (value == null) return null; - if (value instanceof java.time.LocalDateTime) { - return (java.time.LocalDateTime) value; + if (value == null) { + return null; } - if (value instanceof java.sql.Timestamp) { - return ((java.sql.Timestamp) value).toLocalDateTime(); + + if (value instanceof LocalDateTime) { + return (LocalDateTime) value; } + + // 如果是其他类型,可以根据需要添加转换逻辑 return null; } } \ No newline at end of file diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/VectorRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/VectorRepository.java index 6125482..4ea2adc 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/VectorRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/VectorRepository.java @@ -3,7 +3,7 @@ import com.achobeta.domain.rag.model.valobj.LearningInsightVO; import com.achobeta.domain.rag.model.valobj.SimilarQuestionVO; import com.achobeta.domain.rag.service.IVectorService; -import com.achobeta.infrastructure.dao.IVectorDao; +import com.achobeta.infrastructure.dao.vector.IVectorDao; import com.achobeta.infrastructure.dao.po.LearningVector; import com.achobeta.infrastructure.gateway.DashScopeEmbeddingService; import lombok.extern.slf4j.Slf4j; diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IVectorDao.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/vector/IVectorDao.java similarity index 97% rename from refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IVectorDao.java rename to refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/vector/IVectorDao.java index 308e9ba..095f764 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IVectorDao.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/vector/IVectorDao.java @@ -1,4 +1,4 @@ -package com.achobeta.infrastructure.dao; +package com.achobeta.infrastructure.dao.vector; import com.achobeta.infrastructure.dao.po.LearningVector; import com.achobeta.domain.rag.model.valobj.LearningInsightVO; From e7f7c655ba0d21dbd6efcb77866d15bd767c86c8 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Sat, 29 Nov 2025 01:57:57 +0800 Subject: [PATCH 32/49] =?UTF-8?q?feat:=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/ChatModelListenerConfig.java | 2 +- refine-app/src/main/resources/AiAnalyze.txt | 2 ++ .../docs/\345\214\226\345\255\246.txt" | 1 + .../adapter/port/AiGenerationService.java | 6 ++++ .../service/impl/QuestionServiceImpl.java | 34 ++++++++++++++----- .../port/AiGenerationServiceFactory.java | 6 ++-- 6 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 "refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" diff --git a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java index b34e055..078e684 100644 --- a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java @@ -28,7 +28,7 @@ public void onResponse(ChatModelResponseContext responseContext) { @Override public void onError(ChatModelErrorContext errorContext) { - log.info("onError(): {}", errorContext.error().getMessage()); + log.warn("onError(): {}", errorContext.error().getMessage()); } }; } diff --git a/refine-app/src/main/resources/AiAnalyze.txt b/refine-app/src/main/resources/AiAnalyze.txt index 3685ca5..9eb4f3d 100644 --- a/refine-app/src/main/resources/AiAnalyze.txt +++ b/refine-app/src/main/resources/AiAnalyze.txt @@ -1,3 +1,5 @@ 你是一个专业判题助手,可以判断答案是否正确并给出正确答案,根据问题针对学生的答案详细解析,解析详细一点 (对于填数据类型的填空题,允许答案有少量误差) +一步一步详细分析答案是怎么得出来的 不添加其他多余文字,例如:"希望这可以帮助澄清你的疑问!如果有其他问题,请随时告诉我。" +以纯文本形式输出,不要带有没必要的符号如** \ No newline at end of file diff --git "a/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" "b/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" new file mode 100644 index 0000000..6851ceb --- /dev/null +++ "b/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" @@ -0,0 +1 @@ +化学能是没用的 \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java index 0aaf5ec..749d071 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java @@ -14,10 +14,16 @@ public interface AiGenerationService { @SystemMessage(fromResource = "AiGeneration.txt") QuestionResponseDTO Generation(@MemoryId String subject, @UserMessage String message); + @SystemMessage(fromResource = "AiGeneration.txt") + QuestionResponseDTO Generation(@UserMessage String message); + //流式输出 @SystemMessage(fromResource = "AiAnalyze.txt") Flux aiJudgeStream(@MemoryId String subject, @UserMessage String message); + @SystemMessage(fromResource = "AiAnalyze.txt") + Flux aiJudgeStream(@UserMessage String message); + // 会话 String chat(String message); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index 16d8d88..e274416 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -60,10 +60,18 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues if (null == knowledgePointName) { throw new AppException("找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); } - String toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; // 调用外部接口生成新题目 - QuestionResponseDTO question = aiGenerationService.Generation(subject, toAi); + String toAi; + QuestionResponseDTO question; + if (subject.equals("未分类")) { + toAi = "你是一位专业出题老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; + question = aiGenerationService.Generation(toAi); + } else { + toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; + question = aiGenerationService.Generation(subject, toAi); + } + if (null == question.getContent() || null == question.getAnswer()) { throw new AppException(GlobalServiceStatusCode.QUESTION_GENERATION_FAIL); } @@ -147,12 +155,22 @@ public Flux> aiJudge(String userId, String questionId, S // 将ai流式调用提交到自定义线程池 String subject = value.getSubject(); - return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) - .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) - .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) - .map(chunk -> ServerSentEvent.builder() - .data(chunk) - .build()); + if (subject.equals("未分类")) { + return Flux.defer(() -> aiGenerationService.aiJudgeStream(chat)) + .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) + .map(chunk -> ServerSentEvent.builder() + .data(chunk) + .build()); + } else { + return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) + .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) + .map(chunk -> ServerSentEvent.builder() + .data(chunk) + .build()); + } + } private void autoRecord(String userId, String questionId, String answer, String correctAnswer) { diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java index 8ce82ea..4564309 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java @@ -31,8 +31,10 @@ public AiGenerationService aiGenerationService() { .chatModel(myQwenChatModel) .streamingChatModel(qwenStreamingChatModel) //流式输出 .contentRetriever(contentRetriever) // RAG检索增强 - .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) - .build(); + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder() + .maxMessages(2) // 核心:0 条消息保留 → 每次都是新会话 + .id(memoryId) // 仍需绑定 memoryId(即 fileName),不影响传递 + .build()).build(); return build; } From 2e7b4634aa32e1768308b15310a61264feee477f Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:03:58 +0800 Subject: [PATCH 33/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DvectorMapper?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 69b4bed..e56e317 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -16,10 +16,10 @@ SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata, created_at, updated_at, - 1 - (embedding <> #{queryVector}::vector) as similarity + 1 - (embedding <> #{queryVector}::vector) as similarity FROM user_learning_vectors WHERE user_id = #{userId} - ORDER BY embedding <> #{queryVector}::vector + ORDER BY embedding <> #{queryVector}::vector LIMIT #{limit} From cd4648580d4bdbf1b315d11f5f243cab5880e32b Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:13:21 +0800 Subject: [PATCH 34/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DvectorMapper?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index e56e317..72749e1 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -16,10 +16,10 @@ SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata, created_at, updated_at, - 1 - (embedding <> #{queryVector}::vector) as similarity + 1 - (embedding <-> #{queryVector}::vector) as similarity FROM user_learning_vectors WHERE user_id = #{userId} - ORDER BY embedding <> #{queryVector}::vector + ORDER BY embedding <-> #{queryVector}::vector LIMIT #{limit} From e53962c71d4e1c0314e4a2d245011224e255cf07 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:23:05 +0800 Subject: [PATCH 35/49] =?UTF-8?q?Revert=20"fix:=20=E4=BF=AE=E5=A4=8Dvector?= =?UTF-8?q?Mapper=E6=96=87=E4=BB=B6=E6=98=A0=E5=B0=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 72749e1..e56e317 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -16,10 +16,10 @@ SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata, created_at, updated_at, - 1 - (embedding <-> #{queryVector}::vector) as similarity + 1 - (embedding <> #{queryVector}::vector) as similarity FROM user_learning_vectors WHERE user_id = #{userId} - ORDER BY embedding <-> #{queryVector}::vector + ORDER BY embedding <> #{queryVector}::vector LIMIT #{limit} From 8cac7eb9e90418f110cbdbe3e1664981d9691c58 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:39:28 +0800 Subject: [PATCH 36/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DvectorMapper?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mybatis/mapper/VectorMapper.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 72749e1..30ce5c4 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -16,10 +16,10 @@ SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata, created_at, updated_at, - 1 - (embedding <-> #{queryVector}::vector) as similarity + 1 - (embedding <> #{queryVector}::vector) as similarity FROM user_learning_vectors WHERE user_id = #{userId} - ORDER BY embedding <-> #{queryVector}::vector + ORDER BY embedding <> #{queryVector}::vector LIMIT #{limit} @@ -42,7 +42,7 @@ INSERT INTO learning_insights (user_id, insight_type, title, description, confidence_score) VALUES - (#{userId}, #{insight.type}, #{insight.title}, #{insight.description}, #{insight.confidenceScore}) + (#{userId}, #{insightType}, #{title}, #{description}, #{confidenceScore}) From 77ffb033e3d8fa35be02e33d15aa23a3a2343bb8 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:49:57 +0800 Subject: [PATCH 37/49] =?UTF-8?q?Revert=20"fix:=20=E4=BF=AE=E5=A4=8Dvector?= =?UTF-8?q?Mapper=E6=96=87=E4=BB=B6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/mybatis/mapper/VectorMapper.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 30ce5c4..72749e1 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -16,10 +16,10 @@ SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata, created_at, updated_at, - 1 - (embedding <> #{queryVector}::vector) as similarity + 1 - (embedding <-> #{queryVector}::vector) as similarity FROM user_learning_vectors WHERE user_id = #{userId} - ORDER BY embedding <> #{queryVector}::vector + ORDER BY embedding <-> #{queryVector}::vector LIMIT #{limit} @@ -42,7 +42,7 @@ INSERT INTO learning_insights (user_id, insight_type, title, description, confidence_score) VALUES - (#{userId}, #{insightType}, #{title}, #{description}, #{confidenceScore}) + (#{userId}, #{insight.type}, #{insight.title}, #{insight.description}, #{insight.confidenceScore}) From fb3bb2611454296326fc8b4e533897e9783530e5 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:50:30 +0800 Subject: [PATCH 38/49] =?UTF-8?q?Revert=20"fix:=20=E4=BF=AE=E5=A4=8Dvector?= =?UTF-8?q?Mapper=E6=96=87=E4=BB=B6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 93c7f5b38aec7b2ba3d9eb705d352fcca80c3b93 Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 02:51:51 +0800 Subject: [PATCH 39/49] =?UTF-8?q?Revert=20"feat:=E4=BF=AE=E5=A4=8Dbug"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/ChatModelListenerConfig.java | 2 +- refine-app/src/main/resources/AiAnalyze.txt | 2 -- .../docs/\345\214\226\345\255\246.txt" | 1 - .../adapter/port/AiGenerationService.java | 6 ---- .../service/impl/QuestionServiceImpl.java | 34 +++++-------------- .../port/AiGenerationServiceFactory.java | 6 ++-- 6 files changed, 11 insertions(+), 40 deletions(-) delete mode 100644 "refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" diff --git a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java index 078e684..b34e055 100644 --- a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java @@ -28,7 +28,7 @@ public void onResponse(ChatModelResponseContext responseContext) { @Override public void onError(ChatModelErrorContext errorContext) { - log.warn("onError(): {}", errorContext.error().getMessage()); + log.info("onError(): {}", errorContext.error().getMessage()); } }; } diff --git a/refine-app/src/main/resources/AiAnalyze.txt b/refine-app/src/main/resources/AiAnalyze.txt index 9eb4f3d..3685ca5 100644 --- a/refine-app/src/main/resources/AiAnalyze.txt +++ b/refine-app/src/main/resources/AiAnalyze.txt @@ -1,5 +1,3 @@ 你是一个专业判题助手,可以判断答案是否正确并给出正确答案,根据问题针对学生的答案详细解析,解析详细一点 (对于填数据类型的填空题,允许答案有少量误差) -一步一步详细分析答案是怎么得出来的 不添加其他多余文字,例如:"希望这可以帮助澄清你的疑问!如果有其他问题,请随时告诉我。" -以纯文本形式输出,不要带有没必要的符号如** \ No newline at end of file diff --git "a/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" "b/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" deleted file mode 100644 index 6851ceb..0000000 --- "a/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" +++ /dev/null @@ -1 +0,0 @@ -化学能是没用的 \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java index 749d071..0aaf5ec 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java @@ -14,16 +14,10 @@ public interface AiGenerationService { @SystemMessage(fromResource = "AiGeneration.txt") QuestionResponseDTO Generation(@MemoryId String subject, @UserMessage String message); - @SystemMessage(fromResource = "AiGeneration.txt") - QuestionResponseDTO Generation(@UserMessage String message); - //流式输出 @SystemMessage(fromResource = "AiAnalyze.txt") Flux aiJudgeStream(@MemoryId String subject, @UserMessage String message); - @SystemMessage(fromResource = "AiAnalyze.txt") - Flux aiJudgeStream(@UserMessage String message); - // 会话 String chat(String message); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index e274416..16d8d88 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -60,18 +60,10 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues if (null == knowledgePointName) { throw new AppException("找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); } + String toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; // 调用外部接口生成新题目 - String toAi; - QuestionResponseDTO question; - if (subject.equals("未分类")) { - toAi = "你是一位专业出题老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; - question = aiGenerationService.Generation(toAi); - } else { - toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; - question = aiGenerationService.Generation(subject, toAi); - } - + QuestionResponseDTO question = aiGenerationService.Generation(subject, toAi); if (null == question.getContent() || null == question.getAnswer()) { throw new AppException(GlobalServiceStatusCode.QUESTION_GENERATION_FAIL); } @@ -155,22 +147,12 @@ public Flux> aiJudge(String userId, String questionId, S // 将ai流式调用提交到自定义线程池 String subject = value.getSubject(); - if (subject.equals("未分类")) { - return Flux.defer(() -> aiGenerationService.aiJudgeStream(chat)) - .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) - .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) - .map(chunk -> ServerSentEvent.builder() - .data(chunk) - .build()); - } else { - return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) - .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) - .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) - .map(chunk -> ServerSentEvent.builder() - .data(chunk) - .build()); - } - + return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) + .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) + .map(chunk -> ServerSentEvent.builder() + .data(chunk) + .build()); } private void autoRecord(String userId, String questionId, String answer, String correctAnswer) { diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java index 4564309..8ce82ea 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java @@ -31,10 +31,8 @@ public AiGenerationService aiGenerationService() { .chatModel(myQwenChatModel) .streamingChatModel(qwenStreamingChatModel) //流式输出 .contentRetriever(contentRetriever) // RAG检索增强 - .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder() - .maxMessages(2) // 核心:0 条消息保留 → 每次都是新会话 - .id(memoryId) // 仍需绑定 memoryId(即 fileName),不影响传递 - .build()).build(); + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) + .build(); return build; } From edf58724e5abe5bee710b3ae9f416a6805c2ddc4 Mon Sep 17 00:00:00 2001 From: liche <3372134858@qq.com> Date: Sat, 29 Nov 2025 11:34:50 +0800 Subject: [PATCH 40/49] =?UTF-8?q?Revert=20"Revert=20"feat:=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dbug""?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/ChatModelListenerConfig.java | 2 +- refine-app/src/main/resources/AiAnalyze.txt | 2 ++ .../docs/\345\214\226\345\255\246.txt" | 1 + .../adapter/port/AiGenerationService.java | 6 ++++ .../service/impl/QuestionServiceImpl.java | 34 ++++++++++++++----- .../port/AiGenerationServiceFactory.java | 6 ++-- 6 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 "refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" diff --git a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java index b34e055..078e684 100644 --- a/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/ChatModelListenerConfig.java @@ -28,7 +28,7 @@ public void onResponse(ChatModelResponseContext responseContext) { @Override public void onError(ChatModelErrorContext errorContext) { - log.info("onError(): {}", errorContext.error().getMessage()); + log.warn("onError(): {}", errorContext.error().getMessage()); } }; } diff --git a/refine-app/src/main/resources/AiAnalyze.txt b/refine-app/src/main/resources/AiAnalyze.txt index 3685ca5..9eb4f3d 100644 --- a/refine-app/src/main/resources/AiAnalyze.txt +++ b/refine-app/src/main/resources/AiAnalyze.txt @@ -1,3 +1,5 @@ 你是一个专业判题助手,可以判断答案是否正确并给出正确答案,根据问题针对学生的答案详细解析,解析详细一点 (对于填数据类型的填空题,允许答案有少量误差) +一步一步详细分析答案是怎么得出来的 不添加其他多余文字,例如:"希望这可以帮助澄清你的疑问!如果有其他问题,请随时告诉我。" +以纯文本形式输出,不要带有没必要的符号如** \ No newline at end of file diff --git "a/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" "b/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" new file mode 100644 index 0000000..6851ceb --- /dev/null +++ "b/refine-app/src/main/resources/docs/\345\214\226\345\255\246.txt" @@ -0,0 +1 @@ +化学能是没用的 \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java index 0aaf5ec..749d071 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java @@ -14,10 +14,16 @@ public interface AiGenerationService { @SystemMessage(fromResource = "AiGeneration.txt") QuestionResponseDTO Generation(@MemoryId String subject, @UserMessage String message); + @SystemMessage(fromResource = "AiGeneration.txt") + QuestionResponseDTO Generation(@UserMessage String message); + //流式输出 @SystemMessage(fromResource = "AiAnalyze.txt") Flux aiJudgeStream(@MemoryId String subject, @UserMessage String message); + @SystemMessage(fromResource = "AiAnalyze.txt") + Flux aiJudgeStream(@UserMessage String message); + // 会话 String chat(String message); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index 16d8d88..e274416 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -60,10 +60,18 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues if (null == knowledgePointName) { throw new AppException("找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); } - String toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; // 调用外部接口生成新题目 - QuestionResponseDTO question = aiGenerationService.Generation(subject, toAi); + String toAi; + QuestionResponseDTO question; + if (subject.equals("未分类")) { + toAi = "你是一位专业出题老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; + question = aiGenerationService.Generation(toAi); + } else { + toAi = "你是一位" + subject + "老师,请根据\"" + knowledgePointName + "\"知识点出一道题目"; + question = aiGenerationService.Generation(subject, toAi); + } + if (null == question.getContent() || null == question.getAnswer()) { throw new AppException(GlobalServiceStatusCode.QUESTION_GENERATION_FAIL); } @@ -147,12 +155,22 @@ public Flux> aiJudge(String userId, String questionId, S // 将ai流式调用提交到自定义线程池 String subject = value.getSubject(); - return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) - .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) - .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) - .map(chunk -> ServerSentEvent.builder() - .data(chunk) - .build()); + if (subject.equals("未分类")) { + return Flux.defer(() -> aiGenerationService.aiJudgeStream(chat)) + .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) + .map(chunk -> ServerSentEvent.builder() + .data(chunk) + .build()); + } else { + return Flux.defer(() -> aiGenerationService.aiJudgeStream(subject, chat)) + .subscribeOn(Schedulers.fromExecutor(aiExclusiveThreadPool)) + .doOnError(e -> log.error("流式聊天异常:用户Id={},题目id={}, 用户判题回答={}", userId, questionId, userAnswer, e)) + .map(chunk -> ServerSentEvent.builder() + .data(chunk) + .build()); + } + } private void autoRecord(String userId, String questionId, String answer, String correctAnswer) { diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java index 8ce82ea..4564309 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/port/AiGenerationServiceFactory.java @@ -31,8 +31,10 @@ public AiGenerationService aiGenerationService() { .chatModel(myQwenChatModel) .streamingChatModel(qwenStreamingChatModel) //流式输出 .contentRetriever(contentRetriever) // RAG检索增强 - .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) - .build(); + .chatMemoryProvider(memoryId -> MessageWindowChatMemory.builder() + .maxMessages(2) // 核心:0 条消息保留 → 每次都是新会话 + .id(memoryId) // 仍需绑定 memoryId(即 fileName),不影响传递 + .build()).build(); return build; } From 7207053f67271ef293ebfaa9d85719f64f3ec61c Mon Sep 17 00:00:00 2001 From: malog <2153315236@qq.com> Date: Sat, 29 Nov 2025 11:55:47 +0800 Subject: [PATCH 41/49] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DvectorMapper?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=9A=82=E6=97=B6=E5=88=A0=E9=99=A4=E5=90=91?= =?UTF-8?q?=E9=87=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mybatis/mapper/VectorMapper.xml | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index e56e317..75a669e 100644 --- a/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -2,37 +2,41 @@ - + INSERT INTO user_learning_vectors - (user_id, question_id, action_type, question_content, subject, knowledge_point_id, embedding, metadata) + (user_id, question_id, action_type, question_content, subject, knowledge_point_id, metadata) VALUES (#{userId}, #{questionId}, #{actionType}, #{questionContent}, #{subject}, #{knowledgePointId}, - #{embedding}::vector, #{metadata}::jsonb) + #{metadata}::jsonb) - + - + - + @@ -56,9 +60,11 @@ AND is_active = true ORDER BY created_at DESC - + SELECT id, user_id, question_id, action_type, question_content, subject, knowledge_point_id, - metadata, created_at, updated_at, + created_at, updated_at, 0.5 as similarity FROM user_learning_vectors WHERE user_id = #{userId} @@ -23,19 +22,19 @@ LIMIT #{limit} - + - + - + select question_content as context, k.knowledge_desc as knowledgePoint, m.question_status as status from MistakeQuestion m join knowledgePoint k on m.knowledge_point_id = k.knowledge_point_id - where m.user_id = #{userId} and update_time >= date_sub(now(), interval 14 day) + where m.user_id = #{userId} and update_time >= now() - interval 14 day \ No newline at end of file diff --git a/refine-app/src/main/resources/mybatis/mapper/ReviewQuestionMapper.xml b/refine-app/src/main/resources/mybatis/mapper/ReviewQuestionMapper.xml index add7378..2888332 100644 --- a/refine-app/src/main/resources/mybatis/mapper/ReviewQuestionMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/ReviewQuestionMapper.xml @@ -2,21 +2,21 @@ - delete from MistakeQuestion where user_id = #{userId} and id in + delete from MistakeQuestion where user_id = #{userId} and question_id in #{questionId} diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/IReviewQuestionRepository.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/IReviewQuestionRepository.java index bdd3738..4858014 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/IReviewQuestionRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/IReviewQuestionRepository.java @@ -13,8 +13,8 @@ public interface IReviewQuestionRepository { - @Select("SELECT COUNT(1) FROM MistakeQuestion " + - "WHERE user_id = #{userId} AND date_sub(now(), interval 7 day) >= update_time") + @Select("SELECT COUNT(id) FROM MistakeQuestion " + + "WHERE user_id = #{userId} AND now()- interval 7 day >= update_time") int queryReviewQuestions(@Param("userId") String userId); void deleteBatch(@Param("userId") String userId, @Param("questionIds") List questionIds); diff --git a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java index 08ed217..3cd5680 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/Feetback/adapter/repository/ITrickyKnowledgeRepository.java @@ -15,7 +15,7 @@ public interface ITrickyKnowledgeRepository { */ @Select("select m.knowledge_point_id, k.knowledge_desc from MistakeQuestion m" + " join knowledgePoint k on m.knowledge_point_id = k.knowledge_point_id " + - "where m.user_id = #{userId} and question_status = 0 and m.create_time >= subdate(now(), 14)" + + "where m.user_id = #{userId} and question_status = 0 and m.create_time >= now() - interval 14 day " + "group by m.knowledge_point_id, k.knowledge_desc " + "having count(m.knowledge_point_id) >= 3") List getTrickyKnowledgePoints(String userId); diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java index e0fa7a1..1d888ad 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java @@ -11,36 +11,36 @@ @Mapper public interface KeyPointsMapper { @Select("select knowledge_point_id as id, knowledge_point_name as keyPoints from knowledgePoint" + - " where parent_knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + " where user_id = #{userId} and parent_knowledge_point_id = #{knowledgeId}") List getSonKeyPoints(@Param("knowledgeId") String knowledgeId, @Param("userId") String userId); @Select("select knowledge_point_id as id, knowledge_point_name as keyPoints from knowledgePoint" + - " where parent_knowledge_point_id = #{subjectId} and user_id = #{userId}") + " where user_id = #{userId} and parent_knowledge_point_id = #{subjectId} ") List getKeyPoints(int subjectId, String userId); @Select("select knowledge_desc from knowledgePoint" + - " where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + " where user_id = #{userId} and knowledge_point_id = #{knowledgeId} ") String getKnowledgedescById(String knowledgeId, String userId); - @Select("select count(id) as updateCount, count(update_time >= SUBDATE(now(), 7)) as reviewCount from MistakeQuestion" + - " where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + @Select("select count(id) as updateCount, count(update_time >= now() - interval 7 day) as reviewCount from MistakeQuestion" + + " where user_id = #{userId} and knowledge_point_id = #{knowledgeId}") WrongQuestionVO getRelatedWrongQuecstions(String knowledgeId, String userId); @Select("select k.parent_knowledge_point_id as id, k1.knowledge_point_name as keyPoints from knowledgePoint k " + "join knowledgePoint k1 on k.parent_knowledge_point_id = k1.knowledge_point_id" + - " where k.knowledge_point_id = #{knowledgeId} and k.user_id = #{userId}") + " where k.user_id = #{userId} and k.knowledge_point_id = #{knowledgeId}") KeyPointsVO getParentKeyPoints(String knowledgeId, String userId); - @Select("update knowledgePoint set note = #{note} where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + @Select("update knowledgePoint set note = #{note} where user_id = #{userId} and knowledge_point_id = #{knowledgeId}") void savedNote(String note, String knowledgeId, String userId); - @Select("update knowledgePoint set status = 1 where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + @Select("update knowledgePoint set status = 1 where user_id = #{userId} and knowledge_point_id = #{knowledgeId}") void markAsMastered(String knowledgeId, String userId); - @Select("update knowledgePoint set knowledge_point_name = #{newName} where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + @Select("update knowledgePoint set knowledge_point_name = #{newName} where user_id = #{userId} and knowledge_point_id = #{knowledgeId}") void renameNode(String knowledgeId, String newName, String userId); - @Select("select count(id) from MistakeQuestion where knowledge_point_id = #{knowledgeId} and user_id = #{userId}") + @Select("select count(id) from MistakeQuestion where user_id = #{userId} and knowledge_point_id = #{knowledgeId}") int getTotalById(String knowledgeId, String userId); @Select("select count(id) from MistakeQuestion where knowledge_point_id = #{knowledgeId} and user_id = #{userId} and question_status = 0") diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java index 9afca10..1cafb6e 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IStudyOverviewRepository.java @@ -20,12 +20,5 @@ public interface IStudyOverviewRepository { @Select("select count(*) from MistakeQuestion where user_id = #{userId} and update_time between #{localDateTime} and #{localDateTime1}") Integer countByUserIdAndUpdateTimeBetween(@Param("userId") String userId, LocalDateTime localDateTime, LocalDateTime localDateTime1); - @Select("select count(question_id) from MistakeQuestion where user_id = #{userId}") - int queryQuestionsNum(String userId); - @Select("select count(question_id) from MistakeQuestion where user_id = #{userId} and question_status = 1") - int queryMasteredQuestions(String userId); - - @Select("select study_time from UserData where user_id = #{userId}") - int queryStudyTime(String userId); } diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IUserOverviewRepository.java b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IUserOverviewRepository.java new file mode 100644 index 0000000..0670c97 --- /dev/null +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/adapter/repository/IUserOverviewRepository.java @@ -0,0 +1,38 @@ +package com.achobeta.domain.overview.adapter.repository; + +import com.achobeta.domain.Feetback.model.valobj.TrickyKnowledgePointVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import java.util.List; + +@Mapper +public interface IUserOverviewRepository { + @Select("select count(distinct knowledge_point_id) from MistakeQuestion " + + "where user_id = #{userId} and create_time >= subdate(now(), 14) and question_status = 0 " + + "group by knowledge_point_id " + + "having count(knowledge_point_id) >= 3") + int getHardQuestions(String userId); + + @Select("select count(id) from MistakeQuestion " + + "where user_id = #{userId}") + int getQuestionsNum(String userId); + + @Select("select count(id) from MistakeQuestion " + + "where user_id = #{userId} and question_status = 1") + int getHardQuestionsNum(String userId); + + @Update("update UserData set hard_questions = #{hardQuestions}, " + + "questions_num = #{questionsNum}, " + + "review_rate = #{reviewRate} " + + "where user_id = #{userId}") + void updateUserOverview(String userId, int hardQuestions, int questionsNum, double reviewRate); + + @Select("select user_id from UserInformation where user_status = 1") + List getUserIds(); + + @Update("update UserData set study_time = study_time + #{duration} " + + "where user_id = #{userId}") + void updateUserDuration(String userId, int duration); +} diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java index a402454..5c97066 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java @@ -26,17 +26,7 @@ public class LearningOverviewService implements ILearningOverviewService{ private IReviewFeedbackService reviewFeedbackService; @Override public StudyOverviewVO getOverview(String userId) { - StudyOverviewVO vo = new StudyOverviewVO(); - vo.setQuestionsNum(repository.queryQuestionsNum(userId)); - vo.setStudyTime(repository.queryStudyTime(userId)); - vo.setHardQuestions(reviewFeedbackService.getTrickyKnowledgePoint(userId).size()); - - if (vo.getQuestionsNum() == 0) { - vo.setReviewRate(0); - return vo; - } - - vo.setReviewRate(repository.queryMasteredQuestions(userId)*1.0 / vo.getQuestionsNum()); + StudyOverviewVO vo = repository.queryStudyOverview(userId); return vo; } diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/UserOverviewService.java b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/UserOverviewService.java new file mode 100644 index 0000000..da56d6c --- /dev/null +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/UserOverviewService.java @@ -0,0 +1,37 @@ +package com.achobeta.domain.overview.service.extendbiz; + +import com.achobeta.domain.overview.adapter.repository.IUserOverviewRepository; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class UserOverviewService { + @Autowired + private IUserOverviewRepository userOverviewRepository; + + @Scheduled(cron="0 */3 * * * *") + public void handleUserOverview() { + List userIds = userOverviewRepository.getUserIds(); + for (String userId : userIds) { + //查询用户易错知识点数 + int hardQuestions = userOverviewRepository.getHardQuestions(userId); + //查询用户总错题数 + int questionsNum = userOverviewRepository.getQuestionsNum(userId); + //查询用户已掌握错题数 + int hardQuestionsNum = userOverviewRepository.getHardQuestionsNum(userId); + + double reviewRate = questionsNum == 0 ? 0 : hardQuestionsNum * 1.0 / questionsNum; + + userOverviewRepository.updateUserOverview(userId, hardQuestions, questionsNum, reviewRate); + } + } + + public void updateUserDuration(String userId, int duration) { + userOverviewRepository.updateUserDuration(userId, duration); + } +} diff --git a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java index 043237e..23fcf0a 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/user/service/impl/UserAccountServiceImpl.java @@ -19,6 +19,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * 用户领域服务实现 @@ -36,7 +37,6 @@ public class UserAccountServiceImpl implements IUserAccountService { private final ApplicationEventPublisher eventPublisher; - /** * 用户注册(添加验证码验证逻辑) * diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/UserRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/UserRepository.java index 336e5a5..df149e6 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/UserRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/UserRepository.java @@ -4,6 +4,7 @@ import com.achobeta.domain.user.model.entity.UserEntity; import com.achobeta.infrastructure.dao.UserAccountMapper; import com.achobeta.domain.IRedisService; +import com.achobeta.infrastructure.dao.UserDataMapper; import com.achobeta.types.support.id.SnowflakeIdWorker; import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; @@ -21,6 +22,7 @@ public class UserRepository implements IUserRepository { private IRedisService redis; private final UserAccountMapper userAccountMapper; + private final UserDataMapper userDataMapper; /** * 这里简化了,分布式部署的话要从配置文件读入workerId(每台机器设置唯一不同) @@ -48,6 +50,7 @@ public void save(UserEntity user) { // 生成唯一用户ID user.setUserId(INSTANCE.nextIdStr()); userAccountMapper.insert(user); + userDataMapper.insert(user.getUserId()); } @Override diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/UserDataMapper.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/UserDataMapper.java new file mode 100644 index 0000000..7f594f7 --- /dev/null +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/UserDataMapper.java @@ -0,0 +1,10 @@ +package com.achobeta.infrastructure.dao; + +import com.achobeta.domain.user.model.entity.UserEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface UserDataMapper { + + void insert(String userId); +} diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java b/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java index 0b729e7..cf78df8 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/event/UserLoginEventListener.java @@ -1,5 +1,6 @@ package com.achobeta.trigger.event; +import com.achobeta.domain.overview.service.extendbiz.UserOverviewService; import com.achobeta.domain.rag.service.impl.LearningAnalysisService; import com.achobeta.domain.rag.model.valobj.LearningDynamicVO; import com.achobeta.domain.user.event.UserLoginEvent; diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 016cb09..64039a2 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -1,7 +1,9 @@ package com.achobeta.trigger.http; +import cn.hutool.core.date.DateTime; import com.achobeta.api.dto.LoginRequestDTO; import com.achobeta.api.dto.RegisterRequestDTO; +import com.achobeta.domain.overview.service.extendbiz.UserOverviewService; import com.achobeta.domain.user.model.valobj.UserLoginVO; import com.achobeta.domain.user.service.IEmailVerificationService; import com.achobeta.domain.user.service.IUserAccountService; @@ -17,7 +19,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.Date; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * @author liangchaowen @@ -34,6 +38,9 @@ public class UserAccountController { private final IUserAccountService userAccountService; + private final UserOverviewService userOverviewService; + private final Map sessionMap = new ConcurrentHashMap<>(); + /** * 发送邮箱验证码 @@ -70,6 +77,7 @@ public Response login(@RequestBody LoginRequestDTO request) { try { user = userAccountService.login(request.getUserAccount(), request.getUserPassword()); log.info("用户 {} 登录", request.getUserAccount()); + sessionMap.put(user.getUserId(), DateTime.now()); } catch (AppException e) { return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { @@ -84,6 +92,9 @@ public Response logout(@RequestHeader("refresh-token") String refreshToken) { try { userAccountService.logout(refreshToken); log.info("用户 {} 登出", UserContext.getUserId()); + int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / 3600; + userOverviewService.updateUserDuration(UserContext.getUserId(), duration); + sessionMap.remove(UserContext.getUserId()); } catch (AppException e) { return Response.CUSTOMIZE_MSG_ERROR(e.getCode(), e.getMessage(), null); } catch (Exception e) { @@ -132,6 +143,9 @@ public Response> refreshToken(@RequestHeader("refresh-token" Map newToken; try { newToken = userAccountService.refreshToken(refreshToken); + int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / 3600; + userOverviewService.updateUserDuration(UserContext.getUserId(), duration); + sessionMap.put(UserContext.getUserId(), DateTime.now()); log.info("用户刷新access-token"); return Response.SYSTEM_SUCCESS(newToken); } catch (AppException e) { From ade36db37369134077447d9a211380810b50f1dc Mon Sep 17 00:00:00 2001 From: root Date: Sat, 29 Nov 2025 17:08:59 +0800 Subject: [PATCH 45/49] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E8=AF=AF=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E5=AF=BC=E8=87=B4=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/extendbiz/LearningOverviewService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java index 5c97066..637efd6 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/overview/service/extendbiz/LearningOverviewService.java @@ -27,6 +27,14 @@ public class LearningOverviewService implements ILearningOverviewService{ @Override public StudyOverviewVO getOverview(String userId) { StudyOverviewVO vo = repository.queryStudyOverview(userId); + if(vo == null || vo.getQuestionsNum() == 0){ + vo = StudyOverviewVO.builder() + .questionsNum(0) + .reviewRate(0.0) + .hardQuestions(0) + .studyTime(0) + .build(); + } return vo; } From d27c4b96801027d599018e5cdba6984a0d6aad61 Mon Sep 17 00:00:00 2001 From: YOLO <3372134858@qq.com> Date: Sat, 29 Nov 2025 18:43:00 +0800 Subject: [PATCH 46/49] =?UTF-8?q?fix:=E5=AE=8C=E5=96=84=E5=BD=95=E5=85=A5?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=97=B6=E7=9A=84=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/AnalyzeKnowledge.txt | 15 ++++++++++ ...3\350\257\225\346\226\207\346\241\243.txt" | 1 - .../adapter/repository/KeyPointsMapper.java | 6 ++++ .../ocr/model/entity/QuestionEntity.java | 5 ++++ .../impl/MistakeQuestionServiceImpl.java | 5 ++++ .../adapter/port/AiGenerationService.java | 4 +++ .../model/entity/MistakeQuestionEntity.java | 5 ++++ .../service/impl/QuestionServiceImpl.java | 8 ++++-- .../repository/MistakeQuestionRepository.java | 2 +- .../adapter/repository/MistakeRepository.java | 1 + .../dao/IMistakeQuestionMapper.java | 4 +-- .../infrastructure/dao/po/MistakePO.java | 1 + .../achobeta/trigger/http/OcrController.java | 28 +++++++++++++++++++ 13 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 refine-app/src/main/resources/AnalyzeKnowledge.txt delete mode 100644 "refine-app/src/main/resources/docs/\346\265\213\350\257\225\346\226\207\346\241\243.txt" diff --git a/refine-app/src/main/resources/AnalyzeKnowledge.txt b/refine-app/src/main/resources/AnalyzeKnowledge.txt new file mode 100644 index 0000000..1cd6eb5 --- /dev/null +++ b/refine-app/src/main/resources/AnalyzeKnowledge.txt @@ -0,0 +1,15 @@ +我只给一道题目给你 + +请严格按照以下规则提取题目核心知识点,仅输出结果,不添加任何额外解释: + +1. 领域分类优先:先明确题目所属大领域(如“化学”“Java编程”“数据结构”“小学数学”),再补充子分类(如需),最后聚焦核心考点; +2. 去冗余去细节:忽略题目中的具体数值、案例、干扰项等非关键信息,只保留“考察什么能力/概念/公式/方法”; +3. 格式统一规范:采用“大领域-子分类-核心知识点”的层级结构(子分类可省略,核心知识点不超过15字); +4. 精准不泛化:不扩大知识点范围,也不缩小(如题目考“氧化还原反应配平”,不写成“化学反应”或“氧化还原反应细节”); +5. 多知识点限制:若题目跨多个考点,仅提取2个最核心的,用“|”分隔。 + +示例: +- 题目:“写出H₂+O₂=H₂O的配平化学方程式” → 输出:“化学-化学反应-氧化还原反应配平” +- 题目:“Spring Boot中如何通过注解实现属性注入?” → 输出:“Java编程-Spring Boot-@Value属性注入” +- 题目:“一个长方形长5cm、宽3cm,求它的周长” → 输出:“小学数学-几何图形-长方形周长计算” +- 题目:“简述哈希表的原理及解决冲突的方法” → 输出:“数据结构-哈希表-原理|哈希冲突解决” diff --git "a/refine-app/src/main/resources/docs/\346\265\213\350\257\225\346\226\207\346\241\243.txt" "b/refine-app/src/main/resources/docs/\346\265\213\350\257\225\346\226\207\346\241\243.txt" deleted file mode 100644 index 78182ac..0000000 --- "a/refine-app/src/main/resources/docs/\346\265\213\350\257\225\346\226\207\346\241\243.txt" +++ /dev/null @@ -1 +0,0 @@ -111test \ No newline at end of file diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java index e0fa7a1..40fb0e2 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/KeyPointsMapper.java @@ -2,6 +2,7 @@ import cn.hutool.core.date.DateTime; import com.achobeta.domain.keypoints_explanation.model.valobj.*; +import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; @@ -60,4 +61,9 @@ public interface KeyPointsMapper { @Select("insert into knowledgePoint(knowledge_point_id, knowledge_point_name, knowledge_desc, parent_knowledge_point_id, user_id)" + " values(#{node.pointId}, #{node.pointName}, #{node.pointDesc}, #{parentId}, #{userId})") void saveMindMapTree(String userId, SonPointVO node, String parentId); + + @Insert("insert into knowledgePoint(knowledge_point_id, knowledge_point_name, user_id)" + + " values(#{knowledgePointId}, #{knowledgePointName}, #{userId})") + void insertNewPoint4MistakeQuestion(String userId, String knowledgePointId, String knowledgePointName); + } diff --git a/refine-domain/src/main/java/com/achobeta/domain/ocr/model/entity/QuestionEntity.java b/refine-domain/src/main/java/com/achobeta/domain/ocr/model/entity/QuestionEntity.java index eaf4804..2d2176b 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ocr/model/entity/QuestionEntity.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ocr/model/entity/QuestionEntity.java @@ -31,4 +31,9 @@ public class QuestionEntity implements Serializable { */ private String questionText; + /** + * 知识点名称 + */ + private String knowledgePointId; + } diff --git a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/MistakeQuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/MistakeQuestionServiceImpl.java index 8d15e86..857499e 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/MistakeQuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/ocr/service/impl/MistakeQuestionServiceImpl.java @@ -44,6 +44,11 @@ public boolean saveMistakeQuestion(QuestionEntity questionEntity) { return false; } + if (questionEntity.getKnowledgePointId() == null || questionEntity.getKnowledgePointId().isEmpty()) { + log.warn("知识点id为空,无法保存错题"); + return false; + } + // 保存错题数据 boolean success = mistakeQuestionRepository.save(questionEntity); diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java index 749d071..98c91d9 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/adapter/port/AiGenerationService.java @@ -27,4 +27,8 @@ public interface AiGenerationService { // 会话 String chat(String message); + // 知识点分析 + @SystemMessage(fromResource = "AnalyzeKnowledge.txt") + String knowledgeAnalysis(String questionText); + } diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/model/entity/MistakeQuestionEntity.java b/refine-domain/src/main/java/com/achobeta/domain/question/model/entity/MistakeQuestionEntity.java index b51388c..1db635c 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/model/entity/MistakeQuestionEntity.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/model/entity/MistakeQuestionEntity.java @@ -20,6 +20,11 @@ public class MistakeQuestionEntity { private String userId; + /** + * 错题ID + */ + private String questionId; + /** * 错题内容 */ diff --git a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index e274416..bc17558 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/refine-domain/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -1,5 +1,6 @@ package com.achobeta.domain.question.service.impl; +import cn.hutool.core.lang.UUID; import com.achobeta.domain.question.adapter.port.AiGenerationService; import com.achobeta.domain.question.adapter.repository.IKnowledgeRepository; import com.achobeta.domain.question.adapter.repository.IMistakeRepository; @@ -55,10 +56,10 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues String knowledgePointName = knowledgeRepository.findKnowledgeNameById(knowledgeId); if (null == subject) { - throw new AppException("找不到题目所属科目,mistakeQuestionId" + mistakeQuestionId); + throw new AppException("(ai出题)找不到题目所属科目,mistakeQuestionId" + mistakeQuestionId); } if (null == knowledgePointName) { - throw new AppException("找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); + throw new AppException("(ai出题)找不到该题知识点名称,mistakeQuestionId" + mistakeQuestionId); } // 调用外部接口生成新题目 @@ -77,7 +78,7 @@ public QuestionResponseDTO questionGeneration(String userId, Integer mistakeQues } // 构建错题实体 - String questionId = String.valueOf(mistakeQuestionId + System.currentTimeMillis()); + String questionId = UUID.fastUUID().toString().substring(0, 19)+ System.currentTimeMillis(); MistakeQuestionDTO dto = new MistakeQuestionDTO(); dto.setUserId(userId); @@ -112,6 +113,7 @@ public void recordMistakeQuestion(String userId, String questionId) { mistakeRepository.save(MistakeQuestionEntity.builder() .userId(userId) + .questionId(questionId) .questionContent(value.getQuestionContent()) .subject(value.getSubject()) .knowledgePointId(value.getKnowledgePointId()) diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeQuestionRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeQuestionRepository.java index 7934b56..d5dc2ff 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeQuestionRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeQuestionRepository.java @@ -37,6 +37,7 @@ public boolean save(QuestionEntity questionEntity) { mistakeQuestion.setUserId(questionEntity.getUserId()); mistakeQuestion.setQuestionId(questionEntity.getQuestionId()); mistakeQuestion.setQuestionContent(questionEntity.getQuestionText()); + mistakeQuestion.setKnowledgePointId(questionEntity.getKnowledgePointId()); // 设置默认值 mistakeQuestion.setSubject("未分类"); // 默认学科 @@ -45,7 +46,6 @@ public boolean save(QuestionEntity questionEntity) { mistakeQuestion.setIsCalculateErr(0); mistakeQuestion.setIsTimeShortage(0); mistakeQuestion.setOtherReason(""); - mistakeQuestion.setKnowledgePointId(""); mistakeQuestion.setStudyNote(""); mistakeQuestion.setQuestionStatus(0); // 默认未理解状态 diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java index e71e3c5..6708458 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/adapter/repository/MistakeRepository.java @@ -1,5 +1,6 @@ package com.achobeta.infrastructure.adapter.repository; +import cn.hutool.core.lang.UUID; import com.achobeta.domain.question.adapter.repository.IMistakeRepository; import com.achobeta.domain.question.model.entity.MistakeQuestionEntity; import com.achobeta.domain.question.model.po.MistakeKnowledgePO; diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IMistakeQuestionMapper.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IMistakeQuestionMapper.java index 89c0582..2db8114 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IMistakeQuestionMapper.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/IMistakeQuestionMapper.java @@ -12,8 +12,8 @@ @Mapper public interface IMistakeQuestionMapper { - @Insert("INSERT INTO MistakeQuestion (user_id, question_content, subject, other_reason, knowledge_point_id,create_time, update_time) " + - "VALUES (#{userId}, #{questionContent}, #{subject}, #{otherReason}, #{knowledgePointId}, #{createTime}, #{updateTime})") + @Insert("INSERT INTO MistakeQuestion (user_id, question_id, question_content, subject, other_reason, knowledge_point_id,create_time, update_time) " + + "VALUES (#{userId}, #{questionId}, #{questionContent}, #{subject}, #{otherReason}, #{knowledgePointId}, #{createTime}, #{updateTime})") void insert(MistakePO mistakePO); @Select("SELECT subject, knowledge_point_id AS knowledgeId FROM MistakeQuestion WHERE id = #{mistakeQuestionId}") diff --git a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/po/MistakePO.java b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/po/MistakePO.java index 765a4bf..7656faf 100644 --- a/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/po/MistakePO.java +++ b/refine-infrastructure/src/main/java/com/achobeta/infrastructure/dao/po/MistakePO.java @@ -18,6 +18,7 @@ public class MistakePO { private String userId; + private String questionId; private String questionContent; private String subject; private String otherReason; diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java index 9503202..3657a91 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/OcrController.java @@ -1,10 +1,13 @@ package com.achobeta.trigger.http; +import cn.hutool.core.lang.UUID; import com.achobeta.api.dto.QuestionInfoResponseDTO; +import com.achobeta.domain.keypoints_explanation.adapter.repository.KeyPointsMapper; import com.achobeta.domain.ocr.model.entity.QuestionEntity; import com.achobeta.domain.ocr.service.IMistakeQuestionService; import com.achobeta.domain.ocr.service.IOcrService; import com.achobeta.domain.ocr.adapter.port.redis.IQuestionRedisRepository; +import com.achobeta.domain.question.adapter.port.AiGenerationService; import com.achobeta.types.Response; import com.achobeta.types.annotation.GlobalInterception; import com.achobeta.types.common.UserContext; @@ -12,6 +15,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -29,9 +33,15 @@ @RequiredArgsConstructor public class OcrController { + // 通用异步线程池 + private final ThreadPoolTaskExecutor threadPoolExecutor; + + private final KeyPointsMapper keyPointsMapper; + private final IOcrService ocrService; private final IMistakeQuestionService mistakeQuestionService; private final IQuestionRedisRepository questionRedisRepository; + private final AiGenerationService aiGenerationService; /** * 抽取第一个问题 @@ -65,7 +75,25 @@ public Response extractFirst(@RequestPart("file") Multi // 调用OCR服务提取文件中的第一个题目 QuestionEntity questionEntity = ocrService.extractQuestionContent(userId, file.getBytes(), ft); + //TODO + // ai根据向量库(和mysql知识点表里的完全一致)(新建一个表)查询有没有相似知识点,再返回知识点名称 + String knowledgeName = aiGenerationService.knowledgeAnalysis(questionEntity.getQuestionText()); + if (knowledgeName == null || knowledgeName.isEmpty()) { + log.warn("ai生成的知识点为空,请检查ai生成知识点的逻辑"); + } + String knowledgePointId = UUID.fastUUID().toString(); + //把新知识点录入数据库中 + threadPoolExecutor.execute(() -> { + try { + keyPointsMapper.insertNewPoint4MistakeQuestion(userId, knowledgePointId, knowledgeName); + log.info("ai生成知识点录入表成功, knowledgeName:{} 题目id:{}", knowledgeName, questionEntity.getQuestionId()); + } catch (Exception e) { + log.error("ai生成知识点录入表失败, knowledgeName:{} 题目id:{}", knowledgeName, questionEntity.getQuestionId(), e); + } + }); + // 将错题数据保存到数据库中 + questionEntity.setKnowledgePointId(knowledgePointId); boolean saveSuccess = mistakeQuestionService.saveMistakeQuestion(questionEntity); if (!saveSuccess) { log.warn("错题保存失败,但继续返回OCR识别结果: userId={}, questionId={}", From d382a538ce20ec5ecf4f27175974923b96ca20aa Mon Sep 17 00:00:00 2001 From: root Date: Sat, 29 Nov 2025 19:42:10 +0800 Subject: [PATCH 47/49] =?UTF-8?q?=E5=AE=8C=E5=96=84SubjectTransportMappeer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/repository/SubjectTransportMappeer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/SubjectTransportMappeer.java b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/SubjectTransportMappeer.java index 0625b3f..454e8b0 100644 --- a/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/SubjectTransportMappeer.java +++ b/refine-domain/src/main/java/com/achobeta/domain/keypoints_explanation/adapter/repository/SubjectTransportMappeer.java @@ -14,5 +14,7 @@ public class SubjectTransportMappeer { FIELD_MAP.put("政治", -5); FIELD_MAP.put("历史", -6); FIELD_MAP.put("语文", -7); + FIELD_MAP.put("地理", -8); + FIELD_MAP.put("生物", -9); } } From 29e7afa9f6efab3023830240d624b4d635013078 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 29 Nov 2025 20:34:25 +0800 Subject: [PATCH 48/49] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=99=BB=E5=87=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../trigger/http/UserAccountController.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 64039a2..65c7207 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -143,9 +143,16 @@ public Response> refreshToken(@RequestHeader("refresh-token" Map newToken; try { newToken = userAccountService.refreshToken(refreshToken); - int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / 3600; - userOverviewService.updateUserDuration(UserContext.getUserId(), duration); - sessionMap.put(UserContext.getUserId(), DateTime.now()); + if(sessionMap.get(UserContext.getUserId()) == null){ + // 这里异常状态其实用心跳机制或websocket比较好,但是服务器不知道能不能行,因此默认1小时 + userOverviewService.updateUserDuration(UserContext.getUserId(), 1); + sessionMap.put(UserContext.getUserId(), DateTime.now()); + }else{ + // 其实时长应该double比较合适,改换为int可能学几天也未必有1小时 + int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / 3600; + userOverviewService.updateUserDuration(UserContext.getUserId(), duration); + sessionMap.put(UserContext.getUserId(), DateTime.now()); + } log.info("用户刷新access-token"); return Response.SYSTEM_SUCCESS(newToken); } catch (AppException e) { From 9b68485e9406568553552f0718de3a0179fda0db Mon Sep 17 00:00:00 2001 From: root Date: Sat, 29 Nov 2025 20:52:05 +0800 Subject: [PATCH 49/49] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=99=BB=E5=87=BA?= =?UTF-8?q?=E9=80=BB=E8=BE=91=20--=20=E5=BD=95=E5=85=A5=E5=AD=A6=E4=B9=A0?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E5=8D=95=E4=BD=8D=E7=BA=A0=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/achobeta/trigger/http/UserAccountController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java index 65c7207..bcd533b 100644 --- a/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java +++ b/refine-trigger/src/main/java/com/achobeta/trigger/http/UserAccountController.java @@ -149,7 +149,7 @@ public Response> refreshToken(@RequestHeader("refresh-token" sessionMap.put(UserContext.getUserId(), DateTime.now()); }else{ // 其实时长应该double比较合适,改换为int可能学几天也未必有1小时 - int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / 3600; + int duration = (int) (DateTime.now().getTime() - sessionMap.get(UserContext.getUserId()).getTime()) / (1000 * 3600); userOverviewService.updateUserDuration(UserContext.getUserId(), duration); sessionMap.put(UserContext.getUserId(), DateTime.now()); }