Document secure secrets and prune unused assets

This commit is contained in:
Simon
2025-12-13 01:35:56 +08:00
parent c4bb32b163
commit 1adf30d476
28 changed files with 157 additions and 1451 deletions

View File

@@ -7,8 +7,11 @@ DATABASE_URL=sqlite+aiosqlite:///./qquiz.db
# For Local: mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db # For Local: mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db
# DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db # DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db
# JWT Secret (Please change this in production!) # JWT Secret (must be at least 32 characters; generate randomly for production)
SECRET_KEY=your-super-secret-key-change-in-production-minimum-32-characters SECRET_KEY=
# Default admin password (must be at least 12 characters; generate randomly for production)
ADMIN_PASSWORD=
# AI Provider Configuration # AI Provider Configuration
AI_PROVIDER=gemini AI_PROVIDER=gemini

View File

@@ -55,11 +55,13 @@ docker run -d \
从源码构建,一个容器包含前后端和 SQLite 数据库: 从源码构建,一个容器包含前后端和 SQLite 数据库:
```bash ```bash
# 1. 配置环境变量 # 1. 配置环境变量(必须提供强密码和密钥)
cp .env.example .env cp .env.example .env
# 编辑 .env填入你的 API Key # 编辑 .env填入至少 32 位的 SECRET_KEY 和至少 12 位的 ADMIN_PASSWORD建议使用随机生成值
# 2. 启动服务 # 2. 启动服务(未设置强密码/密钥会直接报错终止)
SECRET_KEY=$(openssl rand -base64 48) \
ADMIN_PASSWORD=$(openssl rand -base64 16) \
docker-compose -f docker-compose-single.yml up -d docker-compose -f docker-compose-single.yml up -d
# 3. 访问应用: http://localhost:8000 # 3. 访问应用: http://localhost:8000
@@ -71,7 +73,9 @@ docker-compose -f docker-compose-single.yml up -d
前后端分离 + MySQL 前后端分离 + MySQL
```bash ```bash
# 启动服务 # 启动服务(建议直接在命令行生成强密钥和管理员密码)
SECRET_KEY=$(openssl rand -base64 48) \
ADMIN_PASSWORD=$(openssl rand -base64 16) \
docker-compose up -d docker-compose up -d
# 前端: http://localhost:3000 # 前端: http://localhost:3000
@@ -85,14 +89,6 @@ docker-compose up -d
- Node.js 18+ - Node.js 18+
- MySQL 8.0+ 或 Docker (用于运行 MySQL) - MySQL 8.0+ 或 Docker (用于运行 MySQL)
**Windows 用户:**
```bash
# 双击运行以下脚本之一:
scripts\fix_and_start.bat # 推荐:自动修复并启动(支持 Docker/本地数据库)
scripts\start_with_docker_db.bat # 使用 Docker 数据库启动
scripts\setup.bat # 仅配置环境变量
```
**Linux/macOS 用户:** **Linux/macOS 用户:**
```bash ```bash
# 1. 配置环境变量 # 1. 配置环境变量
@@ -110,23 +106,6 @@ chmod +x scripts/run_local.sh
**MySQL 安装指南:** 详见 [docs/MYSQL_SETUP.md](docs/MYSQL_SETUP.md) **MySQL 安装指南:** 详见 [docs/MYSQL_SETUP.md](docs/MYSQL_SETUP.md)
### Docker 构建加速(中国用户)
如果你在中国大陆Docker 构建速度可能较慢。我们提供了使用国内镜像的可选配置:
**详细指南:** 参见 [docs/CHINA_MIRROR_GUIDE.md](docs/CHINA_MIRROR_GUIDE.md)
**快速使用:**
```bash
# 后端使用中国镜像构建
docker build -f backend/Dockerfile.china -t qquiz-backend ./backend
# 前端使用中国镜像构建
docker build -f frontend/Dockerfile.china -t qquiz-frontend ./frontend
```
构建速度可提升 3-5 倍 ⚡
## GitHub Actions 自动构建设置 ## GitHub Actions 自动构建设置
如果你 fork 了本项目并想启用自动构建 Docker 镜像功能: 如果你 fork 了本项目并想启用自动构建 Docker 镜像功能:
@@ -161,9 +140,9 @@ docker pull ghcr.io/handsomezhuzhu/qquiz:latest
**管理员账户:** **管理员账户:**
- 用户名: `admin` - 用户名: `admin`
- 密码: `admin123` - 密码: 取自环境变量 `ADMIN_PASSWORD`(必须至少 12 位,建议随机生成)
⚠️ **重要**: 首次登录后请立即修改密码! ⚠️ **重要**: 在部署前就必须设置强管理员密码;如果需要轮换密码,请更新环境变量后重启服务。
## 项目结构 ## 项目结构
@@ -186,16 +165,9 @@ QQuiz/
│ ├── package.json # Node 依赖 │ ├── package.json # Node 依赖
│ └── vite.config.js # Vite 配置 │ └── vite.config.js # Vite 配置
├── scripts/ # 部署和启动脚本 ├── scripts/ # 部署和启动脚本
── fix_and_start.bat # Windows 快速启动(推荐) ── run_local.sh # Linux/macOS 启动脚本
│ ├── start_with_docker_db.bat # Docker 数据库启动
│ ├── setup.bat # 环境配置脚本
│ ├── run_local.sh # Linux/macOS 启动脚本
│ └── [其他辅助脚本...]
├── docs/ # 文档目录 ├── docs/ # 文档目录
│ ├── AI_CONFIGURATION.md # AI 提供商配置指南(重要)
│ ├── MYSQL_SETUP.md # MySQL 安装配置指南 │ ├── MYSQL_SETUP.md # MySQL 安装配置指南
│ ├── CHINA_MIRROR_GUIDE.md # 中国镜像加速指南
│ ├── WINDOWS_DEPLOYMENT.md # Windows 部署详细指南
│ └── PROJECT_STRUCTURE.md # 项目架构详解 │ └── PROJECT_STRUCTURE.md # 项目架构详解
├── test_data/ # 测试数据 ├── test_data/ # 测试数据
│ └── sample_questions.txt # 示例题目 │ └── sample_questions.txt # 示例题目
@@ -233,7 +205,8 @@ QQuiz/
| 变量 | 说明 | 默认值 | | 变量 | 说明 | 默认值 |
|------|------|--------| |------|------|--------|
| `DATABASE_URL` | 数据库连接字符串 | - | | `DATABASE_URL` | 数据库连接字符串 | - |
| `SECRET_KEY` | JWT 密钥 | - | | `SECRET_KEY` | JWT 密钥(必须至少 32 位随机字符串) | - |
| `ADMIN_PASSWORD` | 默认管理员密码(必须至少 12 位,建议随机生成) | - |
| `AI_PROVIDER` | AI 提供商 (gemini/openai/anthropic/qwen) | gemini | | `AI_PROVIDER` | AI 提供商 (gemini/openai/anthropic/qwen) | gemini |
| `GEMINI_API_KEY` | Google Gemini API 密钥 | - | | `GEMINI_API_KEY` | Google Gemini API 密钥 | - |
| `GEMINI_BASE_URL` | Gemini API 地址(可选,支持代理) | https://generativelanguage.googleapis.com | | `GEMINI_BASE_URL` | Gemini API 地址(可选,支持代理) | https://generativelanguage.googleapis.com |

