21830d507d
P0 修复 — /itportal/ HTTP 500(连续 3+ 天阻塞)
根因分析:
- portal 源码 frontend-portal/ 在 commit bea288e4 (2026-07-11) 被误删
- portal dist 不存在(前端目录已不存在)
- backend/app/api/portal.py 也已删除
- /api/portal/* API 不再在 router.py 注册
- 但 nginx 配置 + docker-compose 仍引用 portal dist → try_files 指向空目录 → 500
- nginx-split.conf 还有根路径重定向 / → /itportal/ → 用户访问根域名直接 500
修复(最小集、最安全):
1. nginx 6 个配置(nginx/nginx.conf + 5 个 deploy-server 历史变体):
- 删除 location /itportal/ { alias ... } 静态块
- 保留 location /itportal/meetingroom/ API 块(最长前缀仍命中)
- nginx-split.conf / nginx-full.conf / nginx-green-upstream.conf 根重定向
/itportal/ → /itdesk/
2. docker-compose 4 个配置(+split):
- 删除 ./frontend-portal/dist 或 ./html/itportal 卷挂载
3. deploy-server/deploy.sh:
- 删除 portal dist 备份/解压步骤
- 部署完成提示去掉 /itportal/ 入口
预期效果:
- /itportal/ → nginx 404(不再 500)
- /itportal/meetingroom/ → API 仍工作(终端+H5 共用会议室预定)
- /itdesk/ /itagent/ /itadmin/ /itterminal/ 不受影响
- 根域名访问 → 重定向到 /itdesk/(H5 入口)
文件改动:
- nginx/nginx.conf(双 server block)
- deploy-server/nginx.conf(双 server block)
- deploy-server/nginx-full.conf + green-upstream.conf + nginx.conf + nginx-split.conf
- docker-compose.yml + 3 个 deploy-server 变体
- deploy-server/deploy.sh
⚠️ 待部署(无 jumpserver/VPN):本地已就绪,需在 jumpserver-V2 流程下
scp 上述 11 个文件到 /opt/wecom-it-desk/ 覆盖 + docker compose restart nginx 验证
验证命令:curl -I https://itsupport.servyou.com.cn/itportal/ 期望返回 404(或 301/302)
curl -I https://itsupport.servyou.com.cn/itportal/meetingroom/... 期望 200/401/403
curl -I https://itsupport.servyou.com.cn/ 期望 302 → /itdesk/
Refs: 滴答清单任务 6a6bfc29e4b06440c36701e1 (P0 逾期 1 天)
432 lines
14 KiB
YAML
432 lines
14 KiB
YAML
# =============================================================================
|
||
# 企微IT智能服务台 — Docker Compose(多服务拆分版)
|
||
# =============================================================================
|
||
# 目标服务器:10.90.5.110
|
||
# 域名:itsupport.servyou.com.cn
|
||
#
|
||
# 架构说明:
|
||
# - 将单体 backend 拆分为 5 个独立服务
|
||
# - 共享 PostgreSQL 和 Redis
|
||
# - Nginx 根据路径路由到不同服务
|
||
#
|
||
# 服务划分:
|
||
# - core-svc: 核心服务(鉴权、员工、角色)
|
||
# - conversation-svc: 会话服务(会话、消息、H5、WS)
|
||
# - agent-svc: 坐席服务(坐席管理、快捷回复)
|
||
# - ai-svc: AI 服务(Dify 调用、Wingman)
|
||
# - admin-svc: 管理服务(仪表盘、配置、集成、审核)
|
||
#
|
||
# 用法:
|
||
# 1. 上传部署包到服务器
|
||
# 2. cp .env.example .env && vim .env # 填入真实配置
|
||
# 3. docker compose -f docker-compose.split.yml up -d # 启动所有服务
|
||
# 4. docker compose -f docker-compose.split.yml logs -f # 查看日志
|
||
#
|
||
# 蓝绿部署:
|
||
# ./blue-green-deploy.sh deploy blue # 部署到 blue 环境
|
||
# ./blue-green-deploy.sh deploy green # 部署到 green 环境
|
||
# ./blue-green-deploy.sh switch blue # 切换流量到 blue
|
||
# =============================================================================
|
||
|
||
services:
|
||
# --------------------------------------------------------------------------
|
||
# PostgreSQL 16 — 持久化数据库(共享)
|
||
# --------------------------------------------------------------------------
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
container_name: wecom_it_postgres
|
||
restart: unless-stopped
|
||
environment:
|
||
POSTGRES_USER: ${POSTGRES_USER:-wecom}
|
||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-wecom_secret}
|
||
POSTGRES_DB: ${POSTGRES_DB:-wecom_it_desk}
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-wecom}"]
|
||
interval: 5s
|
||
timeout: 5s
|
||
retries: 5
|
||
networks:
|
||
- it-desk-internal
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Redis 7 — 缓存服务(共享)
|
||
# --------------------------------------------------------------------------
|
||
redis:
|
||
image: redis:7-alpine
|
||
container_name: wecom_it_redis
|
||
restart: unless-stopped
|
||
command: redis-server --appendonly yes --save 900 1 --save 300 10
|
||
volumes:
|
||
- redis_data:/data
|
||
healthcheck:
|
||
test: ["CMD", "redis-cli", "ping"]
|
||
interval: 5s
|
||
timeout: 5s
|
||
retries: 5
|
||
networks:
|
||
- it-desk-internal
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Core Service — 核心服务(鉴权、员工、角色)
|
||
# --------------------------------------------------------------------------
|
||
core-svc:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
image: wecom-it-desk:${CORE_VERSION:-latest}
|
||
container_name: wecom_it_core
|
||
restart: unless-stopped
|
||
environment:
|
||
# 服务标识
|
||
- SERVICE_NAME=core
|
||
# 企微凭证
|
||
- WECOM_CORP_ID=${WECOM_CORP_ID}
|
||
- WECOM_AGENT_ID=${WECOM_AGENT_ID}
|
||
- WECOM_SECRET=${WECOM_SECRET}
|
||
- WECOM_TOKEN=${WECOM_TOKEN}
|
||
- WECOM_ENCODING_AES_KEY=${WECOM_ENCODING_AES_KEY}
|
||
# 数据库
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-wecom}:${POSTGRES_PASSWORD:-wecom_secret}@postgres:5432/${POSTGRES_DB:-wecom_it_desk}
|
||
# Redis
|
||
- REDIS_URL=redis://redis:6379/0
|
||
# CORS
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-http://itsupport.servyou.com.cn}
|
||
# Mock 登录
|
||
- MOCK_LOGIN_ENABLED=${MOCK_LOGIN_ENABLED:-false}
|
||
# 服务配置
|
||
- BACKEND_HOST=0.0.0.0
|
||
- BACKEND_PORT=8001
|
||
volumes:
|
||
- backend-uploads:/app/uploads
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
command: uvicorn app.main:app --host 0.0.0.0 --port 8001 --workers 1
|
||
networks:
|
||
- it-desk-internal
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
memory: 512M
|
||
reservations:
|
||
memory: 256M
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8001/health').read()"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Conversation Service — 会话服务(会话、消息、H5、WebSocket)
|
||
# --------------------------------------------------------------------------
|
||
conversation-svc:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
image: wecom-it-desk:${CONVERSATION_VERSION:-latest}
|
||
container_name: wecom_it_conversation
|
||
restart: unless-stopped
|
||
environment:
|
||
- SERVICE_NAME=conversation
|
||
# 企微凭证
|
||
- WECOM_CORP_ID=${WECOM_CORP_ID}
|
||
- WECOM_AGENT_ID=${WECOM_AGENT_ID}
|
||
- WECOM_SECRET=${WECOM_SECRET}
|
||
- WECOM_TOKEN=${WECOM_TOKEN}
|
||
- WECOM_ENCODING_AES_KEY=${WECOM_ENCODING_AES_KEY}
|
||
# 数据库
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-wecom}:${POSTGRES_PASSWORD:-wecom_secret}@postgres:5432/${POSTGRES_DB:-wecom_it_desk}
|
||
# Redis
|
||
- REDIS_URL=redis://redis:6379/0
|
||
# CORS
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-http://itsupport.servyou.com.cn}
|
||
# Mock 登录
|
||
- MOCK_LOGIN_ENABLED=${MOCK_LOGIN_ENABLED:-false}
|
||
# 服务配置
|
||
- BACKEND_HOST=0.0.0.0
|
||
- BACKEND_PORT=8002
|
||
volumes:
|
||
- backend-uploads:/app/uploads
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
command: uvicorn app.main:app --host 0.0.0.0 --port 8002 --workers 2
|
||
networks:
|
||
- it-desk-internal
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
memory: 1G
|
||
reservations:
|
||
memory: 512M
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8002/health').read()"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "15m"
|
||
max-file: "5"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Agent Service — 坐席服务
|
||
# --------------------------------------------------------------------------
|
||
agent-svc:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
image: wecom-it-desk:${AGENT_VERSION:-latest}
|
||
container_name: wecom_it_agent
|
||
restart: unless-stopped
|
||
environment:
|
||
- SERVICE_NAME=agent
|
||
# 企微凭证
|
||
- WECOM_CORP_ID=${WECOM_CORP_ID}
|
||
- WECOM_AGENT_ID=${WECOM_AGENT_ID}
|
||
- WECOM_SECRET=${WECOM_SECRET}
|
||
- WECOM_TOKEN=${WECOM_TOKEN}
|
||
- WECOM_ENCODING_AES_KEY=${WECOM_ENCODING_AES_KEY}
|
||
# 数据库
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-wecom}:${POSTGRES_PASSWORD:-wecom_secret}@postgres:5432/${POSTGRES_DB:-wecom_it_desk}
|
||
# Redis
|
||
- REDIS_URL=redis://redis:6379/0
|
||
# CORS
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-http://itsupport.servyou.com.cn}
|
||
# Mock 登录
|
||
- MOCK_LOGIN_ENABLED=${MOCK_LOGIN_ENABLED:-false}
|
||
# 服务配置
|
||
- BACKEND_HOST=0.0.0.0
|
||
- BACKEND_PORT=8003
|
||
volumes:
|
||
- backend-uploads:/app/uploads
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
command: uvicorn app.main:app --host 0.0.0.0 --port 8003 --workers 1
|
||
networks:
|
||
- it-desk-internal
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
memory: 512M
|
||
reservations:
|
||
memory: 256M
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8003/health').read()"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# AI Service — AI 服务(Dify 调用、Wingman)
|
||
# --------------------------------------------------------------------------
|
||
ai-svc:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
image: wecom-it-desk:${AI_VERSION:-latest}
|
||
container_name: wecom_it_ai
|
||
restart: unless-stopped
|
||
environment:
|
||
- SERVICE_NAME=ai
|
||
# 企微凭证
|
||
- WECOM_CORP_ID=${WECOM_CORP_ID}
|
||
- WECOM_AGENT_ID=${WECOM_AGENT_ID}
|
||
- WECOM_SECRET=${WECOM_SECRET}
|
||
- WECOM_TOKEN=${WECOM_TOKEN}
|
||
- WECOM_ENCODING_AES_KEY=${WECOM_ENCODING_AES_KEY}
|
||
# 数据库
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-wecom}:${POSTGRES_PASSWORD:-wecom_secret}@postgres:5432/${POSTGRES_DB:-wecom_it_desk}
|
||
# Redis
|
||
- REDIS_URL=redis://redis:6379/0
|
||
# CORS
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-http://itsupport.servyou.com.cn}
|
||
# Mock 登录
|
||
- MOCK_LOGIN_ENABLED=${MOCK_LOGIN_ENABLED:-false}
|
||
# AI 服务(Dify)
|
||
- DIFY_API_URL=${DIFY_API_URL}
|
||
- DIFY_API_KEY=${DIFY_API_KEY}
|
||
- DIFY_TIMEOUT=${DIFY_TIMEOUT:-30}
|
||
# AI Wingman
|
||
- DIFY_WINGMAN_API_URL=${DIFY_WINGMAN_API_URL:-}
|
||
- DIFY_WINGMAN_API_KEY=${DIFY_WINGMAN_API_KEY:-}
|
||
- DIFY_WINGMAN_TIMEOUT=${DIFY_WINGMAN_TIMEOUT:-30}
|
||
# 服务配置
|
||
- BACKEND_HOST=0.0.0.0
|
||
- BACKEND_PORT=8004
|
||
volumes:
|
||
- backend-uploads:/app/uploads
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
command: uvicorn app.main:app --host 0.0.0.0 --port 8004 --workers 1
|
||
networks:
|
||
- it-desk-internal
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
memory: 1G
|
||
reservations:
|
||
memory: 512M
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8004/health').read()"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "15m"
|
||
max-file: "5"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Admin Service — 管理服务(仪表盘、配置、集成、审核)
|
||
# --------------------------------------------------------------------------
|
||
admin-svc:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
image: wecom-it-desk:${ADMIN_VERSION:-latest}
|
||
container_name: wecom_it_admin
|
||
restart: unless-stopped
|
||
environment:
|
||
- SERVICE_NAME=admin
|
||
# 企微凭证
|
||
- WECOM_CORP_ID=${WECOM_CORP_ID}
|
||
- WECOM_AGENT_ID=${WECOM_AGENT_ID}
|
||
- WECOM_SECRET=${WECOM_SECRET}
|
||
- WECOM_TOKEN=${WECOM_TOKEN}
|
||
- WECOM_ENCODING_AES_KEY=${WECOM_ENCODING_AES_KEY}
|
||
# 数据库
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-wecom}:${POSTGRES_PASSWORD:-wecom_secret}@postgres:5432/${POSTGRES_DB:-wecom_it_desk}
|
||
# Redis
|
||
- REDIS_URL=redis://redis:6379/0
|
||
# CORS
|
||
- CORS_ORIGINS=${CORS_ORIGINS:-http://itsupport.servyou.com.cn}
|
||
# Mock 登录
|
||
- MOCK_LOGIN_ENABLED=${MOCK_LOGIN_ENABLED:-false}
|
||
# 服务配置
|
||
- BACKEND_HOST=0.0.0.0
|
||
- BACKEND_PORT=8005
|
||
volumes:
|
||
- backend-uploads:/app/uploads
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_healthy
|
||
command: uvicorn app.main:app --host 0.0.0.0 --port 8005 --workers 1
|
||
networks:
|
||
- it-desk-internal
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
memory: 512M
|
||
reservations:
|
||
memory: 256M
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8005/health').read()"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
start_period: 40s
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# --------------------------------------------------------------------------
|
||
# Nginx — 反向代理 + 静态文件服务 + 路由分发
|
||
# --------------------------------------------------------------------------
|
||
nginx:
|
||
image: nginx:1.27-alpine
|
||
container_name: wecom_it_nginx
|
||
restart: unless-stopped
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./nginx/nginx-split.conf:/etc/nginx/nginx.conf:ro
|
||
- ./nginx/ssl:/etc/nginx/ssl:ro
|
||
- ./frontend-h5/dist:/usr/share/nginx/html/itdesk:ro
|
||
- ./frontend-agent/dist:/usr/share/nginx/html/itagent:ro
|
||
- ./frontend-admin/dist:/usr/share/nginx/html/itadmin:ro
|
||
depends_on:
|
||
core-svc:
|
||
condition: service_healthy
|
||
conversation-svc:
|
||
condition: service_healthy
|
||
agent-svc:
|
||
condition: service_healthy
|
||
ai-svc:
|
||
condition: service_healthy
|
||
admin-svc:
|
||
condition: service_healthy
|
||
networks:
|
||
- it-desk-internal
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "curl -f http://localhost:80/itdesk/health || exit 1"]
|
||
interval: 15s
|
||
timeout: 5s
|
||
retries: 3
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
# =============================================================================
|
||
# 网络
|
||
# =============================================================================
|
||
networks:
|
||
it-desk-internal:
|
||
driver: bridge
|
||
|
||
# =============================================================================
|
||
# 数据卷
|
||
# =============================================================================
|
||
volumes:
|
||
postgres_data:
|
||
name: wecom_it_postgres_data
|
||
redis_data:
|
||
name: wecom_it_redis_data
|
||
backend-uploads:
|
||
name: wecom_it_backend_uploads
|