From b7073d9c5b03eed2b02fe6b56586bfd1491d422a Mon Sep 17 00:00:00 2001 From: yuqei Date: Thu, 18 Jun 2026 22:27:00 +0800 Subject: [PATCH] feat: enhance production deployment configuration and documentation - Update .env.example with production-ready environment variables - Add frontend Aliyun deployment configuration with nginx gateway - Refactor docker-compose.server.yml for external services - Add comprehensive deployment guide (docs/DEPLOYMENT.md) - Enhance troubleshooting documentation with Git Bash path issues - Add deployment scripts (build-frontend.sh, deploy-frontend.sh) - Add deploy directory with docker-compose and nginx config - Update .gitignore to exclude temporary directories Co-Authored-By: Claude --- .env.example | 50 +++-- .gitignore | 3 + deploy/README.md | 117 ++++++++++++ deploy/docker-compose.yml | 36 ++++ deploy/web-gateway.conf | 71 +++++++ docker-compose.frontend.aliyun.yml | 45 ++++- docker-compose.server.yml | 40 ++-- docs/DEPLOYMENT.md | 289 +++++++++++++++++++++++++++++ docs/DEPLOYMENT_TROUBLESHOOTING.md | 112 +++++++++-- scripts/build-frontend.sh | 36 ++++ scripts/deploy-frontend.sh | 33 ++++ 11 files changed, 768 insertions(+), 64 deletions(-) create mode 100644 deploy/README.md create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/web-gateway.conf create mode 100644 docs/DEPLOYMENT.md create mode 100644 scripts/build-frontend.sh create mode 100644 scripts/deploy-frontend.sh diff --git a/.env.example b/.env.example index c12f0289..41fee33b 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,35 @@ -# frontend -NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 - -# backend +# ===== 应用 ===== APP_NAME=AI Contract Platform -APP_ENV=development -SHARED_AI_PLATFORM_BASE_URL= -# Default shared-ai contract version is v0.2. Override to /api/v0.1 only for temporary compatibility with legacy deployments. +APP_ENV=production + +# ===== 安全 ===== +SECRET_KEY=change_me_to_a_random_64_plus_character_secret_for_production + +# ===== 数据库(外部共享 PostgreSQL) ===== +DATABASE_URL=postgresql+psycopg://yuqei:change_me@192.168.3.90:5432/ai_contract_platform + +# ===== Redis ===== +REDIS_URL=redis://redis:6379/0 + +# ===== MinIO(外部共享) ===== +MINIO_ENDPOINT=192.168.3.90:9000 +MINIO_ACCESS_KEY=change_me_minio_access_key +MINIO_SECRET_KEY=change_me_minio_secret_key +MINIO_BUCKET=contracts + +# ===== Shared AI Platform ===== +SHARED_AI_PLATFORM_BASE_URL=http://192.168.3.90:18011 SHARED_AI_PLATFORM_API_PREFIX=/api/v0.2 SHARED_AI_PLATFORM_APP_ID=ai-contract-platform -SHARED_AI_PLATFORM_API_KEY= -SHARED_AI_PLATFORM_TIMEOUT_SECONDS=30 -SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED=false -SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL=true -SECRET_KEY=change-me -DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/ai_contract_platform -REDIS_URL=redis://localhost:6379/0 -MINIO_ENDPOINT=localhost:9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin -MINIO_BUCKET=contracts +SHARED_AI_PLATFORM_API_KEY=change_me_shared_ai_api_key +SHARED_AI_PLATFORM_TIMEOUT_SECONDS=180 +SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED=true +SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL=false + +# ===== Legal Change Impact Worker ===== +LEGAL_CHANGE_IMPACT_WORKER_BATCH_SIZE=10 +LEGAL_CHANGE_IMPACT_WORKER_INTERVAL_SECONDS=300 +LEGAL_CHANGE_IMPACT_SEMANTIC_ENABLED=true + +# ===== 前端(阿里云部署) ===== +NEXT_PUBLIC_API_BASE_URL=/api/v1 diff --git a/.gitignore b/.gitignore index d287a170..c3123dbe 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ project-docs/automation-artifacts/ backend/project-docs/qa-artifacts/ .claude/worktrees/ .gstack/ + +# Temporary directories +temp-*/ diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..6f34cac1 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,117 @@ +# AI Contract Platform 部署说明 + +## 部署架构 + +与 hyburk.com 部署方式一致,采用前后端分离部署: + +| 组件 | 部署位置 | 目录 | 说明 | +|------|---------|------|------| +| 前端 Next.js | 阿里云 ECS | `/opt/apps/ai-contract-web` | nginx gateway + Next.js 容器 | +| 后端 FastAPI | 192.168.3.90 | `/opt/apps/ai-contract-platform` | backend + worker + redis | +| 后端 Gateway | 192.168.3.90 | `/opt/apps/ai-contract-platform-gateway` | nginx 代理 API | +| 共享 PostgreSQL | 192.168.3.90 | - | 端口 5432 | +| 共享 Redis | 192.168.3.90 | - | 端口 6379 | +| 共享 MinIO | 192.168.3.90 | - | 端口 9000 | + +**域名**: `contact.yuqei.com` + +**流量路径**: +``` +contact.yuqei.com → 阿里云 nginx (127.0.0.1:18083) + ├── /api/ → 后端服务器 10.20.0.11:18083 → backend:8000 + ├── /health → 后端服务器 health check + └── 其他 → 阿里云 Next.js 容器:3000 +``` + +## 配置文件说明 + +| 文件 | 用途 | +|------|------| +| `docker-compose.server.yml` | 后端服务器部署(192.168.3.90) | +| `docker-compose.frontend.aliyun.yml` | 前端阿里云部署模板 | +| `deploy/web-gateway.conf` | 阿里云 nginx 配置(前端 gateway) | +| `.env.example` | 后端环境变量模板 | + +## 阿里云前端部署目录结构 + +``` +/opt/apps/ai-contract-web/ +├── releases/ +│ └── YYYYMMDDHHMMSS/ +│ ├── docker-compose.yml # 使用 docker-compose.frontend.aliyun.yml +│ ├── nginx/ +│ │ └── web-gateway.conf # nginx 配置 +│ └── app/ # 前端代码(从 frontend/ 复制) +│ ├── Dockerfile +│ └── ... (Next.js 项目文件) +├── current → releases/YYYYMMDDHHMMSS # 软链接指向当前版本 +└── logs/ + └── nginx/ +``` + +## 后端服务器部署目录结构 + +``` +/opt/apps/ai-contract-platform/ +├── docker-compose.server.yml +├── .env +├── backend/ +│ ├── Dockerfile +│ └── ... (FastAPI 项目文件) +└── frontend/ # 可选,本地开发用 +``` + +## 部署步骤 + +### 1. 后端部署(192.168.3.90) + +```bash +cd /opt/apps/ai-contract-platform +git pull +docker compose -f docker-compose.server.yml up -d --build +``` + +### 2. 前端部署(阿里云) + +```bash +# 创建发布目录 +TIMESTAMP=$(date +%Y%m%d%H%M%S) +mkdir -p /opt/apps/ai-contract-web/releases/$TIMESTAMP +cd /opt/apps/ai-contract-web/releases/$TIMESTAMP + +# 复制配置和代码 +cp docker-compose.yml . # 来自项目 docker-compose.frontend.aliyun.yml +mkdir -p nginx app +cp web-gateway.conf nginx/ # 来自项目 deploy/web-gateway.conf +cp -r frontend/* app/ # 复制前端代码 + +# 构建和启动 +docker compose up -d --build + +# 更新软链接 +ln -sfn /opt/apps/ai-contract-web/releases/$TIMESTAMP /opt/apps/ai-contract-web/current +``` + +### 3. 验证 + +```bash +# 后端健康检查 +curl http://10.20.0.11:18083/health + +# 前端健康检查 +curl http://127.0.0.1:18083/healthz + +# 完整检查 +curl http://contact.yuqei.com/health +``` + +## 端口说明 + +| 端口 | 服务 | 位置 | +|------|------|------| +| 18083 | 前端 nginx gateway | 阿里云 127.0.0.1 | +| 18083 | 后端 nginx gateway | 192.168.3.90 10.20.0.11 | +| 8001 | 后端 FastAPI | 192.168.3.90 | +| 5432 | PostgreSQL | 192.168.3.90 | +| 6379 | Redis | 192.168.3.90 | +| 9000 | MinIO | 192.168.3.90 | \ No newline at end of file diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 00000000..1f5893ed --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,36 @@ +services: + ai-contract-web: + build: + context: ./app + dockerfile: Dockerfile + image: local/ai-contract-web:0.0.1 + container_name: ai-contract-web + restart: unless-stopped + environment: + NODE_ENV: production + HOSTNAME: 0.0.0.0 + PORT: 3000 + BACKEND_BASE_URL: http://10.20.0.11:18083 + NEXT_PUBLIC_API_BASE_URL: /api/v1 + extra_hosts: + - "10.20.0.11:host-gateway" + networks: + - ai-contract-web-proxy + + ai-contract-web-gateway: + image: nginx:1.27-alpine + container_name: ai-contract-web-gateway + restart: unless-stopped + ports: + - "127.0.0.1:18083:80" + volumes: + - ./nginx/web-gateway.conf:/etc/nginx/conf.d/default.conf:ro + - /opt/apps/ai-contract-web/logs/nginx:/var/log/nginx + depends_on: + - ai-contract-web + networks: + - ai-contract-web-proxy + +networks: + ai-contract-web-proxy: + name: ai-contract-web-proxy \ No newline at end of file diff --git a/deploy/web-gateway.conf b/deploy/web-gateway.conf new file mode 100644 index 00000000..1b03ba9e --- /dev/null +++ b/deploy/web-gateway.conf @@ -0,0 +1,71 @@ +upstream ai_contract_web { + server ai-contract-web:3000; +} + +upstream ai_contract_api { + server 10.20.0.11:18083; +} + +server { + listen 80; + server_name contact.yuqei.com; + + charset utf-8; + client_max_body_size 100m; + + location = /healthz { + access_log off; + return 200 'ok'; + add_header Content-Type text/plain; + } + + location /api/ { + proxy_pass http://ai_contract_api/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_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_buffering off; + proxy_connect_timeout 10s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + location = /health { + proxy_pass http://ai_contract_api/health; + 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; + } + + location /_next/static/ { + proxy_pass http://ai_contract_web; + proxy_http_version 1.1; + proxy_set_header Host $host; + add_header Cache-Control "public, max-age=31536000, immutable" always; + } + + location / { + proxy_pass http://ai_contract_web; + 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_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_hide_header Cache-Control; + proxy_hide_header Expires; + add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" always; + add_header Pragma "no-cache" always; + add_header Expires "0" always; + proxy_connect_timeout 10s; + proxy_send_timeout 120s; + proxy_read_timeout 120s; + } +} diff --git a/docker-compose.frontend.aliyun.yml b/docker-compose.frontend.aliyun.yml index a84f4448..8524f085 100644 --- a/docker-compose.frontend.aliyun.yml +++ b/docker-compose.frontend.aliyun.yml @@ -1,16 +1,47 @@ +# AI Contract Platform 前端阿里云部署配置 +# 部署位置: 阿里云 /opt/apps/ai-contract-web +# +# ⚠️ 重要提示:NEXT_PUBLIC_API_BASE_URL 和 BACKEND_BASE_URL 必须在构建时设置! +# 运行时的 environment 设置无效,因为这些值会被硬编码到 JS 文件中。 +# 构建命令:MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build + version: "3.9" services: - frontend: + ai-contract-web: build: - context: ./frontend + context: ./app dockerfile: Dockerfile - args: - BACKEND_BASE_URL: ${BACKEND_BASE_URL:-http://10.20.0.11:8001} + image: local/ai-contract-web:0.0.1 + container_name: ai-contract-web restart: unless-stopped environment: - NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-/api/v1} - BACKEND_BASE_URL: ${BACKEND_BASE_URL:-http://10.20.0.11:8001} NODE_ENV: production + HOSTNAME: 0.0.0.0 + PORT: 3000 + # 注意:以下环境变量在运行时设置无效! + # 它们必须在构建时通过 npm run build 设置: + # MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build + extra_hosts: + # 让容器能访问内网 IP(映射到 Docker 宿主机) + - "10.20.0.11:host-gateway" + networks: + - ai-contract-web-proxy + + ai-contract-web-gateway: + image: nginx:1.27-alpine + container_name: ai-contract-web-gateway + restart: unless-stopped ports: - - "${FRONTEND_HOST_PORT:-3003}:3000" + - "127.0.0.1:18083:80" + volumes: + - ./nginx/web-gateway.conf:/etc/nginx/conf.d/default.conf:ro + - /opt/apps/ai-contract-web/logs/nginx:/var/log/nginx + depends_on: + - ai-contract-web + networks: + - ai-contract-web-proxy + +networks: + ai-contract-web-proxy: + name: ai-contract-web-proxy \ No newline at end of file diff --git a/docker-compose.server.yml b/docker-compose.server.yml index be6c6d1c..078b2562 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -1,9 +1,16 @@ -version: "3.9" +# AI Contract Platform 后端生产部署配置 +# 部署位置: 192.168.3.90:/opt/apps/ai-contract-platform +# 使用外部共享服务(PostgreSQL、Redis、MinIO 在 192.168.3.90) +# 前端部署在阿里云,见 docker-compose.frontend.aliyun.yml + +name: ai-contract-platform services: redis: image: redis:7 restart: unless-stopped + networks: + - ai-contract-backend backend: build: @@ -37,6 +44,10 @@ services: - backend_storage:/app/storage ports: - "8001:8000" + extra_hosts: + - "host.docker.internal:192.168.3.90" + networks: + - ai-contract-backend legal-change-impact-worker: build: @@ -72,25 +83,14 @@ services: --interval-seconds 300 volumes: - backend_storage:/app/storage - - frontend: - profiles: - - single-host - build: - context: ./frontend - dockerfile: Dockerfile - args: - BACKEND_BASE_URL: ${FRONTEND_BACKEND_BASE_URL:-http://backend:8000} - restart: unless-stopped - depends_on: - backend: - condition: service_started - environment: - NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-/api/v1} - BACKEND_BASE_URL: ${FRONTEND_BACKEND_BASE_URL:-http://backend:8000} - NODE_ENV: production - ports: - - "${FRONTEND_HOST_PORT:-3003}:3000" + extra_hosts: + - "host.docker.internal:192.168.3.90" + networks: + - ai-contract-backend volumes: backend_storage: + +networks: + ai-contract-backend: + name: ai-contract-platform_default diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 00000000..5355a2e4 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,289 @@ +# AI Contract Platform 生产部署指南 + +> 本文档记录了实际部署过程中遇到的问题和解决方案,避免以后部署走弯路。 + +## 部署架构 + +| 组件 | 部署位置 | 目录 | 端口 | +|------|---------|------|------| +| 前端 Next.js | 阿里云 ECS (47.94.178.212) | `/opt/apps/ai-contract-web/current` | 容器内 3000 | +| 前端 Gateway | 阿里云 ECS | 同上 | 127.0.0.1:18083 | +| 后端 FastAPI | 192.168.3.90 | `/opt/apps/ai-contract-platform` | 8001:8000 | +| 后端 Gateway | 192.168.3.90 | `/opt/apps/ai-contract-platform-gateway` | 10.20.0.11:18083 | +| PostgreSQL | 192.168.3.90 | - | 5432 | +| Redis | 192.168.3.90 | - | 6379 | +| MinIO | 192.168.3.90 | - | 9000 | + +**域名**: `contact.yuqei.com` + +**流量路径**: +``` +contact.yuqei.com → 阿里云 nginx (127.0.0.1:18083) + ├── /api/ → 后端服务器 10.20.0.11:18083 → backend:8000 + ├── /health → 后端服务器 health check + └── 其他 → 阿里云 Next.js 容器:3000 +``` + +## 构建注意事项(重要!) + +### ⚠️ Git Bash 路径转换问题 + +**问题**: 在 Windows Git Bash 环境下构建 Next.js 时,环境变量 `/api/v1` 会被错误地转换成 Windows 路径 `C:/Program Files/Git/api/v1`。 + +**解决方案**: 使用 `MSYS_NO_PATHCONV=1` 环境变量防止路径转换: + +```bash +# 正确的构建命令(在 Git Bash 中执行) +MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build +``` + +**错误示例**: +```bash +# 这会导致 API_BASE_URL = "C:/Program Files/Git/api/v1" +NEXT_PUBLIC_API_BASE_URL=/api/v1 npm run build + +# 这会导致 API_BASE_URL = "//api/v1",浏览器解析为 "http://api/v1" +NEXT_PUBLIC_API_BASE_URL="//api/v1" npm run build +``` + +### Next.js 环境变量特性 + +**关键点**: `NEXT_PUBLIC_*` 和 `BACKEND_BASE_URL` 是**构建时**嵌入的环境变量,运行时设置无效! + +- `NEXT_PUBLIC_API_BASE_URL` - 嵌入到浏览器 JS 中,用于客户端 API 请求 +- `BACKEND_BASE_URL` - 嵌入到 `server.js` 中,用于 Next.js rewrites(服务器端代理) + +构建后这些值被硬编码到 JS 文件中,docker-compose 的 environment 设置无法覆盖。 + +## 部署步骤 + +### 1. 本地构建前端 + +```bash +cd frontend + +# 使用 MSYS_NO_PATHCONV=1 防止 Git Bash 路径转换 +MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build + +# 打包 standalone 输出 +rm -rf .next/standalone/.next/static +cp -r .next/static .next/standalone/.next/ +tar -czf frontend-standalone.tar.gz -C .next/standalone . +``` + +### 2. 上传到阿里云 + +```bash +scp frontend-standalone.tar.gz root@47.94.178.212:/tmp/ +``` + +### 3. 在阿里云部署 + +```bash +ssh root@47.94.178.212 + +# 解压到 app 目录 +cd /opt/apps/ai-contract-web/current/app +rm -rf .next server.js node_modules public +tar -xzf /tmp/frontend-standalone.tar.gz + +# 重启容器 +cd /opt/apps/ai-contract-web/current +docker compose down +docker compose up -d --build +``` + +### 4. 后端部署(192.168.3.90) + +```bash +ssh root@192.168.3.90 + +cd /opt/apps/ai-contract-platform +git pull +docker compose -f docker-compose.server.yml up -d --build +``` + +## 配置文件 + +### docker-compose.yml(阿里云前端) + +```yaml +services: + ai-contract-web: + build: + context: ./app + dockerfile: Dockerfile + image: local/ai-contract-web:0.0.1 + container_name: ai-contract-web + restart: unless-stopped + environment: + NODE_ENV: production + HOSTNAME: 0.0.0.0 + PORT: 3000 + # 注意:以下环境变量在运行时设置无效,必须在构建时设置 + # BACKEND_BASE_URL: http://10.20.0.11:18083 + # NEXT_PUBLIC_API_BASE_URL: /api/v1 + extra_hosts: + - "10.20.0.11:host-gateway" # 让容器能访问内网 IP + networks: + - ai-contract-web-proxy + + ai-contract-web-gateway: + image: nginx:1.27-alpine + container_name: ai-contract-web-gateway + restart: unless-stopped + ports: + - "127.0.0.1:18083:80" + volumes: + - ./nginx/web-gateway.conf:/etc/nginx/conf.d/default.conf:ro + - /opt/apps/ai-contract-web/logs/nginx:/var/log/nginx + depends_on: + - ai-contract-web + networks: + - ai-contract-web-proxy + +networks: + ai-contract-web-proxy: + name: ai-contract-web-proxy +``` + +### nginx/web-gateway.conf(阿里云) + +```nginx +upstream ai_contract_web { + server ai-contract-web:3000; +} + +upstream ai_contract_api { + server 10.20.0.11:18083; # 后端服务器 gateway +} + +server { + listen 80; + server_name contact.yuqei.com; + charset utf-8; + client_max_body_size 100m; + + # API 代理到后端服务器 + location /api/ { + proxy_pass http://ai_contract_api/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_buffering off; + proxy_connect_timeout 10s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + # 前端 Next.js + location / { + proxy_pass http://ai_contract_web; + 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_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +``` + +## 默认账号 + +| 账号 | 密码 | 角色 | +|------|------|------| +| admin@example.com | 123456 | admin | + +## 常见问题排查 + +### 1. 登录报"网络异常" + +**原因**: `NEXT_PUBLIC_API_BASE_URL` 构建时值错误(如 `C:/Program Files/Git/api/v1` 或 `//api/v1`) + +**排查**: +```bash +# 检查部署的 JS 文件中的 API_BASE_URL +curl -s 'http://contact.yuqei.com/_next/static/chunks/*.js' | grep -oP 'tl="[^"]*"' +# 正确值应为: tl="/api/v1" +# 错误值: tl="C:/Program Files/Git/api/v1" 或 tl="//api/v1" +``` + +**解决**: 使用 `MSYS_NO_PATHCONV=1` 重新构建前端 + +### 2. 容器无法访问后端 + +**原因**: 容器内无法访问内网 IP `10.20.0.11` + +**解决**: 在 docker-compose.yml 中添加 `extra_hosts` + +### 3. API 请求 404 + +**检查**: nginx 是否正确代理 `/api/` 到后端 gateway + +```bash +# 测试 nginx gateway 代理 +ssh root@47.94.178.212 "curl -s http://127.0.0.1:18083/api/v1/auth/login -X POST -H 'Content-Type: application/json' -d '{\"account\":\"admin@example.com\",\"password\":\"123456\"}'" +``` + +### 4. 检查后端日志 + +```bash +ssh root@192.168.3.90 "docker logs ai-contract-platform-backend-1 --tail 30" +``` + +## 验证部署 + +```bash +# 1. 测试前端页面 +curl -s -o /dev/null -w "%{http_code}" http://contact.yuqei.com/ + +# 2. 测试 API 登录 +curl -s -X POST 'http://contact.yuqei.com/api/v1/auth/login' \ + -H 'Content-Type: application/json' \ + -d '{"account":"admin@example.com","password":"123456"}' + +# 3. 测试后端 health check +curl -s http://10.20.0.11:18083/health +``` + +## 目录结构 + +### 阿里云 `/opt/apps/ai-contract-web/current/` + +``` +├── docker-compose.yml +├── nginx/ +│ └── web-gateway.conf +└── app/ + ├── Dockerfile + ├── server.js # Next.js standalone 服务器 + ├── .next/ + │ ├── static/ # 静态 JS/CSS + │ └── server/ # 服务端渲染代码 + ├── public/ + ├── node_modules/ + └── package.json +``` + +### 后端服务器 `/opt/apps/ai-contract-platform/` + +``` +├── docker-compose.server.yml +├── .env +├── backend/ +│ ├── Dockerfile +│ ├── app/ +│ ├── migrations/ +│ └── requirements.txt +└── frontend/ # 本地开发用,不部署 +``` + +## 相关文档 + +- [[deploy/README.md]] - 部署说明 +- [[deploy/web-gateway.conf]] - nginx 配置模板 +- [[deploy/docker-compose.yml]] - docker-compose 模板 \ No newline at end of file diff --git a/docs/DEPLOYMENT_TROUBLESHOOTING.md b/docs/DEPLOYMENT_TROUBLESHOOTING.md index 2ef7265b..5307d6a0 100644 --- a/docs/DEPLOYMENT_TROUBLESHOOTING.md +++ b/docs/DEPLOYMENT_TROUBLESHOOTING.md @@ -27,19 +27,6 @@ environment: **Rule of thumb**: Any `%` in a URL must be written as `%25`. This applies to `DATABASE_URL`, `REDIS_URL`, and any other URL-based configuration. -**Quick fix command** (on server): -```bash -cd /opt/ai-contract-platform -# Stop backend -docker compose -f docker-compose.server.yml stop backend -# Fix DATABASE_URL: replace % with %25 in docker-compose.server.yml -sed -i 's|zmfCSR%816709301|zmfCSR%25816709301|g' docker-compose.server.yml -# Verify -grep DATABASE_URL docker-compose.server.yml -# Restart -docker compose -f docker-compose.server.yml up -d backend -``` - ### 2. Missing Template Files at Startup **Problem**: Backend crashes on startup with: @@ -73,21 +60,108 @@ def _context(self, current_user: Dict[str, Any]) -> SharedAIRequestContext: ) ``` +### 4. 前端登录报"网络异常" - Git Bash 路径转换问题 ⚠️ + +**问题**: 前端部署后登录失败,浏览器控制台显示请求发送到错误的 URL(如 `http://api/v1/auth/login` 或 `http://C:/Program Files/Git/api/v1/auth/login`) + +**根本原因**: 在 Windows Git Bash 环境下构建 Next.js 时: +- `NEXT_PUBLIC_API_BASE_URL=/api/v1` 被错误地转换成 Windows 路径 `C:/Program Files/Git/api/v1` +- `NEXT_PUBLIC_API_BASE_URL="//api/v1"` 被浏览器解析为 `http://api/v1`(协议相对 URL) + +**错误示例**: +```bash +# 这会导致 API_BASE_URL = "C:/Program Files/Git/api/v1" +NEXT_PUBLIC_API_BASE_URL=/api/v1 npm run build + +# 这会导致 API_BASE_URL = "//api/v1",浏览器解析为 "http://api/v1" +NEXT_PUBLIC_API_BASE_URL="//api/v1" npm run build +``` + +**正确做法**: +```bash +# 使用 MSYS_NO_PATHCONV=1 防止路径转换 +MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build +``` + +**排查方法**: +```bash +# 检查部署的 JS 文件中 API_BASE_URL 的实际值 +curl -s 'http://contact.yuqei.com/_next/static/chunks/*.js' | grep -oP 'tl="[^"]*"' +# 正确值: tl="/api/v1" +# 错误值: tl="C:/Program Files/Git/api/v1" 或 tl="//api/v1" +``` + +### 5. Next.js 环境变量运行时无效 + +**问题**: 在 docker-compose 的 environment 中设置了 `NEXT_PUBLIC_API_BASE_URL`,但前端仍然使用旧值 + +**原因**: `NEXT_PUBLIC_*` 和 `BACKEND_BASE_URL` 是**构建时**嵌入的环境变量,运行时设置无效!这些值会被硬编码到 JS 文件中。 + +**解决方案**: 必须在 `npm run build` 时设置环境变量: +```bash +MSYS_NO_PATHCONV=1 NEXT_PUBLIC_API_BASE_URL="/api/v1" BACKEND_BASE_URL="http://10.20.0.11:18083" npm run build +``` + +### 6. Docker 容器无法访问内网 IP + +**问题**: 前端容器内无法访问后端服务器的内网 IP(如 `10.20.0.11`) + +**解决方案**: 在 docker-compose.yml 中添加 `extra_hosts`: +```yaml +services: + ai-contract-web: + extra_hosts: + - "10.20.0.11:host-gateway" # 映射到 Docker 宿主机 +``` + +## 部署架构 + +| 组件 | 部署位置 | 目录 | 端口 | +|------|---------|------|------| +| 前端 Next.js | 阿里云 ECS (47.94.178.212) | `/opt/apps/ai-contract-web/current` | 容器内 3000 | +| 前端 Gateway | 阿里云 ECS | 同上 | 127.0.0.1:18083 | +| 后端 FastAPI | 192.168.3.90 | `/opt/apps/ai-contract-platform` | 8001:8000 | +| 后端 Gateway | 192.168.3.90 | `/opt/apps/ai-contract-platform-gateway` | 10.20.0.11:18083 | +| PostgreSQL | 192.168.3.90 | - | 5432 | +| Redis | 192.168.3.90 | - | 6379 | +| MinIO | 192.168.3.90 | - | 9000 | + +**域名**: `contact.yuqei.com` + +## 部署脚本 + +项目提供了便捷的部署脚本: + +```bash +# 构建前端 +./scripts/build-frontend.sh + +# 部署前端到阿里云 +./scripts/deploy-frontend.sh +``` + ## Deployment Checklist -1. **Verify password encoding**: Check `DATABASE_URL` in `docker-compose.server.yml` has `%` encoded as `%25` -2. **Pull latest code**: `git pull origin main` -3. **Clean rebuild**: `docker compose -f docker-compose.server.yml down && docker compose -f docker-compose.server.yml up -d --build` -4. **Wait for postgres**: The `backend` container depends on `postgres` health check (up to 20 retries) -5. **Verify**: `curl -s http://localhost:8001/health` should return `{"status":"ok"}` +1. **验证密码编码**: 检查 `DATABASE_URL` 中 `%` 是否编码为 `%25` +2. **前端构建环境变量**: 使用 `MSYS_NO_PATHCONV=1` 防止路径转换 +3. **拉取最新代码**: `git pull origin main` +4. **清理重建**: `docker compose down && docker compose up -d --build` +5. **等待 postgres**: backend 容器依赖 postgres 健康检查(最多 20 次重试) +6. **验证**: `curl -s http://localhost:8001/health` 应返回 `{"status":"ok"}` ## Server Information - **Server**: 192.168.3.90 -- **Project directory**: `/opt/ai-contract-platform` +- **Project directory**: `/opt/apps/ai-contract-platform` - **SSH key**: `~/.ssh/water_audit_ed25519` - **SSH user**: `root` - **Gitea**: http://192.168.3.90:3000 - **Frontend**: http://192.168.3.90:3003 - **Backend API**: http://192.168.3.90:8001 - **Database password**: `zmfCSR%816709301` (use `%25` in URLs) + +## 默认账号 + +| 账号 | 密码 | 角色 | +|------|------|------| +| admin@example.com | 123456 | admin | \ No newline at end of file diff --git a/scripts/build-frontend.sh b/scripts/build-frontend.sh new file mode 100644 index 00000000..5e93c108 --- /dev/null +++ b/scripts/build-frontend.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# 前端构建脚本 - 用于生产部署 +# 解决 Git Bash 路径转换问题 + +set -e + +cd "$(dirname "$0")/frontend" + +echo "Building Next.js frontend..." + +# 使用 MSYS_NO_PATHCONV=1 防止 Git Bash 将 /api/v1 转换为 Windows 路径 +# NEXT_PUBLIC_API_BASE_URL: 浏览器端 API 请求路径(相对路径) +# BACKEND_BASE_URL: 服务器端 rewrites 目标地址 + +export MSYS_NO_PATHCONV=1 +export NEXT_PUBLIC_API_BASE_URL="/api/v1" +export BACKEND_BASE_URL="http://10.20.0.11:18083" + +npm run build + +echo "Copying static assets to standalone..." +rm -rf .next/standalone/.next/static +cp -r .next/static .next/standalone/.next/ + +echo "Creating deployment package..." +tar -czf ../deploy/frontend-standalone.tar.gz -C .next/standalone . + +echo "Build complete! Package: deploy/frontend-standalone.tar.gz" +echo "" +echo "Next steps:" +echo " 1. scp deploy/frontend-standalone.tar.gz root@47.94.178.212:/tmp/" +echo " 2. ssh root@47.94.178.212" +echo " 3. cd /opt/apps/ai-contract-web/current/app" +echo " 4. rm -rf .next server.js node_modules public" +echo " 5. tar -xzf /tmp/frontend-standalone.tar.gz" +echo " 6. cd /opt/apps/ai-contract-web/current && docker compose down && docker compose up -d --build" \ No newline at end of file diff --git a/scripts/deploy-frontend.sh b/scripts/deploy-frontend.sh new file mode 100644 index 00000000..7ece7495 --- /dev/null +++ b/scripts/deploy-frontend.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# 部署前端到阿里云服务器 + +set -e + +ALIYUN_HOST="47.94.178.212" +REMOTE_APP_DIR="/opt/apps/ai-contract-web/current/app" +REMOTE_DIR="/opt/apps/ai-contract-web/current" +PACKAGE="deploy/frontend-standalone.tar.gz" + +if [ ! -f "$PACKAGE" ]; then + echo "Error: $PACKAGE not found. Run scripts/build-frontend.sh first." + exit 1 +fi + +echo "Uploading to Aliyun..." +scp "$PACKAGE" "root@${ALIYUN_HOST}:/tmp/" + +echo "Deploying..." +ssh "root@${ALIYUN_HOST}" << 'REMOTE' +cd /opt/apps/ai-contract-web/current/app +rm -rf .next server.js node_modules public +tar -xzf /tmp/frontend-standalone.tar.gz +cd /opt/apps/ai-contract-web/current +docker compose down +docker compose up -d --build +echo "Waiting for container to start..." +sleep 3 +docker compose ps +REMOTE + +echo "Deployment complete!" +echo "Test: curl -s http://contact.yuqei.com/healthz" \ No newline at end of file