View File

@@ -86,6 +86,10 @@ async def init_default_config(db: AsyncSession):
"ai_provider": os.getenv("AI_PROVIDER", "openai"), "ai_provider": os.getenv("AI_PROVIDER", "openai"),
} }
admin_password = os.getenv("ADMIN_PASSWORD")
if not admin_password or len(admin_password) < 12:
raise ValueError("ADMIN_PASSWORD must be set and at least 12 characters long")
for key, value in default_configs.items(): for key, value in default_configs.items():
result = await db.execute(select(SystemConfig).where(SystemConfig.key == key)) result = await db.execute(select(SystemConfig).where(SystemConfig.key == key))
existing = result.scalar_one_or_none() existing = result.scalar_one_or_none()
@@ -99,15 +103,32 @@ async def init_default_config(db: AsyncSession):
result = await db.execute(select(User).where(User.username == "admin")) result = await db.execute(select(User).where(User.username == "admin"))
admin = result.scalar_one_or_none() admin = result.scalar_one_or_none()
default_admin_id = admin.id if admin else None
if not admin: if not admin:
admin_user = User( admin_user = User(
username="admin", username="admin",
hashed_password=pwd_context.hash("admin123"), # Change this password! hashed_password=pwd_context.hash(admin_password),
is_admin=True is_admin=True
) )
db.add(admin_user) db.add(admin_user)
print("✅ Created default admin user (username: admin, password: admin123)") await db.commit()
print("⚠️ IMPORTANT: Please change the admin password immediately!") await db.refresh(admin_user)
default_admin_id = admin_user.id
print("✅ Created default admin user (username: admin)")
else:
await db.commit()
if default_admin_id is not None:
result = await db.execute(
select(SystemConfig).where(SystemConfig.key == "default_admin_id")
)
default_admin_config = result.scalar_one_or_none()
if not default_admin_config:
db.add(SystemConfig(key="default_admin_id", value=str(default_admin_id)))
elif default_admin_config.value != str(default_admin_id):
default_admin_config.value = str(default_admin_id)
await db.commit() await db.commit()

View File

