This commit is contained in:
zhangqing 2026-06-15 15:38:06 +08:00
parent a4a9c69b37
commit eb44dc3491
6 changed files with 99 additions and 15 deletions

View File

@ -1,3 +1,5 @@
ENTERPRISE_BACKEND_VERSION=prod-v1.1.0
# ==================== 服务器配置 ====================
# API 服务器配置
API.HOST=0.0.0.0

View File

@ -1,16 +1,92 @@
# Python 3.11 + uv与 pyproject 中 requires-python 一致)
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
# =============================================================================
# 阶段一:编译阶段
# 将核心业务目录编译为原生二进制,源码不进入最终镜像
# =============================================================================
FROM python:3.11-slim-bookworm AS builder
WORKDIR /app
WORKDIR /build
# 替换为阿里云 apt 镜像源,解决国内网络访问 Debian 官方源超时问题
RUN sed -i 's|http://deb.debian.org|http://mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources \
|| sed -i 's|http://deb.debian.org|http://mirrors.aliyun.com|g' /etc/apt/sources.list
# 系统依赖gcc/g++ 用于 Nuitka 编译patchelf 用于修正共享库路径
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc g++ patchelf ccache \
&& rm -rf /var/lib/apt/lists/*
# 配置镜像源
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_HTTP_TIMEOUT=120
ENV UV_HTTP_RETRIES=5
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
# nuitka 安装到 venv 中,确保与 .venv/bin/python 一致
RUN uv pip install --no-cache-dir nuitka
# 复制所有源码
COPY . .
# 编译需要保护的目录admin api core logger models services tools utils
# --module编译为可 import 的扩展模块(.so而不是独立可执行文件
# --follow-import-to只跟踪项目自身的包不拉入标准库和第三方包
# --output-dir编译产物输出目录每个包单独编译
RUN for pkg in admin api core logger models services tools utils; do \
uv run python -m nuitka \
--module \
--follow-import-to=admin \
--follow-import-to=api \
--follow-import-to=core \
--follow-import-to=logger \
--follow-import-to=models \
--follow-import-to=services \
--follow-import-to=tools \
--follow-import-to=utils \
--include-package=${pkg} \
--output-dir=/compiled \
--remove-output \
${pkg} ; \
done
# =============================================================================
# 阶段二:运行阶段
# 只包含编译产物和必要运行时,不含任何 .py 源码
# =============================================================================
FROM python:3.11-slim-bookworm
WORKDIR /app
ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/
ENV UV_HTTP_TIMEOUT=120
ENV UV_HTTP_RETRIES=5
RUN pip install --no-cache-dir uv
# 复制依赖声明文件并安装生产依赖
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
# 从编译阶段复制二进制模块(.so 文件)
COPY --from=builder /compiled/ ./
# 保留必要的明文文件
# main.pyuvicorn 入口,只有两行 import不含业务逻辑
COPY main.py ./
# prompt/:提示词模板,运行时动态读取
COPY prompt/ ./prompt/
# services/fonts/:字体资源文件
COPY services/fonts/ ./services/fonts/
# 工作目录固定为 /app所有路径相关代码基于 cwd 解析
ENV PYTHONUNBUFFERED=1
ENV APP_DIR=/app
EXPOSE 7861 7862
EXPOSE 7862
# 端口以环境变量 API.PORT / API_PORT 为准(见 core.config
CMD ["uv", "run", "python", "-m", "main"]

View File

@ -11,8 +11,11 @@ from urllib.parse import quote_plus
from pydantic import AliasChoices, Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
# backend/ 目录(与 uvicorn CWD 无关,始终读取该目录下的 .env
_BACKEND_DIR = Path(__file__).resolve().parent.parent
# backend/ 目录
# 编译为二进制后 __file__ 路径不可靠,优先使用环境变量 APP_DIR
# 其次使用当前工作目录Docker WORKDIR /app 固定为 /app
import os as _os
_BACKEND_DIR = Path(_os.environ.get("APP_DIR", "")).resolve() if _os.environ.get("APP_DIR") else Path(_os.getcwd())
class Settings(BaseSettings):

View File

@ -14,11 +14,14 @@ FastAPI 应用主模块
也可使用 ``uvicorn core.main:app`` ``main:app`` 等价
默认 host/port 来自配置项 api_host / api_port可通过环境变量覆盖
"""
import os
from pathlib import Path
from dotenv import load_dotenv
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
# 编译为二进制后 __file__ 路径不可靠使用工作目录Docker WORKDIR /app
_env_path = Path(os.environ.get("APP_DIR", os.getcwd())) / ".env"
load_dotenv(_env_path)
import os
from contextlib import asynccontextmanager
@ -155,8 +158,8 @@ def create_app() -> FastAPI:
# 注册全局异常处理器
register_exception_handlers(app)
# 静态文件服务(backend/core/main.py -> backend/static
static_path = Path(__file__).resolve().parent.parent / "static"
# 静态文件服务(相对工作目录 /app/static
static_path = Path(os.getcwd()) / "static"
if static_path.exists():
app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
logger.info(f"静态文件目录已挂载: {static_path}")

View File

@ -3,7 +3,7 @@ services:
build:
context: .
dockerfile: Dockerfile
image: registry.cn-hangzhou.aliyuncs.com/qing358/zlsj-huoyan-enterprise-backend-viersin:${ENTERPRISE_BACKEND_VERSION:-latest}
image: registry.cn-hangzhou.aliyuncs.com/qing358/zlsj-huoyan-enterprise-backend:${ENTERPRISE_BACKEND_VERSION:-latest}
env_file:
- .env
ports:

View File

@ -74,10 +74,10 @@ def setup_logger(
if Path(log_dir_name).is_absolute():
log_dir = Path(log_dir_name)
else:
# 相对路径:使用项目根目录
# __file__ 是 backend/logger/logging.py需要向上两级到项目根目录
project_root = Path(__file__).parent.parent
log_dir = project_root / log_dir_name
# 相对路径:使用工作目录(编译为二进制后 __file__ 不可靠)
# Docker WORKDIR /app直接相对于 cwd 解析
import os
log_dir = Path(os.getcwd()) / log_dir_name
else:
log_dir = Path(log_dir)