重写独立dockerfile

This commit is contained in:
2026-05-29 18:19:30 +08:00
parent 823a387118
commit 77bdf56adf
14 changed files with 640 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
# ============================================
# .dockerignore — 排除无关文件,加速构建
# ============================================
# Python
__pycache__/
*.pyc
*.pyo
*.egg-info/
.eggs/
dist/
build/
# Virtual environments
.env
venv/
env/
.conda/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Git
.git/
.gitignore
.gitattributes
# 前端(仅在需要构建时包含)
frontend/node_modules/
frontend/dist/
# 测试
tests/
.pytest_cache/
.coverage
htmlcov/
# 文档
docs/
*.md
README*
# 临时文件
*.log
logs/
temp/
tmp/
# Docker
Dockerfile*
docker-compose*.yml
deploy/
# 其他
.trae/
scripts/
pip_audit_local.json
temp_requirements_audit.txt
local_src.txt
git_src.txt
start.sh
CHANGELOG.md
LICENSE
+64
View File
@@ -0,0 +1,64 @@
# ============================================
# 环境变量模板 — 独立部署版
# ============================================
# 复制为 .env 并修改配置
# 数据库
DB_HOST=postgres
DB_PORT=5432
DB_NAME=moldinsight
DB_USER=moldinsight_user
DB_PASSWORD=moldinsight_password
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
# MinIO (RustFS 兼容存储)
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
# 认证
SECRET_KEY=your-secret-key-change-in-production-min-32-chars
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440
# 管理员
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-this-to-a-secure-password
ADMIN_EMAIL=admin@gemold.com
ADMIN_FULL_NAME=系统管理员
# 端口
MOLDINSIGHT_PORT=8000
INVENTORY_PORT=8001
# 文件上传
UPLOAD_DIR=./uploads
MAX_FILE_SIZE=104857600
ALLOWED_EXTENSIONS=.stp,.step,.stp.gz
# 几何处理
POINTCLOUD_SAMPLE_COUNT=10000
MESH_QUALITY=high
PARALLEL_PROCESSING=true
# RustFS 对象存储
RUSTFS_ENDPOINT=http://minio:9000
RUSTFS_ACCESS_KEY=minioadmin
RUSTFS_SECRET_KEY=minioadmin
RUSTFS_TIMEOUT=30
RUSTFS_PRESIGNED_URL_EXPIRES=3600
# LLM (可选)
LLM_ENABLED=false
LLM_API_URL=https://api.openai.com/v1
LLM_API_KEY=sk-your-api-key
LLM_MODEL=gpt-4o-mini
LLM_TIMEOUT=60
LLM_MAX_TOKENS=2000
# FreeCAD 验证 (可选)
ENABLE_FREECAD_VERIFICATION=false
FREECAD_VERIFICATION_TIMEOUT=120
+17
View File
@@ -0,0 +1,17 @@
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
COPY deploy/requirements-base.txt .
RUN pip install --no-cache-dir -r requirements-base.txt && \
rm requirements-base.txt
COPY src/shared/ /app/src/shared/
ENV PYTHONPATH=/app/src
ENV PYTHONUNBUFFERED=1
+3
View File
@@ -0,0 +1,3 @@
FROM gemold-moldinsight:latest
CMD ["celery", "-A", "celery_app", "worker", "--workdir=/app/src", "--concurrency=2", "--loglevel=info"]
+14
View File
@@ -0,0 +1,14 @@
FROM gemold-base:latest
COPY src/inventory/ /app/src/inventory/
COPY src/entrypoints/ /app/src/entrypoints/
COPY static/ /app/static/
RUN mkdir -p /app/logs
EXPOSE 8001
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=30s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8001/health')" || exit 1
CMD ["python", "-m", "uvicorn", "entrypoints.inventory:app", "--host", "0.0.0.0", "--port", "8001"]
+30
View File
@@ -0,0 +1,30 @@
FROM continuumio/miniconda3:latest AS pythonocc
RUN conda update -n base -c defaults conda -y && \
conda create -n moldinsight python=3.11 pythonocc-core=7.9.0 -c conda-forge -y
FROM gemold-base:latest
COPY --from=pythonocc /opt/conda/envs/moldinsight/lib/python3.11/site-packages/ /usr/local/lib/python3.11/site-packages/
COPY deploy/requirements-moldinsight.txt .
RUN pip install --no-cache-dir -r requirements-moldinsight.txt && \
rm requirements-moldinsight.txt
COPY src/moldinsight/ /app/src/moldinsight/
COPY src/inventory/ /app/src/inventory/
COPY src/entrypoints/ /app/src/entrypoints/
COPY src/celery_app.py src/celery_tasks.py /app/src/
COPY uploads/ /app/uploads/
COPY static/ /app/static/
COPY html_output/ /app/html_output/
RUN mkdir -p /app/logs
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=60s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["python", "-m", "uvicorn", "entrypoints.moldinsight:app", "--host", "0.0.0.0", "--port", "8000"]
+32
View File
@@ -0,0 +1,32 @@
@echo off
REM ============================================
REM 构建脚本 — 独立部署 (Windows)
REM ============================================
cd /d "%~dp0\.."
echo === 构建基础镜像 ===
docker build -t gemold-base:latest -f deploy\Dockerfile.base .
echo.
echo === 构建 MoldInsight 镜像 (含 PythonOCC) ===
docker build -t gemold-moldinsight:latest -f deploy\Dockerfile.moldinsight .
echo.
echo === 构建 Inventory 镜像 ===
docker build -t gemold-inventory:latest -f deploy\Dockerfile.inventory .
echo.
echo === 构建 Celery Worker 镜像 ===
docker build -t gemold-celery:latest -f deploy\Dockerfile.celery .
echo.
echo === 全部构建完成 ===
echo.
echo 启动完整系统:
echo cd deploy ^&^& docker compose --profile full up -d
echo.
echo 仅启动进销存:
echo cd deploy ^&^& docker compose --profile inventory up -d
echo.
echo 仅启动模具分析:
echo cd deploy ^&^& docker compose --profile moldinsight up -d
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
# ============================================
# 构建脚本 — 独立部署
# ============================================
set -e
cd "$(dirname "$0")/.."
echo "=== 构建基础镜像 ==="
docker build -t gemold-base:latest -f deploy/Dockerfile.base .
echo ""
echo "=== 构建 MoldInsight 镜像 (含 PythonOCC) ==="
docker build -t gemold-moldinsight:latest -f deploy/Dockerfile.moldinsight .
echo ""
echo "=== 构建 Inventory 镜像 ==="
docker build -t gemold-inventory:latest -f deploy/Dockerfile.inventory .
echo ""
echo "=== 构建 Celery Worker 镜像 ==="
docker build -t gemold-celery:latest -f deploy/Dockerfile.celery .
echo ""
echo "=== 全部构建完成 ==="
echo ""
echo "启动完整系统:"
echo " cd deploy && docker compose --profile full up -d"
echo ""
echo "仅启动进销存:"
echo " cd deploy && docker compose --profile inventory up -d"
echo ""
echo "仅启动模具分析:"
echo " cd deploy && docker compose --profile moldinsight up -d"
+163
View File
@@ -0,0 +1,163 @@
services:
postgres:
image: postgres:15
container_name: gemold_postgres
environment:
POSTGRES_DB: ${DB_NAME:-moldinsight}
POSTGRES_USER: ${DB_USER:-moldinsight_user}
POSTGRES_PASSWORD: ${DB_PASSWORD:-moldinsight_password}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-moldinsight_user} -d ${DB_NAME:-moldinsight}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- gemold_network
redis:
image: redis:7-alpine
container_name: gemold_redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- gemold_network
minio:
image: minio/minio:latest
container_name: gemold_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
restart: unless-stopped
networks:
- gemold_network
moldinsight:
build:
context: ..
dockerfile: deploy/Dockerfile.moldinsight
image: gemold-moldinsight:latest
container_name: gemold_moldinsight
ports:
- "${MOLDINSIGHT_PORT:-8000}:8000"
environment:
HOST: 0.0.0.0
PORT: "8000"
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: ${DB_NAME:-moldinsight}
DB_USER: ${DB_USER:-moldinsight_user}
DB_PASSWORD: ${DB_PASSWORD:-moldinsight_password}
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
RUSTFS_ENDPOINT: http://minio:9000
RUSTFS_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
RUSTFS_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
SECRET_KEY: ${SECRET_KEY:-change-me-in-production}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
restart: unless-stopped
profiles:
- full
- moldinsight
networks:
- gemold_network
moldinsight-celery:
build:
context: ..
dockerfile: deploy/Dockerfile.celery
container_name: gemold_celery
environment:
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: ${DB_NAME:-moldinsight}
DB_USER: ${DB_USER:-moldinsight_user}
DB_PASSWORD: ${DB_PASSWORD:-moldinsight_password}
REDIS_HOST: redis
REDIS_PORT: "6379"
RUSTFS_ENDPOINT: http://minio:9000
RUSTFS_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
RUSTFS_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
depends_on:
- moldinsight
restart: unless-stopped
profiles:
- full
- moldinsight
networks:
- gemold_network
inventory:
build:
context: ..
dockerfile: deploy/Dockerfile.inventory
image: gemold-inventory:latest
container_name: gemold_inventory
ports:
- "${INVENTORY_PORT:-8001}:8001"
environment:
HOST: 0.0.0.0
PORT: "8001"
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: ${DB_NAME:-moldinsight}
DB_USER: ${DB_USER:-moldinsight_user}
DB_PASSWORD: ${DB_PASSWORD:-moldinsight_password}
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
SECRET_KEY: ${SECRET_KEY:-change-me-in-production}
ADMIN_USERNAME: ${ADMIN_USERNAME:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-admin123}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
profiles:
- full
- inventory
networks:
- gemold_network
volumes:
postgres_data:
driver: local
minio_data:
driver: local
networks:
gemold_network:
driver: bridge
+30
View File
@@ -0,0 +1,30 @@
-i https://mirrors.aliyun.com/pypi/simple/
--trusted-host mirrors.aliyun.com
fastapi>=0.100.0
uvicorn[standard]>=0.22.0
pydantic>=2.0.0
python-multipart>=0.0.6
sqlalchemy>=2.0.0
psycopg2-binary>=2.9.0
asyncpg>=0.28.0
alembic>=1.11.0
python-jose[cryptography]>=3.3.0
bcrypt>=4.0.0
passlib>=1.7.4
email-validator>=2.0.0
aiofiles>=23.0.0
orjson>=3.9.0
python-dotenv>=1.0.0
jinja2>=3.1.0
Pillow>=12.2.0
pyyaml>=6.0
python-dateutil>=2.8.0
loguru>=0.7.0
httpx>=0.24.0
redis>=4.5.0
+20
View File
@@ -0,0 +1,20 @@
-i https://mirrors.aliyun.com/pypi/simple/
--trusted-host mirrors.aliyun.com
trimesh>=3.21.0
numpy>=1.24.0
scipy>=1.10.0
pyvista>=0.38.0
minio>=7.1.0
aiohttp>=3.13.4
celery[redis]>=5.3.0
kafka-python>=2.0.2
pytest>=7.0.0
pytest-asyncio>=0.21.0
aiosqlite>=0.19.0
black>=23.0.0
flake8>=6.0.0
mypy>=1.0.0
View File
+78
View File
@@ -0,0 +1,78 @@
import os, sys
from pathlib import Path
src_root = Path(__file__).parent.parent
sys.path.insert(0, str(src_root))
os.chdir(Path(__file__).parent.parent.parent)
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
import asyncio, time
from shared.config.settings import settings
from shared.services.auth_routes import router as auth_router
from shared.utils.logger import setup_logging, get_logger
from shared.database.init_db import init_database
setup_logging()
logger = get_logger(__name__)
app = FastAPI(title="Gemold - 进销存管理系统", version="4.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
if response.status_code >= 400:
logger.warning(f"[HTTP] {request.method} {request.url.path} -> {response.status_code} ({duration:.2f}s)")
return response
@app.on_event("startup")
async def startup_event():
success = await init_database(keep_connected=True)
print(f"[{'OK' if success else 'FAIL'}] 数据库初始化")
try:
from shared.services.redis_task_manager import redis_task_manager
await redis_task_manager.connect()
print(f"[{'OK' if redis_task_manager.is_connected else 'WARN'}] Redis")
except Exception as e:
print(f"[WARN] Redis异常: {e}")
@app.on_event("shutdown")
async def shutdown_event():
try:
from shared.services.redis_task_manager import redis_task_manager
await redis_task_manager.disconnect()
except: pass
app.mount("/static", StaticFiles(directory=os.path.join(os.getcwd(), "static")), name="static")
app.include_router(auth_router)
try:
from inventory.api import inventory_router
app.include_router(inventory_router)
except Exception as e:
print(f"[WARN] Inventory 路由: {e}")
@app.get("/health")
async def health():
from shared.database.database import db_manager
from sqlalchemy import text
db_ok = False; db_error = None
try:
if not db_manager.is_connected: await db_manager.connect()
async with db_manager.engine.begin() as conn: await conn.execute(text("SELECT 1"))
db_ok = True
except Exception as e: db_error = str(e)
return {"status": "healthy", "service": "inventory", "version": "4.0.0", "database_connected": db_ok, "database_error": db_error}
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str):
return FileResponse(os.path.join(os.getcwd(), "static", "index.html"))
+88
View File
@@ -0,0 +1,88 @@
import os, sys
from pathlib import Path
src_root = Path(__file__).parent.parent
sys.path.insert(0, str(src_root))
os.chdir(Path(__file__).parent.parent.parent)
from fastapi import FastAPI, Request
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, FileResponse
import asyncio, time
from shared.config.settings import settings
from shared.services.auth_routes import router as auth_router
from shared.utils.logger import setup_logging, get_logger
from shared.database.init_db import init_database
setup_logging()
logger = get_logger(__name__)
app = FastAPI(title="Gemold - 模具分析引擎", version="4.0.0")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
response = await call_next(request)
duration = time.time() - start_time
if response.status_code >= 400:
logger.warning(f"[HTTP] {request.method} {request.url.path} -> {response.status_code} ({duration:.2f}s)")
return response
@app.on_event("startup")
async def startup_event():
success = await init_database(keep_connected=True)
print(f"[{'OK' if success else 'FAIL'}] 数据库初始化")
try:
from moldinsight.storage.rustfs_storage import rustfs_manager
await rustfs_manager.connect(endpoint=settings.RUSTFS_ENDPOINT, access_key=settings.RUSTFS_ACCESS_KEY, secret_key=settings.RUSTFS_SECRET_KEY, timeout=settings.RUSTFS_TIMEOUT)
print("[OK] RustFS连接成功")
except Exception as e:
print(f"[WARN] RustFS连接失败: {e}")
try:
from shared.services.redis_task_manager import redis_task_manager
await redis_task_manager.connect()
print(f"[{'OK' if redis_task_manager.is_connected else 'WARN'}] Redis")
except Exception as e:
print(f"[WARN] Redis异常: {e}")
@app.on_event("shutdown")
async def shutdown_event():
try:
from shared.services.redis_task_manager import redis_task_manager
await redis_task_manager.disconnect()
except: pass
UPLOAD_DIR = Path("uploads"); UPLOAD_DIR.mkdir(exist_ok=True)
Path("html_output").mkdir(exist_ok=True)
app.mount("/static", StaticFiles(directory=os.path.join(os.getcwd(), "static")), name="static")
app.mount("/html", StaticFiles(directory=os.path.join(os.getcwd(), "html_output")), name="html")
app.include_router(auth_router)
try:
from moldinsight.api import router as moldinsight_router
app.include_router(moldinsight_router, prefix="/api")
except Exception as e:
print(f"[WARN] MoldInsight 路由: {e}")
@app.get("/health")
async def health():
from shared.database.database import db_manager
from sqlalchemy import text
db_ok = False; db_error = None
try:
if not db_manager.is_connected: await db_manager.connect()
async with db_manager.engine.begin() as conn: await conn.execute(text("SELECT 1"))
db_ok = True
except Exception as e: db_error = str(e)
return {"status": "healthy", "service": "moldinsight", "version": "4.0.0", "database_connected": db_ok, "database_error": db_error}
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str):
return FileResponse(os.path.join(os.getcwd(), "static", "index.html"))