refactor: remove legacy frontend code and implement new Next.js structure

- Deleted the old Register page and utility functions.
- Removed Tailwind CSS configuration and Vite configuration files.
- Added a new script for starting a single container with FastAPI and Next.js.
- Updated README to reflect the current status of the Next.js frontend.
- Implemented new login and registration API routes with improved error handling.
- Refactored frontend API calls to use the new proxy structure.
- Enhanced error handling in API response processing.
- Updated components to align with the new API endpoints and structure.
This commit is contained in:
2026-04-17 21:15:06 +08:00
parent cab8b3b483
commit 9a1a9d3247
60 changed files with 819 additions and 7988 deletions

View File

@@ -1,262 +1,140 @@
# MySQL 安装与配置指南
# MySQL 可选配置指南
QQuiz 使用 MySQL 8.0 作为数据库,你可以选择 Docker 部署或本地安装
QQuiz 默认部署路径是单容器 + SQLite。README、根目录 `Dockerfile``docker-compose-single.yml` 和 GitHub Actions 发布镜像都围绕这个模式设计
## 方式一:使用 Docker (推荐)
只有在你明确需要把数据库独立出去时,才需要 MySQL。常见原因
### 优点
- 无需手动安装 MySQL
- 自动配置和初始化
- 隔离环境,不影响系统
- 需要多个应用实例共享同一数据库
- 已有 MySQL 运维体系
- 希望把应用容器和数据库生命周期分开
### 使用步骤
## 场景一:源码部署时附加 MySQL 容器
1. **安装 Docker Desktop**
- 下载地址https://www.docker.com/products/docker-desktop/
- 安装后启动 Docker Desktop
这是当前最直接的 MySQL 用法,适合你已经克隆仓库并接受“应用容器 + MySQL 容器”的可选部署方式。
2. **运行启动脚本**
```bash
scripts\fix_and_start.bat
```
选择 **[1] Use Docker**
3. **完成!**
- Docker 会自动下载 MySQL 镜像
- 自动创建数据库和用户
- 自动启动服务
---
## 方式二:本地安装 MySQL
### 下载 MySQL
1. 访问 MySQL 官网下载页面:
https://dev.mysql.com/downloads/installer/
2. 选择 **MySQL Installer for Windows**
3. 下载 `mysql-installer-community-8.0.x.x.msi`
### 安装步骤
1. **运行安装程序**
- 双击下载的 .msi 文件
2. **选择安装类型**
- 选择 "Developer Default" 或 "Server only"
- 点击 Next
3. **配置 MySQL Server**
- **Config Type**: Development Computer
- **Port**: 3306 (默认)
- **Authentication Method**: 选择 "Use Strong Password Encryption"
4. **设置 Root 密码**
- 输入并记住 root 用户的密码
- 建议密码:`root` (开发环境)
5. **Windows Service 配置**
- ✅ Configure MySQL Server as a Windows Service
- Service Name: MySQL80
- ✅ Start the MySQL Server at System Startup
6. **完成安装**
- 点击 Execute 开始安装
- 等待安装完成
- 点击 Finish
### 验证安装
打开命令提示符,运行:
1. 复制环境变量模板:
```bash
mysql --version
cp .env.example .env
```
应该显示:`mysql Ver 8.0.x for Win64 on x86_64`
Windows PowerShell:
### 配置 QQuiz 数据库
**方式 A使用脚本自动创建 (推荐)**
运行:
```bash
scripts\fix_and_start.bat
```
选择 **[2] Use Local MySQL**
**方式 B手动创建**
1. 打开 MySQL 命令行客户端:
```bash
mysql -u root -p
```
2. 输入 root 密码
3. 创建数据库和用户:
```sql
CREATE DATABASE qquiz_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'qquiz'@'localhost' IDENTIFIED BY 'qquiz_password';
GRANT ALL PRIVILEGES ON qquiz_db.* TO 'qquiz'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
---
## 数据库配置说明
### .env 文件配置
确保 `.env` 文件中的数据库连接字符串正确:
**本地 MySQL:**
```env
DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db
```powershell
Copy-Item .env.example .env
```
**Docker MySQL:**
2.`.env` 中的数据库连接改成 MySQL 容器地址:
```env
DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql:3306/qquiz_db
```
### 连接参数说明
3. 启动应用和 MySQL
- `mysql+aiomysql://` - 使用 aiomysql 异步驱动
- `qquiz` - 数据库用户名
- `qquiz_password` - 数据库密码
- `localhost` 或 `mysql` - 数据库主机地址
- `3306` - MySQL 默认端口
- `qquiz_db` - 数据库名称
---
## 常见问题
### 1. 端口 3306 被占用
**错误信息:**
```
Error: Port 3306 is already in use
```bash
docker compose -f docker-compose.yml -f docker-compose.mysql.yml up -d --build
```
**解决方案:**
- 检查是否已经有 MySQL 运行:`netstat -ano | findstr :3306`
- 停止现有的 MySQL 服务
- 或修改 `.env` 中的端口号
4. 访问:
### 2. 无法连接到 MySQL
- 前端:`http://localhost:3000`
- 后端:`http://localhost:8000`
**错误信息:**
```
Can't connect to MySQL server on 'localhost'
```
说明:
**解决方案:**
- 这条路径是 MySQL 兼容部署,不是默认发布路径
- 默认发布镜像仍然是根目录单容器镜像
1. **检查 MySQL 服务是否运行**
- 按 Win+R输入 `services.msc`
- 查找 "MySQL80" 服务
- 确认状态为 "正在运行"
## 场景二:单容器应用连接外部 MySQL
2. **启动 MySQL 服务**
```bash
net start MySQL80
```
如果你想继续使用单容器应用镜像,但数据库由外部 MySQL 托管,可以直接让应用容器连接现有数据库。
3. **检查防火墙设置**
- 确保防火墙允许 MySQL 端口 3306
### 1. 准备 MySQL 8.0 数据库
### 3. 密码验证失败
执行以下 SQL 创建数据库和账号:
**错误信息:**
```
Access denied for user 'qquiz'@'localhost'
```
**解决方案:**
重新创建用户并设置密码:
```sql
mysql -u root -p
DROP USER IF EXISTS 'qquiz'@'localhost';
CREATE USER 'qquiz'@'localhost' IDENTIFIED BY 'qquiz_password';
GRANT ALL PRIVILEGES ON qquiz_db.* TO 'qquiz'@'localhost';
CREATE DATABASE qquiz_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'qquiz'@'%' IDENTIFIED BY 'qquiz_password';
GRANT ALL PRIVILEGES ON qquiz_db.* TO 'qquiz'@'%';
FLUSH PRIVILEGES;
```
### 4. 字符集问题
### 2. 修改 `.env`
**解决方案:**
`DATABASE_URL` 改成你的 MySQL 地址,例如:
```env
DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql.example.com:3306/qquiz_db
UPLOAD_DIR=/app/uploads
```
### 3. 启动单容器镜像
```bash
docker pull ghcr.io/handsomezhuzhu/qquiz:latest
docker volume create qquiz_uploads
docker run -d \
--name qquiz \
--env-file .env \
-v qquiz_uploads:/app/uploads \
-p 8000:8000 \
--restart unless-stopped \
ghcr.io/handsomezhuzhu/qquiz:latest
```
说明:
- 这里不需要本地 SQLite 数据卷,因为数据库已经外置到 MySQL
- 仍然建议保留上传目录卷,避免容器重建后丢失上传文件
## 本地开发连接 MySQL
如果你是在本机直接跑后端,`.env` 中可使用本地 MySQL 地址:
```env
DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db
```
然后分别启动后端和前端:
```bash
cd backend
pip install -r requirements.txt
alembic upgrade head
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
```bash
cd web
npm install
npm run dev
```
## 常见问题
### 1. 连接不上 MySQL
检查以下几项:
- `DATABASE_URL` 中的主机名、端口、用户名和密码是否正确
- MySQL 是否允许对应来源地址连接
- 3306 端口是否开放
### 2. 容器里能连,宿主机里不能连
这是因为容器内部和宿主机访问地址不同:
- 容器之间互联时通常使用服务名,例如 `mysql`
- 宿主机连接本机 MySQL 时通常使用 `localhost`
### 3. 字符集异常
建议数据库和表统一使用 `utf8mb4`
确保数据库使用 UTF-8 字符集:
```sql
ALTER DATABASE qquiz_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
---
## 管理工具推荐
### 1. MySQL Workbench (官方)
- 下载https://dev.mysql.com/downloads/workbench/
- 功能可视化数据库管理、SQL 编辑器、备份还原
### 2. DBeaver (免费开源)
- 下载https://dbeaver.io/download/
- 功能多数据库支持、数据导入导出、ER 图
### 3. phpMyAdmin (Web 界面)
- 适合习惯 Web 界面的用户
---
## 数据库备份与恢复
### 备份数据库
```bash
mysqldump -u qquiz -p qquiz_db > backup.sql
```
### 恢复数据库
```bash
mysql -u qquiz -p qquiz_db < backup.sql
```
---
## 切换回 PostgreSQL
如果需要切换回 PostgreSQL
1. 修改 `requirements.txt`
```
asyncpg==0.29.0 # 替换 aiomysql
```
2. 修改 `.env`
```
DATABASE_URL=postgresql+asyncpg://qquiz:qquiz_password@localhost:5432/qquiz_db
```
3. 修改 `docker-compose.yml`
- 将 `mysql` 服务改回 `postgres`
4. 重新安装依赖:
```bash
pip install -r requirements.txt
```
---
## 技术支持
如遇到其他问题,请:
1. 检查 MySQL 错误日志
2. 确认防火墙和网络配置
3. 查看项目 issues: https://github.com/handsomezhuzhu/QQuiz/issues