@@ -4,18 +4,28 @@ QQuiz FastAPI Application - 单容器模式(前后端整合)
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, FileResponse from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import os import os
from pathlib import Path from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from database import init_db, init_default_config, get_db_context from database import init_db, init_default_config, get_db_context
from rate_limit import limiter
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
async def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded):
return JSONResponse(
status_code=429,
content={"detail": "Rate limit exceeded. Please try again later."}
)
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
"""Application lifespan events""" """Application lifespan events"""
@@ -50,6 +60,10 @@ app = FastAPI(
lifespan=lifespan lifespan=lifespan
) )
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
# Configure CORS # Configure CORS
cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",") cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",")
app.add_middleware( app.add_middleware(

10
backend/rate_limit.py Normal file
View File

@@ -0,0 +1,10 @@
"""Rate limiting utilities for the FastAPI application."""
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
__all__ = ["limiter"]

View File

@@ -12,6 +12,7 @@ python-multipart==0.0.6
passlib==1.7.4 passlib==1.7.4
bcrypt==4.0.1 bcrypt==4.0.1
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.3.0
python-magic==0.4.27
aiofiles==23.2.1 aiofiles==23.2.1
httpx==0.26.0 httpx==0.26.0
openai==1.10.0 openai==1.10.0
@@ -19,3 +20,4 @@ anthropic==0.8.1
python-docx==1.1.0 python-docx==1.1.0
PyPDF2==3.0.1 PyPDF2==3.0.1
openpyxl==3.1.2 openpyxl==3.1.2
slowapi==0.1.9

View File

@@ -24,6 +24,18 @@ router = APIRouter()
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
async def get_default_admin_id(db: AsyncSession) -> Optional[int]:
result = await db.execute(
select(SystemConfig).where(SystemConfig.key == "default_admin_id")
)
config = result.scalar_one_or_none()
if config and config.value.isdigit():
return int(config.value)
return None
@router.get("/config", response_model=SystemConfigResponse) @router.get("/config", response_model=SystemConfigResponse)
async def get_system_config( async def get_system_config(
current_admin: User = Depends(get_current_admin_user), current_admin: User = Depends(get_current_admin_user),
@@ -203,8 +215,10 @@ async def update_user(
detail="User not found" detail="User not found"
) )
protected_admin_id = await get_default_admin_id(db)
# 不允许修改默认管理员的管理员状态 # 不允许修改默认管理员的管理员状态
if user.username == "admin" and user_data.is_admin is not None: if protected_admin_id and user.id == protected_admin_id and user_data.is_admin is not None:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Cannot modify default admin user's admin status" detail="Cannot modify default admin user's admin status"
@@ -240,8 +254,10 @@ async def delete_user(
detail="User not found" detail="User not found"
) )
protected_admin_id = await get_default_admin_id(db)
# 不允许删除默认管理员 # 不允许删除默认管理员
if user.username == "admin": if protected_admin_id and user.id == protected_admin_id:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Cannot delete default admin user" detail="Cannot delete default admin user"

View File

@@ -5,14 +5,17 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select from sqlalchemy import select
from datetime import timedelta from datetime import timedelta
import logging
from database import get_db from database import get_db
from models import User, SystemConfig from models import User, SystemConfig
from schemas import UserCreate, UserLogin, Token, UserResponse from schemas import UserCreate, UserLogin, Token, UserResponse
from utils import hash_password, verify_password, create_access_token from utils import hash_password, verify_password, create_access_token
from rate_limit import limiter
from services.auth_service import get_current_user from services.auth_service import get_current_user
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
@@ -61,6 +64,7 @@ async def register(
@router.post("/login", response_model=Token) @router.post("/login", response_model=Token)
@limiter.limit("5/minute")
async def login( async def login(
user_data: UserLogin, user_data: UserLogin,
db: AsyncSession = Depends(get_db) db: AsyncSession = Depends(get_db)
@@ -86,8 +90,7 @@ async def login(
data={"sub": str(user.id)} # JWT 'sub' must be a string data={"sub": str(user.id)} # JWT 'sub' must be a string
) )
print(f"Login successful: user={user.username}, id={user.id}") logger.info("Login successful", extra={"user_id": user.id})
print(f"🔑 Generated token (first 50 chars): {access_token[:50]}...")
return { return {
"access_token": access_token, "access_token": access_token,

View File

@@ -10,6 +10,7 @@ from datetime import datetime, timedelta
import os import os
import aiofiles import aiofiles
import json import json
import magic
from database import get_db from database import get_db
from models import User, Exam, Question, ExamStatus, SystemConfig from models import User, Exam, Question, ExamStatus, SystemConfig
@@ -24,8 +25,57 @@ from services.config_service import load_llm_config
from services.progress_service import progress_service from services.progress_service import progress_service
from utils import is_allowed_file, calculate_content_hash from utils import is_allowed_file, calculate_content_hash
from dedup_utils import is_duplicate_question from dedup_utils import is_duplicate_question
from rate_limit import limiter
router = APIRouter() router = APIRouter()
ALLOWED_MIME_TYPES = {
"text/plain",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}
async def validate_upload_file(file: UploadFile) -> None:
"""Validate uploaded file by extension and MIME type."""
if not file.filename or not is_allowed_file(file.filename):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid file type. Allowed: txt, pdf, doc, docx, xlsx, xls",
)
try:
sample = await file.read(2048)
except Exception:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Could not read uploaded file for validation",
)
if not sample:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Uploaded file is empty",
)
try:
mime_type = magic.from_buffer(sample, mime=True)
except Exception:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Could not determine file type",
)
finally:
file.file.seek(0)
if mime_type not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid file content type",
)
async def check_upload_limits(user_id: int, file_size: int, db: AsyncSession): async def check_upload_limits(user_id: int, file_size: int, db: AsyncSession):
@@ -485,6 +535,7 @@ async def async_parse_and_save(
@router.post("/create", response_model=ExamUploadResponse, status_code=status.HTTP_201_CREATED) @router.post("/create", response_model=ExamUploadResponse, status_code=status.HTTP_201_CREATED)
@limiter.limit("10/minute")
async def create_exam_with_upload( async def create_exam_with_upload(
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
title: str = Form(...), title: str = Form(...),
@@ -498,11 +549,7 @@ async def create_exam_with_upload(
""" """
# Validate file # Validate file
if not file.filename or not is_allowed_file(file.filename): await validate_upload_file(file)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid file type. Allowed: txt, pdf, doc, docx, xlsx, xls"
)
# Read file content # Read file content
file_content = await file.read() file_content = await file.read()
@@ -538,6 +585,7 @@ async def create_exam_with_upload(
@router.post("/{exam_id}/append", response_model=ExamUploadResponse) @router.post("/{exam_id}/append", response_model=ExamUploadResponse)
@limiter.limit("10/minute")
async def append_document_to_exam( async def append_document_to_exam(
exam_id: int, exam_id: int,
background_tasks: BackgroundTasks, background_tasks: BackgroundTasks,
@@ -572,11 +620,7 @@ async def append_document_to_exam(
) )
# Validate file # Validate file
if not file.filename or not is_allowed_file(file.filename): await validate_upload_file(file)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid file type. Allowed: txt, pdf, doc, docx, xlsx, xls"
)
# Read file content # Read file content
file_content = await file.read() file_content = await file.read()

View File

@@ -13,7 +13,10 @@ import os
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# JWT settings # JWT settings
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-this") SECRET_KEY = os.getenv("SECRET_KEY")
if not SECRET_KEY or len(SECRET_KEY) < 32:
raise ValueError("SECRET_KEY must be set and at least 32 characters long")
ALGORITHM = "HS256" ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days

View File

@@ -15,8 +15,11 @@ services:
# 数据库配置SQLite 默认) # 数据库配置SQLite 默认)
- DATABASE_URL=sqlite+aiosqlite:////app/data/qquiz.db - DATABASE_URL=sqlite+aiosqlite:////app/data/qquiz.db
# JWT 密钥(生产环境请修改 # JWT 密钥(生产环境必须设置为随机字符串
- SECRET_KEY=your-super-secret-key-change-in-production-minimum-32-characters - SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY to a random string of at least 32 characters}
# 管理员密码(生产环境必须设置为随机强密码,至少 12 位)
- ADMIN_PASSWORD=${ADMIN_PASSWORD:?Set ADMIN_PASSWORD to a strong password of at least 12 characters}
# AI 提供商配置 # AI 提供商配置
- AI_PROVIDER=gemini - AI_PROVIDER=gemini

View File

@@ -25,6 +25,8 @@ services:
container_name: qquiz_backend container_name: qquiz_backend
environment: environment:
- DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql:3306/qquiz_db - DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql:3306/qquiz_db
- SECRET_KEY=${SECRET_KEY:?Set SECRET_KEY to a random string of at least 32 characters}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:?Set ADMIN_PASSWORD to a strong password of at least 12 characters}
env_file: env_file:
- .env - .env
volumes: volumes:

View File

@@ -1,201 +0,0 @@
# AI 提供商配置指南
QQuiz 支持多个 AI 提供商,推荐使用 Google Gemini 以获得最佳的 PDF 解析体验。
## 🌟 推荐Google Gemini
### 优势
-**原生 PDF 理解**:直接处理 PDF最多1000页完整保留图片、表格、公式
-**免费额度充足**:每天免费 15 次/分钟1500 次/天
-**多模态理解**:支持图片、图表、公式的理解
-**自定义代理**:支持配置自定义 Base URL 实现 Key 轮训等功能
### 获取 API Key
1. 访问 https://aistudio.google.com/apikey
2. 使用 Google 账号登录
3. 点击 "Create API Key"
4. 复制生成的 API Key格式`AIza...`
### 配置方式
#### 方式一:环境变量配置(推荐首次部署)
编辑 `.env` 文件:
```env
AI_PROVIDER=gemini
GEMINI_API_KEY=AIza-your-actual-gemini-api-key
GEMINI_BASE_URL=https://generativelanguage.googleapis.com
GEMINI_MODEL=gemini-2.0-flash-exp
```
#### 方式二:管理员后台配置(推荐生产环境)
1. 使用管理员账号登录默认admin / admin123
2. 进入"系统设置"
3. 选择 AI 提供商:"Google Gemini (推荐)"
4. 填入 API Key
5. 点击"保存所有设置"
**优势**:无需重启服务即可生效,支持在线修改配置
### 自定义 Gemini Base URL可选
如果需要使用 Key 轮训服务或代理(例如提高速度、负载均衡),可以配置自定义 Base URL
```env
# 使用自定义代理服务
GEMINI_BASE_URL=https://your-proxy-service.com/proxy/gemini-self
```
**使用场景**
- Key 轮训(多个 API Key 负载均衡)
- 国内加速代理
- 自建中转服务
---
## 其他 AI 提供商
### OpenAI (GPT)
**限制**:⚠️ 仅支持文本解析PDF 文件会通过文本提取处理,丢失图片和格式信息
#### 获取 API Key
- 访问https://platform.openai.com/api-keys
- 创建新的 Secret Key
#### 配置
```env
AI_PROVIDER=openai
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
```
**推荐模型**
- `gpt-4o-mini`(推荐,性价比高)
- `gpt-4o`(最强,成本高)
- `gpt-3.5-turbo`(便宜,效果一般)
---
### Anthropic (Claude)
**限制**:⚠️ 仅支持文本解析PDF 文件会通过文本提取处理,丢失图片和格式信息
#### 获取 API Key
- 访问https://console.anthropic.com/settings/keys
- 创建新的 API Key
#### 配置
```env
AI_PROVIDER=anthropic
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key
ANTHROPIC_MODEL=claude-3-haiku-20240307
```
**推荐模型**
- `claude-3-haiku-20240307`(推荐,速度快)
- `claude-3-5-sonnet-20241022`(最强,成本高)
- `claude-3-opus-20240229`(超强,成本很高)
---
### Qwen (通义千问)
**限制**:⚠️ 仅支持文本解析PDF 文件会通过文本提取处理,丢失图片和格式信息
#### 获取 API Key
- 访问https://dashscope.console.aliyun.com/apiKey
- 使用阿里云账号登录
- 创建新的 API Key
#### 配置
```env
AI_PROVIDER=qwen
QWEN_API_KEY=sk-your-qwen-api-key
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
QWEN_MODEL=qwen-plus
```
**推荐模型**
- `qwen-plus`(推荐,平衡)
- `qwen-max`(最强,成本高)
- `qwen-turbo`(快速,效果一般)
- `qwen-long`(超长文本支持)
---
## AI 提供商对比表
| 提供商 | PDF 原生支持 | 图片/表格/公式 | 文本解析 | 免费额度 | 推荐度 |
|--------|--------------|----------------|----------|----------|--------|
| **Google Gemini** | ✅ 支持 | ✅ 完整保留 | ✅ | ⭐⭐⭐⭐⭐ | 最推荐 |
| OpenAI (GPT) | ❌ 不支持 | ❌ 丢失 | ✅ | 💰 | ⭐⭐⭐⭐ |
| Anthropic (Claude) | ❌ 不支持 | ❌ 丢失 | ✅ | 💰 | ⭐⭐⭐⭐ |
| Qwen (通义千问) | ❌ 不支持 | ❌ 丢失 | ✅ | ✅ | ⭐⭐⭐ |
---
## 常见问题
### Q1: 如何判断 AI 提供商是否配置正确?
上传一个包含题目的文档,如果能成功解析出题目,说明配置正确。查看后端日志可以看到详细的解析过程。
### Q2: 可以同时配置多个 AI 提供商吗?
可以。配置多个提供商的 API Key通过 `AI_PROVIDER` 环境变量或管理员后台切换使用哪个提供商。
### Q3: Gemini 原生 PDF 处理和文本提取有什么区别?
- **原生 PDF 处理**GeminiAI 直接"看"PDF能识别图片中的文字、理解表格结构、识别数学公式
- **文本提取**(其他提供商):先用 PyPDF2/python-docx 提取纯文本,然后交给 AI丢失所有图片和格式
**示例**如果试卷中有带图的选择题Gemini 能理解图片内容并提取题目,其他提供商只能提取文字部分。
### Q4: Gemini 自定义 Base URL 有什么用?
- **场景一**:使用多个 API Key 的轮训服务,避免单个 Key 频率限制
- **场景二**:使用国内加速代理,提高访问速度
- **场景三**:自建中转服务,统一管理和监控 API 调用
**格式要求**Base URL 应该是完整的域名,例如 `https://your-service.com`,后端会自动拼接 `/v1beta/models/{model}:generateContent`
### Q5: 如果文档中没有提供答案怎么办?
QQuiz 会自动使用 AI 生成参考答案,标注为"AI参考答案"。这个功能对所有 AI 提供商都支持。
---
## 推荐配置方案
### 方案一:仅使用 Gemini推荐
```env
AI_PROVIDER=gemini
GEMINI_API_KEY=your-key
```
- ✅ 最佳 PDF 支持
- ✅ 免费额度充足
- ✅ 配置简单
### 方案二Gemini + OpenAI备用
```env
AI_PROVIDER=gemini
GEMINI_API_KEY=your-gemini-key
OPENAI_API_KEY=your-openai-key
```
- 主用 Gemini当遇到限制时手动切换到 OpenAI
### 方案三:全配置(多选)
配置所有提供商,根据需求灵活切换
---
**推荐流程**
1. 首次部署:使用 Gemini免费额度充足
2. 生产环境:使用管理员后台配置,支持在线切换
3. 高频使用:配置 Gemini 自定义 Base URL 使用 Key 轮训服务

View File

@@ -1,533 +0,0 @@
# QQuiz Windows 部署指南
## 🚀 方式一Docker Desktop推荐
这是 Windows 上最简单的部署方式。
### 前置要求
1. **安装 Docker Desktop**
- 下载地址https://www.docker.com/products/docker-desktop/
- 系统要求Windows 10/11 64-bit (Pro/Enterprise/Education)
- 需要启用 WSL 2
2. **启用 WSL 2**(如果未启用)
```powershell
# 以管理员身份运行 PowerShell
wsl --install
# 重启计算机
```
### 部署步骤
#### 1. 配置环境变量
```powershell
# 在项目根目录E:\QQuiz打开 PowerShell
cd E:\QQuiz
# 复制环境变量模板
copy .env.example .env
# 使用记事本编辑 .env
notepad .env
```
**必须修改的配置:**
```env
# JWT 密钥(必须修改!至少 32 字符)
SECRET_KEY=your-very-long-secret-key-change-this-to-something-secure
# 选择一个 AI 提供商并配置 API Key
# 方案 1: 使用 OpenAI
AI_PROVIDER=openai
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-4o-mini
# 方案 2: 使用通义千问(国内推荐)
# AI_PROVIDER=qwen
# QWEN_API_KEY=sk-your-qwen-api-key
# QWEN_MODEL=qwen-plus
# 方案 3: 使用 Claude
# AI_PROVIDER=anthropic
# ANTHROPIC_API_KEY=sk-ant-your-key
# ANTHROPIC_MODEL=claude-3-haiku-20240307
```
#### 2. 启动 Docker Desktop
- 打开 Docker Desktop 应用
- 等待 Docker Engine 启动(底部状态显示 "Running"
#### 3. 启动服务
```powershell
# 在项目根目录
cd E:\QQuiz
# 启动所有服务(第一次会比较慢,需要下载镜像)
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f
```
#### 4. 访问应用
- **前端**: http://localhost:3000
- **后端 API**: http://localhost:8000
- **API 文档**: http://localhost:8000/docs
#### 5. 默认账户登录
- **用户名**: `admin`
- **密码**: `admin123`
⚠️ **首次登录后请立即修改密码!**
### 常用 Docker 命令
```powershell
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f backend
docker-compose logs -f frontend
# 停止服务
docker-compose stop
# 重启服务
docker-compose restart
# 停止并删除容器
docker-compose down
# 重新构建并启动
docker-compose up -d --build
# 进入容器内部
docker-compose exec backend bash
docker-compose exec postgres psql -U qquiz qquiz_db
```
### 问题排查
#### 问题 1: Docker Desktop 无法启动
**解决方案:**
```powershell
# 确保 WSL 2 已安装
wsl --list --verbose
# 如果版本是 1升级到 2
wsl --set-version Ubuntu 2
# 设置默认版本为 2
wsl --set-default-version 2
```
#### 问题 2: 端口被占用
```powershell
# 查看端口占用
netstat -ano | findstr :3000
netstat -ano | findstr :8000
netstat -ano | findstr :5432
# 结束占用进程(替换 PID
taskkill /F /PID <进程ID>
```
#### 问题 3: 容器启动失败
```powershell
# 查看详细错误日志
docker-compose logs backend
# 重新构建
docker-compose down
docker-compose build --no-cache
docker-compose up -d
```
---
## 🛠️ 方式二:本地源码部署
适合开发调试,需要手动安装各种依赖。
### 前置要求
1. **Python 3.11+**
- 下载https://www.python.org/downloads/
- 安装时勾选 "Add Python to PATH"
2. **Node.js 18+**
- 下载https://nodejs.org/
- 推荐安装 LTS 版本
3. **PostgreSQL 15+**
- 下载https://www.enterprisedb.com/downloads/postgres-postgresql-downloads
- 记住安装时设置的密码
### 部署步骤
#### 1. 安装 PostgreSQL
1. 下载并安装 PostgreSQL 15
2. 安装完成后,打开 **pgAdmin 4** 或 **SQL Shell (psql)**
3. 创建数据库:
```sql
-- 使用 psql 或 pgAdmin 执行
CREATE DATABASE qquiz_db;
CREATE USER qquiz WITH PASSWORD 'qquiz_password';
GRANT ALL PRIVILEGES ON DATABASE qquiz_db TO qquiz;
```
#### 2. 配置环境变量
```powershell
cd E:\QQuiz
copy .env.example .env
notepad .env
```
**修改数据库连接为本地:**
```env
# 数据库(本地部署)
DATABASE_URL=postgresql+asyncpg://qquiz:qquiz_password@localhost:5432/qquiz_db
# JWT 密钥
SECRET_KEY=your-super-secret-key-at-least-32-characters-long
# AI 配置(选择一个)
AI_PROVIDER=openai
OPENAI_API_KEY=sk-your-openai-api-key
OPENAI_MODEL=gpt-4o-mini
```
#### 3. 启动后端
**打开第一个 PowerShell 窗口:**
```powershell
cd E:\QQuiz\backend
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
.\venv\Scripts\Activate.ps1
# 如果遇到执行策略错误,运行:
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# 安装依赖
pip install -r requirements.txt
# 运行数据库迁移
alembic upgrade head
# 启动后端服务
uvicorn main:app --reload
```
**成功后会看到:**
```
INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Application startup complete.
```
#### 4. 启动前端
**打开第二个 PowerShell 窗口:**
```powershell
cd E:\QQuiz\frontend
# 安装依赖(第一次需要)
npm install
# 启动开发服务器
npm start
```
**成功后会自动打开浏览器:**
```
VITE v5.0.11 ready in 1234 ms
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
```
#### 5. 访问应用
- **前端**: http://localhost:3000
- **后端**: http://localhost:8000
- **API 文档**: http://localhost:8000/docs
### 问题排查
#### 问题 1: PowerShell 执行策略错误
```powershell
# 错误信息:无法加载文件 xxx.ps1因为在此系统上禁止运行脚本
# 解决方案:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# 然后重新激活虚拟环境
.\venv\Scripts\Activate.ps1
```
#### 问题 2: pip 安装依赖失败
```powershell
# 使用国内镜像加速
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
```
#### 问题 3: PostgreSQL 连接失败
```powershell
# 确认 PostgreSQL 服务正在运行
# 打开 "服务" (services.msc)
# 找到 "postgresql-x64-15",确保状态为 "正在运行"
# 或使用命令行
sc query postgresql-x64-15
```
#### 问题 4: npm 安装慢
```powershell
# 使用淘宝镜像
npm config set registry https://registry.npmmirror.com
# 然后重新安装
npm install
```
---
## 🧪 测试部署是否成功
### 1. 检查后端健康状态
浏览器访问http://localhost:8000/health
**预期返回:**
```json
{"status": "healthy"}
```
### 2. 查看 API 文档
访问http://localhost:8000/docs
应该能看到 Swagger UI 界面,显示所有 API 接口。
### 3. 测试登录
1. 访问 http://localhost:3000
2. 使用默认账户登录:
- 用户名:`admin`
- 密码:`admin123`
3. 成功后应该进入 Dashboard
### 4. 测试创建题库
1. 点击「题库管理」
2. 点击「创建题库」
3. 输入题库名称
4. 上传测试文档(可以创建一个简单的 TXT 文件)
**测试文档示例 (test_questions.txt):**
```txt
1. Python 是一种什么类型的语言?
A. 编译型语言
B. 解释型语言
C. 汇编语言
D. 机器语言
答案B
解析Python 是一种解释型、面向对象的高级编程语言。
2. 以下哪个不是 Python 的数据类型?
A. list
B. tuple
C. array
D. dict
答案C
解析Python 内置的数据类型包括 list、tuple、dict 等array 需要导入 array 模块。
```
5. 上传后等待解析完成(状态会从「处理中」变为「就绪」)
6. 点击「开始刷题」测试刷题功能
---
## 📝 Windows 特定配置
### 1. 设置环境变量(可选)
**通过 GUI 设置:**
1. 右键「此电脑」→「属性」
2. 「高级系统设置」→「环境变量」
3. 在「用户变量」中添加:
- `OPENAI_API_KEY`: 你的 API Key
- `SECRET_KEY`: 你的密钥
### 2. 配置防火墙(如果需要局域网访问)
```powershell
# 允许端口访问(以管理员身份运行)
netsh advfirewall firewall add rule name="QQuiz Frontend" dir=in action=allow protocol=TCP localport=3000
netsh advfirewall firewall add rule name="QQuiz Backend" dir=in action=allow protocol=TCP localport=8000
```
### 3. 创建启动脚本
**创建 `start.bat` 文件:**
```batch
@echo off
echo Starting QQuiz...
REM 检查 Docker Desktop 是否运行
docker info >nul 2>&1
if %errorlevel% neq 0 (
echo Starting Docker Desktop...
start "" "C:\Program Files\Docker\Docker\Docker Desktop.exe"
timeout /t 10
)
REM 启动服务
cd /d "%~dp0"
docker-compose up -d
echo.
echo QQuiz is starting...
echo Frontend: http://localhost:3000
echo Backend: http://localhost:8000
echo.
pause
```
**使用方法:**
- 双击 `start.bat` 即可启动服务
---
## 🎯 性能优化建议
### 1. Docker Desktop 配置
1. 打开 Docker Desktop
2. 设置 → Resources
3. 调整资源分配:
- **CPUs**: 4 核(推荐)
- **Memory**: 4 GB推荐
- **Swap**: 1 GB
- **Disk image size**: 60 GB
### 2. WSL 2 优化
**限制 WSL 2 内存占用(可选):**
创建 `%USERPROFILE%\.wslconfig` 文件:
```ini
[wsl2]
memory=4GB
processors=4
swap=1GB
```
重启 WSL
```powershell
wsl --shutdown
```
### 3. 开发工具推荐
- **代码编辑器**: VS Code安装 Python、ESLint、Prettier 插件)
- **API 测试**: Postman 或 Insomnia
- **数据库管理**: pgAdmin 4 或 DBeaver
- **终端**: Windows Terminal更好的 PowerShell 体验)
---
## 🔧 常见问题汇总
### Q: 如何完全重置项目?
```powershell
# Docker 方式
docker-compose down -v # 删除容器和数据卷
docker-compose up -d # 重新启动
# 本地方式
# 1. 删除数据库
DROP DATABASE qquiz_db;
CREATE DATABASE qquiz_db;
# 2. 重新运行迁移
cd E:\QQuiz\backend
alembic upgrade head
```
### Q: 如何查看日志?
```powershell
# Docker 方式
docker-compose logs -f backend
docker-compose logs -f frontend
# 本地方式
# 直接在运行的 PowerShell 窗口中查看
```
### Q: 如何停止服务?
```powershell
# Docker 方式
docker-compose stop
# 本地方式
# 在运行的 PowerShell 窗口中按 Ctrl+C
```
### Q: 如何更新代码后重启?
```powershell
# Docker 方式
docker-compose restart
# 本地方式uvicorn 和 vite 会自动重载)
# 无需操作,保存文件后自动刷新
```
---
## 📞 获取帮助
如果遇到问题:
1. **查看日志**: `docker-compose logs -f`
2. **检查文档**: 阅读 `DEPLOYMENT.md`
3. **查看 API 文档**: http://localhost:8000/docs
4. **GitHub Issues**: 提交问题报告
---
祝你部署顺利!🎉

View File

@@ -1,5 +0,0 @@
@echo off
REM 激活虚拟环境的批处理脚本
cd /d "%~dp0..\backend"
call venv\Scripts\activate.bat
cmd /k

View File

@@ -1,89 +0,0 @@
@echo off
title QQuiz - System Status Check
cd /d "%~dp0.."
echo.
echo ========================================
echo QQuiz System Status Check
echo ========================================
echo.
echo [1] Checking Python...
python --version
if %errorlevel% neq 0 (
echo ERROR: Python not found!
) else (
echo OK
)
echo.
echo [2] Checking Node.js...
node --version
if %errorlevel% neq 0 (
echo ERROR: Node.js not found!
) else (
echo OK
)
echo.
echo [3] Checking PostgreSQL...
psql --version
if %errorlevel% neq 0 (
echo WARNING: PostgreSQL command not found in PATH
) else (
echo OK
)
echo.
echo [4] Checking if backend is running...
curl -s http://localhost:8000/health >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Backend is NOT running on port 8000
echo.
echo Backend must be started first!
echo Run the backend window manually:
echo cd backend
echo venv\Scripts\activate.bat
echo uvicorn main:app --reload
) else (
echo OK - Backend is running
)
echo.
echo [5] Checking if frontend is running...
curl -s http://localhost:3000 >nul 2>&1
if %errorlevel% neq 0 (
echo WARNING: Frontend is NOT running on port 3000
) else (
echo OK - Frontend is running
)
echo.
echo [6] Checking ports...
echo Checking port 8000 (Backend):
netstat -ano | findstr :8000
echo.
echo Checking port 3000 (Frontend):
netstat -ano | findstr :3000
echo.
echo [7] Checking .env configuration...
if exist ".env" (
echo OK - .env file exists
findstr /C:"OPENAI_API_KEY=sk-" .env >nul
if %errorlevel% neq 0 (
echo WARNING: OPENAI_API_KEY may not be configured properly
) else (
echo OK - API Key appears to be configured
)
) else (
echo ERROR: .env file not found!
)
echo.
echo ========================================
echo Diagnosis Complete
echo ========================================
echo.
pause

View File

@@ -1,140 +0,0 @@
@echo off
title QQuiz - Fix and Start
color 0E
echo.
echo ========================================
echo QQuiz - Automatic Fix and Start
echo ========================================
echo.
cd /d "%~dp0.."
REM Check if .env exists
if not exist ".env" (
echo Creating .env file...
copy .env.example .env >nul
echo.
echo IMPORTANT: Edit .env and set your OPENAI_API_KEY
echo Opening .env file...
timeout /t 2 /nobreak >nul
notepad .env
echo.
echo Save and close .env, then press any key to continue...
pause >nul
)
echo.
echo Choose database option:
echo.
echo [1] Use Docker (Recommended - Easy)
echo [2] Use Local PostgreSQL (Advanced)
echo.
choice /C 12 /M "Select option"
if %errorlevel% equ 1 (
echo.
echo Using Docker PostgreSQL...
echo.
REM Check Docker
docker --version >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Docker not found!
echo.
echo Please install Docker Desktop:
echo https://www.docker.com/products/docker-desktop/
echo.
echo After installing Docker, run this script again.
pause
exit /b 1
)
echo Starting MySQL in Docker...
docker-compose up -d mysql
if %errorlevel% neq 0 (
echo.
echo Docker failed to start. Trying to fix...
docker-compose down
docker-compose up -d mysql
)
echo Waiting for database...
timeout /t 10 /nobreak >nul
) else (
echo.
echo Using Local MySQL...
echo.
echo Make sure MySQL is running on port 3306
echo.
echo If you see connection errors, you need to:
echo 1. Start MySQL service
echo 2. Or install MySQL from https://dev.mysql.com/downloads/installer/
echo 3. Or choose option 1 to use Docker instead
echo.
pause
)
echo.
echo ========================================
echo Starting Backend...
echo ========================================
echo.
cd backend
if not exist "venv\Scripts\activate.bat" (
echo Creating virtual environment...
python -m venv venv
)
echo Installing dependencies...
call venv\Scripts\activate.bat
pip install -q -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
echo.
echo Running database migrations...
alembic upgrade head
if %errorlevel% neq 0 (
echo.
echo WARNING: Database migration failed
echo The app will try to create tables automatically
echo.
)
cd ..
echo.
echo Starting services...
echo.
start "QQuiz Backend" cmd /k "cd /d %~dp0..backend && call venv\Scripts\activate.bat && echo Backend: http://localhost:8000 && echo Docs: http://localhost:8000/docs && echo. && uvicorn main:app --reload"
timeout /t 8 /nobreak >nul
start "QQuiz Frontend" cmd /k "cd /d %~dp0..frontend && echo Frontend: http://localhost:3000 && echo. && npm start"
echo.
echo ========================================
echo SUCCESS! QQuiz is starting...
echo ========================================
echo.
echo Frontend: http://localhost:3000
echo Backend: http://localhost:8000
echo.
echo Login: admin / admin123
echo.
echo ========================================
echo.
timeout /t 5 /nobreak >nul
start http://localhost:3000
echo.
echo System is running...
echo Close backend/frontend windows to stop
echo.
pause

