引言
在人工智能技术飞速发展的今天,构建智能问答系统已成为企业数字化转型的重要组成部分。本文将详细介绍如何结合OpenAI的ChatGPT API和Spring Boot框架,从零开始构建一个企业级的智能问答系统。通过本文的学习,读者将掌握API集成、对话管理、安全认证等关键技术点,为AI应用开发打下坚实基础。
项目概述
项目目标
本项目旨在构建一个基于Spring Boot的智能问答系统,该系统能够:
- 集成ChatGPT API实现自然语言理解
- 提供RESTful API接口供外部调用
- 支持多轮对话管理
- 具备基本的安全认证机制
- 具备良好的可扩展性和维护性
技术栈选择
- 后端框架:Spring Boot 2.7+
- API集成:OpenAI ChatGPT API
- 数据库:MySQL 8.0
- 缓存:Redis
- 安全认证:Spring Security + JWT
- 构建工具:Maven
- 开发工具:IntelliJ IDEA
环境准备
开发环境要求
在开始开发之前,确保具备以下环境:
# Java版本要求
java -version
# 输出应为Java 11或更高版本
# Maven版本要求
mvn -version
# Node.js(可选,用于前端开发)
node -v
npm -v
项目初始化
使用Spring Initializr创建项目:
<!-- pom.xml 核心依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
ChatGPT API集成
API访问配置
首先,需要获取OpenAI API密钥并进行配置:
# application.yml
openai:
api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
api-url: https://api.openai.com/v1
model: gpt-3.5-turbo
temperature: 0.7
max-tokens: 1000
timeout: 30000
API客户端实现
@Service
public class OpenAiService {
private final RestTemplate restTemplate;
private final String apiKey;
private final String apiUrl;
private final String model;
public OpenAiService(RestTemplate restTemplate,
@Value("${openai.api-key}") String apiKey,
@Value("${openai.api-url}") String apiUrl,
@Value("${openai.model}") String model) {
this.restTemplate = restTemplate;
this.apiKey = apiKey;
this.apiUrl = apiUrl;
this.model = model;
}
public ChatCompletionResponse chatCompletion(ChatCompletionRequest request) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKey);
HttpEntity<ChatCompletionRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<ChatCompletionResponse> response = restTemplate.exchange(
apiUrl + "/chat/completions",
HttpMethod.POST,
entity,
ChatCompletionResponse.class
);
return response.getBody();
} catch (Exception e) {
throw new RuntimeException("调用OpenAI API失败", e);
}
}
public String getCompletion(String prompt) {
ChatCompletionRequest request = new ChatCompletionRequest();
request.setModel(model);
request.setTemperature(0.7);
request.setMaxTokens(1000);
List<ChatMessage> messages = new ArrayList<>();
messages.add(new ChatMessage("user", prompt));
request.setMessages(messages);
ChatCompletionResponse response = chatCompletion(request);
return response.getChoices().get(0).getMessage().getContent();
}
}
数据模型定义
// 请求模型
public class ChatCompletionRequest {
private String model;
private List<ChatMessage> messages;
private Double temperature;
private Integer maxTokens;
private Integer n;
private Boolean stream;
private String stop;
// 构造函数、getter、setter
public ChatCompletionRequest() {}
public ChatCompletionRequest(String model, List<ChatMessage> messages) {
this.model = model;
this.messages = messages;
}
// getter和setter方法...
}
// 消息模型
public class ChatMessage {
private String role;
private String content;
public ChatMessage() {}
public ChatMessage(String role, String content) {
this.role = role;
this.content = content;
}
// getter和setter方法...
}
// 响应模型
public class ChatCompletionResponse {
private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;
// getter和setter方法...
}
public class Choice {
private Integer index;
private ChatMessage message;
private String finishReason;
// getter和setter方法...
}
public class Usage {
private Integer promptTokens;
private Integer completionTokens;
private Integer totalTokens;
// getter和setter方法...
}
对话管理模块
对话状态管理
@Component
public class ConversationManager {
private final RedisTemplate<String, Object> redisTemplate;
private final long conversationTimeout = 3600; // 1小时
public ConversationManager(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void createConversation(String userId, String conversationId) {
String key = "conversation:" + userId + ":" + conversationId;
Conversation conversation = new Conversation();
conversation.setId(conversationId);
conversation.setUserId(userId);
conversation.setCreatedAt(new Date());
conversation.setMessages(new ArrayList<>());
redisTemplate.opsForValue().set(key, conversation, conversationTimeout, TimeUnit.SECONDS);
}
public Conversation getConversation(String userId, String conversationId) {
String key = "conversation:" + userId + ":" + conversationId;
return (Conversation) redisTemplate.opsForValue().get(key);
}
public void addMessage(String userId, String conversationId, ChatMessage message) {
String key = "conversation:" + userId + ":" + conversationId;
Conversation conversation = (Conversation) redisTemplate.opsForValue().get(key);
if (conversation != null) {
conversation.getMessages().add(message);
redisTemplate.opsForValue().set(key, conversation, conversationTimeout, TimeUnit.SECONDS);
}
}
public void clearConversation(String userId, String conversationId) {
String key = "conversation:" + userId + ":" + conversationId;
redisTemplate.delete(key);
}
}
对话实体类
public class Conversation {
private String id;
private String userId;
private Date createdAt;
private List<ChatMessage> messages;
private Date lastAccessed;
// 构造函数、getter、setter方法
public Conversation() {
this.messages = new ArrayList<>();
this.lastAccessed = new Date();
}
public void updateLastAccessed() {
this.lastAccessed = new Date();
}
// getter和setter方法...
}
RESTful API设计
API控制器实现
@RestController
@RequestMapping("/api/qa")
@CrossOrigin(origins = "*")
public class QaController {
private final QaService qaService;
private final ConversationManager conversationManager;
public QaController(QaService qaService, ConversationManager conversationManager) {
this.qaService = qaService;
this.conversationManager = conversationManager;
}
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest request,
@RequestHeader("Authorization") String token) {
try {
// 验证用户身份
String userId = validateToken(token);
// 处理对话
ChatResponse response = qaService.processChat(userId, request);
return ResponseEntity.ok(response);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ChatResponse("系统错误", null));
}
}
@PostMapping("/new-conversation")
public ResponseEntity<ConversationResponse> newConversation(@RequestHeader("Authorization") String token) {
try {
String userId = validateToken(token);
String conversationId = UUID.randomUUID().toString();
conversationManager.createConversation(userId, conversationId);
return ResponseEntity.ok(new ConversationResponse(conversationId, "对话创建成功"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ConversationResponse(null, "创建对话失败"));
}
}
@GetMapping("/conversation/{conversationId}")
public ResponseEntity<ConversationHistoryResponse> getConversationHistory(
@PathVariable String conversationId,
@RequestHeader("Authorization") String token) {
try {
String userId = validateToken(token);
Conversation conversation = conversationManager.getConversation(userId, conversationId);
if (conversation == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ConversationHistoryResponse(null, "对话不存在"));
}
return ResponseEntity.ok(new ConversationHistoryResponse(conversation.getMessages(), "获取成功"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ConversationHistoryResponse(null, "获取对话历史失败"));
}
}
private String validateToken(String token) {
// 实现JWT token验证逻辑
// 这里简化处理,实际项目中需要完整实现
return "user123"; // 示例用户ID
}
}
API响应模型
public class ChatRequest {
private String conversationId;
private String message;
private String userId;
// getter和setter方法...
}
public class ChatResponse {
private String response;
private String conversationId;
private String status;
public ChatResponse(String response, String conversationId) {
this.response = response;
this.conversationId = conversationId;
this.status = "success";
}
// getter和setter方法...
}
public class ConversationResponse {
private String conversationId;
private String message;
public ConversationResponse(String conversationId, String message) {
this.conversationId = conversationId;
this.message = message;
}
// getter和setter方法...
}
public class ConversationHistoryResponse {
private List<ChatMessage> messages;
private String message;
public ConversationHistoryResponse(List<ChatMessage> messages, String message) {
this.messages = messages;
this.message = message;
}
// getter和setter方法...
}
安全认证实现
JWT认证配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/qa/chat", "/api/qa/new-conversation").authenticated()
.anyRequest().permitAll()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWT工具类
@Component
public class JwtUtil {
private String secret = "mySecretKey1234567890"; // 实际项目中应从配置文件读取
private int jwtExpiration = 86400; // 24小时
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
数据库设计
用户表设计
-- 用户表
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE
);
-- 对话历史表
CREATE TABLE conversation_history (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
conversation_id VARCHAR(100) NOT NULL,
message TEXT NOT NULL,
role VARCHAR(20) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 使用统计表
CREATE TABLE usage_statistics (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
request_count INT DEFAULT 0,
total_tokens INT DEFAULT 0,
created_at DATE NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
JPA实体类
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String email;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
private Boolean isActive = true;
// 构造函数、getter、setter方法...
}
@Entity
@Table(name = "conversation_history")
public class ConversationHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "conversation_id")
private String conversationId;
@Column(columnDefinition = "TEXT")
private String message;
private String role;
@Column(name = "created_at")
private LocalDateTime createdAt;
// 构造函数、getter、setter方法...
}
缓存优化
Redis配置
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LazyCollectionResolver.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.withInitialCacheConfigurations(Collections.singletonMap("conversation", config))
.build();
}
}
性能优化策略
请求限流
@Component
public class RateLimiter {
private final Map<String, AtomicInteger> requestCount = new ConcurrentHashMap<>();
private final Map<String, Long> lastResetTime = new ConcurrentHashMap<>();
private final int maxRequests = 100; // 每分钟最大请求数
private final long timeWindow = 60000; // 1分钟窗口
public boolean isAllowed(String userId) {
long currentTime = System.currentTimeMillis();
String key = "rate_limit:" + userId;
// 检查是否需要重置计数器
Long lastReset = lastResetTime.get(key);
if (lastReset == null || currentTime - lastReset > timeWindow) {
requestCount.put(key, new AtomicInteger(0));
lastResetTime.put(key, currentTime);
}
AtomicInteger count = requestCount.get(key);
int currentCount = count.get();
if (currentCount >= maxRequests) {
return false;
}
count.incrementAndGet();
return true;
}
}
异步处理
@Service
public class AsyncQaService {
private final OpenAiService openAiService;
private final ExecutorService executorService;
public AsyncQaService(OpenAiService openAiService) {
this.openAiService = openAiService;
this.executorService = Executors.newFixedThreadPool(10);
}
@Async
public CompletableFuture<String> processAsync(String prompt) {
try {
String result = openAiService.getCompletion(prompt);
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
return CompletableFuture.failedFuture(e);
}
}
}
错误处理与监控
全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OpenAiApiException.class)
public ResponseEntity<ErrorResponse> handleOpenAiApiException(OpenAiApiException e) {
ErrorResponse error = new ErrorResponse("OPENAI_API_ERROR", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(error);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ErrorResponse> handleValidationException(ValidationException e) {
ErrorResponse error = new ErrorResponse("VALIDATION_ERROR", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e) {
ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
public class ErrorResponse {
private String code;
private String message;
private Long timestamp;
public ErrorResponse(String code, String message) {
this.code = code;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
// getter和setter方法...
}
日志监控
@Component
public class QaLogger {
private static final Logger logger = LoggerFactory.getLogger(QaLogger.class);
public void logChatRequest(String userId, String conversationId, String message) {
logger.info("用户 {} 在对话 {} 中发送消息: {}", userId, conversationId, message);
}
public void logChatResponse(String userId, String conversationId, String response, long duration) {
logger.info("用户 {} 在对话 {} 中收到回复,耗时 {}ms", userId, conversationId, duration);
}
public void logError(String userId, String conversationId, String error) {
logger.error("用户 {} 在对话 {} 中发生错误: {}", userId, conversationId, error);
}
}
测试策略
单元测试
@SpringBootTest
class QaServiceTest {
@Autowired
private QaService qaService;
@MockBean
private OpenAiService openAiService;
@Test
void testProcessChat() {
// 准备测试数据
ChatRequest request = new ChatRequest();
request.setMessage("你好");
request.setConversationId("test-conversation");
// 模拟API响应
ChatCompletionResponse mockResponse = new ChatCompletionResponse();
List<Choice> choices = new ArrayList<>();
Choice choice = new Choice();
ChatMessage message = new ChatMessage("assistant", "你好!有什么我可以帮助你的吗?");
choice.setMessage(message);
choices.add(choice);
mockResponse.setChoices(choices);
when(openAiService.chatCompletion(any(ChatCompletionRequest.class)))
.thenReturn(mockResponse);
// 执行测试
ChatResponse response = qaService.processChat("test-user", request);
// 验证结果
assertThat(response.getResponse()).contains("你好");
assertThat(response.getStatus()).isEqualTo("success");
}
}
集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class QaIntegrationTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testChatEndpoint() {
ChatRequest request = new ChatRequest();
request.setMessage("什么是人工智能?");
request.setConversationId("test-conversation");
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer test-token");
HttpEntity<ChatRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<ChatResponse> response = restTemplate.exchange(
"/api/qa/chat",
HttpMethod.POST,
entity,
ChatResponse.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody().getResponse()).isNotNull();
}
}
部署与运维
Docker部署
# Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/qa-system-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Docker Compose配置
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/qa_system
- SPRING_REDIS_HOST=redis
depends_on:
- db
- redis
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: qa_system
volumes:
- db_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
db_data:
总结
通过本文的详细介绍,我们成功构建了一个基于Spring Boot和ChatGPT API的智能问答系统。该系统具备了以下核心功能:
- API集成:成功集成了OpenAI ChatGPT API,实现了自然语言处理能力
- 对话管理:支持多轮对话和会话状态管理
- 安全认证:实现了JWT认证机制,确保系统安全
- 性能优化:通过缓存、异步处理等技术提升系统性能
- 错误处理:完善的异常处理和监控机制
- 可扩展性:模块化设计,便于后续功能扩展
在实际项目中,还可以进一步完善以下方面:
- 增加更多的对话历史管理功能
- 实现更复杂的用户权限管理
- 添加更详细的监控和日志系统
- 集成更多AI模型和功能
- 优化前端界面和用户体验
这个智能问答系统为企业的客户服务、知识管理、智能助手等应用场景提供了坚实的技术基础,是AI应用开发的优秀实践案例。

评论 (0)