# 手把手教你从零搭建 AI 对话系统 - React + Spring Boot 实战(二)

一个完整的类 ChatGPT 对话系统,支持流式输出、打断,会话历史,前后端分离架构,非常适合拿来练手熟悉技术实现或者面试使用,接上一篇前端

基于 Spring Boot 2.7.18 + MyBatis-Plus + JWT 的 AI 对话系统后端服务。

技术栈

  • Spring Boot 2.7.18 - 核心框架
  • MyBatis-Plus 3.5.5 - ORM 框架
  • JWT - 身份认证
  • MySQL 8.0 - 数据存储
  • DeepSeek API - AI 对话能力
  • Knife4j - API 文档

核心功能

1. 用户认证体系

采用 JWT Token 实现无状态认证:

java 复制代码
// JwtUtil.java - Token 生成与验证
@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String generateToken(Long userId, String username) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .setSubject(String.valueOf(userId))
                .claim("username", username)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
}

2. 用户级 API Key 管理

每个用户独立绑定自己的 DeepSeek API Key,存储于 user.api_key 字段:

java 复制代码
// UserServiceImpl.java - 注册时保存用户 API Key
@Override
@Transactional
public void register(RegisterRequest request) {
    User user = new User();
    user.setUsername(request.getUsername());
    user.setPassword(passwordEncoder.encode(request.getPassword()));
    user.setApiKey(request.getApiKey());  // 用户专属 API Key
    user.setStatus(1);
    save(user);
}

3. 流式对话实现

通过 SSE (Server-Sent Events) 实现流式响应:

java 复制代码
// AiChatServiceImpl.java - 流式对话核心逻辑
@Override
public void streamChat(ChatRequest request, Long userId, HttpServletResponse response) {
    // 1. 从用户获取 API Key
    User user = userService.getById(userId);
    String apiKey = user.getApiKey();

    // 2. 设置 SSE 响应头
    response.setContentType("text/event-stream");
    response.setCharacterEncoding("UTF-8");

    // 3. 调用 DeepSeek API
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    conn.setRequestProperty("Authorization", "Bearer " + apiKey);

    // 4. 流式转发响应
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(conn.getInputStream()))) {
        String line;
        while ((line = reader.readLine()) != null) {
            writer.write(line + "\n");
            writer.flush();

            // 解析内容保存到数据库
            if (line.startsWith("data: ") && !line.equals("data: [DONE]")) {
                parseAndSaveContent(line, aiMessage);
            }
        }
    }
}

4. 会话与消息管理

  • 会话表 (chat_session): 存储对话元数据
  • 消息表 (chat_message): 存储对话内容,支持 reasoning_content 深度思考

5. 打断功能实现

前端通过 AbortController 中断请求,后端检测连接状态:

java 复制代码
// 检测客户端是否断开连接
private boolean isClientConnected(HttpServletResponse response, PrintWriter writer) {
    try {
        writer.write("");
        writer.flush();
        return !writer.checkError();
    } catch (Exception e) {
        return false;
    }
}

项目结构

bash 复制代码
src/main/java/com/webseek/
├── common/          # 通用工具类
│   ├── JwtUtil.java
│   ├── CurrentUser.java
│   └── Result.java
├── config/          # 配置类
│   ├── WebConfig.java
│   └── JwtInterceptor.java
├── controller/      # 控制器层
│   ├── AuthController.java
│   ├── ChatController.java
│   ├── SessionController.java
│   └── UserController.java
├── service/         # 服务层
│   ├── AiChatService.java
│   ├── UserService.java
│   └── impl/
├── entity/          # 实体类
│   ├── User.java
│   ├── ChatSession.java
│   └── ChatMessage.java
├── dto/             # 数据传输对象
│   ├── request/
│   └── response/
└── mapper/          # MyBatis Mapper

数据库表结构

sql 复制代码
-- 用户表
CREATE TABLE `user` (
    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
    `username` VARCHAR(50) NOT NULL UNIQUE,
    `password` VARCHAR(100) NOT NULL,
    `nickname` VARCHAR(50),
    `api_key` VARCHAR(500),        -- DeepSeek API Key
    `status` TINYINT DEFAULT 1,
    `deleted` TINYINT DEFAULT 0,
    `create_time` DATETIME,
    `update_time` DATETIME
);

-- 会话表
CREATE TABLE `chat_session` (
    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
    `session_id` VARCHAR(64) NOT NULL UNIQUE,
    `user_id` BIGINT NOT NULL,
    `title` VARCHAR(200) DEFAULT '新对话',
    `model` VARCHAR(50),
    `deleted` TINYINT DEFAULT 0,
    `create_time` DATETIME,
    `update_time` DATETIME
);

-- 消息表
CREATE TABLE `chat_message` (
    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
    `message_id` VARCHAR(64) NOT NULL UNIQUE,
    `session_id` VARCHAR(64) NOT NULL,
    `user_id` BIGINT NOT NULL,
    `role` VARCHAR(20) NOT NULL,   -- user/assistant
    `content` TEXT,
    `reasoning_content` TEXT,      -- 深度思考内容
    `deleted` TINYINT DEFAULT 0,
    `create_time` DATETIME
);

配置说明

修改 application.yml 中的数据库配置:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://your-host:3306/webseek?useUnicode=true&characterEncoding=utf-8
    username: your-username
    password: your-password

启动方式

bash 复制代码
# 开发环境
mvn spring-boot:run

# 打包
mvn clean package

# 运行
java -jar target/webseek-backend-1.0.0.jar

API 文档

启动后访问:http://localhost:8090/doc.html

核心设计亮点

  1. 用户级 API Key: 每个用户独立配置,安全隔离
  2. 流式响应: SSE 实现打字机效果,支持实时打断
  3. JWT 认证: 无状态设计,支持水平扩展
  4. 逻辑删除: MyBatis-Plus 自动处理软删除
  5. 深度思考: 支持 DeepSeek-R1 推理模型

源码地址[gitee.com/SongTaoo/re...](https://link.juejin.cn?target=https%3A%2F%2Fgitee.com%2FSongTaoo%2Freact-webseek "https://gitee.com/SongTaoo/react-webseek")

相关推荐
犀利豆4 分钟前
Claude Code Tools 研究系列(三)ExitPlanMode
人工智能·后端
醉城夜风~8 分钟前
CSS元素显示模式(display)
前端·css
星栈22 分钟前
oh-my-pi工程级AI编码工使用体验
人工智能·后端·agent
AI大模型-小华1 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
立心者02 小时前
SpringBoot中使用TOTP实现MFA(多因素认证)
java·spring boot·后端
AskHarries3 小时前
SEO 页面怎么生成
后端
做前端的娜娜子3 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
爱吃牛肉的大老虎3 小时前
Rust对象之结构体,枚举,特性
开发语言·后端·rust
石榴3 小时前
NestJS 的请求到底经过了什么:装饰器、守卫、拦截器、管道与中间件如何配合
后端