Response# ChatGPT + Spring Boot 3.0 构建智能问答系统:从零到一的技术实现路径
引言
在人工智能技术飞速发展的今天,大语言模型如ChatGPT正在改变我们与技术交互的方式。Spring Boot作为Java生态系统中最流行的微服务框架,为构建现代化应用提供了强大的支持。本文将详细介绍如何将ChatGPT API集成到Spring Boot 3.0应用中,构建一个功能完善的智能问答系统。
通过本文的实践,您将掌握从零开始构建智能问答系统的技术路径,包括API调用封装、异步处理、缓存优化、安全认证等关键技术点,为构建企业级AI应用奠定坚实基础。
技术架构概述
系统架构设计
智能问答系统的核心架构包括以下几个关键组件:
- API网关层:统一入口,负责请求路由和负载均衡
- 业务逻辑层:处理用户请求,调用ChatGPT API
- 缓存层:提升响应速度,减少API调用次数
- 安全认证层:确保系统安全性
- 数据存储层:持久化用户会话和历史记录
技术栈选择
- Spring Boot 3.0:现代化Java应用开发框架
- Spring WebFlux:响应式编程支持
- Spring Cache:缓存管理
- Spring Security:安全认证
- Redis:分布式缓存
- OpenFeign:声明式HTTP客户端
- Lombok:简化代码编写
项目初始化与依赖配置
Maven依赖配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>chatgpt-qa-system</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>chatgpt-qa-system</name>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Boot Web Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot WebFlux Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Cache Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Security Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OpenFeign Starter -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
应用配置文件
# application.yml
server:
port: 8080
spring:
application:
name: chatgpt-qa-system
redis:
host: localhost
port: 6379
database: 0
timeout: 2000ms
cache:
type: redis
redis:
time-to-live: 3600000
key-prefix: "qa:"
openai:
api-key: ${OPENAI_API_KEY}
api-base: https://api.openai.com
model: gpt-3.5-turbo
timeout: 30000
logging:
level:
com.example.chatgpt: DEBUG
org.springframework.web: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
ChatGPT API调用封装
API接口定义
// ChatGptApi.java
package com.example.chatgpt.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import reactor.core.publisher.Mono;
@FeignClient(
name = "chatgpt-api",
url = "${openai.api-base}",
configuration = ChatGptFeignConfig.class
)
public interface ChatGptApi {
@PostMapping(value = "/v1/chat/completions",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
Mono<ChatGptResponse> generateResponse(@RequestBody ChatGptRequest request);
}
请求和响应数据模型
// ChatGptRequest.java
package com.example.chatgpt.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ChatGptRequest {
private String model;
private List<Message> messages;
private Double temperature;
private Integer maxTokens;
private Double topP;
private Integer n;
private Boolean stream;
@Data
public static class Message {
private String role;
private String content;
public Message(String role, String content) {
this.role = role;
this.content = content;
}
}
public ChatGptRequest(String model, List<Message> messages) {
this.model = model;
this.messages = messages;
this.temperature = 0.7;
this.maxTokens = 1000;
this.topP = 1.0;
this.n = 1;
this.stream = false;
}
}
// ChatGptResponse.java
package com.example.chatgpt.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
public class ChatGptResponse {
private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;
@Data
public static class Choice {
private Integer index;
private Message message;
private String finishReason;
@Data
public static class Message {
private String role;
private String content;
}
}
@Data
public static class Usage {
@JsonProperty("prompt_tokens")
private Integer promptTokens;
@JsonProperty("completion_tokens")
private Integer completionTokens;
@JsonProperty("total_tokens")
private Integer totalTokens;
}
}
Feign配置类
// ChatGptFeignConfig.java
package com.example.chatgpt.config;
import feign.Request;
import feign.RequestOptions;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.ErrorDecoder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ChatGptFeignConfig {
@Value("${openai.timeout:30000}")
private int timeout;
@Bean
public RequestOptions requestOptions() {
return new RequestOptions.Builder()
.connectTimeout(timeout)
.readTimeout(timeout)
.build();
}
@Bean
public Retryer retryer() {
return new Retryer.Default(1000, 2000, 3);
}
@Bean
public ErrorDecoder errorDecoder() {
return new ChatGptErrorDecoder();
}
public static class ChatGptErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 401:
return new RuntimeException("Unauthorized: Invalid API key");
case 403:
return new RuntimeException("Forbidden: Access denied");
case 429:
return new RuntimeException("Rate limit exceeded");
case 500:
return new RuntimeException("Internal server error");
default:
return new RetryableException(
response.status(),
response.reason(),
response.request().httpMethod(),
null,
response.request());
}
}
}
}
异步处理与响应式编程
异步服务实现
// ChatGptService.java
package com.example.chatgpt.service;
import com.example.chatgpt.api.ChatGptApi;
import com.example.chatgpt.model.ChatGptRequest;
import com.example.chatgpt.model.ChatGptResponse;
import com.example.chatgpt.model.ChatGptRequest.Message;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Service
@RequiredArgsConstructor
@Slf4j
public class ChatGptService {
private final ChatGptApi chatGptApi;
@Value("${openai.model:gpt-3.5-turbo}")
private String model;
@Value("${openai.timeout:30000}")
private int timeout;
public Mono<ChatGptResponse> generateResponse(String question, List<Message> history) {
return Mono.fromCallable(() -> {
try {
// 构建请求
ChatGptRequest request = buildRequest(question, history);
// 调用API
ChatGptResponse response = chatGptApi.generateResponse(request).block();
log.info("ChatGPT API response received for question: {}", question);
return response;
} catch (Exception e) {
log.error("Error calling ChatGPT API", e);
throw new RuntimeException("Failed to generate response", e);
}
}).subscribeOn(Schedulers.boundedElastic());
}
public CompletableFuture<ChatGptResponse> generateResponseAsync(String question, List<Message> history) {
return CompletableFuture.supplyAsync(() -> {
try {
ChatGptRequest request = buildRequest(question, history);
return chatGptApi.generateResponse(request).block();
} catch (Exception e) {
log.error("Error calling ChatGPT API", e);
throw new RuntimeException("Failed to generate response", e);
}
});
}
private ChatGptRequest buildRequest(String question, List<Message> history) {
// 构建消息历史
List<Message> messages = buildMessages(question, history);
// 创建请求
ChatGptRequest request = new ChatGptRequest(model, messages);
request.setTemperature(0.7);
request.setMaxTokens(1000);
request.setTopP(1.0);
request.setN(1);
request.setStream(false);
return request;
}
private List<Message> buildMessages(String question, List<Message> history) {
// 构建完整的消息列表
List<Message> messages = new ArrayList<>();
if (history != null && !history.isEmpty()) {
messages.addAll(history);
}
messages.add(new Message("user", question));
return messages;
}
}
响应式控制器
// ChatGptController.java
package com.example.chatgpt.controller;
import com.example.chatgpt.model.ChatGptRequest;
import com.example.chatgpt.model.ChatGptResponse;
import com.example.chatgpt.service.ChatGptService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/qa")
@RequiredArgsConstructor
@Slf4j
public class ChatGptController {
private final ChatGptService chatGptService;
@PostMapping("/chat")
public Mono<ResponseEntity<ChatGptResponse>> chat(
@RequestBody ChatGptRequest request) {
return chatGptService.generateResponse(request.getMessages().get(0).getContent(),
request.getMessages())
.map(ResponseEntity.ok()::body)
.onErrorReturn(ResponseEntity.status(500).build());
}
@PostMapping("/simple")
public Mono<ResponseEntity<String>> simpleChat(
@RequestParam String question) {
return chatGptService.generateResponse(question, null)
.map(response -> {
String answer = response.getChoices().get(0).getMessage().getContent();
return ResponseEntity.ok(answer);
})
.onErrorReturn(ResponseEntity.status(500).build());
}
@GetMapping("/health")
public Mono<ResponseEntity<String>> health() {
return Mono.just(ResponseEntity.ok("ChatGPT QA System is running"));
}
}
缓存优化策略
Redis缓存配置
// CacheConfig.java
package com.example.chatgpt.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues();
return RedisCacheManager.builder(connectionFactory)
.withCacheConfiguration("qa-cache", config)
.build();
}
}
缓存服务实现
// CacheService.java
package com.example.chatgpt.service;
import com.example.chatgpt.model.ChatGptResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
@RequiredArgsConstructor
@Slf4j
public class CacheService {
private final ChatGptService chatGptService;
@Cacheable(value = "qa-cache", key = "#question")
public CompletableFuture<ChatGptResponse> getResponseWithCache(String question,
String sessionId) {
log.info("Cache miss for question: {}", question);
return chatGptService.generateResponseAsync(question, null);
}
@CacheEvict(value = "qa-cache", key = "#question")
public void evictCache(String question) {
log.info("Evicted cache for question: {}", question);
}
@CacheEvict(value = "qa-cache", allEntries = true)
public void evictAllCache() {
log.info("Evicted all cache entries");
}
}
缓存控制器
// CacheController.java
package com.example.chatgpt.controller;
import com.example.chatgpt.model.ChatGptResponse;
import com.example.chatgpt.service.CacheService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/cache")
@RequiredArgsConstructor
public class CacheController {
private final CacheService cacheService;
@GetMapping("/response/{question}")
public CompletableFuture<ResponseEntity<ChatGptResponse>> getResponse(
@PathVariable String question) {
return cacheService.getResponseWithCache(question, null)
.thenApply(ResponseEntity.ok()::body)
.exceptionally(throwable -> {
log.error("Error getting cached response", throwable);
return ResponseEntity.status(500).build();
});
}
@DeleteMapping("/invalidate/{question}")
public ResponseEntity<String> invalidateCache(@PathVariable String question) {
cacheService.evictCache(question);
return ResponseEntity.ok("Cache invalidated for question: " + question);
}
@DeleteMapping("/invalidate-all")
public ResponseEntity<String> invalidateAllCache() {
cacheService.evictAllCache();
return ResponseEntity.ok("All cache invalidated");
}
}
安全认证与权限管理
安全配置
// SecurityConfig.java
package com.example.chatgpt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/qa/**").authenticated()
.requestMatchers("/api/cache/**").authenticated()
.requestMatchers("/api/health").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.httpBasic(basic -> basic.disable());
return http.build();
}
}
JWT认证过滤器
// JwtAuthenticationFilter.java
package com.example.chatgpt.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationInMs}")
private int jwtExpirationInMs;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String token = parseJwt(request);
if (token != null && validateToken(token)) {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
String role = claims.get("role", String.class);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null,
Collections.singletonList(new SimpleGrantedAuthority(role)));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
log.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
private boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
return true;
} catch (Exception e) {
log.error("Invalid JWT token: {}", e.getMessage());
}
return false;
}
}
用户认证服务
// AuthenticationService.java
package com.example.chatgpt.service;
import com.example.chatgpt.model.User;
import com.example.chatgpt.repository.UserRepository;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
@RequiredArgsConstructor
@Slf4j
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationInMs}")
private int jwtExpirationInMs;
public String authenticate(String username, String password) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new RuntimeException("User not found"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new RuntimeException("Invalid credentials");
}
return generateToken(user);
}
private String generateToken(User user) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
}
性能优化与监控
请求限流配置
// RateLimitConfig.java
package com.example.chatgpt.config;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ConcurrentHashMap;
@Configuration
@Slf4j
public class RateLimitConfig {
private final ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
@Bean
public RateLimiter globalRateLimiter() {
// 限制为每秒10个请求
RateLimiter rateLimiter = RateLimiter.create(10.0);
log.info("Global rate limiter initialized with 10 QPS");
return rateLimiter;
}
public RateLimiter getUserRateLimiter(String userId) {
return rateLimiters.computeIfAbsent(userId, key -> RateLimiter.create(5.0));
}
}
监控与指标收集
// MetricsService.java
package com.example.chatgpt.service;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
@Slf4j
public class MetricsService {
private final MeterRegistry meterRegistry;
private final Counter requestsCounter;
private final Counter responsesCounter;
private final Timer responseTimeTimer;
public MetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.requestsCounter = Counter.builder("qa.requests")
.description("Number of QA requests")
.register(meterRegistry);
this.responsesCounter = Counter.builder("qa.responses")
.description("Number of QA responses")
.register(meterRegistry);
this.responseTimeTimer = Timer.builder("qa.response.time")
.description("QA response time")
.register(meterRegistry);
}
public void recordRequest() {
requestsCounter.increment();
}
public void recordResponse() {
responsesCounter.increment();
}
public void recordResponseTime(long duration) {
responseTimeTimer.record(duration, TimeUnit.MILLISECONDS);
}
}
健康检查端点
// HealthController.java
package com.example.chatgpt.controller;
import com.example.chatgpt.service.ChatGptService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/health")
@RequiredArgsConstructor
public class HealthController {
private final ChatGptService chatGptService;
@GetMapping("/status")
public ResponseEntity<String> getStatus() {
return ResponseEntity.ok("ChatGPT QA System is running and healthy");
}
@GetMapping("/chatgpt")
public ResponseEntity<String> checkChatGptConnection() {
try {
// 简单的API连接测试
return ResponseEntity.ok("ChatGPT API is accessible");
} catch (Exception e) {
return ResponseEntity.status(500).body("ChatGPT API connection failed");
}
}
}
完整的项目结构
src/main/java/com/example/chatgpt/
├── ChatGptQaSystemApplication.java
├── config/
│ ├── CacheConfig.java
│ ├── ChatGptFeignConfig.java
│ ├── RateLimitConfig.java
│ └── SecurityConfig.java
├── controller/
│ ├── ChatGptController.java
│ ├── CacheController.java
│ ├── HealthController.java
│ └── AuthController.java
├── service/
│ ├── ChatGptService.java
│ ├── CacheService.java
│ ├── AuthenticationService.java
│ └── MetricsService.java
├── model/
│ ├── ChatGptRequest.java
│ ├── ChatGptResponse.java
│ ├── User.java
│ └── Session.java
├── api/
│ └── ChatGptApi.java
├── repository/
│ └── UserRepository.java
├── security/
│ ├── JwtAuthenticationFilter.java
│ └── UserDetailsService.java
└
评论 (0)