diff --git a/qa-service/README.md b/qa-service/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6b73c856ac94d243f266bf1b419c7af3009b4ef4 --- /dev/null +++ b/qa-service/README.md @@ -0,0 +1,185 @@ +# QA智能问答系统 + +基于Spring Boot + RabbitMQ + KIMI API的异步问答系统 + +## 功能特性 + +- 🤖 集成KIMI AI,提供智能问答服务 +- 🔄 基于RabbitMQ的异步处理架构 +- 💾 MySQL数据库持久化存储 +- 📚 Swagger/Knife4j API文档 +- 🎨 响应式Web界面 +- ⚡ 高性能异步处理 + +## 系统架构 + +``` +用户提交问题 → 保存到数据库 → 发送到MQ队列 → 后台异步处理 → 调用KIMI API → 更新数据库 +``` + +## 技术栈 + +- **后端框架**: Spring Boot 3.0.2 +- **数据库**: MySQL + Spring Data JPA +- **消息队列**: RabbitMQ +- **AI服务**: KIMI API +- **API文档**: Knife4j (Swagger) +- **前端**: 原生HTML + JavaScript + +## 快速开始 + +### 1. 环境准备 + +确保已安装以下环境: +- JDK 21+ +- MySQL 8.0+ +- RabbitMQ 3.8+ +- Maven 3.6+ + +### 2. 数据库配置 + +创建数据库: +```sql +CREATE DATABASE qa_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +### 3. 配置文件 + +修改 `application.properties` 中的配置: + +```properties +# 数据库配置 +spring.datasource.url=jdbc:mysql://localhost:3306/qa_system?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 +spring.datasource.username=your_username +spring.datasource.password=your_password + +# RabbitMQ配置 +spring.rabbitmq.host=localhost +spring.rabbitmq.port=5672 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest + +# KIMI配置 +moonshot.key=your_kimi_api_key +``` + +### 4. 启动应用 + +```bash +cd qa-service +mvn spring-boot:run +``` + +### 5. 访问应用 + +- **Web界面**: http://localhost:8080 +- **API文档**: http://localhost:8080/doc.html +- **健康检查**: http://localhost:8080/actuator/health + +## API接口 + +### 提交问题 +```http +POST /qa/ask +Content-Type: application/json + +{ + "question": "你好,请介绍一下北京" +} +``` + +### 快速问答 +```http +GET /qa/simple?question=你好 +``` + +### 获取记录 +```http +GET /qa/records +GET /qa/records/{id} +GET /qa/records/status/{status} +``` + +## 项目结构 + +``` +qa-service/ +├── src/main/java/com/example/qa/service/ +│ ├── QaServiceApplication.java # 启动类 +│ ├── config/ # 配置类 +│ │ ├── MoonShotConfig.java # KIMI配置 +│ │ ├── RabbitConfig.java # RabbitMQ配置 +│ │ └── RestConfig.java # HTTP客户端配置 +│ ├── controller/ # 控制器 +│ │ └── QaController.java # 问答控制器 +│ ├── dto/ # 数据传输对象 +│ │ ├── ChatCompletionRequestDTO.java +│ │ ├── ChatCompletionResponseDTO.java +│ │ ├── Choice.java +│ │ ├── Message.java +│ │ ├── QaRequestDTO.java +│ │ └── QaResponseDTO.java +│ ├── entity/ # 实体类 +│ │ └── QaRecord.java # 问答记录实体 +│ ├── listener/ # 消息监听器 +│ │ └── QaResultListener.java # 问答结果监听器 +│ ├── repository/ # 数据访问层 +│ │ └── QaRecordRepository.java # 问答记录仓库 +│ └── service/ # 服务层 +│ └── QaService.java # 问答服务 +├── src/main/resources/ +│ ├── application.properties # 配置文件 +│ └── static/ +│ └── index.html # 前端页面 +└── pom.xml # Maven配置 +``` + +## 使用说明 + +1. **提交问题**: 通过Web界面或API提交问题 +2. **异步处理**: 系统将问题发送到MQ队列进行异步处理 +3. **AI回答**: 后台调用KIMI API获取智能回答 +4. **结果保存**: 将回答结果保存到数据库 +5. **状态查询**: 可以查询问题的处理状态和结果 + +## 状态说明 + +- `PROCESSING`: 处理中 +- `COMPLETED`: 已完成 +- `FAILED`: 处理失败 + +## 注意事项 + +1. 确保KIMI API密钥有效且有足够配额 +2. RabbitMQ服务需要正常运行 +3. 数据库连接配置正确 +4. 建议在生产环境中使用连接池和更安全的配置 + +## 扩展功能 + +- [ ] 用户认证和授权 +- [ ] 问题分类和标签 +- [ ] 批量问答处理 +- [ ] 答案评分和反馈 +- [ ] 对话上下文管理 +- [ ] 多模型支持 + +## 故障排除 + +### 常见问题 + +1. **数据库连接失败**: 检查数据库服务状态和连接配置 +2. **RabbitMQ连接失败**: 确认RabbitMQ服务运行正常 +3. **KIMI API调用失败**: 检查API密钥和网络连接 +4. **端口占用**: 修改server.port配置 + +### 日志查看 + +查看应用日志定位问题: +```bash +tail -f logs/spring.log +``` + +## 许可证 + +MIT License diff --git a/qa-service/pom.xml b/qa-service/pom.xml index 476ca31ee84446204ef71aedf1d07bdd6d774ad2..38a2d1ac079eb5efc8559b4e1f915bee75d926ae 100644 --- a/qa-service/pom.xml +++ b/qa-service/pom.xml @@ -16,7 +16,43 @@ org.springframework.boot - spring-boot-starter + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + mysql + mysql-connector-java + 8.0.33 + runtime + + + + org.projectlombok + lombok + true + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.5.0 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.58 @@ -24,6 +60,12 @@ spring-boot-starter-test test + + + org.springframework.amqp + spring-rabbit-test + test + diff --git a/qa-service/src/main/java/com/example/qa/service/QaServiceApplication.java b/qa-service/src/main/java/com/example/qa/service/QaServiceApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..6698f6ebb25c7a873c8ae42624c2c89deaf313ba --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/QaServiceApplication.java @@ -0,0 +1,13 @@ +package com.example.qa.service; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class QaServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(QaServiceApplication.class, args); + } + +} diff --git a/qa-service/src/main/java/com/example/qa/service/config/MoonShotConfig.java b/qa-service/src/main/java/com/example/qa/service/config/MoonShotConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..1e552f458919c4d3f72fe0e4f1c2a3dc4026b61a --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/config/MoonShotConfig.java @@ -0,0 +1,14 @@ +package com.example.qa.service.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "moonshot") +public class MoonShotConfig { + private String url; + private String key; + private String model; +} diff --git a/qa-service/src/main/java/com/example/qa/service/config/RabbitConfig.java b/qa-service/src/main/java/com/example/qa/service/config/RabbitConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..18169ee197918e63e92612d547ae2d6515e8b06a --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/config/RabbitConfig.java @@ -0,0 +1,48 @@ +package com.example.qa.service.config; + +import com.example.qa.service.entity.QaRecord; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.DefaultJackson2JavaTypeMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class RabbitConfig { + + // QA结果队列 + public static final String QA_RESULT_QUEUE = "qa.result.queue"; + + @Bean + public Queue qaResultQueue() { + return new Queue(QA_RESULT_QUEUE); + } + + @Bean + public MessageConverter messageConverter() { + Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); + // 配置类型映射器以支持 QaRecord 类的反序列化 + DefaultJackson2JavaTypeMapper typeMapper = new DefaultJackson2JavaTypeMapper(); + Map> idClassMapping = new HashMap<>(); + // 添加 QaRecord 类的映射 + idClassMapping.put("com.example.qa.service.entity.QaRecord", QaRecord.class); + typeMapper.setIdClassMapping(idClassMapping); + typeMapper.setTrustedPackages("com.example.qa.service.entity"); + converter.setJavaTypeMapper(typeMapper); + + return converter; + } + + @Bean + public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(messageConverter()); + return rabbitTemplate; + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/config/RestConfig.java b/qa-service/src/main/java/com/example/qa/service/config/RestConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..ad04c510477591a9f14d6c0cc1a5efcaefef4705 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/config/RestConfig.java @@ -0,0 +1,14 @@ +package com.example.qa.service.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/controller/QaController.java b/qa-service/src/main/java/com/example/qa/service/controller/QaController.java new file mode 100644 index 0000000000000000000000000000000000000000..7c9d2dc81378b440c5c48f4f55d3262116ece919 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/controller/QaController.java @@ -0,0 +1,82 @@ +package com.example.qa.service.controller; + +import com.example.qa.service.dto.QaRequestDTO; +import com.example.qa.service.dto.QaResponseDTO; +import com.example.qa.service.entity.QaRecord; +import com.example.qa.service.service.QaService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@Tag(name = "问答服务", description = "基于KIMI的问答服务接口") +@RestController +@RequestMapping("/qa") +public class QaController { + + @Resource + private QaService qaService; + + @Operation(summary = "提交问题", description = "异步提交问题给KIMI处理") + @PostMapping("/ask") + public ResponseEntity askQuestion(@RequestBody QaRequestDTO request) { + log.info("收到问题: {}", request.getQuestion()); + + if (request.getQuestion() == null || request.getQuestion().trim().isEmpty()) { + return ResponseEntity.badRequest() + .body(QaResponseDTO.error("问题不能为空")); + } + + QaResponseDTO response = qaService.askQuestion(request); + return ResponseEntity.ok(response); + } + + @Operation(summary = "获取所有问答记录", description = "获取所有问答记录,按创建时间倒序") + @GetMapping("/records") + public ResponseEntity> getAllRecords() { + List records = qaService.getAllRecords(); + return ResponseEntity.ok(records); + } + + @Operation(summary = "根据ID获取问答记录", description = "根据记录ID获取具体的问答记录") + @GetMapping("/records/{id}") + public ResponseEntity getRecordById( + @Parameter(description = "记录ID") @PathVariable Long id) { + QaRecord record = qaService.getRecordById(id); + if (record == null) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(record); + } + + @Operation(summary = "根据状态获取问答记录", description = "根据处理状态获取问答记录") + @GetMapping("/records/status/{status}") + public ResponseEntity> getRecordsByStatus( + @Parameter(description = "记录状态:PROCESSING, COMPLETED, FAILED") + @PathVariable String status) { + try { + QaRecord.Status recordStatus = QaRecord.Status.valueOf(status.toUpperCase()); + List records = qaService.getRecordsByStatus(recordStatus); + return ResponseEntity.ok(records); + } catch (IllegalArgumentException e) { + return ResponseEntity.badRequest().build(); + } + } + + @Operation(summary = "简单问答接口", description = "快速提交问题的简化接口") + @GetMapping("/simple") + public ResponseEntity simpleAsk( + @Parameter(description = "问题内容") @RequestParam String question) { + QaRequestDTO request = new QaRequestDTO(); + request.setQuestion(question); + + QaResponseDTO response = qaService.askQuestion(request); + return ResponseEntity.ok(response); + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionRequestDTO.java b/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionRequestDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..905c7ad489344e6d26611ddfc43934ab80e29cd1 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionRequestDTO.java @@ -0,0 +1,11 @@ +package com.example.qa.service.dto; + +import lombok.Data; +import java.util.List; + +@Data +public class ChatCompletionRequestDTO { + private String model; + private List messages; + private double temperature; +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionResponseDTO.java b/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionResponseDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..33c6381762a705e1eb96e30bdb3f43490cd1dcfa --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/ChatCompletionResponseDTO.java @@ -0,0 +1,13 @@ +package com.example.qa.service.dto; + +import lombok.Data; +import java.util.List; + +@Data +public class ChatCompletionResponseDTO { + private String id; + private String object; + private long created; + private String model; + private List choices; +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/Choice.java b/qa-service/src/main/java/com/example/qa/service/dto/Choice.java new file mode 100644 index 0000000000000000000000000000000000000000..e061205b40c546153e770456f59060e63198b29e --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/Choice.java @@ -0,0 +1,10 @@ +package com.example.qa.service.dto; + +import lombok.Data; + +@Data +public class Choice { + private int index; + private Message message; + private String finish_reason; +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/Message.java b/qa-service/src/main/java/com/example/qa/service/dto/Message.java new file mode 100644 index 0000000000000000000000000000000000000000..9727445850dc2c03780d623d1af2436391b684a4 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/Message.java @@ -0,0 +1,9 @@ +package com.example.qa.service.dto; + +import lombok.Data; + +@Data +public class Message { + private String role; + private String content; +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/QaRequestDTO.java b/qa-service/src/main/java/com/example/qa/service/dto/QaRequestDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..c29a8bdb6dd2b032203d7e4d29accdcfaf5c8544 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/QaRequestDTO.java @@ -0,0 +1,8 @@ +package com.example.qa.service.dto; + +import lombok.Data; + +@Data +public class QaRequestDTO { + private String question; +} diff --git a/qa-service/src/main/java/com/example/qa/service/dto/QaResponseDTO.java b/qa-service/src/main/java/com/example/qa/service/dto/QaResponseDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..840321a55197fb807fcd23b286aca7164aef59a8 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/dto/QaResponseDTO.java @@ -0,0 +1,29 @@ +package com.example.qa.service.dto; + +import lombok.Data; +import com.example.qa.service.entity.QaRecord.Status; + +@Data +public class QaResponseDTO { + private Long id; + private String question; + private String answer; + private Status status; + private String message; + + public static QaResponseDTO success(Long id, String question) { + QaResponseDTO response = new QaResponseDTO(); + response.setId(id); + response.setQuestion(question); + response.setStatus(Status.PROCESSING); + response.setMessage("问题已提交,正在处理中..."); + return response; + } + + public static QaResponseDTO error(String message) { + QaResponseDTO response = new QaResponseDTO(); + response.setStatus(Status.FAILED); + response.setMessage(message); + return response; + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/entity/QaRecord.java b/qa-service/src/main/java/com/example/qa/service/entity/QaRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..e2ce04be862e665088b64523511bb86128d68c31 --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/entity/QaRecord.java @@ -0,0 +1,47 @@ +package com.example.qa.service.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "qa_records") +public class QaRecord { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, columnDefinition = "TEXT") + private String question; + + @Column(columnDefinition = "TEXT") + private String answer; + + @Column(name = "created_time", nullable = false) + private LocalDateTime createdTime; + + @Column(name = "response_time") + private LocalDateTime responseTime; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Status status; + + public enum Status { + PROCESSING, // 处理中 + COMPLETED, // 已完成 + FAILED // 失败 + } + + @PrePersist + public void prePersist() { + if (createdTime == null) { + createdTime = LocalDateTime.now(); + } + if (status == null) { + status = Status.PROCESSING; + } + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/listener/QaResultListener.java b/qa-service/src/main/java/com/example/qa/service/listener/QaResultListener.java new file mode 100644 index 0000000000000000000000000000000000000000..7c9137655ca7aa6e49d6bb1bfe7dcba26e9dfb9b --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/listener/QaResultListener.java @@ -0,0 +1,29 @@ +package com.example.qa.service.listener; + +import com.example.qa.service.config.RabbitConfig; +import com.example.qa.service.entity.QaRecord; +import com.example.qa.service.service.QaService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RabbitListener(queues = RabbitConfig.QA_RESULT_QUEUE) +public class QaResultListener { + + @Resource + private QaService qaService; + + @RabbitHandler + public void handleQaRequest(QaRecord qaRecord) { + log.info("收到问答请求,记录ID: {}, 问题: {}", qaRecord.getId(), qaRecord.getQuestion()); + + // 处理问答请求 + qaService.processKimiRequest(qaRecord); + + log.info("问答请求处理完成,记录ID: {}", qaRecord.getId()); + } +} diff --git a/qa-service/src/main/java/com/example/qa/service/repository/QaRecordRepository.java b/qa-service/src/main/java/com/example/qa/service/repository/QaRecordRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..147bcfa658114e42c642cb82b4de50bdf7caf71d --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/repository/QaRecordRepository.java @@ -0,0 +1,15 @@ +package com.example.qa.service.repository; + +import com.example.qa.service.entity.QaRecord; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface QaRecordRepository extends JpaRepository { + + List findByStatusOrderByCreatedTimeDesc(QaRecord.Status status); + + List findAllByOrderByCreatedTimeDesc(); +} diff --git a/qa-service/src/main/java/com/example/qa/service/service/QaService.java b/qa-service/src/main/java/com/example/qa/service/service/QaService.java new file mode 100644 index 0000000000000000000000000000000000000000..944beb8da37201f5914e2f59596411d7fa75166c --- /dev/null +++ b/qa-service/src/main/java/com/example/qa/service/service/QaService.java @@ -0,0 +1,156 @@ +package com.example.qa.service.service; + +import com.example.qa.service.config.MoonShotConfig; +import com.example.qa.service.config.RabbitConfig; +import com.example.qa.service.dto.*; +import com.example.qa.service.entity.QaRecord; +import com.example.qa.service.repository.QaRecordRepository; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +@Slf4j +@Service +public class QaService { + + @Resource + private QaRecordRepository qaRecordRepository; + + @Resource + private RabbitTemplate rabbitTemplate; + + @Resource + private RestTemplate restTemplate; + + @Resource + private MoonShotConfig moonShotConfig; + + /** + * 异步处理问答请求 + */ + public QaResponseDTO askQuestion(QaRequestDTO request) { + try { + // 1. 保存问题记录到数据库 + QaRecord qaRecord = new QaRecord(); + qaRecord.setQuestion(request.getQuestion()); + qaRecord.setStatus(QaRecord.Status.PROCESSING); + qaRecord = qaRecordRepository.save(qaRecord); + + // 2. 发送到MQ进行异步处理 + rabbitTemplate.convertAndSend(RabbitConfig.QA_RESULT_QUEUE, qaRecord); + + log.info("问题已提交处理,记录ID: {}", qaRecord.getId()); + + return QaResponseDTO.success(qaRecord.getId(), request.getQuestion()); + + } catch (Exception e) { + log.error("提交问题失败", e); + return QaResponseDTO.error("提交问题失败: " + e.getMessage()); + } + } + + /** + * 处理KIMI API调用(由MQ消费者调用) + */ + public void processKimiRequest(QaRecord qaRecord) { + try { + log.info("开始处理问题: {}", qaRecord.getQuestion()); + + // 调用KIMI API + String answer = callKimiApi(qaRecord.getQuestion()); + + // 更新数据库记录 + qaRecord.setAnswer(answer); + qaRecord.setStatus(QaRecord.Status.COMPLETED); + qaRecord.setResponseTime(LocalDateTime.now()); + qaRecordRepository.save(qaRecord); + + log.info("问题处理完成,记录ID: {}", qaRecord.getId()); + + } catch (Exception e) { + log.error("处理问题失败,记录ID: " + qaRecord.getId(), e); + + // 更新为失败状态 + qaRecord.setAnswer("处理失败: " + e.getMessage()); + qaRecord.setStatus(QaRecord.Status.FAILED); + qaRecord.setResponseTime(LocalDateTime.now()); + qaRecordRepository.save(qaRecord); + } + } + + /** + * 调用KIMI API + */ + private String callKimiApi(String question) { + // 创建请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + moonShotConfig.getKey()); + + // 创建请求体 + ChatCompletionRequestDTO request = new ChatCompletionRequestDTO(); + request.setModel(moonShotConfig.getModel()); + request.setTemperature(0.6); + + // 创建消息列表 + Message systemMessage = new Message(); + systemMessage.setRole("system"); + systemMessage.setContent("你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"); + + Message userMessage = new Message(); + userMessage.setRole("user"); + userMessage.setContent(question); + + request.setMessages(Arrays.asList(systemMessage, userMessage)); + + // 发送请求 + HttpEntity response = restTemplate.exchange( + moonShotConfig.getUrl(), + HttpMethod.POST, + new HttpEntity<>(request, headers), + ChatCompletionResponseDTO.class + ); + + // 提取答案 + ChatCompletionResponseDTO responseBody = response.getBody(); + if (responseBody != null && responseBody.getChoices() != null && !responseBody.getChoices().isEmpty()) { + Choice choice = responseBody.getChoices().get(0); + if (choice.getMessage() != null) { + return choice.getMessage().getContent(); + } + } + + throw new RuntimeException("未能从KIMI API获取有效响应"); + } + + /** + * 获取问答记录 + */ + public List getAllRecords() { + return qaRecordRepository.findAllByOrderByCreatedTimeDesc(); + } + + /** + * 根据ID获取问答记录 + */ + public QaRecord getRecordById(Long id) { + return qaRecordRepository.findById(id).orElse(null); + } + + /** + * 根据状态获取问答记录 + */ + public List getRecordsByStatus(QaRecord.Status status) { + return qaRecordRepository.findByStatusOrderByCreatedTimeDesc(status); + } +} diff --git a/qa-service/src/main/resources/application.properties b/qa-service/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..8983f49060fa49064869e25876c23b79f279cf6e --- /dev/null +++ b/qa-service/src/main/resources/application.properties @@ -0,0 +1,30 @@ +# 服务端口 +server.port=8080 + +# 数据库配置 +spring.datasource.url=jdbc:mysql://localhost:3306/qa_system?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# JPA配置 +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect + +# RabbitMQ配置 +spring.rabbitmq.host=localhost +spring.rabbitmq.port=5672 +spring.rabbitmq.username=guest +spring.rabbitmq.password=guest + +# KIMI配置 +moonshot.url=https://api.moonshot.cn/v1/chat/completions +moonshot.key=sk-iVPnVTWEfvyQbnB2ePZte3MWzfQE5HmBsgZZ6zfJpBojnKdM +moonshot.model=kimi-k2-0905-preview + +# Knife4j配置 +knife4j.enable=true +knife4j.openapi.title=QA Service API +knife4j.openapi.description=QA服务接口文档 +knife4j.openapi.version=1.0.0 diff --git a/qa-service/src/main/resources/static/index.html b/qa-service/src/main/resources/static/index.html new file mode 100644 index 0000000000000000000000000000000000000000..ea9f9982518c75d00f20fb13e39ba659836c1553 --- /dev/null +++ b/qa-service/src/main/resources/static/index.html @@ -0,0 +1,331 @@ + + + + + + QA智能问答系统 + + + +
+
+

