From 3ac83e9305bdf9cc0eb0d97e5c3ebb7fdd9a9158 Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 9 Jan 2026 00:07:26 -0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20SSL=20=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E8=87=AA=E5=8A=A8=E7=94=B3=E8=AF=B7=E5=92=8C=20Kong?= =?UTF-8?q?=20API=20Gateway=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - deploy.sh 添加 ssl 命令支持 Let's Encrypt 证书自动申请 - deploy.sh 添加 kong 命令支持 API Gateway 路由配置 - deploy.sh 添加 deploy-full 命令一键完整部署(含SSL) - Nginx 配置更新为 HTTPS,支持 HTTP->HTTPS 重定向 - 域名配置为 iconsulting.szaiai.com SSL 功能: - ./deploy.sh ssl install 安装 certbot - ./deploy.sh ssl obtain 申请证书 - ./deploy.sh ssl renew 续期证书 - ./deploy.sh ssl auto-renew 配置自动续期 Kong 功能: - ./deploy.sh kong setup 配置所有微服务路由 - ./deploy.sh kong status 查看网关状态 - ./deploy.sh kong services 查看服务列表 - ./deploy.sh kong routes 查看路由列表 Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 5 +- .env.example | 6 + deploy.sh | 379 +++++++++++++++++++++++++++++++++++- nginx/conf.d/default.conf | 72 ++++--- 4 files changed, 433 insertions(+), 29 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 13d5074..bae05cc 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -7,7 +7,10 @@ "Bash(pnpm add:*)", "Bash(git init:*)", "Bash(git checkout:*)", - "Bash(git add:*)" + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(git remote add:*)", + "Bash(git push:*)" ] } } diff --git a/.env.example b/.env.example index 006850c..f9a4a24 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,12 @@ NODE_ENV=production APP_NAME=iConsulting +# =========================================== +# 域名配置 +# =========================================== +DOMAIN=iconsulting.szaiai.com +ADMIN_EMAIL=admin@szaiai.com + # =========================================== # 服务器网络配置 # =========================================== diff --git a/deploy.sh b/deploy.sh index 90810e8..4c65a9b 100644 --- a/deploy.sh +++ b/deploy.sh @@ -53,6 +53,10 @@ cd "$PROJECT_ROOT" COMPOSE_FILE="docker-compose.yml" ENV_FILE=".env" +# 域名配置 +DOMAIN="${DOMAIN:-iconsulting.szaiai.com}" +ADMIN_EMAIL="${ADMIN_EMAIL:-admin@szaiai.com}" + # 服务端口配置 declare -A SERVICE_PORTS=( ["conversation"]=3004 @@ -714,14 +718,346 @@ do_deploy() { # 启动 do_start all "$mode" + # 配置 Kong 路由 + setup_kong_routes + log_success "部署完成!" echo "" echo -e "${CYAN}访问地址:${NC}" - echo " 用户前端: http://localhost" - echo " 管理后台: http://localhost/admin" - echo " API 网关: http://localhost:8000" - echo " Kong 管理: http://localhost:8001" + echo " 用户前端: https://$DOMAIN" + echo " 管理后台: https://$DOMAIN/admin" + echo " API 网关: https://$DOMAIN/api" + echo " Kong 管理: http://localhost:8001 (仅本地)" echo "" + echo -e "${YELLOW}提示: 如需配置 SSL 证书,请执行:${NC}" + echo " ./deploy.sh ssl obtain" + echo "" +} + +# 完整部署 (含 SSL) +do_deploy_full() { + log_info "开始完整部署 (含 SSL 证书)..." + + # 基础部署 + do_deploy docker + + # 安装并获取 SSL 证书 + do_ssl obtain + + # 配置自动续期 + do_ssl auto-renew + + log_success "完整部署完成 (含 SSL)!" + echo "" + echo -e "${CYAN}访问地址:${NC}" + echo " 用户前端: https://$DOMAIN" + echo " 管理后台: https://$DOMAIN/admin" + echo "" +} + +#=============================================================================== +# SSL 证书管理 (Let's Encrypt) +#=============================================================================== + +# 安装 Certbot +install_certbot() { + log_step "检查/安装 Certbot..." + + if command -v certbot &> /dev/null; then + log_success "Certbot 已安装" + return 0 + fi + + # 检测操作系统 + if [ -f /etc/debian_version ]; then + # Debian/Ubuntu + apt-get update + apt-get install -y certbot python3-certbot-nginx + elif [ -f /etc/redhat-release ]; then + # CentOS/RHEL + yum install -y epel-release + yum install -y certbot python3-certbot-nginx + elif [ -f /etc/alpine-release ]; then + # Alpine + apk add certbot certbot-nginx + else + log_error "无法识别的操作系统,请手动安装 certbot" + exit 1 + fi + + log_success "Certbot 安装完成" +} + +# 申请 SSL 证书 +obtain_ssl_cert() { + local domain=${1:-$DOMAIN} + local email=${2:-$ADMIN_EMAIL} + + log_step "申请 SSL 证书: $domain..." + + # 创建证书目录 + mkdir -p "$PROJECT_ROOT/nginx/ssl" + + # 停止 Nginx (释放 80 端口) + docker-compose stop nginx 2>/dev/null || true + + # 使用 standalone 模式申请证书 + certbot certonly \ + --standalone \ + --non-interactive \ + --agree-tos \ + --email "$email" \ + -d "$domain" \ + --cert-path "$PROJECT_ROOT/nginx/ssl/cert.pem" \ + --key-path "$PROJECT_ROOT/nginx/ssl/privkey.pem" \ + --fullchain-path "$PROJECT_ROOT/nginx/ssl/fullchain.pem" + + # 复制证书到项目目录 + if [ -d "/etc/letsencrypt/live/$domain" ]; then + cp "/etc/letsencrypt/live/$domain/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/" + cp "/etc/letsencrypt/live/$domain/privkey.pem" "$PROJECT_ROOT/nginx/ssl/" + chmod 644 "$PROJECT_ROOT/nginx/ssl/fullchain.pem" + chmod 600 "$PROJECT_ROOT/nginx/ssl/privkey.pem" + log_success "SSL 证书已获取并复制到 nginx/ssl/" + else + log_error "证书申请失败" + return 1 + fi + + # 重启 Nginx + docker-compose up -d nginx + + log_success "SSL 证书配置完成" +} + +# 续期 SSL 证书 +renew_ssl_cert() { + log_step "续期 SSL 证书..." + + certbot renew --quiet + + # 复制新证书 + if [ -d "/etc/letsencrypt/live/$DOMAIN" ]; then + cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "$PROJECT_ROOT/nginx/ssl/" + cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "$PROJECT_ROOT/nginx/ssl/" + + # 重载 Nginx + docker-compose exec nginx nginx -s reload + log_success "SSL 证书续期完成" + fi +} + +# 配置证书自动续期 +setup_ssl_auto_renew() { + log_step "配置证书自动续期..." + + # 创建续期脚本 + cat > /etc/cron.d/certbot-renew << EOF +# 每天凌晨 3 点检查证书续期 +0 3 * * * root certbot renew --quiet && cp /etc/letsencrypt/live/$DOMAIN/*.pem $PROJECT_ROOT/nginx/ssl/ && docker-compose -f $PROJECT_ROOT/docker-compose.yml exec -T nginx nginx -s reload +EOF + + log_success "自动续期已配置 (每天 3:00 检查)" +} + +# SSL 操作入口 +do_ssl() { + local action=${1:-status} + + case $action in + install) + install_certbot + ;; + obtain|get) + install_certbot + obtain_ssl_cert "$2" "$3" + ;; + renew) + renew_ssl_cert + ;; + auto-renew) + setup_ssl_auto_renew + ;; + status) + echo -e "${PURPLE}SSL 证书状态:${NC}" + if [ -f "$PROJECT_ROOT/nginx/ssl/fullchain.pem" ]; then + echo "证书文件: 存在" + openssl x509 -in "$PROJECT_ROOT/nginx/ssl/fullchain.pem" -noout -dates 2>/dev/null || echo "无法读取证书信息" + else + echo "证书文件: 不存在" + fi + + if command -v certbot &> /dev/null; then + echo "" + echo "Certbot 证书列表:" + certbot certificates 2>/dev/null || echo "无证书" + fi + ;; + *) + log_error "未知 SSL 操作: $action (可选: install, obtain, renew, auto-renew, status)" + exit 1 + ;; + esac +} + +#=============================================================================== +# Kong API Gateway 配置 +#=============================================================================== + +# 配置 Kong 路由 +setup_kong_routes() { + local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}" + + log_step "配置 Kong API Gateway 路由..." + + # 等待 Kong 就绪 + local max_attempts=30 + local attempt=1 + while [ $attempt -le $max_attempts ]; do + if curl -s "$kong_admin" > /dev/null 2>&1; then + break + fi + echo -n "." + sleep 2 + attempt=$((attempt + 1)) + done + + if [ $attempt -gt $max_attempts ]; then + log_error "Kong Admin API 不可用" + return 1 + fi + + echo "" + log_info "Kong Admin API 就绪" + + # 创建服务和路由 + log_info "创建微服务..." + + # User Service + curl -s -X POST "$kong_admin/services" \ + -d "name=user-service" \ + -d "url=http://user-service:3001" > /dev/null 2>&1 || true + + curl -s -X POST "$kong_admin/services/user-service/routes" \ + -d "name=user-route" \ + -d "paths[]=/v1/users" \ + -d "paths[]=/v1/auth" \ + -d "strip_path=false" > /dev/null 2>&1 || true + + # Payment Service + curl -s -X POST "$kong_admin/services" \ + -d "name=payment-service" \ + -d "url=http://payment-service:3002" > /dev/null 2>&1 || true + + curl -s -X POST "$kong_admin/services/payment-service/routes" \ + -d "name=payment-route" \ + -d "paths[]=/v1/payments" \ + -d "paths[]=/v1/balance" \ + -d "strip_path=false" > /dev/null 2>&1 || true + + # Knowledge Service + curl -s -X POST "$kong_admin/services" \ + -d "name=knowledge-service" \ + -d "url=http://knowledge-service:3003" > /dev/null 2>&1 || true + + curl -s -X POST "$kong_admin/services/knowledge-service/routes" \ + -d "name=knowledge-route" \ + -d "paths[]=/v1/knowledge" \ + -d "strip_path=false" > /dev/null 2>&1 || true + + # Conversation Service + curl -s -X POST "$kong_admin/services" \ + -d "name=conversation-service" \ + -d "url=http://conversation-service:3004" > /dev/null 2>&1 || true + + curl -s -X POST "$kong_admin/services/conversation-service/routes" \ + -d "name=conversation-route" \ + -d "paths[]=/v1/conversations" \ + -d "strip_path=false" > /dev/null 2>&1 || true + + # Evolution Service (Admin) + curl -s -X POST "$kong_admin/services" \ + -d "name=evolution-service" \ + -d "url=http://evolution-service:3005" > /dev/null 2>&1 || true + + curl -s -X POST "$kong_admin/services/evolution-service/routes" \ + -d "name=evolution-route" \ + -d "paths[]=/v1/evolution" \ + -d "paths[]=/v1/memory" \ + -d "paths[]=/v1/admin" \ + -d "strip_path=false" > /dev/null 2>&1 || true + + log_info "配置全局插件..." + + # Rate Limiting + curl -s -X POST "$kong_admin/plugins" \ + -d "name=rate-limiting" \ + -d "config.minute=100" \ + -d "config.policy=local" > /dev/null 2>&1 || true + + # CORS + curl -s -X POST "$kong_admin/plugins" \ + -d "name=cors" \ + -d "config.origins=https://$DOMAIN,http://localhost" \ + -d "config.methods=GET,POST,PUT,DELETE,OPTIONS,PATCH" \ + -d "config.headers=Accept,Authorization,Content-Type,X-User-Id,X-Request-Id" \ + -d "config.credentials=true" \ + -d "config.max_age=3600" > /dev/null 2>&1 || true + + # Request ID + curl -s -X POST "$kong_admin/plugins" \ + -d "name=correlation-id" \ + -d "config.header_name=X-Request-Id" \ + -d "config.generator=uuid" > /dev/null 2>&1 || true + + log_success "Kong 路由配置完成" + + # 显示配置结果 + echo "" + echo -e "${PURPLE}已配置的服务:${NC}" + curl -s "$kong_admin/services" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do + echo " - $name" + done + + echo "" + echo -e "${PURPLE}已配置的路由:${NC}" + curl -s "$kong_admin/routes" | grep -o '"name":"[^"]*"' | sed 's/"name":"//g;s/"//g' | while read name; do + echo " - $name" + done +} + +# Kong 操作入口 +do_kong() { + local action=${1:-status} + + case $action in + setup|init) + setup_kong_routes + ;; + status) + local kong_admin="${KONG_ADMIN_URL:-http://localhost:8001}" + echo -e "${PURPLE}Kong 状态:${NC}" + curl -s "$kong_admin" > /dev/null 2>&1 && echo "Kong Admin API: 运行中" || echo "Kong Admin API: 未运行" + curl -s "http://localhost:8000" > /dev/null 2>&1 && echo "Kong Proxy: 运行中" || echo "Kong Proxy: 未运行" + + echo "" + echo "服务数量: $(curl -s "$kong_admin/services" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)" + echo "路由数量: $(curl -s "$kong_admin/routes" 2>/dev/null | grep -o '"total":[0-9]*' | cut -d: -f2 || echo 0)" + ;; + services) + curl -s "http://localhost:8001/services" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/services" + ;; + routes) + curl -s "http://localhost:8001/routes" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/routes" + ;; + plugins) + curl -s "http://localhost:8001/plugins" | python3 -m json.tool 2>/dev/null || curl -s "http://localhost:8001/plugins" + ;; + *) + log_error "未知 Kong 操作: $action (可选: setup, status, services, routes, plugins)" + exit 1 + ;; + esac } #=============================================================================== @@ -796,10 +1132,11 @@ do_db() { #=============================================================================== show_help() { - cat << 'EOF' + cat << EOF ╔═══════════════════════════════════════════════════════════════════════════════╗ ║ iConsulting 部署管理脚本 ║ +║ 域名: $DOMAIN ║ ╚═══════════════════════════════════════════════════════════════════════════════╝ 用法: ./deploy.sh [target] [options] @@ -834,6 +1171,22 @@ show_help() { deploy [mode] 完整部署 (构建 + 启动) mode: docker (默认), local + deploy-full 完整部署 (含 SSL 证书自动申请) + + ssl SSL 证书管理 (Let's Encrypt) + action: install - 安装 certbot + obtain - 申请证书 + renew - 续期证书 + auto-renew - 配置自动续期 + status - 查看证书状态 + + kong Kong API Gateway 管理 + action: setup - 配置路由和插件 + status - 查看状态 + services - 查看服务列表 + routes - 查看路由列表 + plugins - 查看插件列表 + db 数据库操作 action: migrate, seed, backup, restore, reset, status @@ -841,6 +1194,9 @@ show_help() { 示例: ./deploy.sh deploy # 完整部署 + ./deploy.sh deploy-full # 完整部署 (含 SSL) + ./deploy.sh ssl obtain # 申请 SSL 证书 + ./deploy.sh kong setup # 配置 Kong 路由 ./deploy.sh build conversation # 只构建对话服务 ./deploy.sh start backend local # 本地模式启动所有后端 ./deploy.sh restart user docker # 重启用户服务 (Docker) @@ -849,6 +1205,10 @@ show_help() { ./deploy.sh db backup # 备份数据库 ./deploy.sh db migrate # 执行数据库迁移 +环境变量: + DOMAIN 域名 (默认: iconsulting.szaiai.com) + ADMIN_EMAIL 管理员邮箱 (用于 SSL 证书) + EOF } @@ -885,6 +1245,15 @@ main() { deploy) do_deploy "$@" ;; + deploy-full) + do_deploy_full + ;; + ssl) + do_ssl "$@" + ;; + kong) + do_kong "$@" + ;; db) do_db "$@" ;; diff --git a/nginx/conf.d/default.conf b/nginx/conf.d/default.conf index ebd9d9a..3788b6a 100644 --- a/nginx/conf.d/default.conf +++ b/nginx/conf.d/default.conf @@ -1,6 +1,8 @@ #=============================================================================== # iConsulting Nginx 配置 # +# 域名: iconsulting.szaiai.com +# # 路由规则: # / -> web-client (用户前端) # /admin -> admin-client (管理后台) @@ -9,9 +11,55 @@ # #=============================================================================== +# HTTP -> HTTPS 重定向 server { listen 80; - server_name _; + server_name iconsulting.szaiai.com; + + # Let's Encrypt 验证路径 + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # 健康检查端点 (不重定向) + location /health { + access_log off; + return 200 'OK'; + add_header Content-Type text/plain; + } + + # 其他请求重定向到 HTTPS + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS 主服务 +server { + listen 443 ssl http2; + server_name iconsulting.szaiai.com; + + # SSL 证书配置 + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + + # SSL 优化配置 + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + # 现代 SSL 配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # HSTS (启用后浏览器会强制使用 HTTPS) + add_header Strict-Transport-Security "max-age=63072000" always; + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; # 健康检查端点 location /health { @@ -106,25 +154,3 @@ server { log_not_found off; } } - -# HTTPS 配置 (如有SSL证书,取消注释) -# server { -# listen 443 ssl http2; -# server_name _; -# -# ssl_certificate /etc/nginx/ssl/fullchain.pem; -# ssl_certificate_key /etc/nginx/ssl/privkey.pem; -# ssl_session_timeout 1d; -# ssl_session_cache shared:SSL:50m; -# ssl_session_tickets off; -# -# ssl_protocols TLSv1.2 TLSv1.3; -# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; -# ssl_prefer_server_ciphers off; -# -# # HSTS -# add_header Strict-Transport-Security "max-age=63072000" always; -# -# # 其他配置同上... -# include /etc/nginx/conf.d/locations.conf; -# }