feat: 添加 SSL 证书自动申请和 Kong API Gateway 配置

新增功能:
- 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-09 00:07:26 -08:00
parent a7add8ff90
commit 3ac83e9305
4 changed files with 433 additions and 29 deletions

View File

@ -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:*)"
]
}
}

View File

@ -9,6 +9,12 @@
NODE_ENV=production
APP_NAME=iConsulting
# ===========================================
# 域名配置
# ===========================================
DOMAIN=iconsulting.szaiai.com
ADMIN_EMAIL=admin@szaiai.com
# ===========================================
# 服务器网络配置
# ===========================================

379
deploy.sh
View File

@ -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 <command> [target] [options]
@ -834,6 +1171,22 @@ show_help() {
deploy [mode] 完整部署 (构建 + 启动)
mode: docker (默认), local
deploy-full 完整部署 (含 SSL 证书自动申请)
ssl <action> SSL 证书管理 (Let's Encrypt)
action: install - 安装 certbot
obtain - 申请证书
renew - 续期证书
auto-renew - 配置自动续期
status - 查看证书状态
kong <action> Kong API Gateway 管理
action: setup - 配置路由和插件
status - 查看状态
services - 查看服务列表
routes - 查看路由列表
plugins - 查看插件列表
db <action> 数据库操作
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 "$@"
;;

View File

@ -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;
# }