View File

@@ -1,48 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 重新构建并启动后端服务
echo ========================================
echo.
cd /d "%~dp0.."
echo [1/3] 停止后端容器...
docker-compose stop backend
if %ERRORLEVEL% NEQ 0 (
echo ❌ 停止后端失败
pause
exit /b 1
)
echo ✅ 后端已停止
echo.
echo [2/3] 重新构建后端镜像(这可能需要几分钟)...
docker-compose build backend
if %ERRORLEVEL% NEQ 0 (
echo ❌ 构建失败
pause
exit /b 1
)
echo ✅ 构建完成
echo.
echo [3/3] 启动后端容器...
docker-compose up -d backend
if %ERRORLEVEL% NEQ 0 (
echo ❌ 启动失败
pause
exit /b 1
)
echo ✅ 后端已启动
echo.
echo ========================================
echo 重新构建完成!
echo ========================================
echo.
echo 现在可以查看后端日志:
echo docker-compose logs -f backend
echo.
echo 或者按任意键退出...
pause >nul

View File

@@ -1,31 +0,0 @@
@echo off
title QQuiz - Restart Backend
color 0B
cd /d "%~dp0.."
echo.
echo ========================================
echo Restarting Backend Container
echo ========================================
echo.
echo Restarting backend...
docker-compose restart backend
echo.
echo Waiting for backend to start...
timeout /t 5 /nobreak >nul
echo.
echo ========================================
echo Backend Restarted!
echo ========================================
echo.
echo Backend URL: http://localhost:8000
echo API Docs: http://localhost:8000/docs
echo.
echo View logs: scripts\view_backend_logs.bat
echo.
pause

