Files
ai-contract-platform/project-docs/DEPLOYMENT.md
T

13 KiB
Raw Blame History

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

上线稳定性基线

生产发布前先跑本地预检,再跑生产健康/发布门禁:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-deployment-preflight.ps1

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-env-check.ps1 `
  -Remote `
  -ServerHost "192.168.3.90" `
  -RemoteEnvPath "/opt/apps/ai-contract-platform/.env"

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-health-check.ps1 `
  -BackendBaseUrl "http://192.168.3.90:8001" `
  -FrontendBaseUrl "https://contact.yuqei.com"

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 `
  -BackendBaseUrl "http://192.168.3.90:8001" `
  -FrontendBaseUrl "https://contact.yuqei.com" `
  -ApiUrl "https://contact.yuqei.com/api/v1" `
  -ReadinessUrl "https://contact.yuqei.com/api/v1/system/readiness"

本地预检覆盖:

  • backend/tests/test_system_api.py、合同台账/导出、统一搜索回归。
  • npm --prefix frontend run lint。
  • npm --prefix frontend run build。
  • git diff --check。

后端健康检查分两层:

  • GET /health:只确认 FastAPI 进程可响应,适合容器存活探针。
  • GET /api/v1/system/readiness:确认数据库、运行配置、对象存储、共享 AI 配置和默认管理员密码状态,适合上线门禁。

生产环境 APP_ENV=production 时,readiness 会把以下问题作为必需项失败并返回 status=degraded:

  • SECRET_KEY 为空、过短或仍为 change_me... 占位值。
  • DATABASE_URL 为空或仍包含占位密码。
  • FILE_STORAGE_BACKEND=minio 时,MINIO_ENDPOINT、MINIO_BUCKET、MINIO_ACCESS_KEY、MINIO_SECRET_KEY 缺失或仍为占位值;生产环境下 minioadmin 默认凭据也会失败。
  • SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED=true 时,SHARED_AI_PLATFORM_BASE_URL 或 SHARED_AI_PLATFORM_API_KEY 缺失/占位。

.env.example 只作为模板,生产 .env 必须替换所有 change_me... 值。若暂时不启用远端共享 AI 法律审查,应明确设置:

SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED=false

远端 .env 核对脚本不会把密钥值写入报告,只保存键名清单和 pass/fail 状态。MINIO_ACCESS_KEY / MINIO_SECRET_KEY 不能继续使用 minioadmin、admin、password 这类默认弱值;替换后需要同步重启后端和 worker,再复跑 run-production-env-check.ps1 与 run-production-deployment-rehearsal.ps1。

构建注意事项(重要!)

⚠️ Git Bash 路径转换问题

问题: 在 Windows Git Bash 环境下构建 Next.js 时,环境变量 /api/v1 会被错误地转换成 Windows 路径 C:/Program Files/Git/api/v1。

解决方案: 使用 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

错误示例:

# 这会导致 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. 本地构建前端

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. 上传到阿里云

scp frontend-standalone.tar.gz root@47.94.178.212:/tmp/

3. 在阿里云部署

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)

当前线上目录 /opt/apps/ai-contract-platform 可能是发布包解压目录,不一定是 Git 工作区。 执行 git pull 前必须先确认 git rev-parse --is-inside-work-tree 返回 true。

方式 A:服务器目录是 Git 工作区

ssh root@192.168.3.90

cd /opt/apps/ai-contract-platform
git rev-parse --is-inside-work-tree
git pull origin main
docker compose -f docker-compose.server.yml build backend legal-change-impact-worker
docker compose -f docker-compose.server.yml up -d backend legal-change-impact-worker

方式 B:服务器目录是发布包目录(当前线上常见形态)

此方式用于后端单独热修复或后端发布包部署。发布前先备份将被覆盖的文件,发布后只重建后端服务,避免无关前端变更进入线上。

# 本地同步修复文件示例
scp backend/vendor/shared-ai-platform-sdk/shared_ai_platform_sdk/client.py \
  root@192.168.3.90:/opt/apps/ai-contract-platform/backend/vendor/shared-ai-platform-sdk/shared_ai_platform_sdk/client.py

# 服务器上重建并重启后端相关容器
ssh root@192.168.3.90
cd /opt/apps/ai-contract-platform
docker compose -f docker-compose.server.yml build backend legal-change-impact-worker
docker compose -f docker-compose.server.yml up -d backend legal-change-impact-worker

发布后检查:

docker ps --format "table {{.Names}}\t{{.Status}}" | grep ai-contract-platform
docker logs --since 5m ai-contract-platform-backend-1

配置文件

docker-compose.yml(阿里云前端)

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(阿里云)

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)

排查:

# 检查部署的 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

# 测试 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. 检查后端日志

ssh root@192.168.3.90 "docker logs ai-contract-platform-backend-1 --tail 30"

5. Chat 页面历史会话 500

现象: 登录后进入 http://contact.yuqei.com/chat,页面提示 历史会话加载失败 Request failed with status code 500。

排查:

ssh root@192.168.3.90 "docker logs ai-contract-platform-backend-1 --tail 120"

如果日志出现如下错误,说明后端容器内的 vendored shared AI SDK 与业务代码配置不一致:

TypeError: SharedAIPlatformClient.__init__() got an unexpected keyword argument 'api_prefix'

解决: 发布包含 backend/vendor/shared-ai-platform-sdk/shared_ai_platform_sdk/client.py 修复的后端代码,并重建 backend 与 legal-change-impact-worker。

验证部署

# 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

# 4. 测试生产 readiness
curl -s http://contact.yuqei.com/api/v1/system/readiness

登录态页面验证重点:

  • http://contact.yuqei.com/workspace 可正常进入工作台
  • http://contact.yuqei.com/contracts 可加载合同列表
  • http://contact.yuqei.com/chat 不再出现历史会话 500,后端日志中 /api/v1/conversations?page=1&page_size=20 返回 200

目录结构

阿里云 /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/              # 本地开发用,不部署

相关文档