#6 feat:增加文章分句,分词,词汇等级划分的处理和获取处理结果的接口

Zlúčené
BaiQi mergnuté 1 commitov z FanYanPeng/feat/behavior_record do FanYanPeng/refactor 1 rok pred

+ 7 - 0
pom.xml

@@ -211,6 +211,13 @@
             <version>2.11.5</version>
             <version>2.11.5</version>
         </dependency>
         </dependency>
 
 
+        <!-- 斯坦福大学自然语言处理工具包CoreNLP -->
+        <dependency>
+            <groupId>edu.stanford.nlp</groupId>
+            <artifactId>stanford-corenlp</artifactId>
+            <version>4.5.8</version>
+        </dependency>
+
     </dependencies>
     </dependencies>
 
 
     <build>
     <build>

+ 12 - 0
src/main/java/com/njuzr/eaibackend/config/ThreadPoolConfig.java

@@ -64,6 +64,18 @@ public class ThreadPoolConfig {
         return executor;
         return executor;
     }
     }
 
 
+    @Bean("textAnalysisTaskExecutor")
+    public ThreadPoolTaskExecutor textAnalysisTaskExecutor() {
+        ThreadPoolTaskExecutor executor = getExecutor();
+        executor.setCorePoolSize(20);
+        executor.setMaxPoolSize(50);
+        executor.setQueueCapacity(100);
+        executor.setThreadNamePrefix("text-analysis-");
+        executor.initialize();
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        return executor;
+    }
+
     private static ThreadPoolTaskExecutor getExecutor() {
     private static ThreadPoolTaskExecutor getExecutor() {
         return new ThreadPoolTaskExecutor() {
         return new ThreadPoolTaskExecutor() {
             @Override
             @Override

+ 9 - 0
src/main/java/com/njuzr/eaibackend/controller/BehaviorRecordController.java

@@ -1,6 +1,7 @@
 package com.njuzr.eaibackend.controller;
 package com.njuzr.eaibackend.controller;
 
 
 import com.njuzr.eaibackend.service.BehaviorRecordService;
 import com.njuzr.eaibackend.service.BehaviorRecordService;
+import com.njuzr.eaibackend.vo.TextAnalysisVO;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -37,6 +38,14 @@ public class BehaviorRecordController {
         return MyResponse.success(behaviorRecordService.findALL(assignmentId, studentId));
         return MyResponse.success(behaviorRecordService.findALL(assignmentId, studentId));
     }
     }
 
 
+    @GetMapping("/textAnalysis")
+    public MyResponse getTextAnalysis(
+            @RequestParam Long studentId,
+            @RequestParam Long assignmentId) {
+        TextAnalysisVO vo = behaviorRecordService.getTextAnalysis(studentId, assignmentId);
+        return MyResponse.success(vo);
+    }
+
     @PostMapping()
     @PostMapping()
     public MyResponse addBehaviorRecord(
     public MyResponse addBehaviorRecord(
             @AuthenticationPrincipal(expression = "id") Long studentId,
             @AuthenticationPrincipal(expression = "id") Long studentId,

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

@@ -22,4 +22,6 @@ public interface StudentAssignmentMapper extends BaseMapper<Engagement> {
 
 
     List<Engagement> findEngagementByAssignmentId(Long assignmentId);
     List<Engagement> findEngagementByAssignmentId(Long assignmentId);
     void delete(Long studentId, Long assignmentId);
     void delete(Long studentId, Long assignmentId);
+
+    String getTextContent(Long studentId, Long assignmentId);
 }
 }

+ 49 - 0
src/main/java/com/njuzr/eaibackend/po/TextAnalysis.java

@@ -0,0 +1,49 @@
+package com.njuzr.eaibackend.po;
+
+import com.njuzr.eaibackend.vo.TextAnalysisVO;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.redis.core.index.Indexed;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Auther: WuZilong
+ * @Date: 2025/7/18 00:37
+ * @Description:
+ */
+@Data
+@Document(collection = "textAnalyses")
+public class TextAnalysis {
+
+    @Id
+    private String id; // MongoDB自动生成的ObjectId
+    // 1. 标识信息
+    @Indexed
+    private Long studentId; // 学生ID
+
+    @Indexed
+    private Long assignmentId; // 作业ID
+
+    // 2. 原始文本
+    private String textContent; // 学生写作的原始文本
+
+    // 3. 分句结果(CoreNLP分句后的数据)
+    private List<String> sentences; // 存储分句后的句子列表(如["句子1", "句子2"])
+
+    // 4. 分词及级别结果(CoreNLP分词 + 词汇级别匹配后的数据)
+    private List<WordLevel> wordLevels; // 存储词汇及对应级别
+
+    /**
+     * 嵌套类:存储单个词汇及对应的基础词级别
+     */
+    @Data
+    public static class WordLevel {
+        private String word; // 分词后的词汇(如"student")
+        private Integer basewordLevel; // 基础词级别(如1、2、0,0表示标点符号,对应BNC/COCA的10级体系)
+    }
+
+}

+ 5 - 0
src/main/java/com/njuzr/eaibackend/service/BehaviorRecordService.java

@@ -16,4 +16,9 @@ public interface BehaviorRecordService {
     String upload(Long assignmentId, Long studentId, MultipartFile file);
     String upload(Long assignmentId, Long studentId, MultipartFile file);
 
 
     BehaviorRecordVO findALL(Long assignmentId, Long studentId);
     BehaviorRecordVO findALL(Long assignmentId, Long studentId);
+
+    /**
+     * 查询MongoDB中textAnalyses分析结果
+     */
+    TextAnalysisVO getTextAnalysis(Long studentId, Long assignmentId);
 }
 }

+ 20 - 0
src/main/java/com/njuzr/eaibackend/service/TextAnalysisService.java

@@ -0,0 +1,20 @@
+package com.njuzr.eaibackend.service;
+
+/**
+ * 文本分析服务接口
+ * 用于异步处理学生写作内容的文本分析
+ * 
+ * @author AI Assistant
+ * @date 2025-01-27
+ */
+public interface TextAnalysisService {
+
+    /**
+     * 异步分析并保存文本内容
+     * 该方法会在后台异步执行,不会阻塞主流程
+     * 
+     * @param studentId    学生ID
+     * @param assignmentId 作业ID
+     */
+    void analyzeAndSaveTextContentAsync(Long studentId, Long assignmentId);
+}

+ 15 - 1
src/main/java/com/njuzr/eaibackend/service/impl/AssignmentServiceImpl.java

@@ -15,6 +15,7 @@ import com.njuzr.eaibackend.exception.MyException;
 import com.njuzr.eaibackend.mapper.*;
 import com.njuzr.eaibackend.mapper.*;
 import com.njuzr.eaibackend.po.*;
 import com.njuzr.eaibackend.po.*;
 import com.njuzr.eaibackend.service.AssignmentService;
 import com.njuzr.eaibackend.service.AssignmentService;
+import com.njuzr.eaibackend.service.TextAnalysisService;
 import com.njuzr.eaibackend.utils.*;
 import com.njuzr.eaibackend.utils.*;
 import com.njuzr.eaibackend.vo.*;
 import com.njuzr.eaibackend.vo.*;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -50,18 +51,22 @@ public class AssignmentServiceImpl implements AssignmentService {
 
 
     private final FileUtil fileUtil = new FileUtil();
     private final FileUtil fileUtil = new FileUtil();
 
 
+    private final TextAnalysisService textAnalysisService;
+
     public AssignmentServiceImpl(AssignmentMapper assignmentMapper,
     public AssignmentServiceImpl(AssignmentMapper assignmentMapper,
                                  CourseMapper courseMapper,
                                  CourseMapper courseMapper,
                                  StudentAssignmentMapper studentAssignmentMapper,
                                  StudentAssignmentMapper studentAssignmentMapper,
                                  UserMapper userMapper,
                                  UserMapper userMapper,
                                  OssUtil ossUtil,
                                  OssUtil ossUtil,
                                  CourseServiceImpl courseServiceImpl,
                                  CourseServiceImpl courseServiceImpl,
-                                 CourseStudentMapper courseStudentMapper) {
+                                 CourseStudentMapper courseStudentMapper,
+                                 TextAnalysisService textAnalysisService) {
         this.assignmentMapper = assignmentMapper;
         this.assignmentMapper = assignmentMapper;
         this.courseMapper = courseMapper;
         this.courseMapper = courseMapper;
         this.studentAssignmentMapper = studentAssignmentMapper;
         this.studentAssignmentMapper = studentAssignmentMapper;
         this.userMapper = userMapper;
         this.userMapper = userMapper;
         this.ossUtil = ossUtil;
         this.ossUtil = ossUtil;
+        this.textAnalysisService = textAnalysisService;
     }
     }
 
 
     @Override
     @Override
@@ -345,6 +350,15 @@ public class AssignmentServiceImpl implements AssignmentService {
             int code = studentAssignmentMapper.updateById(engagement);
             int code = studentAssignmentMapper.updateById(engagement);
             if (code == 0)
             if (code == 0)
                 throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
                 throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
+            // 异步启动文本分析,不影响主流程返回速度
+            try {
+                textAnalysisService.analyzeAndSaveTextContentAsync(studentId, assignmentId);
+                log.info("已启动异步文本分析 - studentId: {}, assignmentId: {}", studentId, assignmentId);
+            } catch (Exception e) {
+                log.error("启动异步文本分析失败 - studentId: {}, assignmentId: {}, error: {}",
+                        studentId, assignmentId, e.getMessage());
+                // 不抛出异常,避免影响主流程
+            }
         } catch (Exception e) {
         } catch (Exception e) {
             log.error(e.getMessage());
             log.error(e.getMessage());
             throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");
             throw MyException.create(HttpStatus.INTERNAL_SERVER_ERROR, "更新文件版本失败");

+ 22 - 0
src/main/java/com/njuzr/eaibackend/service/impl/BehaviorRecordServiceImpl.java

@@ -7,6 +7,8 @@ import com.njuzr.eaibackend.po.BehaviorRecord;
 import com.njuzr.eaibackend.service.BehaviorRecordService;
 import com.njuzr.eaibackend.service.BehaviorRecordService;
 import com.njuzr.eaibackend.utils.OssUtil;
 import com.njuzr.eaibackend.utils.OssUtil;
 import com.njuzr.eaibackend.vo.BehaviorRecordVO;
 import com.njuzr.eaibackend.vo.BehaviorRecordVO;
+import com.njuzr.eaibackend.po.TextAnalysis;
+import com.njuzr.eaibackend.vo.TextAnalysisVO;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.HttpStatus;
@@ -21,6 +23,7 @@ import java.util.stream.Collectors;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.domain.Sort;
 import org.springframework.data.mongodb.core.MongoTemplate;
 import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Query;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.query.Criteria;
 import org.springframework.data.mongodb.core.aggregation.Aggregation;
 import org.springframework.data.mongodb.core.aggregation.Aggregation;
 import org.springframework.data.mongodb.core.aggregation.AggregationResults;
 import org.springframework.data.mongodb.core.aggregation.AggregationResults;
@@ -113,6 +116,25 @@ public class BehaviorRecordServiceImpl implements BehaviorRecordService {
         return simplified;
         return simplified;
     }
     }
 
 
+    @Override
+    public TextAnalysisVO getTextAnalysis(Long studentId, Long assignmentId) {
+        Query query = new Query();
+        query.addCriteria(Criteria.where("studentId").is(studentId).and("assignmentId").is(assignmentId));
+        TextAnalysis analysis = mongoTemplate.findOne(query, TextAnalysis.class, "textAnalyses");
+        if (analysis == null) {
+            return null;
+        }
+        TextAnalysisVO vo = new TextAnalysisVO();
+        vo.setStudentId(analysis.getStudentId());
+        vo.setAssignmentId(analysis.getAssignmentId());
+        vo.setTextContent(analysis.getTextContent());
+        vo.setSentences(analysis.getSentences());
+        vo.setWordLevels(analysis.getWordLevels());
+        vo.setSentenceCount(analysis.getSentences() != null ? analysis.getSentences().size() : 0);
+        vo.setWordCount(analysis.getWordLevels() != null ? analysis.getWordLevels().size() : 0);
+        return vo;
+    }
+
 //    public String upload(Long assignmentId, Long studentId, MultipartFile file) {
 //    public String upload(Long assignmentId, Long studentId, MultipartFile file) {
 //        try {
 //        try {
 //            Date now = new Date();
 //            Date now = new Date();

+ 241 - 0
src/main/java/com/njuzr/eaibackend/service/impl/TextAnalysisServiceImpl.java

@@ -0,0 +1,241 @@
+package com.njuzr.eaibackend.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.njuzr.eaibackend.mapper.StudentAssignmentMapper;
+import com.njuzr.eaibackend.po.TextAnalysis;
+import com.njuzr.eaibackend.service.TextAnalysisService;
+import edu.stanford.nlp.simple.Document;
+import edu.stanford.nlp.simple.Sentence;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 文本分析服务实现类
+ * 提供异步文本分析功能,包括分句、分词和词汇级别分析
+ * 
+ * @author AI Assistant
+ * @date 2025-01-27
+ */
+@Slf4j
+@Service
+public class TextAnalysisServiceImpl implements TextAnalysisService {
+
+    private final StudentAssignmentMapper studentAssignmentMapper;
+    private final MongoTemplate mongoTemplate;
+    private final ObjectMapper objectMapper;
+    private final RestTemplate restTemplate;
+
+    // 外部API配置
+    private static final String WORD_LEVEL_API_URL = "https://laurenceanthony.net/software/wordfamilyfinder/get_result.php";
+    private static final String DATABASE = "basewords_130.db";
+    private static final String CORPUS = "bnc_freq";
+    private static final int MAX_RETRY_COUNT = 5;
+    private static final long RETRY_DELAY_MS = 500;
+
+    @Autowired
+    public TextAnalysisServiceImpl(StudentAssignmentMapper studentAssignmentMapper,
+            MongoTemplate mongoTemplate) {
+        this.studentAssignmentMapper = studentAssignmentMapper;
+        this.mongoTemplate = mongoTemplate;
+        this.objectMapper = new ObjectMapper();
+        this.restTemplate = new RestTemplate();
+    }
+
+    /**
+     * 异步分析并保存文本内容
+     * 该方法会在后台异步执行,不会阻塞主流程
+     * 
+     * @param studentId    学生ID
+     * @param assignmentId 作业ID
+     */
+    @Async("textAnalysisTaskExecutor")
+    @Override
+    public void analyzeAndSaveTextContentAsync(Long studentId, Long assignmentId) {
+        log.info("开始异步分析文本内容 - studentId: {}, assignmentId: {}", studentId, assignmentId);
+
+        try {
+            // 1. 获取最新的text_content
+            String textContent = studentAssignmentMapper.getTextContent(studentId, assignmentId);
+            if (textContent == null || textContent.trim().isEmpty()) {
+                log.warn("文本内容为空,跳过分析 - studentId: {}, assignmentId: {}", studentId, assignmentId);
+                return;
+            }
+
+            log.info("获取到文本内容,长度: {} - studentId: {}, assignmentId: {}",
+                    textContent.length(), studentId, assignmentId);
+
+            // 2. 使用Stanford CoreNLP进行分句和分词
+            Document doc = new Document(textContent);
+            List<Sentence> allSentences = new ArrayList<>(doc.sentences()); // 一次性加载所有句子到内存
+
+            // 后续操作都使用allSentences集合,避免频繁调用doc.sentences()方法
+            List<String> sentences = allSentences.stream()
+                    .map(Sentence::text)
+                    .toList();
+
+            log.info("分句完成,共{}个句子 - studentId: {}, assignmentId: {}",
+                    sentences.size(), studentId, assignmentId);
+
+            // 3. 分词并获取词汇级别
+            List<TextAnalysis.WordLevel> wordLevels = new ArrayList<>();
+            for (Sentence sentence : allSentences) {
+                for (String word : sentence.words()) {
+                    TextAnalysis.WordLevel wordLevel = new TextAnalysis.WordLevel();
+                    wordLevel.setWord(word);
+
+                    // 判断是否为标点符号
+                    if (isPunctuation(word)) {
+                        wordLevel.setBasewordLevel(0);
+                    } else {
+                        // 获取词汇级别(带重试机制)
+                        Integer level = fetchBasewordLevelWithRetry(word);
+                        wordLevel.setBasewordLevel(level);
+                    }
+
+                    wordLevels.add(wordLevel);
+                }
+            }
+
+            log.info("分词完成,共{}个词汇 - studentId: {}, assignmentId: {}",
+                    wordLevels.size(), studentId, assignmentId);
+
+            // 4. 组装TextAnalysis对象
+            TextAnalysis analysis = new TextAnalysis();
+            analysis.setStudentId(studentId);
+            analysis.setAssignmentId(assignmentId);
+            analysis.setTextContent(textContent);
+            analysis.setSentences(sentences);
+            analysis.setWordLevels(wordLevels);
+
+            // 查找是否已有数据,若有则复用_id,实现数据的更新;若没有,则新插入
+            Query query = new Query();
+            query.addCriteria(Criteria.where("studentId").is(studentId).and("assignmentId").is(assignmentId));
+            TextAnalysis old = mongoTemplate.findOne(query, TextAnalysis.class, "textAnalyses");
+            if (old != null) {
+                analysis.setId(old.getId());
+            }
+
+            // 5. 保存到MongoDB
+            mongoTemplate.save(analysis, "textAnalyses");
+
+            log.info("文本分析完成并保存到MongoDB - studentId: {}, assignmentId: {}",
+                    studentId, assignmentId);
+
+        } catch (Exception e) {
+            log.error("文本分析失败 - studentId: {}, assignmentId: {}, error: {}",
+                    studentId, assignmentId, e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 判断字符串是否为标点符号
+     * 
+     * @param word 待判断的字符串
+     * @return 是否为标点符号
+     */
+    private boolean isPunctuation(String word) {
+        return word.matches("\\p{Punct}");
+    }
+
+    /**
+     * 带重试机制的词汇级别获取
+     * 
+     * @param word 待查询的词汇
+     * @return 词汇级别,如果查询失败返回0
+     */
+    private Integer fetchBasewordLevelWithRetry(String word) {
+        for (int retry = 0; retry < MAX_RETRY_COUNT; retry++) {
+            try {
+                Integer level = fetchBasewordLevel(word);
+                if (level != null) {
+                    return level;
+                }
+            } catch (Exception e) {
+                log.warn("获取词汇级别失败,第{}次重试 - word: {}, error: {}",
+                        retry + 1, word, e.getMessage());
+            }
+
+            // 重试前等待
+            if (retry < MAX_RETRY_COUNT - 1) {
+                try {
+                    Thread.sleep(RETRY_DELAY_MS);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    break;
+                }
+            }
+        }
+
+        log.warn("获取词汇级别失败,已达到最大重试次数 - word: {}", word);
+        return 0;
+    }
+
+    /**
+     * 调用外部API获取词汇级别
+     * 
+     * @param word 待查询的词汇
+     * @return 词汇级别,如果查询失败返回null
+     */
+    private Integer fetchBasewordLevel(String word) {
+        try {
+            // 设置请求头
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+
+            // 设置请求参数
+            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+            params.add("search", word);
+            params.add("database", DATABASE);
+            params.add("corpus", CORPUS);
+
+            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
+
+            // 发送POST请求
+            ResponseEntity<String> response = restTemplate.postForEntity(
+                    WORD_LEVEL_API_URL, request, String.class);
+
+            if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+                // 解析JSON响应
+                JsonNode jsonNode = objectMapper.readTree(response.getBody());
+
+                // 检查响应格式:[[VALUE, level, ...]]
+                if (jsonNode.isArray() && jsonNode.size() > 0) {
+                    JsonNode firstElement = jsonNode.get(0);
+                    if (firstElement.isArray() && firstElement.size() > 1) {
+                        JsonNode levelNode = firstElement.get(1);
+                        try {
+                            return levelNode.asInt(); // 直接返回整数
+                        } catch (NumberFormatException e) {
+                            log.warn("level不是有效数字: {}", levelNode);
+                            return 0;
+                        }
+                    }
+                }
+            }
+
+            log.warn("API响应格式异常 - word: {}, response: {}", word, response.getBody());
+            return 0;
+
+        } catch (Exception e) {
+            log.error("调用词汇级别API失败 - word: {}, error: {}", word, e.getMessage());
+            return 0;
+        }
+    }
+}

+ 45 - 0
src/main/java/com/njuzr/eaibackend/vo/TextAnalysisVO.java

@@ -0,0 +1,45 @@
+package com.njuzr.eaibackend.vo;
+
+import com.njuzr.eaibackend.po.TextAnalysis;
+import lombok.Data;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.redis.core.index.Indexed;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @Auther: WuZilong
+ * @Date: 2025/7/18 00:04
+ * @Description:
+ */
+
+@Data
+public class TextAnalysisVO {
+    // 1. 标识信息
+    private Long studentId; // 学生ID
+    private Long assignmentId; // 作业ID
+
+    // 2. 原始文本
+    private String textContent; // 学生写作的原始文本
+
+    // 3. 分句所得的句子数量
+    private Integer sentenceCount;
+
+    // 4. 分词所得的词汇数量
+    private Integer wordCount;
+
+    // 5. 分句结果(CoreNLP分句后的数据)
+    private List<String> sentences; // 存储分句后的句子列表(如["句子1", "句子2"])
+
+    // 6. 分词及级别结果(CoreNLP分词 + 词汇级别匹配后的数据)
+    private List<TextAnalysis.WordLevel> wordLevels; // 存储词汇及对应级别
+
+    /**
+     * 嵌套类:存储单个词汇及对应的基础词级别
+     */
+    public static class WordLevel {
+        private String word; // 分词后的词汇(如"student")
+        private Integer basewordLevel; // 基础词级别(如"1st 1000"、"3rd 1000",对应BNC/COCA的10级体系)
+    }
+}

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

@@ -14,6 +14,13 @@
         WHERE assignment_id = #{assignmentId}
         WHERE assignment_id = #{assignmentId}
     </select>
     </select>
 
 
+    <!--  通过studentId和assignmentID查找text_content  -->
+    <select id="getTextContent" resultType="java.lang.String">
+        SELECT text_content
+        FROM student_assignment
+        WHERE student_id = #{studentId} AND assignment_id = #{assignmentId}
+    </select>
+
     <delete id="delete">
     <delete id="delete">
         DELETE FROM student_assignment
         DELETE FROM student_assignment
         WHERE student_id = #{studentId} AND assignment_id = #{assignmentId}
         WHERE student_id = #{studentId} AND assignment_id = #{assignmentId}