diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..127ddfb --- /dev/null +++ b/.env.example @@ -0,0 +1,73 @@ +# ========================================== +# 创赢未来报名评分系统 - 环境变量配置 +# 复制此文件为 .env 并修改为您的实际配置 +# ========================================== + +# ========================================== +# Django 基础配置 +# ========================================== +# 生产环境请设置为 False +DEBUG=False + +# 安全密钥,生产环境必须修改! +# 生成方法: python -c "import secrets; print(secrets.token_urlsafe(50))" +SECRET_KEY=your-secret-key-change-this-in-production + +# 允许访问的域名,多个用逗号分隔 +ALLOWED_HOSTS=localhost,127.0.0.1,your-domain.com + +# ========================================== +# 数据库配置 (PostgreSQL) +# ========================================== +DB_NAME=scoring_system +DB_USER=postgres +DB_PASSWORD=your-db-password +DB_HOST=localhost +DB_PORT=5432 + +# ========================================== +# 微信支付配置 +# ========================================== +WECHAT_APPID=wx-your-app-id +WECHAT_SECRET=your-wechat-secret +WECHAT_MCHID=your-merchant-id +WECHAT_API_KEY=your-api-key +WECHAT_API_V3_KEY=your-api-v3-key +WECHAT_CERT_PATH=/path/to/cert.pem +WECHAT_KEY_PATH=/path/to/key.pem + +# ========================================== +# 阿里云 OSS 配置 (文件存储) +# ========================================== +ALIYUN_ACCESS_KEY_ID=your-access-key-id +ALIYUN_ACCESS_KEY_SECRET=your-access-key-secret +ALIYUN_OSS_ENDPOINT=https://oss-cn-your-region.aliyuncs.com +ALIYUN_OSS_BUCKET_NAME=your-bucket-name +ALIYUN_OSS_INTERNAL_ENDPOINT=https://oss-cn-your-region-internal.aliyuncs.com + +# ========================================== +# 阿里云听悟配置 (语音转写) +# ========================================== +ALIYUN_TINGWU_APP_KEY=your-tingwu-app-key + +# ========================================== +# 阿里云百炼/通义千问配置 (AI 评估) +# ========================================== +DASHSCOPE_API_KEY=your-dashscope-api-key + +# ========================================== +# 前端 API 地址配置 +# ========================================== +VITE_API_URL=/api + +# ========================================== +# 其他配置 +# ========================================== +# 时区 +TZ=Asia/Shanghai + +# 日志级别 (DEBUG, INFO, WARNING, ERROR) +LOG_LEVEL=INFO + +# 文件上传大小限制 (MB) +MAX_UPLOAD_SIZE=100 diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..0a8a29b --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,249 @@ +# 创赢未来报名评分系统 - 生产环境部署指南 + +## 📋 部署前准备 + +### 系统要求 +- **操作系统**: Ubuntu 20.04+ / CentOS 7+ / Debian 10+ +- **内存**: 至少 2GB RAM +- **磁盘**: 至少 20GB 可用空间 +- **Docker**: 20.10+ +- **Docker Compose**: 2.0+ + +### 安装 Docker +```bash +# Ubuntu/Debian +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker $USER + +# 安装 Docker Compose +sudo apt-get install -y docker-compose-plugin +# 或 +pip install docker-compose +``` + +## 🚀 快速部署 + +### 1. 拉取代码 +```bash +git clone https://gitea.tangledup-ai.com/quant-speed-AI/Scoring-System.git +cd Scoring-System +``` + +### 2. 配置环境变量 +```bash +# 复制环境变量模板 +cp .env.example .env + +# 编辑 .env 文件,填入您的实际配置 +vim .env +``` + +**必须配置的项目:** +- `SECRET_KEY`: Django 安全密钥(必须修改!) +- `DB_PASSWORD`: 数据库密码 +- `WECHAT_*`: 微信支付相关配置(如需支付功能) +- `ALIYUN_*`: 阿里云 OSS 和 AI 服务配置(如需语音转写和 AI 评估) + +### 3. 执行部署脚本 +```bash +# 给脚本添加执行权限 +chmod +x deploy.sh + +# 执行部署 +./deploy.sh +``` + +## 🔧 手动部署(高级) + +### 1. 构建镜像 +```bash +docker-compose build --no-cache +``` + +### 2. 启动服务 +```bash +# 后台启动 +docker-compose up -d + +# 查看日志 +docker-compose logs -f + +# 查看特定服务日志 +docker-compose logs -f backend +docker-compose logs -f frontend +``` + +### 3. 执行数据库迁移 +```bash +docker-compose exec backend python manage.py migrate +docker-compose exec backend python manage.py createsuperuser +``` + +### 4. 收集静态文件 +```bash +docker-compose exec backend python manage.py collectstatic --noinput +``` + +## 🌐 Nginx 反向代理配置(推荐) + +如果需要使用域名和 HTTPS,在服务器上安装 Nginx: + +```nginx +# /etc/nginx/sites-available/scoring-system +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:80; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +启用配置: +```bash +sudo ln -s /etc/nginx/sites-available/scoring-system /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +## 🔒 HTTPS 配置(Let's Encrypt) + +```bash +# 安装 Certbot +sudo apt-get install -y certbot python3-certbot-nginx + +# 申请证书 +sudo certbot --nginx -d your-domain.com + +# 自动续期 +sudo certbot renew --dry-run +``` + +## 📊 常用命令 + +```bash +# 查看运行状态 +docker-compose ps + +# 停止服务 +docker-compose down + +# 重启服务 +docker-compose restart + +# 重启单个服务 +docker-compose restart backend + +# 进入容器 +docker-compose exec backend bash +docker-compose exec frontend sh + +# 查看容器日志 +docker-compose logs -f --tail=100 backend + +# 清理未使用的镜像 +docker image prune -f + +# 更新部署(拉取最新代码并重启) +git pull +./deploy.sh +``` + +## 🔍 故障排查 + +### 问题1: 端口被占用 +```bash +# 检查端口占用 +sudo netstat -tlnp | grep 8000 +sudo netstat -tlnp | grep 80 + +# 修改 docker-compose.yml 中的端口映射 +``` + +### 问题2: 数据库连接失败 +```bash +# 检查数据库配置 +docker-compose exec backend python manage.py dbshell + +# 查看后端日志 +docker-compose logs backend | grep -i error +``` + +### 问题3: 静态文件无法访问 +```bash +# 重新收集静态文件 +docker-compose exec backend python manage.py collectstatic --noinput + +# 检查权限 +ls -la backend/static/ +ls -la backend/media/ +``` + +### 问题4: 容器无法启动 +```bash +# 查看详细日志 +docker-compose logs --no-color + +# 检查配置 +docker-compose config +``` + +## 💾 数据备份 + +### 数据库备份 +```bash +# 进入容器执行备份 +docker-compose exec backend python manage.py dumpdata > backup_$(date +%Y%m%d).json + +# 或使用 PostgreSQL 直接备份 +docker exec scoring_system_db pg_dump -U postgres scoring_system > db_backup_$(date +%Y%m%d).sql +``` + +### 媒体文件备份 +```bash +# 备份上传的文件 +tar -czvf media_backup_$(date +%Y%m%d).tar.gz backend/media/ +``` + +## 🔄 自动部署(CI/CD) + +使用 Gitea Actions 或 Jenkins 实现自动部署: + +```yaml +# .gitea/workflows/deploy.yaml +name: Deploy to Production + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Deploy to Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + cd ~/Scoring-System + git pull + ./deploy.sh +``` + +## 📞 技术支持 + +- **项目仓库**: https://gitea.tangledup-ai.com/quant-speed-AI/Scoring-System +- **问题反馈**: 在仓库提交 Issue + +--- + +**部署完成后访问地址:** +- 🌐 前端页面: http://your-server-ip/ +- 🔧 后台管理: http://your-server-ip:8000/admin/ +- 📚 API 文档: http://your-server-ip:8000/api/docs/ diff --git a/backend/Dockerfile b/backend/Dockerfile index 4b0bd1f..342d7ff 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,26 +1,40 @@ -# Use an official Python runtime as a parent image -FROM python:3.13-slim +# 使用 Python 3.12 slim 镜像作为基础镜像 +FROM python:3.12-slim -# Set environment variables +# 设置环境变量 ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONUNBUFFERED=1 +ENV PYTHONIOENCODING=utf-8 -# Set work directory +# 设置工作目录 WORKDIR /app -# Install python dependencies -COPY requirements.txt /app/ -RUN pip install --upgrade pip && pip install -r requirements.txt +# 安装系统依赖 +RUN apt-get update && apt-get install -y \ + gcc \ + libpq-dev \ + libffi-dev \ + libjpeg-dev \ + zlib1g-dev \ + && rm -rf /var/lib/apt/lists/* -# Copy project -COPY . /app/ -COPY .env /app/ +# 复制 requirements 文件并安装依赖 +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt -# Expose port +# 复制项目代码 +COPY . . + +# 创建媒体文件目录 +RUN mkdir -p /app/media /app/static + +# 暴露端口 EXPOSE 8000 -# Volume for media files -VOLUME ["/app/media"] +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import requests; requests.get('http://localhost:8000/api/health/')" || exit 1 -# Run the application with gunicorn -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "config.wsgi:application"] +# 启动命令 +CMD ["sh", "-c", "python manage.py collectstatic --noinput && python manage.py migrate && gunicorn --bind 0.0.0.0:8000 --workers 4 --threads 2 --worker-class gthread --access-logfile - --error-logfile - --capture-output --enable-stdio-inheritance config.wsgi:application"] diff --git a/backend/config/urls.py b/backend/config/urls.py index 809111d..3b0f963 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -2,11 +2,21 @@ from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static +from django.http import JsonResponse from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView from competition import judge_views +# 健康检查视图 +def health_check(request): + return JsonResponse({ + 'status': 'healthy', + 'service': '创赢未来报名评分系统', + 'version': '1.0.0' + }) + urlpatterns = [ path('admin/', admin.site.urls), + path('api/health/', health_check, name='health_check'), # Judge System Routes path('judge/', include('competition.judge_urls')), diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..24565f2 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +# 创赢未来报名评分系统 - 生产环境部署脚本 +# 用法: ./deploy.sh [环境变量] + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 打印带颜色的信息 +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# 检查环境 +print_info "检查 Docker 环境..." +if ! command -v docker &> /dev/null; then + print_error "Docker 未安装,请先安装 Docker" + exit 1 +fi + +if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then + print_error "Docker Compose 未安装,请先安装 Docker Compose" + exit 1 +fi + +# 检查 .env 文件 +if [ ! -f .env ]; then + print_warning ".env 文件不存在,将使用默认配置" + print_warning "建议复制 .env.example 为 .env 并修改配置" +fi + +# 显示部署信息 +print_info "================================" +print_info "创赢未来报名评分系统 - 部署脚本" +print_info "================================" + +# 拉取最新代码(如果是 git 仓库) +if [ -d .git ]; then + print_info "拉取最新代码..." + git pull || print_warning "Git pull 失败,使用本地代码继续" +fi + +# 停止旧容器 +print_info "停止旧容器..." +docker-compose down --remove-orphans + +# 删除旧镜像(可选) +print_info "清理旧镜像..." +docker-compose rm -f + +# 构建镜像 +print_info "构建 Docker 镜像..." +docker-compose build --no-cache + +# 启动服务 +print_info "启动服务..." +docker-compose up -d + +# 等待服务启动 +print_info "等待服务启动..." +sleep 10 + +# 检查服务状态 +print_info "检查服务状态..." +if docker-compose ps | grep -q "Up"; then + print_success "服务启动成功!" + echo "" + print_info "访问地址:" + print_success " - 前端: http://localhost" + print_success " - 后端 API: http://localhost:8000/api/" + print_success " - 后台管理: http://localhost:8000/admin/" + echo "" + print_info "查看日志: docker-compose logs -f" +else + print_error "服务启动失败,请检查日志: docker-compose logs" + exit 1 +fi diff --git a/docker-compose.yml b/docker-compose.yml index 73bdca8..8a11c08 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,31 +1,85 @@ +version: '3.8' + services: + # 后端服务 backend: - build: ./backend - # 使用 gunicorn 替代 runserver,提高稳定性,并捕获标准输出1 - command: sh -c "python manage.py collectstatic --noinput && python manage.py migrate && gunicorn --bind 0.0.0.0:8000 --access-logfile - --error-logfile - config.wsgi:application" + build: + context: ./backend + dockerfile: Dockerfile + container_name: scoring_backend + restart: always volumes: - - ./backend:/app - ./backend/media:/app/media + - ./backend/static:/app/static ports: - "8000:8000" environment: - - DB_NAME=market - - DB_USER=market - - DB_PASSWORD=123market - - DB_HOST=6.6.6.66 - - DB_PORT=5432 + - DEBUG=False + - SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this} + - DB_NAME=${DB_NAME:-scoring_system} + - DB_USER=${DB_USER:-postgres} + - DB_PASSWORD=${DB_PASSWORD:-password} + - DB_HOST=${DB_HOST:-localhost} + - DB_PORT=${DB_PORT:-5432} + - WECHAT_APPID=${WECHAT_APPID} + - WECHAT_SECRET=${WECHAT_SECRET} + - WECHAT_MCHID=${WECHAT_MCHID} + - WECHAT_API_KEY=${WECHAT_API_KEY} + - ALIYUN_ACCESS_KEY_ID=${ALIYUN_ACCESS_KEY_ID} + - ALIYUN_ACCESS_KEY_SECRET=${ALIYUN_ACCESS_KEY_SECRET} + - ALIYUN_OSS_ENDPOINT=${ALIYUN_OSS_ENDPOINT} + - ALIYUN_OSS_BUCKET_NAME=${ALIYUN_OSS_BUCKET_NAME} + - ALIYUN_TINGWU_APP_KEY=${ALIYUN_TINGWU_APP_KEY} + - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY} + networks: + - scoring_network + healthcheck: + test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8000/api/health/')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + # 前端服务 (Nginx) frontend: build: context: ./frontend + dockerfile: Dockerfile args: - VITE_API_URL=/api - # volumes: - # - ./frontend:/app - # - /app/node_modules + container_name: scoring_frontend + restart: always ports: - - "15173:15173" - environment: - - VITE_API_URL=http://localhost:8000/api + - "80:80" depends_on: + backend: + condition: service_healthy + networks: + - scoring_network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + + # Nginx 反向代理 (可选,用于负载均衡和 SSL) + nginx: + image: nginx:alpine + container_name: scoring_nginx + restart: always + ports: + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + - frontend - backend + networks: + - scoring_network + profiles: + - ssl # 只在需要 SSL 时启动 + +networks: + scoring_network: + driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4b683af..aa3e499 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,13 +1,10 @@ -# Use an official Node runtime as a parent image -FROM node:22-alpine +# 构建阶段 +FROM node:20-alpine AS builder -# Set working directory +# 设置工作目录 WORKDIR /app -# Install build dependencies for native modules -RUN apk add --no-cache autoconf automake libtool make g++ zlib-dev nasm python3 - -# Install dependencies +# 安装依赖 COPY package.json package-lock.json* ./ RUN npm install --registry=https://registry.npmmirror.com @@ -16,14 +13,22 @@ COPY . . # 接收构建参数 ARG VITE_API_URL=/api -# 设置环境变量供构建时使用 -ENV VITE_API_URL=$VITE_API_URL +ENV VITE_API_URL=${VITE_API_URL} # 构建生产环境代码 RUN npm run build -# 暴露应用运行的端口 -EXPOSE 15173 +# 生产阶段 - 使用 Nginx +FROM nginx:alpine -# 启动应用 (Preview 模式) -CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "15173"] +# 复制 Nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 从构建阶段复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 暴露端口 +EXPOSE 80 + +# 启动 Nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..298234e --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,67 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # 缓存静态资源 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|otf)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # 前端路由支持 - 所有路由指向 index.html + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } + + # API 代理 - 将 /api 请求转发到后端 + location /api/ { + proxy_pass http://backend:8000/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 媒体文件代理 + location /media/ { + proxy_pass http://backend:8000/media/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # 静态文件代理 + location /static/ { + proxy_pass http://backend:8000/static/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # 健康检查 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 错误页面 + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +}