diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1ebae53 --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# ============================================================ +# FilesReadSystem 环境变量配置模板 +# 复制此文件为 .env 并填入实际值 +# ============================================================ + +# ==================== 应用配置 ==================== +DEBUG=false + +# ==================== MongoDB ==================== +MONGO_ROOT_USER=admin +MONGO_ROOT_PASSWORD=your_mongo_password +MONGODB_DB_NAME=document_system + +# ==================== MySQL ==================== +MYSQL_PASSWORD=your_mysql_password +MYSQL_DATABASE=document + +# ==================== Redis ==================== +REDIS_PASSWORD=your_redis_password + +# ==================== LLM AI ==================== +LLM_API_KEY=your_llm_api_key +LLM_BASE_URL=https://api.deepseek.com +LLM_MODEL_NAME=deepseek-chat + +# ==================== Supabase ==================== +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_ANON_KEY=your_anon_key +SUPABASE_SERVICE_KEY=your_service_key + +# ==================== Embedding / RAG ==================== +EMBEDDING_MODEL=all-MiniLM-L6-v2 + +# ==================== 前端配置 ==================== +VITE_APP_ID=your_app_id \ No newline at end of file diff --git a/README.md b/README.md index 833bc1b..ce8ff46 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,77 @@ pnpm dev --- +## Docker 部署 / Docker Deployment + +### 快速启动 / Quick Start + +```bash +# 1. 复制环境变量模板并编辑 +cp .env.example .env +# 编辑 .env 填入实际配置 + +# 2. 启动所有服务 +docker compose up -d + +# 3. 查看日志 +docker compose logs -f + +# 4. 检查服务状态 +docker compose ps + +# 5. 更新部署 +docker compose up -d --build +``` + +### 服务说明 / Services + +| 服务 | 端口 | 说明 | +|:---|:---|:---| +| frontend | 80 | React 前端 (Nginx) | +| backend | 8000 | FastAPI 后端 | +| mongodb | 27017 | MongoDB 数据库 | +| mysql | 3306 | MySQL 数据库 | +| redis | 6379 | Redis 缓存/队列 | + +### 环境变量 / Environment Variables + +创建 `.env` 文件,参考 `.env.example`: + +```bash +# 数据库配置 +MONGO_ROOT_USER=admin +MONGO_ROOT_PASSWORD=your_password +MONGODB_DB_NAME=document_system +MYSQL_PASSWORD=your_password +MYSQL_DATABASE=document +REDIS_PASSWORD=your_password + +# LLM 配置 +LLM_API_KEY=your_api_key +LLM_BASE_URL=https://api.deepseek.com +LLM_MODEL_NAME=deepseek-chat + +# Supabase 配置 +SUPABASE_URL=https://your-project.supabase.co +SUPABASE_ANON_KEY=your_anon_key +SUPABASE_SERVICE_KEY=your_service_key +``` + +### 验证部署 / Verify Deployment + +```bash +# 检查所有服务状态 +docker compose ps + +# 访问前端 +curl http://localhost + +# 检查后端健康 +curl http://localhost:8000/health +``` + +--- + ## 许可证 / License ISC diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..da3dfb2 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,40 @@ +# ============================================================ +# FilesReadSystem Backend Docker Image +# ============================================================ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# 安装系统依赖 (FAISS, Pillow, tesseract 等) +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + g++ \ + libgl1-mesa-glx \ + libglib2.0-0 \ + tesseract-ocr \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# 先复制依赖文件,再安装(利用 Docker 缓存) +COPY requirements.txt . + +# 安装 Python 依赖 +RUN pip install --no-cache-dir -r requirements.txt + +# 复制应用代码 +COPY app/ ./app/ + +# 创建数据目录 +RUN mkdir -p /app/data/uploads /app/data/faiss /app/data/logs + +# 暴露端口 +EXPOSE 8000 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD python -c "import httpx; httpx.get('http://localhost:8000/health')" || exit 1 + +# 启动命令 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file diff --git a/backend/app/celery_app.py b/backend/app/celery_app.py new file mode 100644 index 0000000..25ccae2 --- /dev/null +++ b/backend/app/celery_app.py @@ -0,0 +1,27 @@ +# ============================================================ +# Celery 应用配置 +# ============================================================ +from celery import Celery + +# 优先使用环境变量,否则使用默认值 +import os + +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/1") +CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/2") + +celery_app = Celery( + "filesread", + broker=CELERY_BROKER_URL, + backend=CELERY_RESULT_BACKEND, +) + +celery_app.conf.update( + task_serializer="json", + accept_content=["json"], + result_serializer="json", + timezone="Asia/Shanghai", + enable_utc=True, + task_track_started=True, + task_time_limit=3600, # 1小时超时 + worker_prefetch_multiplier=1, +) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f3df492 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,203 @@ +# ============================================================ +# FilesReadSystem Docker Compose +# 全栈 AI 文档理解与数据融合系统 +# ============================================================ +version: "3.8" + +services: + # ==================== 数据库服务 ==================== + + mongodb: + image: mongo:7.0 + container_name: filesread_mongodb + restart: unless-stopped + ports: + - "27017:27017" + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USER:-admin} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:-20060825fhy} + MONGO_INITDB_DATABASE: ${MONGODB_DB_NAME:-document_system} + volumes: + - mongodb_data:/data/db + networks: + - filesread_network + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')", "--quiet"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + mysql: + image: mysql:8.0 + container_name: filesread_mysql + restart: unless-stopped + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD:-123456} + MYSQL_DATABASE: ${MYSQL_DATABASE:-document} + volumes: + - mysql_data:/var/lib/mysql + networks: + - filesread_network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_PASSWORD:-123456}"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + redis: + image: redis:7-alpine + container_name: filesread_redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + networks: + - filesread_network + command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-} + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + # ==================== 应用服务 ==================== + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: filesread_backend + restart: unless-stopped + ports: + - "8000:8000" + environment: + # 应用配置 + APP_NAME: FilesReadSystem + DEBUG: ${DEBUG:-false} + API_V1_STR: /api/v1 + + # MongoDB 配置 (使用 docker-compose 服务名) + MONGODB_URL: mongodb://${MONGO_ROOT_USER:-admin}:${MONGO_ROOT_PASSWORD:-20060825fhy}@mongodb:27017/admin + MONGODB_DB_NAME: ${MONGODB_DB_NAME:-document_system} + + # MySQL 配置 + MYSQL_HOST: mysql + MYSQL_PORT: 3306 + MYSQL_USER: root + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456} + MYSQL_DATABASE: ${MYSQL_DATABASE:-document} + MYSQL_CHARSET: utf8mb4 + + # Redis 配置 + REDIS_URL: redis://:${REDIS_PASSWORD:-}@redis:6379/0 + + # LLM AI 配置 + LLM_API_KEY: ${LLM_API_KEY} + LLM_BASE_URL: ${LLM_BASE_URL:-https://api.deepseek.com} + LLM_MODEL_NAME: ${LLM_MODEL_NAME:-deepseek-chat} + + # Supabase 配置 + SUPABASE_URL: ${SUPABASE_URL} + SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} + SUPABASE_SERVICE_KEY: ${SUPABASE_SERVICE_KEY} + + # Embedding / RAG 配置 + EMBEDDING_MODEL: ${EMBEDDING_MODEL:-all-MiniLM-L6-v2} + FAISS_INDEX_DIR: /app/data/faiss + + # 文件路径配置 + UPLOAD_DIR: /app/data/uploads + MAX_UPLOAD_SIZE: 104857600 + + # Celery 配置 + CELERY_BROKER_URL: redis://:${REDIS_PASSWORD:-}@redis:6379/1 + CELERY_RESULT_BACKEND: redis://:${REDIS_PASSWORD:-}@redis:6379/2 + volumes: + - backend_data:/app/data + networks: + - filesread_network + depends_on: + mongodb: + condition: service_healthy + mysql: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + celery_worker: + build: + context: ./backend + dockerfile: Dockerfile + container_name: filesread_celery + restart: unless-stopped + command: celery -A app.celery_app worker --loglevel=info --prefetch-multiplier=1 + environment: + # Celery 配置 + CELERY_BROKER_URL: redis://:${REDIS_PASSWORD:-}@redis:6379/1 + CELERY_RESULT_BACKEND: redis://:${REDIS_PASSWORD:-}@redis:6379/2 + + # 复用后端的数据库配置 + MONGODB_URL: mongodb://${MONGO_ROOT_USER:-admin}:${MONGO_ROOT_PASSWORD:-20060825fhy}@mongodb:27017/admin + MONGODB_DB_NAME: ${MONGODB_DB_NAME:-document_system} + MYSQL_HOST: mysql + MYSQL_PORT: 3306 + MYSQL_USER: root + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-123456} + MYSQL_DATABASE: ${MYSQL_DATABASE:-document} + REDIS_URL: redis://:${REDIS_PASSWORD:-}@redis:6379/0 + + # LLM 配置 + LLM_API_KEY: ${LLM_API_KEY} + LLM_BASE_URL: ${LLM_BASE_URL:-https://api.deepseek.com} + LLM_MODEL_NAME: ${LLM_MODEL_NAME:-deepseek-chat} + + # Embedding 配置 + EMBEDDING_MODEL: ${EMBEDDING_MODEL:-all-MiniLM-L6-v2} + FAISS_INDEX_DIR: /app/data/faiss + volumes: + - backend_data:/app/data + networks: + - filesread_network + depends_on: + - redis + - mongodb + - mysql + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: filesread_frontend + restart: unless-stopped + ports: + - "80:80" + environment: + VITE_APP_ID: ${VITE_APP_ID:-} + VITE_SUPABASE_URL: ${SUPABASE_URL} + VITE_SUPABASE_ANON_KEY: ${SUPABASE_ANON_KEY} + VITE_BACKEND_API_URL: /api/v1 + networks: + - filesread_network + depends_on: + - backend + +networks: + filesread_network: + driver: bridge + +volumes: + mongodb_data: + mysql_data: + redis_data: + backend_data: \ No newline at end of file diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..7d4838a --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,36 @@ +# ============================================================ +# FilesReadSystem Frontend - React + Vite +# 多阶段构建: Node 构建 -> Nginx 运行 +# ============================================================ + +# === 阶段1: 构建阶段 === +FROM node:20-alpine AS builder + +WORKDIR /app + +# 复制 package 文件和锁文件 +COPY package.json pnpm-lock.yaml* ./ + +# 安装 pnpm 并安装依赖 +RUN npm install -g pnpm && \ + pnpm install --frozen-lockfile + +# 复制源码 +COPY . . + +# 构建生产版本 +RUN pnpm build + +# === 阶段2: 运行阶段 === +FROM nginx:alpine + +# 复制 nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 复制构建产物 +COPY --from=builder /app/dist /usr/share/nginx/html + +# 暴露端口 +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..53cdf46 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,47 @@ +# ============================================================ +# FilesReadSystem Nginx 配置 +# 反向代理 API 请求到后端 +# ============================================================ + +server { + listen 80; + server_name localhost; + + # 前端静态文件 + root /usr/share/nginx/html; + index index.html; + + # SPA 支持 - 所有请求都尝试返回 index.html + location / { + try_files $uri $uri/ /index.html; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 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 /uploads/ { + proxy_pass http://backend:8000/uploads/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + client_max_body_size 100M; + } +} \ No newline at end of file