mirror of
https://github.com/handsomezhuzhu/QQuiz.git
synced 2026-02-20 12:00:14 +00:00
feat: 实现数据库驱动的API配置管理和项目结构重组
## 新功能 - 实现管理后台API配置管理(OpenAI/Anthropic/Qwen) - API配置保存到数据库,实时生效无需重启 - API密钥遮罩显示(前10位+后4位) - 完整endpoint URL自动显示 ## 后端改进 - 新增 config_service.py 用于加载数据库配置 - LLMService 支持动态配置注入,回退到环境变量 - 更新 exam.py 和 question.py 使用数据库配置 - 扩展 schemas.py 支持所有API配置字段 ## 前端改进 - 重写 AdminSettings.jsx 增强UI体验 - API密钥显示/隐藏切换 - 当前使用的提供商可视化标识 - 移除"需要重启"的误导性提示 ## 项目结构重组 - 移动所有脚本到 scripts/ 目录 - 移动所有文档到 docs/ 目录 - 清理 Python 缓存文件 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Database Configuration
|
||||
# For Docker: postgresql+asyncpg://qquiz:qquiz_password@postgres:5432/qquiz_db
|
||||
# For Local: postgresql+asyncpg://localhost:5432/qquiz_db
|
||||
DATABASE_URL=postgresql+asyncpg://localhost:5432/qquiz_db
|
||||
# For Docker: mysql+aiomysql://qquiz:qquiz_password@mysql: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
|
||||
|
||||
# JWT Secret (Please change this in production!)
|
||||
SECRET_KEY=your-super-secret-key-change-in-production-minimum-32-characters
|
||||
|
||||
70
README.md
70
README.md
@@ -39,22 +39,33 @@ docker-compose up -d
|
||||
#### 前置要求
|
||||
- Python 3.11+
|
||||
- Node.js 18+
|
||||
- PostgreSQL 15+
|
||||
- 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. 配置环境变量
|
||||
cp .env.example .env
|
||||
# 编辑 .env,修改 DATABASE_URL 为本地数据库地址
|
||||
|
||||
# 2. 启动 PostgreSQL
|
||||
# macOS: brew services start postgresql
|
||||
# Linux: sudo systemctl start postgresql
|
||||
# 2. 启动 MySQL
|
||||
# macOS: brew services start mysql
|
||||
# Linux: sudo systemctl start mysql
|
||||
|
||||
# 3. 运行启动脚本
|
||||
chmod +x run_local.sh
|
||||
./run_local.sh
|
||||
chmod +x scripts/run_local.sh
|
||||
./scripts/run_local.sh
|
||||
```
|
||||
|
||||
**MySQL 安装指南:** 详见 [docs/MYSQL_SETUP.md](docs/MYSQL_SETUP.md)
|
||||
|
||||
## 默认账户
|
||||
|
||||
**管理员账户:**
|
||||
@@ -69,23 +80,35 @@ chmod +x run_local.sh
|
||||
QQuiz/
|
||||
├── backend/ # FastAPI 后端
|
||||
│ ├── alembic/ # 数据库迁移
|
||||
│ ├── routers/ # API 路由 (Step 2)
|
||||
│ ├── services/ # 业务逻辑 (Step 2)
|
||||
│ ├── models.py # 数据模型 ✅
|
||||
│ ├── database.py # 数据库配置 ✅
|
||||
│ ├── main.py # 应用入口 ✅
|
||||
│ └── requirements.txt # Python 依赖 ✅
|
||||
│ ├── routers/ # API 路由
|
||||
│ ├── services/ # 业务逻辑
|
||||
│ ├── models.py # 数据模型
|
||||
│ ├── database.py # 数据库配置
|
||||
│ ├── main.py # 应用入口
|
||||
│ └── requirements.txt # Python 依赖
|
||||
├── frontend/ # React 前端
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API 客户端 (Step 3)
|
||||
│ │ ├── pages/ # 页面组件 (Step 4)
|
||||
│ │ ├── components/ # 通用组件 (Step 4)
|
||||
│ │ └── App.jsx # 应用入口 ✅
|
||||
│ ├── package.json # Node 依赖 ✅
|
||||
│ └── vite.config.js # Vite 配置 ✅
|
||||
├── docker-compose.yml # Docker 编排 ✅
|
||||
├── .env.example # 环境变量模板 ✅
|
||||
└── run_local.sh # 本地运行脚本 ✅
|
||||
│ │ ├── api/ # API 客户端
|
||||
│ │ ├── pages/ # 页面组件
|
||||
│ │ ├── components/ # 通用组件
|
||||
│ │ └── App.jsx # 应用入口
|
||||
│ ├── 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 启动脚本
|
||||
├── docs/ # 文档目录
|
||||
│ ├── QUICK_START.md # 快速入门指南
|
||||
│ ├── WINDOWS_DEPLOYMENT.md # Windows 部署指南
|
||||
│ ├── DOCKER_MIRROR_SETUP.md # Docker 镜像加速配置
|
||||
│ └── PROJECT_STRUCTURE.md # 项目架构详解
|
||||
├── test_data/ # 测试数据
|
||||
│ └── sample_questions.txt # 示例题目
|
||||
├── docker-compose.yml # Docker 编排
|
||||
├── .env.example # 环境变量模板
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 核心业务流程
|
||||
@@ -128,9 +151,10 @@ QQuiz/
|
||||
|
||||
**后端:**
|
||||
- FastAPI - 现代化 Python Web 框架
|
||||
- SQLAlchemy - ORM
|
||||
- SQLAlchemy 2.0 - 异步 ORM
|
||||
- Alembic - 数据库迁移
|
||||
- PostgreSQL - 数据库
|
||||
- MySQL 8.0 - 数据库
|
||||
- aiomysql - MySQL 异步驱动
|
||||
- Pydantic - 数据验证
|
||||
|
||||
**前端:**
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
fastapi==0.109.0
|
||||
uvicorn[standard]==0.27.0
|
||||
sqlalchemy==2.0.25
|
||||
asyncpg==0.29.0
|
||||
aiomysql==0.2.0
|
||||
pymysql==1.1.0
|
||||
alembic==1.13.1
|
||||
pydantic==2.5.3
|
||||
pydantic-settings==2.1.0
|
||||
python-dotenv==1.0.0
|
||||
python-multipart==0.0.6
|
||||
passlib[bcrypt]==1.7.4
|
||||
passlib==1.7.4
|
||||
bcrypt==4.0.1
|
||||
python-jose[cryptography]==3.3.0
|
||||
aiofiles==23.2.1
|
||||
httpx==0.26.0
|
||||
|
||||
@@ -24,11 +24,26 @@ async def get_system_config(
|
||||
result = await db.execute(select(SystemConfig))
|
||||
configs = {config.key: config.value for config in result.scalars().all()}
|
||||
|
||||
# Mask API keys (show only first 10 and last 4 characters)
|
||||
def mask_api_key(key):
|
||||
if not key or len(key) < 20:
|
||||
return key
|
||||
return f"{key[:10]}...{key[-4:]}"
|
||||
|
||||
return {
|
||||
"allow_registration": configs.get("allow_registration", "true").lower() == "true",
|
||||
"max_upload_size_mb": int(configs.get("max_upload_size_mb", "10")),
|
||||
"max_daily_uploads": int(configs.get("max_daily_uploads", "20")),
|
||||
"ai_provider": configs.get("ai_provider", "openai")
|
||||
"ai_provider": configs.get("ai_provider", "openai"),
|
||||
# API Configuration
|
||||
"openai_api_key": mask_api_key(configs.get("openai_api_key")),
|
||||
"openai_base_url": configs.get("openai_base_url", "https://api.openai.com/v1"),
|
||||
"openai_model": configs.get("openai_model", "gpt-4o-mini"),
|
||||
"anthropic_api_key": mask_api_key(configs.get("anthropic_api_key")),
|
||||
"anthropic_model": configs.get("anthropic_model", "claude-3-haiku-20240307"),
|
||||
"qwen_api_key": mask_api_key(configs.get("qwen_api_key")),
|
||||
"qwen_base_url": configs.get("qwen_base_url", "https://dashscope.aliyuncs.com/compatible-mode/v1"),
|
||||
"qwen_model": configs.get("qwen_model", "qwen-plus")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -83,9 +83,12 @@ async def login(
|
||||
|
||||
# Create access token
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.id}
|
||||
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]}...")
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer"
|
||||
|
||||
@@ -17,7 +17,8 @@ from schemas import (
|
||||
)
|
||||
from services.auth_service import get_current_user
|
||||
from services.document_parser import document_parser
|
||||
from services.llm_service import llm_service
|
||||
from services.llm_service import LLMService
|
||||
from services.config_service import load_llm_config
|
||||
from utils import is_allowed_file, calculate_content_hash
|
||||
|
||||
router = APIRouter()
|
||||
@@ -151,6 +152,10 @@ async def async_parse_and_save(
|
||||
if not text_content or len(text_content.strip()) < 10:
|
||||
raise Exception("Document appears to be empty or too short")
|
||||
|
||||
# Load LLM configuration from database
|
||||
llm_config = await load_llm_config(db)
|
||||
llm_service = LLMService(config=llm_config)
|
||||
|
||||
# Parse questions using LLM
|
||||
print(f"[Exam {exam_id}] Calling LLM to extract questions...")
|
||||
questions_data = await llm_service.parse_document(text_content)
|
||||
|
||||
@@ -13,7 +13,8 @@ from schemas import (
|
||||
AnswerSubmit, AnswerCheckResponse
|
||||
)
|
||||
from services.auth_service import get_current_user
|
||||
from services.llm_service import llm_service
|
||||
from services.llm_service import LLMService
|
||||
from services.config_service import load_llm_config
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -177,6 +178,10 @@ async def check_answer(
|
||||
|
||||
# Check answer based on question type
|
||||
if question.type == QuestionType.SHORT:
|
||||
# Load LLM configuration from database
|
||||
llm_config = await load_llm_config(db)
|
||||
llm_service = LLMService(config=llm_config)
|
||||
|
||||
# Use AI to grade short answer
|
||||
grading = await llm_service.grade_short_answer(
|
||||
question.content,
|
||||
|
||||
@@ -45,6 +45,15 @@ class SystemConfigUpdate(BaseModel):
|
||||
max_upload_size_mb: Optional[int] = None
|
||||
max_daily_uploads: Optional[int] = None
|
||||
ai_provider: Optional[str] = None
|
||||
# API Configuration
|
||||
openai_api_key: Optional[str] = None
|
||||
openai_base_url: Optional[str] = None
|
||||
openai_model: Optional[str] = None
|
||||
anthropic_api_key: Optional[str] = None
|
||||
anthropic_model: Optional[str] = None
|
||||
qwen_api_key: Optional[str] = None
|
||||
qwen_base_url: Optional[str] = None
|
||||
qwen_model: Optional[str] = None
|
||||
|
||||
|
||||
class SystemConfigResponse(BaseModel):
|
||||
@@ -52,6 +61,15 @@ class SystemConfigResponse(BaseModel):
|
||||
max_upload_size_mb: int
|
||||
max_daily_uploads: int
|
||||
ai_provider: str
|
||||
# API Configuration
|
||||
openai_api_key: Optional[str] = None
|
||||
openai_base_url: Optional[str] = None
|
||||
openai_model: Optional[str] = None
|
||||
anthropic_api_key: Optional[str] = None
|
||||
anthropic_model: Optional[str] = None
|
||||
qwen_api_key: Optional[str] = None
|
||||
qwen_base_url: Optional[str] = None
|
||||
qwen_model: Optional[str] = None
|
||||
|
||||
|
||||
# ============ Exam Schemas ============
|
||||
|
||||
@@ -28,13 +28,24 @@ async def get_current_user(
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
print(f"🔍 Received token (first 50 chars): {token[:50] if token else 'None'}...")
|
||||
|
||||
# Decode token
|
||||
payload = decode_access_token(token)
|
||||
if payload is None:
|
||||
print(f"❌ Token decode failed - Invalid or expired token")
|
||||
raise credentials_exception
|
||||
|
||||
user_id: int = payload.get("sub")
|
||||
user_id = payload.get("sub")
|
||||
if user_id is None:
|
||||
print(f"❌ No 'sub' in payload: {payload}")
|
||||
raise credentials_exception
|
||||
|
||||
# Convert user_id to int if it's a string
|
||||
try:
|
||||
user_id = int(user_id)
|
||||
except (ValueError, TypeError):
|
||||
print(f"❌ Invalid user_id format: {user_id}")
|
||||
raise credentials_exception
|
||||
|
||||
# Get user from database
|
||||
@@ -42,8 +53,10 @@ async def get_current_user(
|
||||
user = result.scalar_one_or_none()
|
||||
|
||||
if user is None:
|
||||
print(f"❌ User not found with id: {user_id}")
|
||||
raise credentials_exception
|
||||
|
||||
print(f"✅ User authenticated: {user.username} (id={user.id})")
|
||||
return user
|
||||
|
||||
|
||||
|
||||
43
backend/services/config_service.py
Normal file
43
backend/services/config_service.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
Configuration Service - Load system configuration from database
|
||||
"""
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import Dict
|
||||
from models import SystemConfig
|
||||
|
||||
|
||||
async def load_llm_config(db: AsyncSession) -> Dict[str, str]:
|
||||
"""
|
||||
Load LLM configuration from database.
|
||||
|
||||
Returns a dictionary with all LLM-related configuration:
|
||||
{
|
||||
'ai_provider': 'openai',
|
||||
'openai_api_key': 'sk-...',
|
||||
'openai_base_url': 'https://api.openai.com/v1',
|
||||
'openai_model': 'gpt-4o-mini',
|
||||
...
|
||||
}
|
||||
"""
|
||||
# Fetch all config from database
|
||||
result = await db.execute(select(SystemConfig))
|
||||
db_configs = {config.key: config.value for config in result.scalars().all()}
|
||||
|
||||
# Build configuration dictionary
|
||||
config = {
|
||||
'ai_provider': db_configs.get('ai_provider', 'openai'),
|
||||
# OpenAI
|
||||
'openai_api_key': db_configs.get('openai_api_key'),
|
||||
'openai_base_url': db_configs.get('openai_base_url', 'https://api.openai.com/v1'),
|
||||
'openai_model': db_configs.get('openai_model', 'gpt-4o-mini'),
|
||||
# Anthropic
|
||||
'anthropic_api_key': db_configs.get('anthropic_api_key'),
|
||||
'anthropic_model': db_configs.get('anthropic_model', 'claude-3-haiku-20240307'),
|
||||
# Qwen
|
||||
'qwen_api_key': db_configs.get('qwen_api_key'),
|
||||
'qwen_base_url': db_configs.get('qwen_base_url', 'https://dashscope.aliyuncs.com/compatible-mode/v1'),
|
||||
'qwen_model': db_configs.get('qwen_model', 'qwen-plus')
|
||||
}
|
||||
|
||||
return config
|
||||
@@ -15,28 +15,53 @@ from utils import calculate_content_hash
|
||||
class LLMService:
|
||||
"""Service for interacting with various LLM providers"""
|
||||
|
||||
def __init__(self):
|
||||
self.provider = os.getenv("AI_PROVIDER", "openai")
|
||||
def __init__(self, config: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
Initialize LLM Service with optional configuration.
|
||||
If config is not provided, falls back to environment variables.
|
||||
|
||||
Args:
|
||||
config: Dictionary with keys like 'ai_provider', 'openai_api_key', etc.
|
||||
"""
|
||||
# Get provider from config or environment
|
||||
self.provider = (config or {}).get("ai_provider") or os.getenv("AI_PROVIDER", "openai")
|
||||
|
||||
if self.provider == "openai":
|
||||
api_key = (config or {}).get("openai_api_key") or os.getenv("OPENAI_API_KEY")
|
||||
base_url = (config or {}).get("openai_base_url") or os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
self.model = (config or {}).get("openai_model") or os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("OpenAI API key not configured")
|
||||
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
|
||||
api_key=api_key,
|
||||
base_url=base_url
|
||||
)
|
||||
self.model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
|
||||
|
||||
elif self.provider == "anthropic":
|
||||
api_key = (config or {}).get("anthropic_api_key") or os.getenv("ANTHROPIC_API_KEY")
|
||||
self.model = (config or {}).get("anthropic_model") or os.getenv("ANTHROPIC_MODEL", "claude-3-haiku-20240307")
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("Anthropic API key not configured")
|
||||
|
||||
self.client = AsyncAnthropic(
|
||||
api_key=os.getenv("ANTHROPIC_API_KEY")
|
||||
api_key=api_key
|
||||
)
|
||||
self.model = os.getenv("ANTHROPIC_MODEL", "claude-3-haiku-20240307")
|
||||
|
||||
elif self.provider == "qwen":
|
||||
api_key = (config or {}).get("qwen_api_key") or os.getenv("QWEN_API_KEY")
|
||||
base_url = (config or {}).get("qwen_base_url") or os.getenv("QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
self.model = (config or {}).get("qwen_model") or os.getenv("QWEN_MODEL", "qwen-plus")
|
||||
|
||||
if not api_key:
|
||||
raise ValueError("Qwen API key not configured")
|
||||
|
||||
self.client = AsyncOpenAI(
|
||||
api_key=os.getenv("QWEN_API_KEY"),
|
||||
base_url=os.getenv("QWEN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
api_key=api_key,
|
||||
base_url=base_url
|
||||
)
|
||||
self.model = os.getenv("QWEN_MODEL", "qwen-plus")
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported AI provider: {self.provider}")
|
||||
|
||||
@@ -37,6 +37,8 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
print(f"🔑 Creating token with SECRET_KEY (first 20 chars): {SECRET_KEY[:20]}...")
|
||||
print(f"📦 Token payload: {to_encode}")
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@@ -44,9 +46,12 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -
|
||||
def decode_access_token(token: str) -> Optional[dict]:
|
||||
"""Decode a JWT access token"""
|
||||
try:
|
||||
print(f"🔑 SECRET_KEY (first 20 chars): {SECRET_KEY[:20]}...")
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
print(f"✅ Token decoded successfully: {payload}")
|
||||
return payload
|
||||
except JWTError:
|
||||
except JWTError as e:
|
||||
print(f"❌ JWT decode error: {type(e).__name__}: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: qquiz_postgres
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: qquiz_mysql
|
||||
environment:
|
||||
POSTGRES_USER: qquiz
|
||||
POSTGRES_PASSWORD: qquiz_password
|
||||
POSTGRES_DB: qquiz_db
|
||||
MYSQL_ROOT_PASSWORD: root_password
|
||||
MYSQL_DATABASE: qquiz_db
|
||||
MYSQL_USER: qquiz
|
||||
MYSQL_PASSWORD: qquiz_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- mysql_data:/var/lib/mysql
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- "3306:3306"
|
||||
command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U qquiz"]
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "qquiz", "-pqquiz_password"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -22,7 +24,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
container_name: qquiz_backend
|
||||
environment:
|
||||
- DATABASE_URL=postgresql+asyncpg://qquiz:qquiz_password@postgres:5432/qquiz_db
|
||||
- DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql:3306/qquiz_db
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
@@ -31,7 +33,7 @@ services:
|
||||
ports:
|
||||
- "8000:8000"
|
||||
depends_on:
|
||||
postgres:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||
|
||||
@@ -46,11 +48,11 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- REACT_APP_API_URL=http://localhost:8000
|
||||
- VITE_API_URL=http://localhost:8000
|
||||
depends_on:
|
||||
- backend
|
||||
command: npm start
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
mysql_data:
|
||||
upload_files:
|
||||
|
||||
262
docs/MYSQL_SETUP.md
Normal file
262
docs/MYSQL_SETUP.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# MySQL 安装与配置指南
|
||||
|
||||
QQuiz 使用 MySQL 8.0 作为数据库,你可以选择 Docker 部署或本地安装。
|
||||
|
||||
## 方式一:使用 Docker (推荐)
|
||||
|
||||
### 优点
|
||||
- 无需手动安装 MySQL
|
||||
- 自动配置和初始化
|
||||
- 隔离环境,不影响系统
|
||||
|
||||
### 使用步骤
|
||||
|
||||
1. **安装 Docker Desktop**
|
||||
- 下载地址:https://www.docker.com/products/docker-desktop/
|
||||
- 安装后启动 Docker Desktop
|
||||
|
||||
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
|
||||
|
||||
### 验证安装
|
||||
|
||||
打开命令提示符,运行:
|
||||
|
||||
```bash
|
||||
mysql --version
|
||||
```
|
||||
|
||||
应该显示:`mysql Ver 8.0.x for Win64 on x86_64`
|
||||
|
||||
### 配置 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
|
||||
```
|
||||
|
||||
**Docker MySQL:**
|
||||
```env
|
||||
DATABASE_URL=mysql+aiomysql://qquiz:qquiz_password@mysql:3306/qquiz_db
|
||||
```
|
||||
|
||||
### 连接参数说明
|
||||
|
||||
- `mysql+aiomysql://` - 使用 aiomysql 异步驱动
|
||||
- `qquiz` - 数据库用户名
|
||||
- `qquiz_password` - 数据库密码
|
||||
- `localhost` 或 `mysql` - 数据库主机地址
|
||||
- `3306` - MySQL 默认端口
|
||||
- `qquiz_db` - 数据库名称
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 端口 3306 被占用
|
||||
|
||||
**错误信息:**
|
||||
```
|
||||
Error: Port 3306 is already in use
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
- 检查是否已经有 MySQL 运行:`netstat -ano | findstr :3306`
|
||||
- 停止现有的 MySQL 服务
|
||||
- 或修改 `.env` 中的端口号
|
||||
|
||||
### 2. 无法连接到 MySQL
|
||||
|
||||
**错误信息:**
|
||||
```
|
||||
Can't connect to MySQL server on 'localhost'
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
|
||||
1. **检查 MySQL 服务是否运行**
|
||||
- 按 Win+R,输入 `services.msc`
|
||||
- 查找 "MySQL80" 服务
|
||||
- 确认状态为 "正在运行"
|
||||
|
||||
2. **启动 MySQL 服务**
|
||||
```bash
|
||||
net start MySQL80
|
||||
```
|
||||
|
||||
3. **检查防火墙设置**
|
||||
- 确保防火墙允许 MySQL 端口 3306
|
||||
|
||||
### 3. 密码验证失败
|
||||
|
||||
**错误信息:**
|
||||
```
|
||||
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';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
### 4. 字符集问题
|
||||
|
||||
**解决方案:**
|
||||
|
||||
确保数据库使用 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
|
||||
@@ -1,21 +1,38 @@
|
||||
/**
|
||||
* Admin Settings Page
|
||||
* Admin Settings Page - Enhanced with API Configuration
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { adminAPI } from '../api/client'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { Settings, Save, Loader } from 'lucide-react'
|
||||
import { Settings, Save, Loader, Key, Link as LinkIcon, Eye, EyeOff } from 'lucide-react'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export const AdminSettings = () => {
|
||||
const { user } = useAuth()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
const [showApiKeys, setShowApiKeys] = useState({
|
||||
openai: false,
|
||||
anthropic: false,
|
||||
qwen: false
|
||||
})
|
||||
|
||||
const [config, setConfig] = useState({
|
||||
allow_registration: true,
|
||||
max_upload_size_mb: 10,
|
||||
max_daily_uploads: 20,
|
||||
ai_provider: 'openai'
|
||||
ai_provider: 'openai',
|
||||
// OpenAI
|
||||
openai_api_key: '',
|
||||
openai_base_url: 'https://api.openai.com/v1',
|
||||
openai_model: 'gpt-4o-mini',
|
||||
// Anthropic
|
||||
anthropic_api_key: '',
|
||||
anthropic_model: 'claude-3-haiku-20240307',
|
||||
// Qwen
|
||||
qwen_api_key: '',
|
||||
qwen_base_url: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
qwen_model: 'qwen-plus'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
@@ -54,6 +71,36 @@ export const AdminSettings = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const toggleApiKeyVisibility = (provider) => {
|
||||
setShowApiKeys({
|
||||
...showApiKeys,
|
||||
[provider]: !showApiKeys[provider]
|
||||
})
|
||||
}
|
||||
|
||||
// Get complete API endpoint URL
|
||||
const getCompleteEndpoint = (provider) => {
|
||||
const endpoints = {
|
||||
openai: '/chat/completions',
|
||||
anthropic: '/messages',
|
||||
qwen: '/chat/completions'
|
||||
}
|
||||
|
||||
let baseUrl = ''
|
||||
if (provider === 'openai') {
|
||||
baseUrl = config.openai_base_url || 'https://api.openai.com/v1'
|
||||
} else if (provider === 'anthropic') {
|
||||
baseUrl = 'https://api.anthropic.com/v1'
|
||||
} else if (provider === 'qwen') {
|
||||
baseUrl = config.qwen_base_url || 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
||||
}
|
||||
|
||||
// Remove trailing slash
|
||||
baseUrl = baseUrl.replace(/\/$/, '')
|
||||
|
||||
return `${baseUrl}${endpoints[provider]}`
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||
@@ -66,7 +113,7 @@ export const AdminSettings = () => {
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
{/* Header */}
|
||||
<div className="bg-white shadow">
|
||||
<div className="max-w-4xl mx-auto px-4 py-6">
|
||||
<div className="max-w-5xl mx-auto px-4 py-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<Settings className="h-8 w-8 text-primary-600" />
|
||||
<div>
|
||||
@@ -78,8 +125,11 @@ export const AdminSettings = () => {
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||||
<div className="max-w-5xl mx-auto px-4 py-8 space-y-6">
|
||||
{/* Basic Settings */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6 space-y-6">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4">基础设置</h2>
|
||||
|
||||
{/* Allow Registration */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
@@ -144,31 +194,244 @@ export const AdminSettings = () => {
|
||||
<option value="qwen">Qwen (通义千问)</option>
|
||||
</select>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
需在 .env 文件中配置对应的 API Key
|
||||
选择后在下方配置对应的 API 密钥
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* OpenAI Configuration */}
|
||||
<div className={`bg-white rounded-xl shadow-md p-6 space-y-4 ${config.ai_provider === 'openai' ? 'ring-2 ring-primary-500' : ''}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5 text-green-600" />
|
||||
<h2 className="text-xl font-bold text-gray-900">OpenAI 配置</h2>
|
||||
{config.ai_provider === 'openai' && (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-primary-100 text-primary-700 rounded-full">当前使用</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* API Key */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
API Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showApiKeys.openai ? 'text' : 'password'}
|
||||
value={config.openai_api_key || ''}
|
||||
onChange={(e) => handleChange('openai_api_key', e.target.value)}
|
||||
placeholder="sk-proj-..."
|
||||
className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleApiKeyVisibility('openai')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showApiKeys.openai ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1">从 https://platform.openai.com/api-keys 获取</p>
|
||||
</div>
|
||||
|
||||
{/* Base URL */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
Base URL
|
||||
</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
value={config.openai_base_url}
|
||||
onChange={(e) => handleChange('openai_base_url', e.target.value)}
|
||||
placeholder="https://api.openai.com/v1"
|
||||
className="w-full pl-10 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
完整 endpoint: <code className="bg-gray-100 px-2 py-0.5 rounded">{getCompleteEndpoint('openai')}</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="pt-4">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="w-full bg-primary-600 text-white py-3 rounded-lg font-medium hover:bg-primary-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
{/* Model */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
模型
|
||||
</label>
|
||||
<select
|
||||
value={config.openai_model}
|
||||
onChange={(e) => handleChange('openai_model', e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader className="h-5 w-5 animate-spin" />
|
||||
保存中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-5 w-5" />
|
||||
保存设置
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<option value="gpt-4o">gpt-4o (最强)</option>
|
||||
<option value="gpt-4o-mini">gpt-4o-mini (推荐)</option>
|
||||
<option value="gpt-4-turbo">gpt-4-turbo</option>
|
||||
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Anthropic Configuration */}
|
||||
<div className={`bg-white rounded-xl shadow-md p-6 space-y-4 ${config.ai_provider === 'anthropic' ? 'ring-2 ring-primary-500' : ''}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5 text-orange-600" />
|
||||
<h2 className="text-xl font-bold text-gray-900">Anthropic 配置</h2>
|
||||
{config.ai_provider === 'anthropic' && (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-primary-100 text-primary-700 rounded-full">当前使用</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* API Key */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
API Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showApiKeys.anthropic ? 'text' : 'password'}
|
||||
value={config.anthropic_api_key || ''}
|
||||
onChange={(e) => handleChange('anthropic_api_key', e.target.value)}
|
||||
placeholder="sk-ant-..."
|
||||
className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleApiKeyVisibility('anthropic')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showApiKeys.anthropic ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1">从 https://console.anthropic.com/settings/keys 获取</p>
|
||||
</div>
|
||||
|
||||
{/* Base URL (fixed for Anthropic) */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
Base URL (固定)
|
||||
</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
value="https://api.anthropic.com/v1"
|
||||
disabled
|
||||
className="w-full pl-10 px-4 py-2 border border-gray-300 rounded-lg bg-gray-50 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
完整 endpoint: <code className="bg-gray-100 px-2 py-0.5 rounded">{getCompleteEndpoint('anthropic')}</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Model */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
模型
|
||||
</label>
|
||||
<select
|
||||
value={config.anthropic_model}
|
||||
onChange={(e) => handleChange('anthropic_model', e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
<option value="claude-3-5-sonnet-20241022">claude-3-5-sonnet (最强)</option>
|
||||
<option value="claude-3-haiku-20240307">claude-3-haiku (推荐)</option>
|
||||
<option value="claude-3-opus-20240229">claude-3-opus</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Qwen Configuration */}
|
||||
<div className={`bg-white rounded-xl shadow-md p-6 space-y-4 ${config.ai_provider === 'qwen' ? 'ring-2 ring-primary-500' : ''}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Key className="h-5 w-5 text-blue-600" />
|
||||
<h2 className="text-xl font-bold text-gray-900">通义千问 配置</h2>
|
||||
{config.ai_provider === 'qwen' && (
|
||||
<span className="px-2 py-1 text-xs font-medium bg-primary-100 text-primary-700 rounded-full">当前使用</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* API Key */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
API Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showApiKeys.qwen ? 'text' : 'password'}
|
||||
value={config.qwen_api_key || ''}
|
||||
onChange={(e) => handleChange('qwen_api_key', e.target.value)}
|
||||
placeholder="sk-..."
|
||||
className="w-full px-4 py-2 pr-10 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleApiKeyVisibility('qwen')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showApiKeys.qwen ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mt-1">从 https://dashscope.console.aliyun.com/apiKey 获取</p>
|
||||
</div>
|
||||
|
||||
{/* Base URL */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
Base URL
|
||||
</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-gray-400" />
|
||||
<input
|
||||
type="text"
|
||||
value={config.qwen_base_url}
|
||||
onChange={(e) => handleChange('qwen_base_url', e.target.value)}
|
||||
placeholder="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
className="w-full pl-10 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
完整 endpoint: <code className="bg-gray-100 px-2 py-0.5 rounded">{getCompleteEndpoint('qwen')}</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Model */}
|
||||
<div>
|
||||
<label className="block font-medium text-gray-900 mb-2">
|
||||
模型
|
||||
</label>
|
||||
<select
|
||||
value={config.qwen_model}
|
||||
onChange={(e) => handleChange('qwen_model', e.target.value)}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
||||
>
|
||||
<option value="qwen-max">qwen-max (最强)</option>
|
||||
<option value="qwen-plus">qwen-plus (推荐)</option>
|
||||
<option value="qwen-turbo">qwen-turbo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="bg-white rounded-xl shadow-md p-6">
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="w-full bg-primary-600 text-white py-3 rounded-lg font-medium hover:bg-primary-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
{saving ? (
|
||||
<>
|
||||
<Loader className="h-5 w-5 animate-spin" />
|
||||
保存中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="h-5 w-5" />
|
||||
保存所有设置
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@echo off
|
||||
REM 激活虚拟环境的批处理脚本
|
||||
cd /d "%~dp0backend"
|
||||
cd /d "%~dp0..\backend"
|
||||
call venv\Scripts\activate.bat
|
||||
cmd /k
|
||||
@@ -17,7 +17,7 @@ echo 全自动部署脚本
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM ============================================
|
||||
REM 步骤 1: 检查环境
|
||||
@@ -197,7 +197,7 @@ REM ============================================
|
||||
echo [7/8] 启动后端服务...
|
||||
echo.
|
||||
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0backend && call venv\Scripts\activate.bat && echo ======================================== && echo QQuiz 后端服务 && echo ======================================== && echo. && echo API 地址: http://localhost:8000 && echo API 文档: http://localhost:8000/docs && echo. && echo 按 Ctrl+C 停止服务 && echo. && uvicorn main:app --reload"
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0..\backend && call venv\Scripts\activate.bat && echo ======================================== && echo QQuiz 后端服务 && echo ======================================== && echo. && echo API 地址: http://localhost:8000 && echo API 文档: http://localhost:8000/docs && echo. && echo 按 Ctrl+C 停止服务 && echo. && uvicorn main:app --reload"
|
||||
|
||||
echo ✓ 后端服务已在新窗口中启动
|
||||
echo 等待服务启动...
|
||||
@@ -210,7 +210,7 @@ REM ============================================
|
||||
echo [8/8] 启动前端服务...
|
||||
echo.
|
||||
|
||||
start "QQuiz Frontend" cmd /k "cd /d %~dp0frontend && echo ======================================== && echo QQuiz 前端服务 && echo ======================================== && echo. && echo 前端地址: http://localhost:3000 && echo. && echo 按 Ctrl+C 停止服务 && echo. && npm start"
|
||||
start "QQuiz Frontend" cmd /k "cd /d %~dp0..\frontend && echo ======================================== && echo QQuiz 前端服务 && echo ======================================== && echo. && echo 前端地址: http://localhost:3000 && echo. && echo 按 Ctrl+C 停止服务 && echo. && npm start"
|
||||
|
||||
echo ✓ 前端服务已在新窗口中启动
|
||||
echo 等待服务启动...
|
||||
93
scripts/check_postgres.bat
Normal file
93
scripts/check_postgres.bat
Normal file
@@ -0,0 +1,93 @@
|
||||
@echo off
|
||||
title PostgreSQL Status Check
|
||||
color 0B
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo PostgreSQL Database Status Check
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM Check PostgreSQL installation
|
||||
echo [1] Checking PostgreSQL Installation...
|
||||
if exist "C:\Program Files\PostgreSQL\18" (
|
||||
echo OK - PostgreSQL 18 is installed
|
||||
echo Location: C:\Program Files\PostgreSQL\18
|
||||
) else (
|
||||
echo ERROR - PostgreSQL 18 not found!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo.
|
||||
|
||||
REM Check PostgreSQL service
|
||||
echo [2] Checking PostgreSQL Service...
|
||||
sc query postgresql-x64-18 >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo OK - PostgreSQL service exists
|
||||
echo.
|
||||
echo Service details:
|
||||
sc query postgresql-x64-18
|
||||
echo.
|
||||
) else (
|
||||
echo WARNING - PostgreSQL service not found!
|
||||
echo Trying alternative names...
|
||||
sc query | findstr /i "postgres"
|
||||
)
|
||||
echo.
|
||||
|
||||
REM Check if port 5432 is listening
|
||||
echo [3] Checking Port 5432...
|
||||
netstat -ano | findstr ":5432" | findstr "LISTENING" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
echo OK - PostgreSQL is listening on port 5432
|
||||
netstat -ano | findstr ":5432" | findstr "LISTENING"
|
||||
) else (
|
||||
echo ERROR - Port 5432 is NOT listening!
|
||||
echo PostgreSQL service is probably not running.
|
||||
echo.
|
||||
echo To start the service, you can:
|
||||
echo 1. Open Services (services.msc)
|
||||
echo 2. Find "postgresql-x64-18" service
|
||||
echo 3. Right-click and select "Start"
|
||||
echo.
|
||||
echo OR run: net start postgresql-x64-18
|
||||
)
|
||||
echo.
|
||||
|
||||
REM Try to connect to database
|
||||
echo [4] Testing Database Connection...
|
||||
set PGPASSWORD=postgres
|
||||
"C:\Program Files\PostgreSQL\18\pgAdmin 4\runtime\psql.exe" -h localhost -U postgres -c "SELECT version();" postgres >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo OK - Successfully connected to PostgreSQL!
|
||||
echo.
|
||||
"C:\Program Files\PostgreSQL\18\pgAdmin 4\runtime\psql.exe" -h localhost -U postgres -c "SELECT version();" postgres
|
||||
echo.
|
||||
|
||||
REM Check if qquiz database exists
|
||||
echo [5] Checking QQuiz Database...
|
||||
"C:\Program Files\PostgreSQL\18\pgAdmin 4\runtime\psql.exe" -h localhost -U postgres -c "\l" postgres | findstr "qquiz_db" >nul
|
||||
if %errorlevel% equ 0 (
|
||||
echo OK - qquiz_db database exists
|
||||
) else (
|
||||
echo INFO - qquiz_db database does not exist yet
|
||||
echo This is normal for first-time setup
|
||||
)
|
||||
) else (
|
||||
echo ERROR - Cannot connect to PostgreSQL!
|
||||
echo.
|
||||
echo Possible reasons:
|
||||
echo 1. PostgreSQL service is not running
|
||||
echo 2. Default password 'postgres' is incorrect
|
||||
echo 3. PostgreSQL is not configured to accept local connections
|
||||
echo.
|
||||
echo Please start the PostgreSQL service first!
|
||||
)
|
||||
echo.
|
||||
|
||||
echo ========================================
|
||||
echo Check Complete
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
@@ -1,6 +1,8 @@
|
||||
@echo off
|
||||
title QQuiz - System Status Check
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo QQuiz System Status Check
|
||||
@@ -8,7 +8,7 @@ echo QQuiz - Automatic Fix and Start
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM Check if .env exists
|
||||
if not exist ".env" (
|
||||
@@ -50,28 +50,28 @@ if %errorlevel% equ 1 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Starting PostgreSQL in Docker...
|
||||
docker-compose up -d postgres
|
||||
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 postgres
|
||||
docker-compose up -d mysql
|
||||
)
|
||||
|
||||
echo Waiting for database...
|
||||
timeout /t 8 /nobreak >nul
|
||||
timeout /t 10 /nobreak >nul
|
||||
|
||||
) else (
|
||||
echo.
|
||||
echo Using Local PostgreSQL...
|
||||
echo Using Local MySQL...
|
||||
echo.
|
||||
echo Make sure PostgreSQL is running on port 5432
|
||||
echo Make sure MySQL is running on port 3306
|
||||
echo.
|
||||
echo If you see connection errors, you need to:
|
||||
echo 1. Start PostgreSQL service
|
||||
echo 2. Or install PostgreSQL from https://www.postgresql.org/download/
|
||||
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
|
||||
@@ -111,11 +111,11 @@ echo.
|
||||
echo Starting services...
|
||||
echo.
|
||||
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0backend && call venv\Scripts\activate.bat && echo Backend: http://localhost:8000 && echo Docs: http://localhost:8000/docs && echo. && uvicorn main:app --reload"
|
||||
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 %~dp0frontend && echo Frontend: http://localhost:3000 && echo. && npm start"
|
||||
start "QQuiz Frontend" cmd /k "cd /d %~dp0..frontend && echo Frontend: http://localhost:3000 && echo. && npm start"
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
@@ -2,6 +2,8 @@
|
||||
chcp 65001 >nul
|
||||
title QQuiz - 查看日志
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo QQuiz 服务日志
|
||||
@@ -8,7 +8,7 @@ echo 推送 QQuiz 到 GitHub
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM 检查是否有远程仓库
|
||||
git remote -v | findstr "origin" >nul
|
||||
@@ -48,7 +48,7 @@ echo [重要] 如果是首次推送,需要输入 GitHub 认证:
|
||||
echo Username: handsomezhuzhu
|
||||
echo Password: 使用 Personal Access Token (不是密码!)
|
||||
echo.
|
||||
echo 如何获取 Token: 参考 GITHUB_PUSH_GUIDE.md
|
||||
echo 如何获取 Token: 参考 docs/GITHUB_PUSH_GUIDE.md
|
||||
echo.
|
||||
pause
|
||||
|
||||
@@ -74,7 +74,7 @@ if %errorlevel% equ 0 (
|
||||
echo 3. 网络连接问题
|
||||
echo.
|
||||
echo 解决方案:
|
||||
echo 1. 阅读 GITHUB_PUSH_GUIDE.md 配置认证
|
||||
echo 1. 阅读 docs/GITHUB_PUSH_GUIDE.md 配置认证
|
||||
echo 2. 确认 Personal Access Token 有效
|
||||
echo 3. 检查网络连接
|
||||
echo.
|
||||
@@ -8,7 +8,7 @@ echo QQuiz 快速配置
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM 创建 .env 文件
|
||||
echo 正在创建配置文件...
|
||||
@@ -51,6 +51,6 @@ pause >nul
|
||||
notepad .env
|
||||
|
||||
echo.
|
||||
echo 配置完成!现在可以运行 auto_setup_and_run.bat 启动系统
|
||||
echo 配置完成!现在可以运行 scripts\auto_setup_and_run.bat 启动系统
|
||||
echo.
|
||||
pause
|
||||
31
scripts/restart_backend.bat
Normal file
31
scripts/restart_backend.bat
Normal file
@@ -0,0 +1,31 @@
|
||||
@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
|
||||
47
scripts/restart_docker.bat
Normal file
47
scripts/restart_docker.bat
Normal file
@@ -0,0 +1,47 @@
|
||||
@echo off
|
||||
title QQuiz - Restart Docker Services
|
||||
color 0B
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Restarting QQuiz Docker Services
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo [1/4] Stopping existing containers...
|
||||
docker-compose down
|
||||
echo OK
|
||||
echo.
|
||||
|
||||
echo [2/4] Rebuilding backend image...
|
||||
docker-compose build backend --no-cache
|
||||
echo OK
|
||||
echo.
|
||||
|
||||
echo [3/4] Starting all services...
|
||||
docker-compose up -d
|
||||
echo OK
|
||||
echo.
|
||||
|
||||
echo [4/4] Waiting for services to be ready...
|
||||
timeout /t 15 /nobreak >nul
|
||||
echo.
|
||||
|
||||
echo ========================================
|
||||
echo Services Restarted!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Checking service status...
|
||||
docker-compose ps
|
||||
echo.
|
||||
|
||||
echo Frontend: http://localhost:3000
|
||||
echo Backend: http://localhost:8000
|
||||
echo Database: MySQL on port 3306
|
||||
echo.
|
||||
echo Login: admin / admin123
|
||||
echo.
|
||||
|
||||
pause
|
||||
@@ -8,7 +8,7 @@ echo QQuiz Backend - 本地启动
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0backend"
|
||||
cd /d "%~dp0..\backend"
|
||||
|
||||
REM 检查虚拟环境是否存在
|
||||
if not exist "venv\Scripts\activate.bat" (
|
||||
@@ -8,7 +8,7 @@ echo QQuiz Frontend - 本地启动
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0frontend"
|
||||
cd /d "%~dp0..\frontend"
|
||||
|
||||
REM 检查 node_modules 是否存在
|
||||
if not exist "node_modules" (
|
||||
@@ -1,11 +1,11 @@
|
||||
@echo off
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo Creating .env configuration file...
|
||||
echo.
|
||||
|
||||
(
|
||||
echo DATABASE_URL=postgresql+asyncpg://qquiz:qquiz_password@localhost:5432/qquiz_db
|
||||
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
|
||||
@@ -8,7 +8,7 @@ echo QQuiz - Auto Deploy Script
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
REM Check if .env exists
|
||||
if not exist ".env" (
|
||||
@@ -40,17 +40,13 @@ if %errorlevel% neq 0 (
|
||||
echo OK - Node.js installed
|
||||
echo.
|
||||
|
||||
echo [3/7] Creating PostgreSQL database...
|
||||
echo [3/7] Creating MySQL database...
|
||||
echo.
|
||||
echo Please enter PostgreSQL admin password when prompted
|
||||
echo (Default password is usually: postgres)
|
||||
echo Please enter MySQL root password when prompted
|
||||
echo (Leave blank if no password set)
|
||||
echo.
|
||||
|
||||
set PGPASSWORD=postgres
|
||||
psql -U postgres -h localhost -c "DROP DATABASE IF EXISTS qquiz_db;" 2>nul
|
||||
psql -U postgres -h localhost -c "DROP USER IF EXISTS qquiz;" 2>nul
|
||||
psql -U postgres -h localhost -c "CREATE USER qquiz WITH PASSWORD 'qquiz_password';" 2>nul
|
||||
psql -U postgres -h localhost -c "CREATE DATABASE qquiz_db OWNER qquiz;" 2>nul
|
||||
mysql -u root -p -e "DROP DATABASE IF EXISTS qquiz_db; DROP USER IF EXISTS 'qquiz'@'localhost'; 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;" 2>nul
|
||||
|
||||
echo Database setup complete
|
||||
echo.
|
||||
@@ -88,11 +84,11 @@ echo.
|
||||
echo [7/7] Starting services...
|
||||
echo.
|
||||
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0backend && call venv\Scripts\activate.bat && echo Backend API: http://localhost:8000 && echo API Docs: http://localhost:8000/docs && echo. && uvicorn main:app --reload"
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0..\backend && call venv\Scripts\activate.bat && echo Backend API: http://localhost:8000 && echo API Docs: http://localhost:8000/docs && echo. && uvicorn main:app --reload"
|
||||
|
||||
timeout /t 5 /nobreak >nul
|
||||
|
||||
start "QQuiz Frontend" cmd /k "cd /d %~dp0frontend && echo Frontend: http://localhost:3000 && echo. && npm start"
|
||||
start "QQuiz Frontend" cmd /k "cd /d %~dp0..\frontend && echo Frontend: http://localhost:3000 && echo. && npm start"
|
||||
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
@@ -8,7 +8,7 @@ echo Starting QQuiz Backend Server
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0backend"
|
||||
cd /d "%~dp0..\backend"
|
||||
|
||||
if not exist "venv\Scripts\activate.bat" (
|
||||
echo Creating virtual environment...
|
||||
@@ -8,7 +8,7 @@ echo Starting QQuiz Frontend Server
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0frontend"
|
||||
cd /d "%~dp0..\frontend"
|
||||
|
||||
if not exist "node_modules" (
|
||||
echo Installing dependencies (first time only)...
|
||||
44
scripts/start_postgres.bat
Normal file
44
scripts/start_postgres.bat
Normal file
@@ -0,0 +1,44 @@
|
||||
@echo off
|
||||
title Start PostgreSQL Service
|
||||
color 0B
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Starting PostgreSQL Service
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo Attempting to start PostgreSQL service...
|
||||
echo.
|
||||
|
||||
net start postgresql-x64-18
|
||||
|
||||
if %errorlevel% equ 0 (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo SUCCESS!
|
||||
echo ========================================
|
||||
echo.
|
||||
echo PostgreSQL service is now running
|
||||
echo Port: 5432
|
||||
echo.
|
||||
echo You can now run: scripts\fix_and_start.bat
|
||||
echo.
|
||||
) else (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Failed to start service
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Please try starting manually:
|
||||
echo 1. Press Win+R
|
||||
echo 2. Type: services.msc
|
||||
echo 3. Find "postgresql-x64-18"
|
||||
echo 4. Right-click and select "Start"
|
||||
echo.
|
||||
echo OR run this script as Administrator:
|
||||
echo Right-click this bat file and select "Run as administrator"
|
||||
echo.
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -2,6 +2,8 @@
|
||||
chcp 65001 >nul
|
||||
title QQuiz - 启动服务
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo QQuiz - 智能刷题与题库管理平台
|
||||
@@ -2,6 +2,8 @@
|
||||
chcp 65001 >nul
|
||||
title QQuiz - 启动服务 (国内优化版)
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo QQuiz - 智能刷题与题库管理平台
|
||||
@@ -56,7 +58,7 @@ if %errorlevel% neq 0 (
|
||||
if %errorlevel% equ 1 (
|
||||
echo.
|
||||
echo 请按照提示配置 Docker Desktop 镜像加速器...
|
||||
call setup_docker_mirror.bat
|
||||
call scripts\setup_docker_mirror.bat
|
||||
echo.
|
||||
echo 配置完成后,请重新运行此脚本
|
||||
pause
|
||||
@@ -90,9 +92,9 @@ if %errorlevel% neq 0 (
|
||||
echo 3. 配置错误 - 检查 .env 文件
|
||||
echo.
|
||||
echo 解决方案:
|
||||
echo 1. 配置镜像加速器: 运行 setup_docker_mirror.bat
|
||||
echo 1. 配置镜像加速器: 运行 scripts\setup_docker_mirror.bat
|
||||
echo 2. 查看详细错误: docker-compose logs
|
||||
echo 3. 阅读文档: DOCKER_MIRROR_SETUP.md
|
||||
echo 3. 阅读文档: docs\DOCKER_MIRROR_SETUP.md
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
@@ -8,7 +8,7 @@ echo QQuiz - Starting with Docker DB
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0"
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo [1/4] Checking Docker...
|
||||
docker --version >nul 2>&1
|
||||
@@ -21,31 +21,32 @@ if %errorlevel% neq 0 (
|
||||
echo OK - Docker installed
|
||||
echo.
|
||||
|
||||
echo [2/4] Starting PostgreSQL in Docker...
|
||||
docker-compose up -d postgres
|
||||
echo [2/4] Starting MySQL in Docker...
|
||||
docker-compose up -d mysql
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo ERROR: Failed to start PostgreSQL
|
||||
echo ERROR: Failed to start MySQL
|
||||
echo Try: docker-compose down
|
||||
echo Then run this script again
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo OK - PostgreSQL started
|
||||
echo OK - MySQL started
|
||||
echo Waiting for database to be ready...
|
||||
timeout /t 5 /nobreak >nul
|
||||
echo.
|
||||
timeout /t 10 /nobreak >nul
|
||||
echo.
|
||||
|
||||
echo [3/4] Starting Backend...
|
||||
start "QQuiz Backend" cmd /k "cd /d %~dp0backend && 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"
|
||||
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 %~dp0frontend && echo ======================================== && echo QQuiz Frontend Server && echo ======================================== && echo. && echo URL: http://localhost:3000 && echo. && npm start"
|
||||
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 ========================================
|
||||
@@ -2,6 +2,8 @@
|
||||
chcp 65001 >nul
|
||||
title QQuiz - 停止服务
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo 停止 QQuiz 服务
|
||||
15
scripts/view_backend_logs.bat
Normal file
15
scripts/view_backend_logs.bat
Normal file
@@ -0,0 +1,15 @@
|
||||
@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
|
||||
Reference in New Issue
Block a user