# AI Platform 打包部署指南 ## 项目概况 | 项目 | 说明 | |------|------| | 项目名称 | ai-platform | | 前端框架 | React 18 + TypeScript + Vite 8 | | UI 库 | Ant Design 5 + Tailwind CSS 3 | | 构建产物 | 静态文件(`dist/`),由 nginx 提供服务 | | 容器化 | Docker 多阶段构建(builder → nginx:alpine) | | 部署端口 | 8901 | | 后端代理 | `http://host.docker.internal:8000`(可运行时覆盖) | ## 目录结构要点 ``` ai-platform/ ├── Dockerfile # 多阶段构建:node:20-alpine 构建 → nginx:alpine 运行 ├── nginx.conf # nginx 配置模板(支持 ${BACKEND_URL} 环境变量替换) ├── .dockerignore # 排除 node_modules / dist / .git / *.md 等 ├── package.json # build 脚本:tsc -b && vite build ├── vite.config.ts # 开发代理配置(/api/v1, /health, /bexapi, /iam-api) ├── index.html # 入口 HTML ├── src/ # 源码 ├── public/ # 静态资源 └── docs/ # 文档(本文件所在目录) ``` ## Docker 镜像构建 ### Dockerfile 构建流程 1. **Stage 1 — builder(node:20-alpine)** - 复制 `package.json` + `package-lock.json`,执行 `npm install` - 复制全部源码,执行 `npm run build`(即 `tsc -b && vite build`) - 产物输出到 `/app/dist` 2. **Stage 2 — 运行时(nginx:alpine)** - 将 builder 阶段的 `/app/dist` 复制到 `/usr/share/nginx/html` - 复制 `nginx.conf` 到 `/etc/nginx/templates/default.conf.template`(nginx 入口脚本会通过 `envsubst` 替换其中的 `${BACKEND_URL}`) - 默认环境变量 `BACKEND_URL=http://host.docker.internal:8000` - 暴露端口 8901 ### 构建命令 ```bash sudo docker build -t ai-platform:1.0.0 . ``` 构建过程利用了 Docker 缓存层:如果 `package.json` / `package-lock.json` 未变,`npm install` 层会命中缓存,仅重新执行源码复制和构建。 ## 容器部署 ### 启动命令 ```bash sudo docker run -d \ --name ai-platform \ -p 8901:8901 \ --add-host=host.docker.internal:host-gateway \ -e BACKEND_URL=http://host.docker.internal:8000 \ ai-platform:1.0.0 ``` ### 参数说明 | 参数 | 说明 | |------|------| | `-d` | 后台运行 | | `--name ai-platform` | 容器名称 | | `-p 8901:8901` | 宿主机 8901 端口映射到容器 8901 | | `--add-host=host.docker.internal:host-gateway` | **必须添加**,否则 nginx 启动时无法解析 `host.docker.internal`,容器会立即退出(exit code 1) | | `-e BACKEND_URL=...` | 后端 API 地址,运行时可覆盖。默认 `http://host.docker.internal:8000` | > **踩坑记录**:Docker 29.x 版本中,`host.docker.internal` 不会自动解析,必须通过 `--add-host=host.docker.internal:host-gateway` 显式添加。如果不加此参数,nginx 启动报错:`host not found in upstream "host.docker.internal"`,容器退出码为 1。 ### 重新部署(完整流程) ```bash # 1. 构建新镜像 sudo docker build -t ai-platform:1.0.0 . # 2. 停止并移除旧容器 sudo docker stop ai-platform sudo docker rm ai-platform # 3. 启动新容器 sudo docker run -d \ --name ai-platform \ -p 8901:8901 \ --add-host=host.docker.internal:host-gateway \ -e BACKEND_URL=http://host.docker.internal:8000 \ ai-platform:1.0.0 ``` ### 验证部署 ```bash # 检查容器运行状态 sudo docker ps --filter "name=ai-platform" # 从容器内部验证 nginx 响应 sudo docker exec ai-platform curl -s -o /dev/null -w "HTTP %{http_code}" http://localhost:8901/ # 预期输出:HTTP 200 # 查看页面内容 sudo docker exec ai-platform curl -s http://localhost:8901/ # 查看 nginx 日志 sudo docker logs ai-platform ``` ## nginx 配置说明 `nginx.conf` 作为模板文件放在 `/etc/nginx/templates/default.conf.template`,nginx 容器入口脚本会自动用 `envsubst` 将 `${BACKEND_URL}` 替换为环境变量值,输出到 `/etc/nginx/conf.d/default.conf`。 ### 路由规则 | 路径 | 代理目标 | 说明 | |------|----------|------| | `/api/v1/` | `${BACKEND_URL}/api/v1/` | 后端 API,支持 SSE 长连接(关闭 buffering,300s 超时) | | `/health` | `${BACKEND_URL}/health` | 健康检查 | | `/bexapi/` | `https://bexell-scheduling.dongsk.top/api/` | Bexell 排程 API | | `/iam-api/` | `https://iam.digiwincloud.com.cn/` | 鼎捷云 IAM 登录服务 | | `/` | `try_files $uri $uri/ /index.html` | SPA 路由 fallback | | `/assets/` | 本地静态文件 | 长缓存 1 年,`Cache-Control: public, immutable` | | `*.svg/png/jpg/...` | 本地静态文件 | 缓存 30 天 | ### 开发环境 vs 生产环境代理对照 开发时由 Vite dev server 代理(`vite.config.ts`),生产时由 nginx 代理(`nginx.conf`),两者路径需保持一致: | 路径 | Vite target | nginx proxy_pass | |------|------------|-------------------| | `/api/v1` | `http://localhost:8000` | `${BACKEND_URL}/api/v1/` | | `/health` | `http://localhost:8000` | `${BACKEND_URL}/health` | | `/bexapi` | `https://bexell-scheduling.dongsk.top`(rewrite → `/api`) | `https://bexell-scheduling.dongsk.top/api/` | | `/iam-api` | `https://iam.digiwincloud.com.cn`(rewrite → 去前缀) | `https://iam.digiwincloud.com.cn/` | ## 常见问题排查 ### 容器启动后立即退出(exit code 1) **原因**:nginx 无法解析 `host.docker.internal`。 **解决**:启动时添加 `--add-host=host.docker.internal:host-gateway`。 **验证**:`sudo docker logs ai-platform` 查看是否有 `host not found in upstream` 错误。 ### 构建时 TypeScript 编译失败 `npm run build` 先执行 `tsc -b` 类型检查,类型错误会导致构建中断。需修复所有 TS 错误后重新构建。 ### 构建产物体积过大 当前主 chunk 约 1.9MB(gzip 后 581KB),Vite 会输出警告。如需优化: - 使用动态 `import()` 对路由模块做懒加载(代码分割) - 在 `vite.config.ts` 中配置 `build.rollupOptions.output.manualChunks` 手动分包 - 调整 `build.chunkSizeWarningLimit` 提高警告阈值(仅消除警告,不减小体积) ### 端口被占用 ```bash # 查看占用 8901 端口的进程 sudo lsof -i :8901 # 停止旧容器后释放端口 sudo docker stop ai-platform ``` ## 快速部署脚本 将以下内容保存为 `deploy.sh` 即可一键重新打包部署: ```bash #!/bin/bash set -e IMAGE_NAME="ai-platform:1.0.0" CONTAINER_NAME="ai-platform" PORT=8901 BACKEND_URL="${BACKEND_URL:-http://host.docker.internal:8000}" echo ">>> 构建镜像..." sudo docker build -t "$IMAGE_NAME" . echo ">>> 停止旧容器..." sudo docker stop "$CONTAINER_NAME" 2>/dev/null || true sudo docker rm "$CONTAINER_NAME" 2>/dev/null || true echo ">>> 启动新容器..." sudo docker run -d \ --name "$CONTAINER_NAME" \ -p "$PORT:8901" \ --add-host=host.docker.internal:host-gateway \ -e BACKEND_URL="$BACKEND_URL" \ "$IMAGE_NAME" echo ">>> 验证..." sleep 2 sudo docker exec "$CONTAINER_NAME" curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:8901/ echo ">>> 部署完成: http://localhost:$PORT" ```