引言
在人工智能技术飞速发展的今天,大语言模型如ChatGPT已经成为企业数字化转型的重要工具。将ChatGPT与Spring Boot框架结合,可以快速构建出功能强大、可扩展的智能问答系统。本文将从零开始,详细讲解如何使用Spring Boot和ChatGPT API构建一个完整的智能问答系统,涵盖API集成、对话管理、用户认证等核心技术点。
项目概述
系统架构设计
本智能问答系统采用微服务架构设计,主要包含以下几个核心模块:
- API网关层:负责请求路由和统一认证
- 业务逻辑层:处理用户请求、对话管理、与ChatGPT API交互
- 数据存储层:保存用户信息、对话历史等数据
- 安全认证层:实现用户身份验证和权限控制
技术栈选择
- 后端框架:Spring Boot 2.7.x
- API集成:OpenAI ChatGPT API
- 数据库:MySQL + Redis缓存
- 安全框架:Spring Security + JWT
- 构建工具:Maven
- 前端交互:RESTful API + WebSocket(可选)
环境准备与项目初始化
开发环境配置
首先,确保开发环境已安装以下组件:
# Java版本要求
java -version
# 推荐使用JDK 11或以上版本
# Maven版本检查
mvn -version
# IDE推荐:IntelliJ IDEA 或 VS Code
创建Spring Boot项目
使用Spring Initializr创建基础项目,主要依赖包括:
<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-websocket</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- JWT相关 -->
<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>
配置文件设置
在application.yml中配置基础信息:
server:
port: 8080
spring:
application:
name: chatgpt-qa-system
datasource:
url: jdbc:mysql://localhost:3306/chatgpt_qa?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL8Dialect
redis:
host: localhost
port: 6379
database: 0
# JWT配置
jwt:
secret: your_jwt_secret_key_here
expiration: 86400000 # 24小时
# OpenAI API配置
openai:
api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
base-url: https://api.openai.com/v1
model: gpt-3.5-turbo
ChatGPT API集成实现
API客户端设计
创建ChatGPT API客户端类,负责与OpenAI服务通信:
@Service
public class ChatGPTClient {
private final RestTemplate restTemplate;
private final String apiKey;
private final String baseUrl;
private final String model;
public ChatGPTClient(RestTemplate restTemplate,
@Value("${openai.api-key}") String apiKey,
@Value("${openai.base-url}") String baseUrl,
@Value("${openai.model}") String model) {
this.restTemplate = restTemplate;
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.model = model;
}
/**
* 发送消息到ChatGPT并获取回复
*/
public String getResponse(String userMessage, String conversationId) {
try {
// 构建请求体
ChatRequest request = new ChatRequest();
request.setModel(model);
List<ChatMessage> messages = new ArrayList<>();
// 添加系统消息
messages.add(new ChatMessage("system", "你是一个智能助手,专门帮助用户解答各种问题。"));
// 添加对话历史(如果有)
if (conversationId != null) {
List<String> history = getConversationHistory(conversationId);
for (String msg : history) {
// 这里需要根据实际存储结构解析历史消息
messages.add(new ChatMessage("user", msg));
}
}
// 添加当前用户消息
messages.add(new ChatMessage("user", userMessage));
request.setMessages(messages);
// 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Authorization", "Bearer " + apiKey);
HttpEntity<ChatRequest> entity = new HttpEntity<>(request, headers);
// 发送请求
ResponseEntity<ChatResponse> response = restTemplate.postForEntity(
baseUrl + "/chat/completions",
entity,
ChatResponse.class
);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return response.getBody().getChoices()[0].getMessage().getContent();
}
} catch (Exception e) {
log.error("调用ChatGPT API失败", e);
throw new RuntimeException("AI服务不可用,请稍后重试");
}
return "抱歉,我无法理解您的问题。";
}
/**
* 获取对话历史
*/
private List<String> getConversationHistory(String conversationId) {
// 这里应该从数据库或缓存中获取对话历史
// 简化实现,实际项目中需要完整实现
return new ArrayList<>();
}
}
// 请求对象类
public class ChatRequest {
private String model;
private List<ChatMessage> messages;
private Double temperature = 0.7;
private Integer maxTokens = 1000;
// getter和setter方法
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public List<ChatMessage> getMessages() { return messages; }
public void setMessages(List<ChatMessage> messages) { this.messages = messages; }
public Double getTemperature() { return temperature; }
public void setTemperature(Double temperature) { this.temperature = temperature; }
public Integer getMaxTokens() { return maxTokens; }
public void setMaxTokens(Integer maxTokens) { this.maxTokens = maxTokens; }
}
// 消息对象类
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 String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
// 响应对象类
public class ChatResponse {
private String id;
private String object;
private Long created;
private String model;
private ChatChoice[] choices;
private Usage usage;
// getter和setter方法
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getObject() { return object; }
public void setObject(String object) { this.object = object; }
public Long getCreated() { return created; }
public void setCreated(Long created) { this.created = created; }
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public ChatChoice[] getChoices() { return choices; }
public void setChoices(ChatChoice[] choices) { this.choices = choices; }
public Usage getUsage() { return usage; }
public void setUsage(Usage usage) { this.usage = usage; }
}
public class ChatChoice {
private Integer index;
private ChatMessage message;
private String finishReason;
// getter和setter方法
public Integer getIndex() { return index; }
public void setIndex(Integer index) { this.index = index; }
public ChatMessage getMessage() { return message; }
public void setMessage(ChatMessage message) { this.message = message; }
public String getFinishReason() { return finishReason; }
public void setFinishReason(String finishReason) { this.finishReason = finishReason; }
}
public class Usage {
private Integer promptTokens;
private Integer completionTokens;
private Integer totalTokens;
// getter和setter方法
public Integer getPromptTokens() { return promptTokens; }
public void setPromptTokens(Integer promptTokens) { this.promptTokens = promptTokens; }
public Integer getCompletionTokens() { return completionTokens; }
public void setCompletionTokens(Integer completionTokens) { this.completionTokens = completionTokens; }
public Integer getTotalTokens() { return totalTokens; }
public void setTotalTokens(Integer totalTokens) { this.totalTokens = totalTokens; }
}
RestTemplate配置
在配置类中添加RestTemplate Bean:
@Configuration
public class HttpClientConfig {
@Bean
public RestTemplate restTemplate() {
// 配置超时时间
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
RestTemplate restTemplate = new RestTemplate(factory);
// 添加拦截器处理请求头
restTemplate.setInterceptors(Collections.singletonList(new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 可以在这里添加通用的请求头设置
return execution.execute(request, body);
}
}));
return restTemplate;
}
}
对话管理系统设计
对话实体类
@Entity
@Table(name = "conversations")
public class Conversation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id")
private Long userId;
@Column(name = "title")
private String title;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@Column(name = "is_active")
private Boolean isActive = true;
@OneToMany(mappedBy = "conversation", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ConversationMessage> messages = new ArrayList<>();
// 构造函数、getter和setter方法
public Conversation() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
public Conversation(Long userId, String title) {
this();
this.userId = userId;
this.title = title;
}
// getter和setter方法...
}
@Entity
@Table(name = "conversation_messages")
public class ConversationMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "conversation_id")
private Conversation conversation;
@Column(name = "role")
private String role; // user, assistant
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@Column(name = "created_at")
private LocalDateTime createdAt;
public ConversationMessage() {
this.createdAt = LocalDateTime.now();
}
public ConversationMessage(Conversation conversation, String role, String content) {
this();
this.conversation = conversation;
this.role = role;
this.content = content;
}
// getter和setter方法...
}
对话管理服务
@Service
@Transactional
public class ConversationService {
private final ConversationRepository conversationRepository;
private final ConversationMessageRepository messageRepository;
private final ChatGPTClient chatGptClient;
private final RedisTemplate<String, Object> redisTemplate;
public ConversationService(ConversationRepository conversationRepository,
ConversationMessageRepository messageRepository,
ChatGPTClient chatGptClient,
RedisTemplate<String, Object> redisTemplate) {
this.conversationRepository = conversationRepository;
this.messageRepository = messageRepository;
this.chatGptClient = chatGptClient;
this.redisTemplate = redisTemplate;
}
/**
* 创建新对话
*/
public Conversation createConversation(Long userId, String title) {
Conversation conversation = new Conversation(userId, title);
return conversationRepository.save(conversation);
}
/**
* 获取用户所有对话
*/
public List<Conversation> getUserConversations(Long userId) {
return conversationRepository.findByUserIdOrderByCreatedAtDesc(userId);
}
/**
* 获取特定对话详情
*/
public Conversation getConversation(Long id, Long userId) {
Optional<Conversation> conversation = conversationRepository.findById(id);
if (conversation.isPresent() && conversation.get().getUserId().equals(userId)) {
return conversation.get();
}
throw new RuntimeException("对话不存在或无权限访问");
}
/**
* 发送消息并获取AI回复
*/
public String sendMessage(Long conversationId, Long userId, String message) {
// 验证用户权限
Conversation conversation = getConversation(conversationId, userId);
// 保存用户消息
ConversationMessage userMessage = new ConversationMessage(conversation, "user", message);
messageRepository.save(userMessage);
// 获取AI回复
String aiResponse = chatGptClient.getResponse(message, conversationId.toString());
// 保存AI回复
ConversationMessage aiMessage = new ConversationMessage(conversation, "assistant", aiResponse);
messageRepository.save(aiMessage);
// 更新对话最后更新时间
conversation.setUpdatedAt(LocalDateTime.now());
conversationRepository.save(conversation);
return aiResponse;
}
/**
* 获取对话历史
*/
public List<ConversationMessage> getConversationHistory(Long conversationId, Long userId) {
Conversation conversation = getConversation(conversationId, userId);
return messageRepository.findByConversationOrderByCreatedAtAsc(conversation);
}
/**
* 删除对话
*/
public void deleteConversation(Long id, Long userId) {
Conversation conversation = getConversation(id, userId);
conversation.setIsActive(false);
conversationRepository.save(conversation);
}
}
数据库访问层
@Repository
public interface ConversationRepository extends JpaRepository<Conversation, Long> {
List<Conversation> findByUserIdOrderByCreatedAtDesc(Long userId);
Optional<Conversation> findByIdAndUserId(Long id, Long userId);
}
@Repository
public interface ConversationMessageRepository extends JpaRepository<ConversationMessage, Long> {
List<ConversationMessage> findByConversationOrderByCreatedAtAsc(Conversation conversation);
}
用户认证与安全实现
JWT配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private int jwtExpiration;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
);
http.addFilterBefore(jwtAuthenticationTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
JWT工具类
@Component
public class JwtTokenUtil {
private String secret;
private int jwtExpiration;
@Value("${jwt.secret}")
public void setSecret(String secret) {
this.secret = secret;
}
@Value("${jwt.expiration}")
public void setJwtExpiration(int jwtExpiration) {
this.jwtExpiration = jwtExpiration;
}
/**
* 从token中获取用户名
*/
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 从token中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 从token中获取声明信息
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
/**
* 从token中获取所有声明
*/
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
/**
* 判断token是否过期
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 生成token
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
/**
* 创建token
*/
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))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 验证token是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
用户认证控制器
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationService authenticationService;
private final JwtTokenUtil jwtTokenUtil;
public AuthController(AuthenticationService authenticationService,
JwtTokenUtil jwtTokenUtil) {
this.authenticationService = authenticationService;
this.jwtTokenUtil = jwtTokenUtil;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
try {
Authentication authentication = authenticationService.authenticate(
loginRequest.getUsername(),
loginRequest.getPassword()
);
String token = jwtTokenUtil.generateToken(authentication.getPrincipal());
return ResponseEntity.ok(new JwtResponse(token, "Bearer"));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("用户名或密码错误");
}
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
try {
User user = authenticationService.register(
registerRequest.getUsername(),
registerRequest.getPassword()
);
return ResponseEntity.ok("用户注册成功");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
}
}
// 请求对象类
public class LoginRequest {
private String username;
private String password;
// getter和setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public class RegisterRequest {
private String username;
private String password;
// getter和setter方法
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public class JwtResponse {
private String token;
private String type;
public JwtResponse(String token, String type) {
this.token = token;
this.type = type;
}
// getter和setter方法
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
API接口设计与实现
主要API控制器
@RestController
@RequestMapping("/api/qa")
@PreAuthorize("isAuthenticated()")
public class QAApiController {
private final ConversationService conversationService;
public QAApiController(ConversationService conversationService) {
this.conversationService = conversationService;
}
/**
* 创建新对话
*/
@PostMapping("/conversations")
public ResponseEntity<Conversation> createConversation(
@RequestBody CreateConversationRequest request,
Authentication authentication) {
Long userId = getCurrentUserId(authentication);
Conversation conversation = conversationService.createConversation(userId, request.getTitle());
return ResponseEntity.ok(conversation);
}
/**
* 获取用户所有对话
*/
@GetMapping("/conversations")
public ResponseEntity<List<Conversation>> getUserConversations(Authentication authentication) {
Long userId = getCurrentUserId(authentication);
List<Conversation> conversations = conversationService.getUserConversations(userId);
return ResponseEntity.ok(conversations);
}
/**
* 发送消息并获取回复
*/
@PostMapping("/conversations/{id}/messages")
public ResponseEntity<String> sendMessage(
@PathVariable Long id,
@RequestBody SendMessageRequest request,
Authentication authentication) {
Long userId = getCurrentUserId(authentication);
String response = conversationService.sendMessage(id, userId, request.getMessage());
return ResponseEntity.ok(response);
}
/**
* 获取对话历史
*/
@GetMapping("/conversations/{id}/messages")
public ResponseEntity<List<ConversationMessage>> getConversationHistory(
@PathVariable Long id,
Authentication authentication) {
Long userId = getCurrentUserId(authentication);
List<ConversationMessage> messages = conversationService.getConversationHistory(id, userId);
return ResponseEntity.ok(messages);
}
/**
* 删除对话
*/
@DeleteMapping("/conversations/{id}")
public ResponseEntity<?> deleteConversation(
@PathVariable Long id,
Authentication authentication) {
Long userId = getCurrentUserId(authentication);
conversationService.deleteConversation(id, userId);
return ResponseEntity.ok().build();
}
/**
* 获取当前用户ID
*/
private Long getCurrentUserId(Authentication authentication) {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 这里应该从数据库获取实际的用户ID
return 1L; // 示例值,实际项目中需要完善
}
throw new RuntimeException("无法获取当前用户信息");
}
}
// 请求对象类
public class CreateConversationRequest {
private String title;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
}
public class SendMessageRequest {
private String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }
}
API文档规范
@Api(tags = "智能问答API")
@RestController
@RequestMapping("/api/qa")
@PreAuthorize("isAuthenticated()")
public class QAApiController {
@ApiOperation(value = "创建新对话", notes = "创建一个新的问答对话,返回对话ID")
@ApiResponses({
@ApiResponse(code = 200, message = "创建成功"),
@ApiResponse(code = 401, message = "未授权访问"),
@ApiResponse(code = 500, message = "服务器内部错误")
})
@PostMapping("/conversations")
public ResponseEntity<Conversation> createConversation(
@ApiParam(value = "对话标题", required = true)
@RequestBody CreateConversationRequest request,
Authentication authentication) {
// 实现逻辑...
}
@ApiOperation(value = "发送消息并获取回复", notes = "向指定对话发送消息,获取AI回复")
@ApiResponses({
@ApiResponse(code = 200, message = "回复成功"),
@ApiResponse(code = 400, message = "请求参数错误"),
@ApiResponse(code = 401, message = "未授权访问"),
@ApiResponse(code = 404, message = "对话不存在"),
@ApiResponse(code = 500, message = "服务器内部错误")
})
@PostMapping("/conversations/{id}/messages")
public ResponseEntity<String> sendMessage(
@ApiParam(value = "对话ID", required = true)
@PathVariable Long id,
@ApiParam(value = "消息内容", required = true)
@RequestBody SendMessageRequest request,
Authentication authentication) {
// 实现逻辑...
}
}
性能优化与监控
缓存策略实现
@Service
public class CacheService {
private final RedisTemplate<String, Object> redisTemplate;
private static final String CONVERSATION_CACHE_PREFIX = "conversation:";
private static final String USER_CACHE_PREFIX = "user:";
public CacheService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 缓存对话历史

评论 (0)