View File

@@ -30,36 +30,17 @@ QQuiz/
│ ├── alembic.ini # Alembic 配置
│ └── Dockerfile # 后端 Docker 镜像
├── frontend/ # React 前端
├── web/ # Next.js 前端
│ ├── src/
│ │ ├── api/
│ │ │ └── client.js # API 客户端Axios
│ │ ├── components/
│ │ │ ├── Layout.jsx # 主布局(导航栏)
│ │ │ └── ProtectedRoute.jsx # 路由保护
│ │ ├── context/
│ │ │ └── AuthContext.jsx # 认证上下文
│ │ ├── pages/
│ │ │ ├── Login.jsx # 登录页
│ │ │ ├── Register.jsx # 注册页
│ │ │ ├── Dashboard.jsx # 仪表盘
│ │ │ ├── ExamList.jsx # 题库列表 ⭐
│ │ │ ├── ExamDetail.jsx # 题库详情(追加上传)⭐
│ │ │ ├── QuizPlayer.jsx # 刷题核心页面 ⭐
│ │ │ ├── MistakeList.jsx # 错题本
│ │ │ └── AdminSettings.jsx # 系统设置
│ │ ├── utils/
│ │ │ └── helpers.js # 工具函数
│ │ ├── App.jsx # 应用根组件
│ │ ├── index.jsx # 应用入口
│ │ └── index.css # 全局样式
│ ├── public/
│ │ └── index.html # HTML 模板
│ │ ├── app/ # App Router 页面、布局、Route Handlers
│ │ ├── components/ # 共享 UI 组件
│ │ ├── lib/ # API、认证、格式化等公共逻辑
│ │ └── middleware.ts # 登录态守卫
│ ├── package.json # Node 依赖
│ ├── vite.config.js # Vite 配置
│ ├── tailwind.config.js # Tailwind CSS 配置
│ ├── postcss.config.js # PostCSS 配置
│ └── Dockerfile # 前端 Docker 镜像
│ ├── next.config.mjs # Next.js 配置
│ ├── tailwind.config.ts # Tailwind CSS 配置
│ ├── postcss.config.mjs # PostCSS 配置
│ └── Dockerfile # 分离部署前端镜像
├── docker-compose.yml # Docker 编排配置 ⭐
├── .env.example # 环境变量模板
@@ -133,74 +114,31 @@ for q in questions_data:
### 前端核心
#### `client.js` - API 客户端
封装了所有后端 API
- `authAPI`: 登录、注册、用户信息
- `examAPI`: 题库 CRUD、追加文档
- `questionAPI`: 获取题目、答题
- `mistakeAPI`: 错题本管理
- `adminAPI`: 系统配置
#### `src/lib/api/server.ts` - 服务端 API 访问
用于 Next Server Components 访问后端
- `HttpOnly` Cookie 读取会话令牌
- 直接请求 FastAPI `/api/*`
- 401 时自动重定向回登录页
**特性:**
- 自动添加 JWT Token
- 统一错误处理和 Toast 提示
- 401 自动跳转登录
#### `src/lib/api/browser.ts` - 浏览器端 API 访问
用于客户端交互:
- 请求同源 `/frontend-api/proxy/*`
- 统一处理错误信息
- 默认禁用缓存,保持刷题和后台状态最新
#### `ExamDetail.jsx` - 题库详情
最复杂的前端页面,包含
- **追加上传**: 上传新文档并去重
- **状态轮询**: 每 3 秒轮询一次状态
- **智能按钮**:
- 处理中时禁用「添加文档」
- 就绪后显示「开始/继续刷题」
- **进度展示**: 题目数、完成度、进度条
#### `src/components/exams/exam-detail-client.tsx` - 题库详情
负责
- 追加上传文档
- 展示解析进度
- 通过 `/frontend-api/exams/{examId}/progress` 订阅同源 SSE
- 处理解析完成/失败后的页面刷新
**状态轮询实现:**
```javascript
useEffect(() => {
const interval = setInterval(() => {
pollExamStatus() // 轮询状态
}, 3000)
return () => clearInterval(interval)
}, [examId])
const pollExamStatus = async () => {
const newExam = await examAPI.getDetail(examId)
// 检测状态变化
if (exam?.status === 'processing' && newExam.status === 'ready') {
toast.success('文档解析完成!')
await loadExamDetail() // 重新加载数据
}
setExam(newExam)
}
```
#### `QuizPlayer.jsx` - 刷题核心
实现完整的刷题流程:
1. 基于 `current_index` 加载当前题目
2. 根据题型显示不同的答题界面
3. 提交答案并检查(简答题调用 AI 评分)
4. 答错自动加入错题本
5. 点击下一题自动更新进度
**断点续做实现:**
```javascript
// 始终基于 exam.current_index 加载题目
const loadCurrentQuestion = async () => {
const question = await questionAPI.getCurrentQuestion(examId)
// 后端会根据 current_index 返回对应题目
}
// 下一题时更新进度
const handleNext = async () => {
const newIndex = exam.current_index + 1
await examAPI.updateProgress(examId, newIndex)
await loadCurrentQuestion()
}
```
#### `src/components/practice/quiz-player-client.tsx` - 刷题核心
负责:
- 加载当前题目
- 提交答案并展示结果
- 推进刷题进度
- 管理简答题与错题练习等交互
---
@@ -323,17 +261,17 @@ CREATE UNIQUE INDEX ix_user_mistakes_unique ON user_mistakes(user_id, question_i
- **OpenAI/Anthropic/Qwen**: AI 解析和评分
### 前端
- **Next.js 14 App Router**: 前端运行时
- **React 18**: UI 框架
- **Vite**: 构建工具(比 CRA 更快)
- **TypeScript**: 类型系统
- **Tailwind CSS**: 原子化 CSS
- **Axios**: HTTP 客户端
- **React Router**: 路由管理
- **React Hot Toast**: 消息提示
- **TanStack Query**: 客户端缓存和数据同步
- **Route Handlers**: 同源认证与代理层
### 部署
- **Docker + Docker Compose**: 容器化部署
- **PostgreSQL 15**: 关系型数据库
- **Nginx** (可选): 反向代理
- **SQLite / MySQL**: 关系型数据库
- **FastAPI reverse proxy**: 单容器模式下代理 Next.js
---

View File

@@ -19,17 +19,17 @@ Audit date: 2026-04-17
### Frontend
- Runtime: React 18 + Vite SPA
- Routing: `react-router-dom`
- Auth state: client-only `localStorage` token + context
- API transport: axios interceptor with browser redirects
- Styling: Tailwind CSS with page-local utility classes
- Runtime: Next.js App Router + TypeScript
- Routing: file-system routing + middleware guards
- Auth state: `HttpOnly` cookie managed by Next route handlers
- API transport: server/client fetch helpers with same-origin proxy routes
- Styling: Tailwind CSS + shadcn/ui patterns
### Deployment
- `docker-compose.yml`: development-oriented split stack
- `docker-compose-single.yml`: monolith container with SQLite
- `Dockerfile`: FastAPI serves the built SPA as static assets
- `docker-compose.yml`: split development stack
- `docker-compose-single.yml`: default single-container deployment
- `Dockerfile`: single image running FastAPI + embedded Next.js
## Target Architecture
@@ -51,20 +51,20 @@ Audit date: 2026-04-17
### Deployment
- Split deployment becomes the primary production shape
- Monolith mode remains secondary compatibility mode
- Development and production Compose files must be separated
- Single-container deployment is the primary release path
- Split deployment remains available for development and compatibility testing
- Development and production Compose files must stay explicitly separated
## Core Constraints
1. Do not overwrite existing uncommitted user changes in the legacy frontend.
2. Keep the legacy `frontend/` app available until the new `web/` app reaches functional parity.
3. Preserve backend API contracts where possible during the frontend migration.
4. Fix deployment/documentation drift before treating new frontend work as production-ready.
1. Preserve backend API contracts where possible across frontend changes.
2. Keep single-container and split-stack behavior aligned on the same `web/` frontend.
3. Fix deployment/documentation drift before treating changes as production-ready.
4. Avoid reintroducing duplicate frontend implementations.
## Immediate Workstreams
1. Remove abandoned ESA captcha wiring from the legacy frontend.
2. Write audit documents and freeze the migration backlog.
3. Scaffold the new `web/` frontend without disturbing the legacy app.
4. Fix first-order deployment issues such as health checks and documented mount paths.
1. Keep single-container delivery using the same `web/` frontend as split deployment.
2. Continue moving backend orchestration into typed services.
3. Tighten health checks and deployment docs around the embedded Next runtime.
4. Cover remaining functional gaps with smoke tests.

View File

@@ -1,70 +1,50 @@
# Frontend Migration Plan
# Frontend Cutover Notes
## Decision
The legacy Vite SPA remains in `frontend/` as a fallback.
`web/` is now the only frontend in the repository.
The new frontend is being built in `web/` with:
The previous Vite SPA has been removed so that:
- Next.js App Router
- TypeScript
- Tailwind CSS
- shadcn/ui component model
- split deployment and single-container deployment use the same UI
- documentation no longer has to describe two competing frontend stacks
- future frontend changes only need to be implemented once
The abandoned ESA captcha integration has been removed from the legacy login page.
## Why a Rewrite Instead of an In-Place Port
The legacy frontend mixes too many browser-only assumptions into core runtime
boundaries:
- token storage in `localStorage`
- `window.location` redirects inside transport code
- client-only route protection
- SSE token passing in query strings
Those patterns do not map cleanly onto Next App Router and server-first auth.
## New Runtime Model
## Runtime Model
### Auth
- Login goes through Next route handlers
- Login goes through Next route handlers under `/frontend-api/auth/*`
- Backend JWT is stored in an `HttpOnly` cookie
- Browser code never reads the raw token
### Data
- Server pages use server-side fetch helpers
- Client mutations use browser-side fetch helpers against Next proxy routes
- URL state is used for pagination and filters
- Server pages use server-side fetch helpers against FastAPI
- Client mutations use browser-side fetch helpers against `/frontend-api/proxy/*`
- FastAPI continues to own the public `/api/*` surface
### Streaming
- Browser connects to a same-origin Next progress route
- Browser connects to `/frontend-api/exams/{examId}/progress`
- The route reads the session cookie and proxies backend SSE
- Backend URL tokens are hidden from the browser
- Backend token query parameters stay hidden from the browser
## Directory Map
## Deployment Outcome
```text
web/
src/app/
src/components/
src/lib/
src/middleware.ts
```
### Split Stack
## Migration Order
- `backend` serves API traffic on `:8000`
- `web` serves Next.js on `:3000`
1. Auth shell, layouts, middleware, and proxy routes
2. Dashboard, exams list, questions list, and admin overview
3. Exam detail upload and progress streaming
4. Quiz and mistake-practice flows
5. Cutover, smoke testing, and legacy frontend retirement
### Single Container
## Non-Goals for This First Slice
- the container runs both FastAPI and Next.js
- FastAPI stays on `:8000`
- non-API requests are proxied from FastAPI to the embedded Next server
- No immediate removal of the legacy `frontend/`
- No backend contract rewrite yet
- No server actions as the primary data mutation layer
## Follow-up Expectations
1. New frontend work lands only in `web/`
2. Single-container smoke tests must validate both UI and API paths
3. Deployment docs must continue to describe `web/` as the sole frontend