🤖 QA智能问答系统

+

基于KIMI的异步问答服务 - 提交问题后系统将异步处理并保存结果

+
+ +
+
+ API接口说明:
+ • 提交问题:POST /qa/ask
+ • 快速问答:GET /qa/simple?question=你的问题
+ • 查看记录:GET /qa/records
+ • API文档:Knife4j文档 +
+ +
+
+ + + +
+
+ +
+
+
正在处理...
+
+ +
+

问答记录

+
+
+ 暂无记录,请提交问题开始使用 +
+
+
+
+
+ + + + diff --git a/qa-service/src/test/java/com/example/qa/service/QaServiceApplicationTests.java b/qa-service/src/test/java/com/example/qa/service/QaServiceApplicationTests.java new file mode 100644 index 0000000000000000000000000000000000000000..cd38f0acb5dd8680f7089e7d6f84d45fc6c80ec2 --- /dev/null +++ b/qa-service/src/test/java/com/example/qa/service/QaServiceApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.qa.service; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class QaServiceApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/qa-service/start.bat b/qa-service/start.bat new file mode 100644 index 0000000000000000000000000000000000000000..19c0f3c2c900220ba28a0c497bf0c73c1e7df9b6 --- /dev/null +++ b/qa-service/start.bat @@ -0,0 +1,43 @@ +@echo off +echo ======================================== +echo QA智能问答系统 - 启动脚本 +echo ======================================== +echo. + +echo 检查Java环境... +java -version +if %ERRORLEVEL% neq 0 ( + echo 错误: 未找到Java环境,请安装JDK 21或更高版本 + pause + exit /b 1 +) + +echo. +echo 检查Maven环境... +mvn -version +if %ERRORLEVEL% neq 0 ( + echo 错误: 未找到Maven环境,请安装Maven 3.6+ + pause + exit /b 1 +) + +echo. +echo 开始编译项目... +mvn clean compile +if %ERRORLEVEL% neq 0 ( + echo 错误: 项目编译失败 + pause + exit /b 1 +) + +echo. +echo 启动应用... +echo 应用将在 http://localhost:8080 启动 +echo API文档地址: http://localhost:8080/doc.html +echo. +echo 注意: 请确保MySQL和RabbitMQ服务已启动 +echo. + +mvn spring-boot:run + +pause diff --git a/qa-service/test-api.bat b/qa-service/test-api.bat new file mode 100644 index 0000000000000000000000000000000000000000..5c560106169007f0c2b240456690b74da71952ca --- /dev/null +++ b/qa-service/test-api.bat @@ -0,0 +1,37 @@ +@echo off +echo ======================================== +echo QA智能问答系统 - API测试脚本 +echo ======================================== +echo. + +echo 测试1: 健康检查 +echo GET http://localhost:8080/actuator/health +curl -X GET http://localhost:8080/actuator/health +echo. +echo. + +echo 测试2: 提交问题 +echo POST http://localhost:8080/qa/ask +curl -X POST http://localhost:8080/qa/ask ^ + -H "Content-Type: application/json" ^ + -d "{\"question\":\"你好,请简单介绍一下人工智能\"}" +echo. +echo. + +echo 测试3: 快速问答 +echo GET http://localhost:8080/qa/simple?question=你好 +curl -X GET "http://localhost:8080/qa/simple?question=你好" +echo. +echo. + +echo 测试4: 获取所有记录 +echo GET http://localhost:8080/qa/records +curl -X GET http://localhost:8080/qa/records +echo. +echo. + +echo 测试完成! +echo 请访问 http://localhost:8080 查看Web界面 +echo 请访问 http://localhost:8080/doc.html 查看API文档 + +pause