View File

@@ -1,60 +0,0 @@
@echo off
chcp 65001 >nul
title QQuiz Backend - 本地运行
echo.
echo ==========================================
echo QQuiz Backend - 本地启动
echo ==========================================
echo.
cd /d "%~dp0..\backend"
REM 检查虚拟环境是否存在
if not exist "venv\Scripts\activate.bat" (
echo [1/5] 创建虚拟环境...
python -m venv venv
echo [完成]
echo.
) else (
echo [1/5] 虚拟环境已存在
echo.
)
echo [2/5] 激活虚拟环境...
call venv\Scripts\activate.bat
echo [完成]
echo.
echo [3/5] 安装依赖...
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
if %errorlevel% neq 0 (
echo [错误] 依赖安装失败
pause
exit /b 1
)
echo [完成]
echo.
echo [4/5] 运行数据库迁移...
alembic upgrade head
if %errorlevel% neq 0 (
echo [警告] 数据库迁移失败,请检查 PostgreSQL 是否运行
echo.
)
echo.
echo [5/5] 启动后端服务...
echo.
echo ==========================================
echo 后端服务启动中...
echo ==========================================
echo.
echo API 地址: http://localhost:8000
echo API 文档: http://localhost:8000/docs
echo.
echo 按 Ctrl+C 停止服务
echo ==========================================
echo.
uvicorn main:app --reload

