# 域名部署实施计划 > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将情绪博物馆所有服务迁移到域名 lifescript.happylifeos.com,配置 HTTPS SSL 证书,并实现自动续期。 **Architecture:** - 前端应用通过 Nginx 托管,使用相对路径 API 调用 - 后端 API 通过 Nginx 反向代理暴露 - WebSocket 通过 Nginx 升级协议代理 - SSL 证书使用 Let's Encrypt 通过 certbot 申请,配置 systemd timer 自动续期 **Tech Stack:** Spring Boot, Vue 3, React, Nginx, Let's Encrypt certbot, Python 3 --- ## Chunk 1: SSL 证书申请脚本 ### Task 1: 创建 SSL 证书申请 Python 脚本 **Files:** - Create: `tools/deploy-ssl-cert.py` - [ ] **Step 1: 创建 Python 脚本文件** ```python #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SSL 证书申请与自动续期脚本 使用 certbot 工具申请 Let's Encrypt 证书并配置 nginx """ import argparse import subprocess import sys from pathlib import Path DOMAIN = "lifescript.happylifeos.com" CERT_PATH = f"/etc/letsencrypt/live/{DOMAIN}/fullchain.pem" KEY_PATH = f"/etc/letsencrypt/live/{DOMAIN}/privkey.pem" def log_info(msg): print(f"\033[32m[INFO]\033[0m {msg}") def log_warn(msg): print(f"\033[33m[WARN]\033[0m {msg}") def log_error(msg): print(f"\033[31m[ERROR]\033[0m {msg}") def run_command(cmd, check=True): """执行 shell 命令""" try: result = subprocess.run( cmd, shell=True, check=check, capture_output=False, text=True ) return result.returncode == 0 except subprocess.CalledProcessError as e: log_error(f"命令执行失败:{e}") return False def check_certbot(): """检查 certbot 是否安装""" log_info("检查 certbot 是否安装...") if not run_command("which certbot", check=False): log_error("certbot 未安装,请先安装:apt install certbot python3-certbot-nginx") return False log_info("certbot 已安装") return True def check_nginx(): """检查 nginx 配置是否正确""" log_info("检查 nginx 配置...") if not run_command("nginx -t", check=False): log_error("nginx 配置有误,请先修复") return False log_info("nginx 配置正确") return True def apply_certificate(dry_run=False): """申请 SSL 证书""" log_info(f"开始为 {DOMAIN} 申请 SSL 证书...") cmd = f"certbot --nginx -d {DOMAIN} --non-interactive --agree-tos --email admin@happylifeos.com" if dry_run: cmd += " --dry-run" log_info(f"[DRY RUN] 执行:{cmd}") if run_command(cmd): log_info("证书申请成功") return True else: log_error("证书申请失败") return False def setup_auto_renewal(): """配置自动续期""" log_info("配置证书自动续期...") # 检查是否已存在 systemd timer if run_command("systemctl list-timers | grep certbot", check=False): log_info("certbot 自动续期 timer 已存在") return True # 启用 systemd timer if run_command("systemctl enable certbot.timer && systemctl start certbot.timer"): log_info("已启用 certbot 自动续期 timer") return True else: log_warn("systemd timer 配置失败,尝试配置 cron 任务") return setup_cron_renewal() def setup_cron_renewal(): """配置 cron 自动续期""" cron_job = "0 3 1 * * certbot renew --quiet --deploy-hook 'systemctl reload nginx'" log_info(f"添加 cron 任务:{cron_job}") # 添加到 crontab result = subprocess.run( f"(crontab -l 2>/dev/null | grep -v '{DOMAIN}'; echo '{cron_job}') | crontab -", shell=True, capture_output=True, text=True ) if result.returncode == 0: log_info("cron 任务添加成功") return True else: log_error("cron 任务添加失败") return False def verify_certificate(): """验证证书是否有效""" log_info("验证 SSL 证书...") cmd = f'echo | openssl s_client -connect {DOMAIN}:443 -servername {DOMAIN} 2>/dev/null | openssl x509 -noout -dates' result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode == 0: log_info(f"证书信息:\n{result.stdout}") return True else: log_warn("无法验证证书(可能域名 DNS 尚未生效)") return False def main(): parser = argparse.ArgumentParser(description="SSL 证书申请与自动续期脚本") parser.add_argument("--dry-run", action="store_true", help="模拟运行,不实际申请证书") parser.add_argument("--renew", action="store_true", help="手动续期证书") parser.add_argument("--verify", action="store_true", help="验证证书状态") parser.add_argument("--setup-renewal", action="store_true", help="仅配置自动续期") args = parser.parse_args() # 验证模式 if args.verify: verify_certificate() return 0 # 仅配置自动续期 if args.setup_renewal: setup_auto_renewal() return 0 # 手动续期 if args.renew: run_command("certbot renew --force-renewal") run_command("systemctl reload nginx") return 0 # 完整流程 log_info("=== SSL 证书申请流程 ===") if not check_certbot(): return 1 if not check_nginx(): return 1 if not apply_certificate(dry_run=args.dry_run): return 1 if not args.dry_run: setup_auto_renewal() verify_certificate() log_info("=== SSL 证书申请完成 ===") return 0 if __name__ == "__main__": sys.exit(main()) ``` - [ ] **Step 2: 添加文件执行权限** ```bash chmod +x tools/deploy-ssl-cert.py ``` - [ ] **Step 3: 添加 Python 脚本元信息** 在文件顶部注释中添加: ```python """ Author: [创建人姓名] Created: 2026-03-17 Purpose: SSL 证书申请与自动续期 - 使用 certbot 配置 Let's Encrypt 证书 """ ``` - [ ] **Step 4: 提交** ```bash git add tools/deploy-ssl-cert.py git commit -m "feat: 添加 SSL 证书申请与自动续期脚本" ``` --- ## Chunk 2: 前端环境配置文件修改 ### Task 2: 修改用户前端 (web) 生产环境配置 **Files:** - Modify: `web/.env.production` - [ ] **Step 1: 读取当前文件内容** - [ ] **Step 2: 修改为域名配置** ```env # 生产环境配置 VITE_APP_ENV=prod VITE_APP_TITLE=情绪博物馆 VITE_APP_VERSION=1.0.0 # API 配置 - 使用相对路径,通过 nginx 代理 VITE_API_BASE_URL=/api VITE_WS_BASE_URL=/ws VITE_UPLOAD_URL=/api/upload # 调试配置 VITE_DEBUG=false VITE_MOCK=false # 其他配置 VITE_APP_DESCRIPTION=情绪博物馆 Web 系统 ``` - [ ] **Step 3: 提交** ```bash git add web/.env.production git commit -m "config: 更新 web 前端生产环境配置为域名访问" ``` ### Task 3: 修改管理后台 (web-admin) 生产环境配置 **Files:** - Modify: `web-admin/.env.production` - [ ] **Step 1: 读取当前文件内容** - [ ] **Step 2: 修改为域名配置** ```env # 生产环境配置 VITE_APP_TITLE=情绪博物馆管理后台 # 生产环境使用相对路径,通过 nginx 代理到后端服务 VITE_APP_BASE_API=/api ``` - [ ] **Step 3: 提交** ```bash git add web-admin/.env.production git commit -m "config: 更新管理后台生产环境配置为域名访问" ``` ### Task 4: 修改 Life-Script 生产环境配置 **Files:** - Modify: `life-script/.env.production` - [ ] **Step 1: 读取当前文件内容** - [ ] **Step 2: 修改为域名配置** ```env # 生产环境配置 VITE_API_BASE_URL=/api ``` - [ ] **Step 3: 提交** ```bash git add life-script/.env.production git commit -m "config: 更新 Life-Script 生产环境配置为域名访问" ``` --- ## Chunk 3: Nginx 配置文件修改 ### Task 5: 创建新的 Nginx 配置文件 **Files:** - Modify: `conf/emotion-museum.conf` - [ ] **Step 1: 备份当前配置文件** ```bash cp conf/emotion-museum.conf conf/emotion-museum.conf.backup ``` - [ ] **Step 2: 修改 Nginx 配置为域名版本** ```nginx # Emotion Museum - 域名部署配置 # 域名:lifescript.happylifeos.com # 配置路径:/etc/nginx/sites-available/lifescript.happylifeos.com.conf # HTTP 服务器 - 强制跳转 HTTPS server { listen 80; server_name lifescript.happylifeos.com; # 强制跳转 HTTPS return 301 https://$server_name$request_uri; } # HTTPS 服务器 server { listen 443 ssl http2; server_name lifescript.happylifeos.com; # SSL 证书配置 ssl_certificate /etc/letsencrypt/live/lifescript.happylifeos.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/lifescript.happylifeos.com/privkey.pem; # SSL 优化配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # HSTS (可选,生产环境建议开启) # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # 根路径 - 用户前端应用 location / { alias /data/www/emotion-museum/; autoindex off; # 处理 Vue Router 的 history 模式 try_files $uri $uri/ /index.html; # HTML 文件不缓存 location ~ \.html?$ { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } # 静态资源缓存 1 年 location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; } } # 管理后台应用路径 location /emotion-museum-admin/ { alias /data/www/emotion-museum-admin/; autoindex off; # 处理 Vue Router 的 history 模式 try_files $uri $uri/ /emotion-museum-admin/index.html; # HTML 文件不缓存 location ~ \.html?$ { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } # 静态资源缓存 1 年 location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; } } # 处理不带末尾斜杠的 /emotion-museum-admin 请求 location = /emotion-museum-admin { rewrite ^(.*)$ $1/ permanent; } # Life-Script 应用路径 location /life-script/ { alias /data/www/life-script/; autoindex off; # 处理 React Router 的 history 模式 try_files $uri $uri/ /life-script/index.html; # HTML 文件不缓存 location ~ \.html?$ { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } # 静态资源缓存 1 年 location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; } } # 处理不带末尾斜杠的 /life-script 请求 location = /life-script { rewrite ^ /life-script/ last; } # 后端 API 代理 location /api { proxy_pass http://127.0.0.1:19089; 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; } # WebSocket 代理 location /ws { proxy_pass http://127.0.0.1:19089; 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; # WebSocket 超时设置 proxy_connect_timeout 7d; proxy_send_timeout 7d; proxy_read_timeout 7d; } # 健康检查端点 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } # 禁止访问敏感文件 location ~ /\. { deny all; access_log off; log_not_found off; } access_log /var/log/nginx/lifescript_access.log; error_log /var/log/nginx/lifescript_error.log; } ``` - [ ] **Step 3: 提交** ```bash git add conf/emotion-museum.conf git commit -m "config: 更新 nginx 配置为域名访问,添加 HTTPS SSL 支持" ``` --- ## Chunk 4: 部署脚本修改 ### Task 6: 创建新域名下的一键部署脚本 **Files:** - Create: `deploy-to-prod.sh` - [ ] **Step 1: 创建部署脚本** ```bash #!/bin/bash # 情绪博物馆 - 域名部署脚本 # 域名:lifescript.happylifeos.com # 使用方法:bash deploy-to-prod.sh [ssl|frontend|admin|life-script|backend|all] set -e # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # 日志函数 log_info() { echo -e "${GREEN}[INFO]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_section() { echo "" echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║${NC} $1" echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}" echo "" } # 服务器配置 SERVER_IP="101.200.208.45" USERNAME="root" DOMAIN="lifescript.happylifeos.com" # SSL 证书申请 deploy_ssl() { log_section "申请 SSL 证书" if [ ! -f "tools/deploy-ssl-cert.py" ]; then log_error "SSL 证书脚本不存在:tools/deploy-ssl-cert.py" return 1 fi log_info "在服务器上执行 SSL 证书申请..." scp tools/deploy-ssl-cert.py ${USERNAME}@${SERVER_IP}:/tmp/deploy-ssl-cert.py ssh ${USERNAME}@${SERVER_IP} "python3 /tmp/deploy-ssl-cert.py" if [ $? -eq 0 ]; then log_info "✅ SSL 证书申请完成" return 0 else log_error "❌ SSL 证书申请失败" return 1 fi } # 部署后端 deploy_backend() { log_section "部署后端服务" if [ ! -f "backend-single/deploy.sh" ]; then log_error "后端部署脚本不存在" return 1 fi log_info "执行后端部署..." cd backend-single bash deploy.sh remote local result=$? cd .. if [ $result -eq 0 ]; then log_info "✅ 后端部署完成" return 0 else log_error "❌ 后端部署失败" return 1 fi } # 部署前端 deploy_frontend() { log_section "部署用户前端" if [ ! -f "web/deploy.sh" ]; then log_error "前端部署脚本不存在" return 1 fi log_info "执行前端部署..." cd web bash deploy.sh local result=$? cd .. if [ $result -eq 0 ]; then log_info "✅ 前端部署完成" return 0 else log_error "❌ 前端部署失败" return 1 fi } # 部署管理后台 deploy_admin() { log_section "部署管理后台" if [ ! -f "web-admin/deploy.sh" ]; then log_error "管理后台部署脚本不存在" return 1 fi log_info "执行管理后台部署..." cd web-admin bash deploy.sh local result=$? cd .. if [ $result -eq 0 ]; then log_info "✅ 管理后台部署完成" return 0 else log_error "❌ 管理后台部署失败" return 1 fi } # 部署 Life-Script deploy_life_script() { log_section "部署 Life-Script" if [ ! -f "life-script/deploy.sh" ]; then log_error "Life-Script 部署脚本不存在" return 1 fi log_info "执行 Life-Script 部署..." cd life-script bash deploy.sh local result=$? cd .. if [ $result -eq 0 ]; then log_info "✅ Life-Script 部署完成" return 0 else log_error "❌ Life-Script 部署失败" return 1 fi } # 部署 Nginx 配置 deploy_nginx() { log_section "部署 Nginx 配置" if [ ! -f "conf/emotion-museum.conf" ]; then log_error "Nginx 配置文件不存在" return 1 fi log_info "上传 Nginx 配置文件..." scp conf/emotion-museum.conf ${USERNAME}@${SERVER_IP}:/etc/nginx/sites-available/${DOMAIN}.conf log_info "启用站点配置..." ssh ${USERNAME}@${SERVER_IP} "ln -snf /etc/nginx/sites-available/${DOMAIN}.conf /etc/nginx/sites-enabled/${DOMAIN}.conf" log_info "移除默认配置(如果存在冲突)..." ssh ${USERNAME}@${SERVER_IP} "rm -f /etc/nginx/sites-enabled/default" log_info "验证 Nginx 配置..." if ssh ${USERNAME}@${SERVER_IP} "nginx -t"; then log_info "Nginx 配置验证通过" ssh ${USERNAME}@${SERVER_IP} "systemctl reload nginx" log_info "✅ Nginx 配置重载完成" return 0 else log_error "❌ Nginx 配置验证失败" return 1 fi } # 主程序 main() { DEPLOY_TYPE="${1:-all}" log_section "情绪博物馆 - 域名部署" log_info "域名:https://${DOMAIN}" log_info "部署类型:${DEPLOY_TYPE}" log_info "部署时间:$(date '+%Y-%m-%d %H:%M:%S')" case "$DEPLOY_TYPE" in ssl) deploy_ssl ;; backend) deploy_backend ;; frontend) deploy_frontend ;; admin) deploy_admin ;; life-script) deploy_life_script ;; nginx) deploy_nginx ;; all) deploy_ssl deploy_nginx deploy_backend deploy_frontend deploy_admin deploy_life_script ;; *) log_error "无效的部署类型:$DEPLOY_TYPE" echo "使用方法:bash deploy-to-prod.sh [ssl|backend|frontend|admin|life-script|nginx|all]" exit 1 ;; esac log_section "部署完成" if [ "$DEPLOY_TYPE" = "all" ] || [ "$DEPLOY_TYPE" = "ssl" ]; then log_info "📋 验证 SSL 证书:运行 python3 tools/deploy-ssl-cert.py --verify" fi if [ "$DEPLOY_TYPE" = "all" ] || [ "$DEPLOY_TYPE" = "nginx" ]; then log_info "🌐 访问地址:" log_info " 用户前端:https://${DOMAIN}/" log_info " 管理后台:https://${DOMAIN}/emotion-museum-admin/" log_info " Life-Script: https://${DOMAIN}/life-script/" log_info " API 地址:https://${DOMAIN}/api" log_info " WebSocket: wss://${DOMAIN}/ws" fi } main "$@" ``` - [ ] **Step 2: 添加执行权限** ```bash chmod +x deploy-to-prod.sh ``` - [ ] **Step 3: 添加元信息** 在文件顶部添加: ```bash #!/bin/bash # Author: [创建人姓名] # Created: 2026-03-17 # Purpose: 域名部署入口脚本 - 支持 SSL 证书申请、前端、后端、nginx 配置部署 ``` - [ ] **Step 4: 提交** ```bash git add deploy-to-prod.sh git commit -m "feat: 添加域名一键部署脚本" ``` ### Task 7: 修改用户前端部署脚本 **Files:** - Modify: `web/deploy.sh` - [ ] **Step 1: 读取当前文件** - [ ] **Step 2: 修改远程路径和访问地址提示** 将访问地址提示修改为: ```bash echo "✅ 部署完成!" echo "📱 访问地址:https://lifescript.happylifeos.com/" ``` - [ ] **Step 3: 提交** ```bash git add web/deploy.sh git commit -m "config: 更新前端部署脚本访问地址提示" ``` ### Task 8: 修改管理后台部署脚本 **Files:** - Modify: `web-admin/deploy.sh` - [ ] **Step 1: 读取当前文件** - [ ] **Step 2: 修改访问地址提示** 将访问地址提示修改为: ```bash echo "✅ 管理后台部署完成!" echo "🔧 访问地址:https://lifescript.happylifeos.com/emotion-museum-admin/" ``` - [ ] **Step 3: 提交** ```bash git add web-admin/deploy.sh git commit -m "config: 更新管理后台部署脚本访问地址提示" ``` --- ## Chunk 5: 后端配置与验证 ### Task 9: 检查并修改后端 CORS 配置 **Files:** - Modify: `backend-single/src/main/resources/application.yml` (或相应配置文件) - [ ] **Step 1: 读取当前后端配置文件** 检查 CORS 配置是否允许新域名访问 - [ ] **Step 2: 如需修改,添加域名白名单** 确保 CORS 配置包含 `https://lifescript.happylifeos.com` - [ ] **Step 3: 提交** ```bash git add backend-single/src/main/resources/application.yml git commit -m "config: 更新 CORS 配置允许新域名访问" ``` ### Task 10: 执行部署验证 **Files:** - 无需修改文件 - [ ] **Step 1: 本地类型检查** ```bash cd web && npm run type-check cd ../web-admin && npm run type-check cd ../life-script && npm run type-check ``` - [ ] **Step 2: 推送代码到服务器** ```bash git push origin master ``` - [ ] **Step 3: 执行 SSL 证书申请** ```bash bash deploy-to-prod.sh ssl ``` - [ ] **Step 4: 执行完整部署** ```bash bash deploy-to-prod.sh all ``` - [ ] **Step 5: 浏览器验证** 使用浏览器访问以下地址验证: - https://lifescript.happylifeos.com/ - https://lifescript.happylifeos.com/emotion-museum-admin/ - https://lifescript.happylifeos.com/life-script/ - https://lifescript.happylifeos.com/api/health - [ ] **Step 6: 验证浏览器 Console 无错误** 确保所有页面: - ✅ 正常加载 - ✅ 无 JavaScript 错误 - ✅ API 请求成功 - ✅ WebSocket 连接成功 --- ## 部署总结 ### 文件清单 | 文件 | 操作 | 说明 | |------|------|------| | `tools/deploy-ssl-cert.py` | 创建 | SSL 证书申请与自动续期脚本 | | `web/.env.production` | 修改 | 前端 API 配置改为相对路径 | | `web-admin/.env.production` | 修改 | 管理后台 API 配置改为相对路径 | | `life-script/.env.production` | 修改 | Life-Script API 配置改为相对路径 | | `conf/emotion-museum.conf` | 修改 | Nginx 域名配置 | | `deploy-to-prod.sh` | 创建 | 域名部署入口脚本 | | `web/deploy.sh` | 修改 | 更新访问地址提示 | | `web-admin/deploy.sh` | 修改 | 更新访问地址提示 | ### 部署命令 ```bash # 1. 提交所有配置变更 git add . git commit -m "chore: 域名部署配置更新" git push # 2. 申请 SSL 证书 bash deploy-to-prod.sh ssl # 3. 部署所有服务 bash deploy-to-prod.sh all ``` ### 验证检查清单 - [ ] SSL 证书有效且自动续期已配置 - [ ] HTTPS 强制跳转生效 - [ ] 用户前端正常访问 - [ ] 管理后台正常访问 - [ ] Life-Script 正常访问 - [ ] API 代理正常 - [ ] WebSocket 连接正常 - [ ] 浏览器 Console 无错误 - [ ] 所有静态资源正确加载