# 手把手教你从零搭建 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")

相关推荐
kyriewen23 分钟前
2026 年了,这 6 个 npm 包可以卸载了——浏览器原生 API 已经能替代
前端·javascript·npm
沉默王二1 小时前
面试结束后,我反问:“就面个实习至于上这么大强度吗?”面试官:“你对 RAG、Agent、MCP、Skill 理解得很到位,所以要求高一点。”
面试·agent·ai编程
铁皮饭盒2 小时前
bun直接tsx,优雅!
javascript·后端
Cosolar2 小时前
藏在 Claude Code 里的极致浪漫:完整 187 条 Spinner Verbs 全收录
后端·程序员·代码规范
Csvn3 小时前
Linux 防火墙管理 — firewalld 实战
后端
Csvn3 小时前
`functools.lru_cache` —— 一行代码搞定缓存加速
后端·python
Csvn3 小时前
Monorepo 迁移血泪史:从 Multi-Repo 到 Turborepo,这 3 个坑我帮你踩完了
前端
leeyi3 小时前
Multi-Agent:让多个 AI 分工协作完成复杂任务
后端·aigc·agent
长栎3 小时前
你的策略模式是 Map<String, Strategy>?那不过是最廉价的 if-else 替代品
后端
星栈3 小时前
Dioxus 多页面怎么做:`dioxus-router`、嵌套路由、`Outlet` 和页面组织,一篇给你讲顺
前端·rust·前端框架