View File

@@ -1,44 +0,0 @@
@echo off
chcp 65001 >nul
title QQuiz Frontend - 本地运行
echo.
echo ==========================================
echo QQuiz Frontend - 本地启动
echo ==========================================
echo.
cd /d "%~dp0..\frontend"
REM 检查 node_modules 是否存在
if not exist "node_modules" (
echo [1/2] 安装前端依赖(首次运行需要几分钟)...
echo.
echo [提示] 使用淘宝镜像加速下载...
call npm config set registry https://registry.npmmirror.com
call npm install
if %errorlevel% neq 0 (
echo [错误] 依赖安装失败
pause
exit /b 1
)
echo [完成]
echo.
) else (
echo [1/2] 依赖已安装
echo.
)
echo [2/2] 启动前端服务...
echo.
echo ==========================================
echo 前端服务启动中...
echo ==========================================
echo.
echo 前端地址: http://localhost:3000
echo.
echo 按 Ctrl+C 停止服务
echo ==========================================
echo.
call npm start

View File

@@ -1,34 +0,0 @@
@echo off
cd /d "%~dp0.."
echo Creating .env configuration file...
echo.
(
echo DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@localhost:3306/qquiz_db
echo SECRET_KEY=qquiz-secret-key-for-development-change-in-production-32chars
echo AI_PROVIDER=openai
echo OPENAI_API_KEY=sk-your-openai-api-key-here
echo OPENAI_BASE_URL=https://api.openai.com/v1
echo OPENAI_MODEL=gpt-4o-mini
echo ALLOW_REGISTRATION=true
echo MAX_UPLOAD_SIZE_MB=10
echo MAX_DAILY_UPLOADS=20
echo CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
echo UPLOAD_DIR=./uploads
) > .env
echo Done! .env file created.
echo.
echo IMPORTANT: Please edit OPENAI_API_KEY in .env file
echo.
echo Opening .env file now...
timeout /t 2 /nobreak >nul
notepad .env
echo.
echo Configuration complete!
echo Now you can run: start_app.bat
echo.
pause

