diff --git a/.env.example b/.env.example index 47c2e41..250549f 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,14 @@ DATABASE_URL=sqlite+aiosqlite:///./qquiz.db # For Local: 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!) -SECRET_KEY=your-super-secret-key-change-in-production-minimum-32-characters +# JWT Secret (must be at least 32 characters; generate randomly for production) +SECRET_KEY= + +# Default admin username (must be at least 3 characters; default: admin) +ADMIN_USERNAME=admin + +# Default admin password (must be at least 12 characters; generate randomly for production) +ADMIN_PASSWORD= # AI Provider Configuration AI_PROVIDER=gemini diff --git a/Dockerfile b/Dockerfile index 673bdca..29f321e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,11 @@ FROM python:3.11-slim WORKDIR /app +# 安装操作系统依赖(python-magic 需要 libmagic) +RUN apt-get update \ + && apt-get install -y --no-install-recommends libmagic1 \ + && rm -rf /var/lib/apt/lists/* + # 复制后端依赖文件 COPY backend/requirements.txt ./ diff --git a/README.md b/README.md index 7dd3d60..76e5e56 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,13 @@ docker run -d \ 从源码构建,一个容器包含前后端和 SQLite 数据库: ```bash -# 1. 配置环境变量 +# 1. 配置环境变量(必须提供强密码和密钥) 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 # 3. 访问应用: http://localhost:8000 @@ -71,7 +73,9 @@ docker-compose -f docker-compose-single.yml up -d 前后端分离 + MySQL: ```bash -# 启动服务 +# 启动服务(建议直接在命令行生成强密钥和管理员密码) +SECRET_KEY=$(openssl rand -base64 48) \ +ADMIN_PASSWORD=$(openssl rand -base64 16) \ docker-compose up -d # 前端: http://localhost:3000 @@ -85,14 +89,6 @@ docker-compose up -d - Node.js 18+ - 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 用户:** ```bash # 1. 配置环境变量 @@ -110,23 +106,6 @@ chmod +x scripts/run_local.sh **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 自动构建设置 如果你 fork 了本项目并想启用自动构建 Docker 镜像功能: @@ -161,9 +140,9 @@ docker pull ghcr.io/handsomezhuzhu/qquiz:latest **管理员账户:** - 用户名: `admin` -- 密码: `admin123` +- 密码: 取自环境变量 `ADMIN_PASSWORD`(必须至少 12 位,建议随机生成) -⚠️ **重要**: 首次登录后请立即修改密码! +⚠️ **重要**: 在部署前就必须设置强管理员密码;如果需要轮换密码,请更新环境变量后重启服务。 ## 项目结构 @@ -186,16 +165,9 @@ QQuiz/ │ ├── package.json # Node 依赖 │ └── vite.config.js # Vite 配置 ├── scripts/ # 部署和启动脚本 -│ ├── fix_and_start.bat # Windows 快速启动(推荐) -│ ├── start_with_docker_db.bat # Docker 数据库启动 -│ ├── setup.bat # 环境配置脚本 -│ ├── run_local.sh # Linux/macOS 启动脚本 -│ └── [其他辅助脚本...] +│ └── run_local.sh # Linux/macOS 启动脚本 ├── docs/ # 文档目录 -│ ├── AI_CONFIGURATION.md # AI 提供商配置指南(重要) │ ├── MYSQL_SETUP.md # MySQL 安装配置指南 -│ ├── CHINA_MIRROR_GUIDE.md # 中国镜像加速指南 -│ ├── WINDOWS_DEPLOYMENT.md # Windows 部署详细指南 │ └── PROJECT_STRUCTURE.md # 项目架构详解 ├── test_data/ # 测试数据 │ └── sample_questions.txt # 示例题目 @@ -233,7 +205,8 @@ QQuiz/ | 变量 | 说明 | 默认值 | |------|------|--------| | `DATABASE_URL` | 数据库连接字符串 | - | -| `SECRET_KEY` | JWT 密钥 | - | +| `SECRET_KEY` | JWT 密钥(必须至少 32 位随机字符串) | - | +| `ADMIN_PASSWORD` | 默认管理员密码(必须至少 12 位,建议随机生成) | - | | `AI_PROVIDER` | AI 提供商 (gemini/openai/anthropic/qwen) | gemini | | `GEMINI_API_KEY` | Google Gemini API 密钥 | - | | `GEMINI_BASE_URL` | Gemini API 地址(可选,支持代理) | https://generativelanguage.googleapis.com | diff --git a/backend/database.py b/backend/database.py index 115baf1..9cca092 100644 --- a/backend/database.py +++ b/backend/database.py @@ -86,6 +86,15 @@ async def init_default_config(db: AsyncSession): "ai_provider": os.getenv("AI_PROVIDER", "openai"), } + # Validate admin credentials + admin_username = os.getenv("ADMIN_USERNAME", "admin") + if not admin_username or len(admin_username) < 3: + raise ValueError("ADMIN_USERNAME must be at least 3 characters long") + + 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(): result = await db.execute(select(SystemConfig).where(SystemConfig.key == key)) existing = result.scalar_one_or_none() @@ -95,19 +104,40 @@ async def init_default_config(db: AsyncSession): db.add(config) print(f"✅ Created default config: {key} = {value}") - # Create default admin user if not exists - result = await db.execute(select(User).where(User.username == "admin")) + # Create or update default admin user + result = await db.execute(select(User).where(User.username == admin_username)) admin = result.scalar_one_or_none() + default_admin_id = admin.id if admin else None + if not admin: admin_user = User( - username="admin", - hashed_password=pwd_context.hash("admin123"), # Change this password! + username=admin_username, + hashed_password=pwd_context.hash(admin_password), is_admin=True ) db.add(admin_user) - print("✅ Created default admin user (username: admin, password: admin123)") - print("⚠️ IMPORTANT: Please change the admin password immediately!") + await db.commit() + await db.refresh(admin_user) + default_admin_id = admin_user.id + print(f"✅ Created default admin user (username: {admin_username})") + else: + # Update password if it has changed (verify current password doesn't match) + if not pwd_context.verify(admin_password, admin.hashed_password): + admin.hashed_password = pwd_context.hash(admin_password) + print(f"🔄 Updated default admin password (username: {admin_username})") + 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() diff --git a/backend/main.py b/backend/main.py index c2d0051..f9e22b3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,18 +4,28 @@ QQuiz FastAPI Application - 单容器模式(前后端整合) from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from fastapi.responses import HTMLResponse, FileResponse +from fastapi.responses import HTMLResponse, FileResponse, JSONResponse from contextlib import asynccontextmanager import os from pathlib import Path 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 rate_limit import limiter # Load environment variables 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 async def lifespan(app: FastAPI): """Application lifespan events""" @@ -50,6 +60,10 @@ app = FastAPI( lifespan=lifespan ) +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler) +app.add_middleware(SlowAPIMiddleware) + # Configure CORS cors_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000").split(",") app.add_middleware( diff --git a/backend/rate_limit.py b/backend/rate_limit.py new file mode 100644 index 0000000..6e37b2f --- /dev/null +++ b/backend/rate_limit.py @@ -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"] diff --git a/backend/requirements.txt b/backend/requirements.txt index 42efdb2..36c3d64 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -12,6 +12,7 @@ python-multipart==0.0.6 passlib==1.7.4 bcrypt==4.0.1 python-jose[cryptography]==3.3.0 +python-magic==0.4.27 aiofiles==23.2.1 httpx==0.26.0 openai==1.10.0 @@ -19,3 +20,4 @@ anthropic==0.8.1 python-docx==1.1.0 PyPDF2==3.0.1 openpyxl==3.1.2 +slowapi==0.1.9 diff --git a/backend/routers/admin.py b/backend/routers/admin.py index f9b04c6..2bf2b82 100644 --- a/backend/routers/admin.py +++ b/backend/routers/admin.py @@ -24,6 +24,18 @@ router = APIRouter() 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) async def get_system_config( current_admin: User = Depends(get_current_admin_user), @@ -203,8 +215,10 @@ async def update_user( 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( status_code=status.HTTP_403_FORBIDDEN, detail="Cannot modify default admin user's admin status" @@ -240,8 +254,10 @@ async def delete_user( 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( status_code=status.HTTP_403_FORBIDDEN, detail="Cannot delete default admin user" diff --git a/backend/routers/auth.py b/backend/routers/auth.py index bd90425..d58dd72 100644 --- a/backend/routers/auth.py +++ b/backend/routers/auth.py @@ -1,18 +1,21 @@ """ Authentication Router """ -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from datetime import timedelta +import logging from database import get_db from models import User, SystemConfig from schemas import UserCreate, UserLogin, Token, UserResponse from utils import hash_password, verify_password, create_access_token +from rate_limit import limiter from services.auth_service import get_current_user router = APIRouter() +logger = logging.getLogger(__name__) @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) @@ -61,7 +64,9 @@ async def register( @router.post("/login", response_model=Token) +@limiter.limit("5/minute") async def login( + request: Request, user_data: UserLogin, db: AsyncSession = Depends(get_db) ): @@ -86,8 +91,7 @@ async def login( data={"sub": str(user.id)} # JWT 'sub' must be a string ) - print(f"✅ Login successful: user={user.username}, id={user.id}") - print(f"🔑 Generated token (first 50 chars): {access_token[:50]}...") + logger.info("Login successful", extra={"user_id": user.id}) return { "access_token": access_token, diff --git a/backend/routers/exam.py b/backend/routers/exam.py index 674cce1..f03ee0d 100644 --- a/backend/routers/exam.py +++ b/backend/routers/exam.py @@ -1,7 +1,7 @@ """ Exam Router - Handles exam creation, file upload, and deduplication """ -from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, BackgroundTasks +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, BackgroundTasks, Request from fastapi.responses import StreamingResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func, and_ @@ -10,6 +10,7 @@ from datetime import datetime, timedelta import os import aiofiles import json +import magic from database import get_db 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 utils import is_allowed_file, calculate_content_hash from dedup_utils import is_duplicate_question +from rate_limit import limiter 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): @@ -485,7 +535,9 @@ async def async_parse_and_save( @router.post("/create", response_model=ExamUploadResponse, status_code=status.HTTP_201_CREATED) +@limiter.limit("10/minute") async def create_exam_with_upload( + request: Request, background_tasks: BackgroundTasks, title: str = Form(...), file: UploadFile = File(...), @@ -498,11 +550,7 @@ async def create_exam_with_upload( """ # Validate file - 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" - ) + await validate_upload_file(file) # Read file content file_content = await file.read() @@ -538,7 +586,9 @@ async def create_exam_with_upload( @router.post("/{exam_id}/append", response_model=ExamUploadResponse) +@limiter.limit("10/minute") async def append_document_to_exam( + request: Request, exam_id: int, background_tasks: BackgroundTasks, file: UploadFile = File(...), @@ -572,11 +622,7 @@ async def append_document_to_exam( ) # Validate file - 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" - ) + await validate_upload_file(file) # Read file content file_content = await file.read() diff --git a/backend/utils.py b/backend/utils.py index 74f37c4..ea63577 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -13,7 +13,10 @@ import os pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # 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" ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7 # 7 days diff --git a/docker-compose-single.yml b/docker-compose-single.yml index d9a36ff..17a096d 100644 --- a/docker-compose-single.yml +++ b/docker-compose-single.yml @@ -11,38 +11,12 @@ services: container_name: qquiz ports: - "8000:8000" + env_file: + - .env environment: - # 数据库配置(SQLite 默认) + # 数据库配置(SQLite 默认,使用持久化卷) - DATABASE_URL=sqlite+aiosqlite:////app/data/qquiz.db - # JWT 密钥(生产环境请修改) - - SECRET_KEY=your-super-secret-key-change-in-production-minimum-32-characters - - # AI 提供商配置 - - AI_PROVIDER=gemini - - GEMINI_API_KEY=${GEMINI_API_KEY} - - GEMINI_BASE_URL=${GEMINI_BASE_URL:-https://generativelanguage.googleapis.com} - - GEMINI_MODEL=${GEMINI_MODEL:-gemini-2.0-flash-exp} - - # OpenAI 配置(可选) - - OPENAI_API_KEY=${OPENAI_API_KEY:-} - - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1} - - OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o-mini} - - # Anthropic 配置(可选) - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - - ANTHROPIC_MODEL=${ANTHROPIC_MODEL:-claude-3-haiku-20240307} - - # Qwen 配置(可选) - - QWEN_API_KEY=${QWEN_API_KEY:-} - - QWEN_BASE_URL=${QWEN_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1} - - QWEN_MODEL=${QWEN_MODEL:-qwen-plus} - - # 系统配置 - - ALLOW_REGISTRATION=true - - MAX_UPLOAD_SIZE_MB=10 - - MAX_DAILY_UPLOADS=20 - volumes: # 持久化数据卷 - qquiz_data:/app/data # 数据库文件 diff --git a/docker-compose.yml b/docker-compose.yml index 4422b0f..535a97e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,8 @@ services: container_name: qquiz_backend environment: - 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 volumes: diff --git a/docs/AI_CONFIGURATION.md b/docs/AI_CONFIGURATION.md deleted file mode 100644 index c3deb06..0000000 --- a/docs/AI_CONFIGURATION.md +++ /dev/null @@ -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 处理**(Gemini):AI 直接"看"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 轮训服务 diff --git a/docs/WINDOWS_DEPLOYMENT.md b/docs/WINDOWS_DEPLOYMENT.md deleted file mode 100644 index 2a7081a..0000000 --- a/docs/WINDOWS_DEPLOYMENT.md +++ /dev/null @@ -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**: 提交问题报告 - ---- - -祝你部署顺利!🎉 diff --git a/scripts/activate_venv.bat b/scripts/activate_venv.bat deleted file mode 100644 index 0d23fd4..0000000 --- a/scripts/activate_venv.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -REM 激活虚拟环境的批处理脚本 -cd /d "%~dp0..\backend" -call venv\Scripts\activate.bat -cmd /k diff --git a/scripts/check_status.bat b/scripts/check_status.bat deleted file mode 100644 index ed1f6c8..0000000 --- a/scripts/check_status.bat +++ /dev/null @@ -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 diff --git a/scripts/fix_and_start.bat b/scripts/fix_and_start.bat deleted file mode 100644 index 35dd7d3..0000000 --- a/scripts/fix_and_start.bat +++ /dev/null @@ -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 diff --git a/scripts/rebuild_backend.bat b/scripts/rebuild_backend.bat deleted file mode 100644 index 02e1b04..0000000 --- a/scripts/rebuild_backend.bat +++ /dev/null @@ -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 diff --git a/scripts/restart_backend.bat b/scripts/restart_backend.bat deleted file mode 100644 index 70d9728..0000000 --- a/scripts/restart_backend.bat +++ /dev/null @@ -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 diff --git a/scripts/run_backend_local.bat b/scripts/run_backend_local.bat deleted file mode 100644 index 22c604a..0000000 --- a/scripts/run_backend_local.bat +++ /dev/null @@ -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 diff --git a/scripts/run_frontend_local.bat b/scripts/run_frontend_local.bat deleted file mode 100644 index 50cbb50..0000000 --- a/scripts/run_frontend_local.bat +++ /dev/null @@ -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 diff --git a/scripts/setup.bat b/scripts/setup.bat deleted file mode 100644 index fa537ac..0000000 --- a/scripts/setup.bat +++ /dev/null @@ -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 diff --git a/scripts/start_backend_only.bat b/scripts/start_backend_only.bat deleted file mode 100644 index 37a2ad0..0000000 --- a/scripts/start_backend_only.bat +++ /dev/null @@ -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 diff --git a/scripts/start_frontend_only.bat b/scripts/start_frontend_only.bat deleted file mode 100644 index 0b99f39..0000000 --- a/scripts/start_frontend_only.bat +++ /dev/null @@ -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 diff --git a/scripts/start_with_docker_db.bat b/scripts/start_with_docker_db.bat deleted file mode 100644 index 47d2a94..0000000 --- a/scripts/start_with_docker_db.bat +++ /dev/null @@ -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 diff --git a/scripts/stop_windows.bat b/scripts/stop_windows.bat deleted file mode 100644 index 7358a2e..0000000 --- a/scripts/stop_windows.bat +++ /dev/null @@ -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 diff --git a/scripts/view_backend_logs.bat b/scripts/view_backend_logs.bat deleted file mode 100644 index 72c5979..0000000 --- a/scripts/view_backend_logs.bat +++ /dev/null @@ -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 diff --git a/scripts/view_backend_logs_follow.bat b/scripts/view_backend_logs_follow.bat deleted file mode 100644 index 3268b3b..0000000 --- a/scripts/view_backend_logs_follow.bat +++ /dev/null @@ -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