diff --git a/.github/workflows/depoly.yml b/.github/workflows/depoly.yml index 7dd62c0..8e99c5a 100644 --- a/.github/workflows/depoly.yml +++ b/.github/workflows/depoly.yml @@ -58,14 +58,17 @@ 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..." - 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." - - 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 diff --git a/docs/dev-ops/docker-compose-app.yml b/docs/dev-ops/docker-compose-app.yml index 5d68f4d..d387f44 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" diff --git a/pom.xml b/pom.xml index f51ba2e..bdebeaf 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,8 @@ UTF-8 21 21 - 1.1.0-beta7 + 1.6.0-beta12 + 2.19.0 @@ -64,11 +65,11 @@ - - dev.langchain4j - langchain4j-core - 1.1.0 - + + + + + org.postgresql @@ -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} @@ -202,11 +208,6 @@ java-jwt 4.4.0 - - commons-codec - commons-codec - 1.15 - org.projectlombok lombok @@ -227,7 +228,7 @@ dev.langchain4j langchain4j - 1.1.0 + 1.6.0 dev.langchain4j 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-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-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..c38ac82 --- /dev/null +++ b/refine-api/src/main/java/com/achobeta/api/dto/MistakeReasonToggleRequestDTO.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 MistakeReasonToggleRequestDTO implements Serializable { + + @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-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-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/config/AiThreadPoolConfig.java b/refine-app/src/main/java/com/achobeta/config/AiThreadPoolConfig.java index b585593..d06e35a 100644 --- a/refine-app/src/main/java/com/achobeta/config/AiThreadPoolConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/AiThreadPoolConfig.java @@ -1,48 +1,71 @@ package com.achobeta.config; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +/** + * AI 专属线程池配置(用于 AI 流式调用、API 请求等业务逻辑) + * 核心优化:配置容错、参数合理性校验、资源占用控制、生产环境监控 + */ @Configuration +@Slf4j public class AiThreadPoolConfig { - // 最大并发数 - @Value("${ai.dashscope.max-concurrency}") + /** + * AI 最大并发数(从配置文件读取,默认值 10,避免配置缺失导致启动失败) + * 用途:限制 AI 调用的最大并发量,避免触发第三方 API(如 DashScope)限流 + */ + @Value("${ai.dashscope.max-concurrency:10}") private int aiMaxConcurrency; - @Bean + /** + * AI 专属线程池(业务层面:执行 AI 流式调用、模型推理等耗时操作) + */ + @Bean(name = "aiExclusiveThreadPool") // 明确 bean 名称,避免注入冲突 public ThreadPoolTaskExecutor aiExclusiveThreadPool() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 1. 基础参数计算(CPU 核心数) int cpuCore = Runtime.getRuntime().availableProcessors(); + log.info("系统 CPU 核心数:{},配置的 AI 最大并发数:{}", cpuCore, aiMaxConcurrency); - int baseCoreSize = cpuCore * 2 + 1; + // 2. 配置容错校验(避免无效配置) + if (aiMaxConcurrency < 1) { + log.warn("AI 最大并发数配置无效({}),使用默认值 10", aiMaxConcurrency); + aiMaxConcurrency = 10; + } - int corePoolSize = Math.max(baseCoreSize, aiMaxConcurrency); + // 3. 线程池参数优化(核心逻辑) + // 核心线程数:取「CPU核心数*2 +1」和「AI最大并发数的1/2」的较小值(避免空闲线程浪费) + int corePoolSize = Math.min(cpuCore * 2 + 1, aiMaxConcurrency / 2); + // 最大线程数:不超过配置的 AI 最大并发数(避免超预期并发导致限流/资源耗尽) + int maxPoolSize = Math.min(cpuCore * 4 + 1, aiMaxConcurrency); + // 队列容量:核心线程数*3(上限 50),平衡缓冲和内存占用(避免队列过大导致任务堆积) + int queueCapacity = Math.min(corePoolSize * 3, 50); + // 4. 初始化线程池 + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(120); // 空闲线程存活时间(120秒,适配 AI 长耗时调用) + executor.setThreadNamePrefix("ai-exclusive-thread-"); // 线程名前缀(日志排查) + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略 - // 最大线程数:从配置文件读取,确保不超过系统承受能力 - // 若配置值小于核心线程数,则强制使用核心线程数作为上限 - executor.setMaxPoolSize(corePoolSize * 2); - - // 设置缓冲队列大小,避免任务瞬间涌入导致OOM - executor.setQueueCapacity(corePoolSize * 5); + // 5. 线程池销毁时的优雅关闭(等待任务执行完成,避免任务丢失) + executor.setWaitForTasksToCompleteOnShutdown(true); + executor.setAwaitTerminationSeconds(30); // 等待 30 秒后强制关闭(适配 AI 长耗时任务) + executor.setAwaitTerminationMillis(30 * 1000L); // 兼容低版本 Spring(可选) - // 超过核心线程数的线程,空闲120秒后销毁, - executor.setKeepAliveSeconds(120); + // 6. 初始化并打印配置(生产环境监控关键) + executor.initialize(); + log.info("AI 专属线程池初始化完成:核心线程数={},最大线程数={},队列容量={},拒绝策略={}", + corePoolSize, maxPoolSize, queueCapacity, executor.getThreadPoolExecutor().getRejectedExecutionHandler().getClass().getSimpleName()); - // 线程名称前缀,便于日志排查 - executor.setThreadNamePrefix("ai-exclusive-thread-"); - - // 当线程池和队列都满时,让提交任务的线程自己执行,避免任务丢失 - executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); - - executor.initialize();//初始化 return executor; } - -} +} \ No newline at end of file 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/java/com/achobeta/config/RagConfig.java b/refine-app/src/main/java/com/achobeta/config/RagConfig.java index 4ea9db1..9f7d305 100644 --- a/refine-app/src/main/java/com/achobeta/config/RagConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/RagConfig.java @@ -3,17 +3,22 @@ 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; 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; @@ -101,13 +106,43 @@ 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. 从查询元数据中动态提取业务传入的学科(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. 用自定义的Filter构建动态过滤条件 + //Filter filter = MetadataFilterBuilder.metadataKey("subject").isEqualTo(subject); + + //模糊匹配,自定义的Filter构建动态过滤条件 + Filter filter = MetadataFilterBuilder.metadataKey("file_name").containsString(target); + + // 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/config/WebConfig.java b/refine-app/src/main/java/com/achobeta/config/WebConfig.java index 1744b16..9269031 100644 --- a/refine-app/src/main/java/com/achobeta/config/WebConfig.java +++ b/refine-app/src/main/java/com/achobeta/config/WebConfig.java @@ -2,11 +2,17 @@ import com.achobeta.intercepter.LogInterceptor; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.concurrent.ThreadPoolExecutor; + /** * @author BanTanger 半糖 * @date 2024/11/4 @@ -40,4 +46,24 @@ public void addCorsMappings(CorsRegistry registry) { .allowCredentials(true); } + @Bean(name = "mvcAsyncTaskExecutor") + public AsyncTaskExecutor mvcAsyncTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + int cpuCore = Runtime.getRuntime().availableProcessors(); + executor.setCorePoolSize(cpuCore + 1); // 框架层面线程数精简 + executor.setMaxPoolSize(2 * cpuCore + 1); + executor.setQueueCapacity(50); // 避免请求堆积 + executor.setKeepAliveSeconds(60); + executor.setThreadNamePrefix("mvc-async-"); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.initialize(); + return executor; + } + + @Override + public void configureAsyncSupport(AsyncSupportConfigurer configurer) { + configurer.setTaskExecutor(mvcAsyncTaskExecutor()); + configurer.setDefaultTimeout(60 * 1000L); // 异步请求超时(适配 AI 长流式响应) + } + } 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..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; } @@ -86,7 +92,7 @@ public String createRefreshToken(String userId) { * 通用Token生成方法 */ private String createToken(Map claims, Duration ttl) { - claims.put("iat", new Date()); // 添加签发时间 + claims.put("iat", System.currentTimeMillis()/1000); // 添加签发时间,毫秒值 return JWT.create() .addPayloads(claims) // 设置载荷 .setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())) // 过期时间 @@ -139,7 +145,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 +157,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 +173,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 +196,7 @@ public Map refreshAccessToken(String refreshToken, Map 0 && remainingTime < (jwtProperties.getRefreshTokenTtl().toMillis() / 2)) { // 同时会自动更新 Redis 的refresh-token @@ -232,11 +238,16 @@ public void invalidateRefreshToken(String refreshToken) { /** * 提取refresh-token中的iat值 */ - public Date getRefreshTokenIat(String refreshToken) { + public Long getRefreshTokenIat(String refreshToken) { return parseToken(refreshToken, "refresh").getIat(); } + public String getRefreshToken4UserId(String userId) { + return redis.getValue(USER_TOKEN_ID_KEY + userId); + } + + /** * 内部类:封装解析后的Token载荷 */ @@ -245,7 +256,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-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/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/com/achobeta/infrastructure/dao/UserDataMapper.xml b/refine-app/src/main/resources/com/achobeta/infrastructure/dao/UserDataMapper.xml new file mode 100644 index 0000000..726dd42 --- /dev/null +++ b/refine-app/src/main/resources/com/achobeta/infrastructure/dao/UserDataMapper.xml @@ -0,0 +1,10 @@ + + + + + + + INSERT INTO UserData (user_id, questions_num, review_rate, hard_questions, study_time) + VALUES (#{userId}, 0, 0.0, 0, 0) + + \ 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-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-app/src/main/resources/mybatis/mapper/AILearningSuggessionRepository.xml b/refine-app/src/main/resources/mybatis/mapper/AILearningSuggessionRepository.xml index 4aadb99..543a9f8 100644 --- a/refine-app/src/main/resources/mybatis/mapper/AILearningSuggessionRepository.xml +++ b/refine-app/src/main/resources/mybatis/mapper/AILearningSuggessionRepository.xml @@ -5,6 +5,6 @@ \ No newline at end of file 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} 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-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml similarity index 77% rename from refine-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml rename to refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml index 6fed997..43e0953 100644 --- a/refine-infrastructure/src/main/resources/mybatis/mapper/VectorMapper.xml +++ b/refine-app/src/main/resources/mybatis/mapper/VectorMapper.xml @@ -1,38 +1,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) VALUES - (#{userId}, #{questionId}, #{actionType}, #{questionContent}, #{subject}, #{knowledgePointId}, - #{embedding}::vector, #{metadata}::jsonb) + (#{userId}, #{questionId}, #{actionType}, #{questionContent}, #{subject}, #{knowledgePointId}) - + - + - + @@ -56,9 +59,11 @@ AND is_active = true ORDER BY created_at DESC - +