View File

@@ -1,44 +0,0 @@
@echo off
title QQuiz Backend Server
color 0B
echo.
echo ========================================
echo Starting QQuiz Backend Server
echo ========================================
echo.
cd /d "%~dp0..\backend"
if not exist "venv\Scripts\activate.bat" (
echo Creating virtual environment...
python -m venv venv
)
echo Activating virtual environment...
call venv\Scripts\activate.bat
echo.
echo Installing/updating dependencies...
pip install -q -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
echo.
echo Running database migrations...
alembic upgrade head
echo.
echo ========================================
echo Backend Server Starting...
echo ========================================
echo.
echo API URL: http://localhost:8000
echo API Docs: http://localhost:8000/docs
echo Health: http://localhost:8000/health
echo.
echo Press Ctrl+C to stop
echo ========================================
echo.
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
pause

View File

@@ -1,34 +0,0 @@
@echo off
title QQuiz Frontend Server
color 0B
echo.
echo ========================================
echo Starting QQuiz Frontend Server
echo ========================================
echo.
cd /d "%~dp0..\frontend"
if not exist "node_modules" (
echo Installing dependencies (first time only)...
call npm config set registry https://registry.npmmirror.com
call npm install
)
echo.
echo ========================================
echo Frontend Server Starting...
echo ========================================
echo.
echo Frontend URL: http://localhost:3000
echo.
echo Make sure backend is running on port 8000!
echo.
echo Press Ctrl+C to stop
echo ========================================
echo.
npm start
pause

