| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- 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;
- }
- }
- }
|