diff --git a/.gitignore b/.gitignore index 5beb736..475030b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ github-actions.key # Registry Config (包含敏感密码) .registry-config +# 生产环境配置(包含敏感信息) +deploy/.env.prod + # Python __pycache__/ *.py[cod] diff --git a/README_DEPLOY.md b/README_DEPLOY.md new file mode 100644 index 0000000..d23e631 --- /dev/null +++ b/README_DEPLOY.md @@ -0,0 +1,204 @@ +# 部署指南 + +## 概述 + +本项目使用 Docker 容器化部署,镜像存储在私有 Registry:`registry.667788.cool` + +## 镜像信息 + +- **Backend**: `registry.667788.cool/wecom-backend:${TAG}` +- **Admin**: `registry.667788.cool/wecom-admin:${TAG}` +- **Nginx**: `registry.667788.cool/wecom-nginx:${TAG}` + +默认 TAG: `latest` + +## 本地构建 + +### 构建并推送镜像 + +```bash +# 赋予脚本执行权限(首次运行) +chmod +x scripts/build_and_push.sh + +# 使用默认标签 latest +./scripts/build_and_push.sh + +# 或指定标签 +./scripts/build_and_push.sh v1.0.0 +``` + +### 手动构建 + +```bash +# 登录 Registry +docker login registry.667788.cool + +# 设置标签 +export TAG=v1.0.0 + +# 构建镜像 +docker compose -f docker-compose.release.yml build + +# 推送镜像 +docker compose -f docker-compose.release.yml push +``` + +## 云端部署 + +### 准备工作 + +1. **服务器要求**: + - Linux 服务器(Ubuntu 20.04+ / CentOS 7+) + - Docker 和 docker-compose 已安装 + - 开放端口:80、443(HTTP/HTTPS) + +2. **上传部署文件**: + ```bash + # 创建部署目录 + mkdir -p /opt/wecom-ai-assistant + cd /opt/wecom-ai-assistant + + # 上传 deploy/ 目录下的所有文件 + # 或使用 Git 克隆项目后复制 deploy/ 目录 + ``` + +3. **配置环境变量**: + ```bash + cd /opt/wecom-ai-assistant/deploy + + # 复制环境变量模板 + cp .env.prod.example .env.prod + + # 编辑配置文件 + nano .env.prod + ``` + + 填写以下必需配置: + - `TAG`: 镜像标签(默认 latest) + - `POSTGRES_PASSWORD`: 数据库密码 + - `DATABASE_URL`: 数据库连接字符串 + - `WECOM_*`: 企业微信配置 + - `JWT_SECRET`: JWT 密钥 + +### 部署步骤 + +```bash +# 1. 进入部署目录 +cd /opt/wecom-ai-assistant/deploy + +# 2. 登录 Registry(如果需要认证) +docker login registry.667788.cool + +# 3. 拉取最新镜像 +docker compose -f docker-compose.prod.yml --env-file .env.prod pull + +# 4. 启动服务 +docker compose -f docker-compose.prod.yml --env-file .env.prod up -d + +# 5. 查看服务状态 +docker compose -f docker-compose.prod.yml ps + +# 6. 查看日志 +docker compose -f docker-compose.prod.yml logs -f +``` + +### 验证部署 + +```bash +# 健康检查 +curl http://localhost/api/health + +# 查看服务状态 +docker compose -f docker-compose.prod.yml ps + +# 查看日志 +docker compose -f docker-compose.prod.yml logs backend +docker compose -f docker-compose.prod.yml logs admin +docker compose -f docker-compose.prod.yml logs nginx +``` + +## 更新部署 + +### 更新到新版本 + +```bash +# 1. 更新环境变量中的 TAG +# 编辑 .env.prod,修改 TAG=v1.0.1 + +# 2. 拉取新镜像 +docker compose -f docker-compose.prod.yml --env-file .env.prod pull + +# 3. 重启服务 +docker compose -f docker-compose.prod.yml --env-file .env.prod up -d + +# 4. 验证 +docker compose -f docker-compose.prod.yml ps +``` + +### 回滚到旧版本 + +```bash +# 1. 修改 .env.prod 中的 TAG 为旧版本 +# TAG=v1.0.0 + +# 2. 拉取旧镜像 +docker compose -f docker-compose.prod.yml --env-file .env.prod pull + +# 3. 重启服务 +docker compose -f docker-compose.prod.yml --env-file .env.prod up -d +``` + +## 停止和清理 + +```bash +# 停止服务 +docker compose -f docker-compose.prod.yml --env-file .env.prod down + +# 停止并删除数据卷(⚠️ 危险操作) +docker compose -f docker-compose.prod.yml --env-file .env.prod down -v + +# 清理未使用的镜像 +docker image prune -a +``` + +## 服务访问 + +部署成功后,服务可通过以下方式访问: + +- **管理后台**: http://your-server-ip/ +- **后端 API**: http://your-server-ip/api/ +- **健康检查**: http://your-server-ip/api/health + +## 故障排查 + +### 问题:无法拉取镜像 + +**解决方案**: +```bash +# 检查 Registry 登录状态 +docker login registry.667788.cool + +# 手动拉取镜像测试 +docker pull registry.667788.cool/wecom-backend:latest +``` + +### 问题:服务启动失败 + +**检查项**: +1. 环境变量配置是否正确 +2. 数据库连接是否正常 +3. 端口是否被占用 +4. 查看服务日志:`docker compose -f docker-compose.prod.yml logs` + +### 问题:Nginx 无法访问后端 + +**解决方案**: +1. 检查网络连接:`docker network ls` +2. 检查服务是否在同一网络:`docker compose -f docker-compose.prod.yml ps` +3. 检查 Nginx 配置:`cat deploy/nginx.conf` + +## 相关文档 + +- [生产部署详细指南](./docs/deploy.md) +- [宝塔面板部署指南](./docs/baota-docker-setup.md) +- [Docker 镜像加速配置](./docs/docker-mirror.md) diff --git a/deploy/.env.prod.example b/deploy/.env.prod.example new file mode 100644 index 0000000..eef16cb --- /dev/null +++ b/deploy/.env.prod.example @@ -0,0 +1,24 @@ +# 生产环境变量配置模板 +# 复制此文件为 .env.prod 并填写实际值 + +# 镜像标签 +TAG=latest + +# 数据库配置 +DATABASE_URL=postgresql://postgres:CHANGE_ME@db:5432/wecom_ai +DATABASE_URL_SYNC=postgresql://postgres:CHANGE_ME@db:5432/wecom_ai +POSTGRES_USER=postgres +POSTGRES_PASSWORD=CHANGE_ME +POSTGRES_DB=wecom_ai + +# WeCom 配置 +WECOM_CORP_ID=你的企业ID +WECOM_AGENT_ID=你的应用AgentId +WECOM_TOKEN=你的Token +WECOM_ENCODING_AES_KEY=你的43位密钥 + +# JWT 配置 +JWT_SECRET=CHANGE_ME_TO_RANDOM_STRING + +# 其他配置 +ENVIRONMENT=production diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml new file mode 100644 index 0000000..6a0aa9f --- /dev/null +++ b/deploy/docker-compose.prod.yml @@ -0,0 +1,78 @@ +# 生产环境部署配置 +# 用途:云端生产环境一键部署 +# 使用: docker compose -f docker-compose.prod.yml --env-file .env.prod up -d + +version: '3.8' + +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB:-wecom_ai} + volumes: + - db_data:/var/lib/postgresql/data + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-wecom_ai}"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - app-network + + backend: + image: registry.667788.cool/wecom-backend:${TAG:-latest} + env_file: + - .env.prod + environment: + DATABASE_URL: ${DATABASE_URL} + DATABASE_URL_SYNC: ${DATABASE_URL_SYNC} + depends_on: + db: + condition: service_healthy + restart: always + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - app-network + + admin: + image: registry.667788.cool/wecom-admin:${TAG:-latest} + env_file: + - .env.prod + restart: always + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + networks: + - app-network + + nginx: + image: registry.667788.cool/wecom-nginx:${TAG:-latest} + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - backend + - admin + restart: always + networks: + - app-network + +networks: + app-network: + driver: bridge + +volumes: + db_data: diff --git a/deploy/nginx.conf b/deploy/nginx.conf index 21d66b9..08a798e 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -1,36 +1,39 @@ -events { worker_connections 1024; } +server { + listen 80; + server_name _; -http { - upstream backend { - server backend:8000; - } - upstream admin { - server admin:3000; + # 后端 API + location /api/ { + proxy_pass http://backend:8000/; + 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_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; } - server { - listen 80; - server_name _; + # 管理后台 + location / { + proxy_pass http://admin:3000/; + 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; + + # WebSocket 支持(如果需要) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } - # /api -> backend - location /api/ { - proxy_pass http://backend; - 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; - } - - # 其余 -> admin - location / { - proxy_pass http://admin; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - 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 /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; } } diff --git a/deploy/nginx/Dockerfile b/deploy/nginx/Dockerfile new file mode 100644 index 0000000..e721923 --- /dev/null +++ b/deploy/nginx/Dockerfile @@ -0,0 +1,13 @@ +# Nginx 镜像 Dockerfile +# 用途:构建包含自定义配置的 Nginx 镜像 + +FROM nginx:alpine + +# 复制 Nginx 配置 +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# 暴露端口 +EXPOSE 80 443 + +# 启动 Nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/deploy/nginx/nginx.conf b/deploy/nginx/nginx.conf new file mode 100644 index 0000000..08a798e --- /dev/null +++ b/deploy/nginx/nginx.conf @@ -0,0 +1,39 @@ +server { + listen 80; + server_name _; + + # 后端 API + location /api/ { + proxy_pass http://backend:8000/; + 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_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 管理后台 + location / { + proxy_pass http://admin:3000/; + 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; + + # WebSocket 支持(如果需要) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + # 健康检查 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} diff --git a/docker-compose.release.yml b/docker-compose.release.yml new file mode 100644 index 0000000..00e8fd4 --- /dev/null +++ b/docker-compose.release.yml @@ -0,0 +1,30 @@ +# 发布构建配置 +# 用途:构建并推送 Docker 镜像到私有 Registry +# 使用: TAG=v1.0.0 docker compose -f docker-compose.release.yml build + +version: '3.8' + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + image: registry.667788.cool/wecom-backend:${TAG:-latest} + tags: + - registry.667788.cool/wecom-backend:${TAG:-latest} + + admin: + build: + context: ./admin + dockerfile: Dockerfile + image: registry.667788.cool/wecom-admin:${TAG:-latest} + tags: + - registry.667788.cool/wecom-admin:${TAG:-latest} + + nginx: + build: + context: ./deploy/nginx + dockerfile: Dockerfile + image: registry.667788.cool/wecom-nginx:${TAG:-latest} + tags: + - registry.667788.cool/wecom-nginx:${TAG:-latest} diff --git a/scripts/build_and_push.sh b/scripts/build_and_push.sh new file mode 100644 index 0000000..5fc0089 --- /dev/null +++ b/scripts/build_and_push.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# 构建并推送 Docker 镜像到私有 Registry +# 用途:构建 backend、admin、nginx 镜像并推送到 registry.667788.cool +# 使用: ./scripts/build_and_push.sh [TAG] +# 示例: ./scripts/build_and_push.sh v1.0.0 + +set -e + +TAG=${1:-latest} +REGISTRY="registry.667788.cool" + +echo "=== 构建并推送 Docker 镜像 ===" +echo "" +echo "Registry: $REGISTRY" +echo "Tag: $TAG" +echo "" + +# 检查 docker compose 命令 +if command -v docker-compose &> /dev/null; then + DOCKER_COMPOSE_CMD="docker-compose" +elif docker compose version &> /dev/null; then + DOCKER_COMPOSE_CMD="docker compose" +else + echo "错误: 未找到 docker-compose 或 docker compose 命令" + exit 1 +fi + +# 登录 registry +echo "1. 登录 registry..." +if ! docker login $REGISTRY; then + echo "✗ 登录失败,请检查凭据" + exit 1 +fi +echo "✓ 登录成功" +echo "" + +# 构建镜像 +echo "2. 构建镜像..." +export TAG=$TAG +$DOCKER_COMPOSE_CMD -f docker-compose.release.yml build +if [ $? -ne 0 ]; then + echo "✗ 构建失败" + exit 1 +fi +echo "✓ 构建成功" +echo "" + +# 推送镜像 +echo "3. 推送镜像..." +$DOCKER_COMPOSE_CMD -f docker-compose.release.yml push +if [ $? -ne 0 ]; then + echo "✗ 推送失败" + exit 1 +fi +echo "✓ 推送成功" +echo "" + +echo "=== 完成 ✅ ===" +echo "" +echo "镜像已推送到:" +echo " - $REGISTRY/wecom-backend:$TAG" +echo " - $REGISTRY/wecom-admin:$TAG" +echo " - $REGISTRY/wecom-nginx:$TAG" +echo ""