View File

@@ -1,74 +0,0 @@
@echo off
title QQuiz - Start with Docker Database
color 0B
echo.
echo ========================================
echo QQuiz - Starting with Docker DB
echo ========================================
echo.
cd /d "%~dp0.."
echo [1/4] Checking Docker...
docker --version >nul 2>&1
if %errorlevel% neq 0 (
echo ERROR: Docker not found!
echo Please install Docker Desktop from https://www.docker.com/
pause
exit /b 1
)
echo OK - Docker installed
echo.
echo [2/4] Starting MySQL in Docker...
docker-compose up -d mysql
if %errorlevel% neq 0 (
echo ERROR: Failed to start MySQL
echo Try: docker-compose down
echo Then run this script again
pause
exit /b 1
)
echo OK - MySQL started
echo Waiting for database to be ready...
echo.
timeout /t 10 /nobreak >nul
echo.
echo [3/4] Starting Backend...
start "QQuiz Backend" cmd /k "cd /d %~dp0..backend && call venv\Scripts\activate.bat && echo ======================================== && echo QQuiz Backend Server && echo ======================================== && echo. && echo API: http://localhost:8000 && echo Docs: http://localhost:8000/docs && echo. && alembic upgrade head && echo. && uvicorn main:app --reload"
echo Waiting for backend to start...
timeout /t 8 /nobreak >nul
echo.
echo [4/4] Starting Frontend...
start "QQuiz Frontend" cmd /k "cd /d %~dp0..frontend && echo ======================================== && echo QQuiz Frontend Server && echo ======================================== && echo. && echo URL: http://localhost:3000 && echo. && npm start"
echo.
echo ========================================
echo SUCCESS! QQuiz is starting...
echo ========================================
echo.
echo Frontend: http://localhost:3000
echo Backend: http://localhost:8000
echo Database: Running in Docker
echo.
echo Login:
echo Username: admin
echo Password: admin123
echo.
echo ========================================
echo.
timeout /t 5 /nobreak >nul
start http://localhost:3000
echo System running...
echo To stop: Close the backend/frontend windows
echo To stop database: docker-compose down
echo.
pause

View File

@@ -1,24 +0,0 @@
@echo off
chcp 65001 >nul
title QQuiz - 停止服务
cd /d "%~dp0.."
echo.
echo ==========================================
echo 停止 QQuiz 服务
echo ==========================================
echo.
docker-compose down
if %errorlevel% equ 0 (
echo.
echo [完成] 服务已停止
) else (
echo.
echo [错误] 停止服务失败
)
echo.
pause

View File

@@ -1,15 +0,0 @@
@echo off
title QQuiz - Backend Logs
color 0B
cd /d "%~dp0.."
echo.
echo ========================================
echo QQuiz Backend Logs (Real-time)
echo ========================================
echo.
echo Press Ctrl+C to stop viewing logs
echo.
docker-compose logs -f --tail=50 backend

View File

@@ -1,12 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 实时查看后端日志
echo ========================================
echo.
echo 按 Ctrl+C 可以停止查看日志
echo.
cd /d "%~dp0.."
docker-compose logs -f backend