一个基于 React + Spring Boot 的全栈 AI 聊天应用,支持 SSE 流式输出、多轮对话管理、用户认证与对话持久化。
┌─────────────────────────────────────────────────────┐
│ 前端 (React 19) │
│ TypeScript · Vite 8 · Axios · Fetch SSE 流式解析 │
│ │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Sidebar │ │ ChatView │ │ LoginForm │ │
│ │ 对话列表 │ │ 消息列表 │ │ 登录/注册 │ │
│ └─────────┘ └──────────┘ └───────────┘ │
└──────────────────────┬──────────────────────────────┘
│ REST + SSE (port 8080)
┌──────────────────────┴──────────────────────────────┐
│ 后端 (Spring Boot 2.7) │
│ Java 11 · Spring Data JPA · JWT · SseEmitter │
│ │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ ClaudeService │ │ Conversation │ │
│ │ 流式 AI 转发 │ │ Service 持久化 │ │
│ └──────┬───────┘ └───────┬────────┘ │
└─────────┼─────────────────┼─────────────────────────┘
│ │
Claude API (SSE) PostgreSQL
(兼容 OpenAI 格式) (对话持久化)
| 技术 | 版本 | 说明 |
|---|---|---|
| React | 19.2 | UI 框架 |
| TypeScript | 5.9 | 类型安全 |
| Vite | 8.0 | 构建工具 (Oxc 编译) |
| Axios | 1.14 | REST API 调用 |
| Fetch API | — | SSE 流式接收 (POST + ReadableStream) |
| 技术 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 2.7.18 | Web 框架 |
| Java | 11 | 运行环境 |
| Spring Data JPA | — | ORM + PostgreSQL 持久化 |
| Spring Security Crypto | — | BCrypt 密码加密 |
| JJWT | 0.11.5 | JWT Token 签发/验证 |
| Java HttpClient | — | HTTP/2 流式转发 Claude API |
| Maven | — | 构建管理 |
| 技术 | 说明 |
|---|---|
| PostgreSQL | 用户、对话、消息持久化存储 |
- 注册(用户名 + 密码 + 手机号 + 邮箱,密码要求 8 位以上含大小写和数字)
- 登录(JWT Token,24 小时有效期)
- BCrypt 密码加密
- 用户名/邮箱/手机号唯一性校验
- SSE 流式输出,前端逐字显示 + 光标动画
- 多轮对话上下文(内存缓存,最大 50 条消息,30 分钟 TTL 自动清理)
- AI 自动生成对话标题
- 可配置模型(默认
glm-5,兼容 OpenAI Messages API 格式)
- 创建/切换/删除对话
- 对话持久化到 PostgreSQL
- 侧边栏历史列表(中文相对时间:刚刚/x分钟前/x小时前/x天前)
- 新建对话时自动保存
- 可折叠侧边栏
- 右上角头像按钮弹出登录/用户面板(点击外部关闭)
- AI 思考中三点弹跳动画
- 消息气泡自动滚动
- 输入框自适应高度,Enter 发送 / Shift+Enter 换行
gomini/
├── frontend/ # 前端项目
│ ├── src/
│ │ ├── main.tsx # React 入口
│ │ ├── App.tsx # 根组件(全部状态管理 + API 调用)
│ │ ├── App.css # 布局样式
│ │ ├── index.css # CSS 变量 + 全局重置
│ │ └── components/
│ │ ├── LoginForm.tsx/css # 登录/注册表单(双模式切换)
│ │ ├── ChatView.tsx/css # 聊天消息列表(自动滚动)
│ │ ├── ChatInput.tsx/css # 消息输入框(自适应高度)
│ │ ├── MessageBubble.tsx/css # 消息气泡(user/assistant 样式)
│ │ ├── Sidebar.tsx/css # 侧边栏(对话历史 + 新建/删除)
│ │ ├── UserPanel.tsx/css # 用户信息面板
│ │ ├── AvatarButton.tsx/css # 头像按钮(弹出登录或面板)
│ │ └── TypingIndicator.tsx/css # AI 思考中动画
│ ├── public/
│ │ ├── favicon.svg # 站点图标
│ │ └── icons.svg # 图标精灵
│ ├── package.json
│ ├── vite.config.ts
│ ├── tsconfig.json / tsconfig.app.json / tsconfig.node.json
│ └── eslint.config.js
│
├── backend/ # 后端项目
│ ├── src/main/java/com/example/backend/
│ │ ├── BackendApplication.java # Spring Boot 入口
│ │ ├── config/
│ │ │ ├── ClaudeConfig.java # Claude API RestTemplate 配置
│ │ │ └── WebConfig.java # CORS + AuthInterceptor 注册
│ │ ├── controller/
│ │ │ ├── ChatController.java # POST /api/chat (SSE 流式)
│ │ │ ├── ConversationController.java # 对话 CRUD
│ │ │ ├── LoginController.java # POST /api/login, /api/register
│ │ │ └── TitleController.java # POST /api/title (AI 生成标题)
│ │ ├── service/
│ │ │ ├── ClaudeService.java # Claude API 流式/非流式调用 + 内存会话缓存
│ │ │ ├── ConversationService.java # 对话持久化业务逻辑
│ │ │ ├── TitleService.java # AI 标题生成(调用 Claude API)
│ │ │ └── UserService.java # 注册/登录 + BCrypt 加密
│ │ ├── entity/
│ │ │ ├── User.java # 用户表 (users)
│ │ │ ├── Conversation.java # 对话表 (conversations)
│ │ │ └── ConversationMessage.java # 消息表 (conversation_messages)
│ │ ├── repository/
│ │ │ ├── UserRepository.java
│ │ │ └── ConversationRepository.java
│ │ ├── interceptor/
│ │ │ └── AuthInterceptor.java # JWT 认证拦截器
│ │ ├── dto/ # 请求/响应数据传输对象
│ │ │ ├── auth/ # LoginRequest, RegisterRequest
│ │ │ ├── chat/ # ChatRequest, StreamEvent, ClaudeApiResponse...
│ │ │ ├── conversation/ # 对话相关 DTO
│ │ │ ├── common/ # ApiResponse<T> 统一响应
│ │ │ └── user/ # UserDto
│ │ ├── exception/ # 全局异常处理
│ │ └── util/
│ │ └── JwtUtil.java # JWT 签发/解析/验证
│ ├── src/main/resources/
│ │ └── application.properties # 数据库 + Claude API + JWT 配置
│ ├── pom.xml # Maven 依赖
│ └── mvnw / mvnw.cmd # Maven Wrapper
│
└── .gitignore
users conversations conversation_messages
┌──────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ id (PK, BIGSERIAL)│ │ id (PK, BIGSERIAL) │ │ id (PK, BIGSERIAL) │
│ username (50, UQ) │──┐ │ user_id (FK→users) │ │ conversation_id │
│ phone (20, UQ) │ │ │ title (100) │ 1 N │ (FK→conversations) │
│ country_code (10) │ │ │ created_at │────────▶│ role (20) │
│ email (100, UQ) │ └──────▶│ updated_at │ │ content (TEXT) │
│ password_hash │ └──────────────────────┘ │ created_at │
│ created_at │ └─────────────────────┘
└──────────────────┘
users.username/users.phone/users.email均有唯一约束conversations按updated_at降序排列conversation_messages按created_at升序排列- JPA
ddl-auto=update,启动时自动建表
| 方法 | 路径 | 说明 | 请求体 |
|---|---|---|---|
| POST | /api/register |
注册 | { username, password, phone, countryCode, email } |
| POST | /api/login |
登录 | { username, password } → 返回 { token, userInfo } |
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/conversations |
获取当前用户的对话列表 |
| GET | /api/conversations/:id |
获取对话详情(含全部消息) |
| POST | /api/conversations |
创建新对话 { title?, messages[] } |
| PUT | /api/conversations/:id |
更新对话(追加消息) { title?, newMessages[] } |
| DELETE | /api/conversations/:id |
删除对话 |
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/chat |
发送消息,流式返回 AI 回复 |
| POST | /api/title |
根据对话内容生成标题 |
请求体:
{
"conversationId": "可选,为空则创建新会话",
"model": "glm-5",
"maxTokens": 1024,
"messages": [{ "role": "user", "content": "你好" }]
}SSE 事件流(text/event-stream):
| type | data | 说明 |
|---|---|---|
connected |
"连接已建立" |
SSE 连接就绪 |
text_delta |
"文本片段" |
AI 回复逐字推送 |
message_complete |
{ conversationId, model, stopReason, inputTokens, outputTokens } |
回复完成 |
error |
"错误信息" |
异常 |
- 前端: Node.js 18+, npm
- 后端: Java 11+, Maven 3.6+(或使用 Maven Wrapper)
- 数据库: PostgreSQL 12+
# 方式一:本地 PostgreSQL
createdb postgres # 如尚未创建
# 默认连接:localhost:5432, 用户 postgres, 密码 root
# 方式二:Docker
docker run -d --name gomini-postgres \
-e POSTGRES_PASSWORD=root \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=postgres \
-p 5432:5432 postgres:latestcd backend
# 修改数据库配置(如需要)
# 编辑 src/main/resources/application.properties
# 配置 Claude API(必须)
# claude.base-url=<你的 API 地址>
# claude.auth-token=<你的 Token>
# 启动
./mvnw spring-boot:run # Linux/Mac
mvnw.cmd spring-boot:run # Windows后端运行在 http://localhost:8080,JPA 会自动建表。
cd frontend
npm install
npm run dev前端运行在 http://localhost:5173,API 请求发往 http://localhost:8080。
cd frontend
npm run build # 输出到 dist/
npm run preview # 预览生产构建# 数据库
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=root
# Claude API(兼容 OpenAI Messages API 格式)
claude.base-url=http://your-api-server/api # API 基础地址
claude.auth-token=your_token # 认证 Token
claude.model=glm-5 # 默认模型
claude.max-tokens=1024 # 最大输出 token
claude.conversation-ttl-minutes=30 # 内存会话缓存 TTL
# JWT
jwt.secret=YourSuperSecretKey... # 签名密钥(≥256 bit)API 地址硬编码在 frontend/src/App.tsx 中:
const API_BASE = 'http://localhost:8080'生产环境部署时修改此值或通过 Vite 环境变量配置。
用户输入消息
│
▼
POST /api/chat (JSON body)
│
▼
ClaudeService.chatStream()
│ ├─ 生成 conversationId(新会话)
│ ├─ 加载内存会话历史(最多 50 条)
│ └─ 拼装请求 → Claude API (stream=true)
│
▼
SseEmitter 逐行解析上游 SSE
│
├─ content_block_delta → 转发 text_delta 给前端
├─ message_stop → 发送 message_complete + 保存历史
└─ error → 转发错误信息
│
▼
前端逐字渲染 + 保存对话到后端
│
▼
ConversationService 持久化到 PostgreSQL
注册: POST /api/register → BCrypt 加密密码 → 存入 users 表
登录: POST /api/login → BCrypt 验证 → JJWT 签发 Token(24h)
请求: Authorization: Bearer <token> → AuthInterceptor 解析 → 按 username 查 userId
过期: 前端收到 401/403 → 清除 localStorage → 弹出登录框