diff --git a/CLAUDE.md b/CLAUDE.md index 9ff1711..8376e22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,11 +8,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co 情绪博物馆 (Emotion Museum) 是一款基于 AI 技术的心理健康应用,通过智能对话、情绪分析、个性化成长方案等功能,帮助用户建立健康的情绪管理习惯。 -**生产环境地址**: `101.200.208.45` -- 用户前端:http://101.200.208.45/emotion-museum/ -- 管理后台:http://101.200.208.45/emotion-museum-admin/ -- 后端 API:http://101.200.208.45:19089/api -- WebSocket:ws://101.200.208.45:19089/ws +**生产环境地址**: `lifescript.happylifeos.com` +- 用户前端:https://lifescript.happylifeos.com/ +- 管理后台:https://lifescript.happylifeos.com/emotion-museum-admin/ +- 后端 API:https://lifescript.happylifeos.com/api +- WebSocket:wss://lifescript.happylifeos.com/ws --- diff --git a/README.md b/README.md index 968a659..d047082 100644 --- a/README.md +++ b/README.md @@ -200,18 +200,25 @@ bash deploy-all.sh admin # 仅部署管理后台 4. 集成Tailwind CSS样式框架 5. 使用Pinia进行状态管理 -### 访问地址 +## 🌐 访问地址 -#### 生产环境 (101.200.208.45) -- **用户前端**: http://101.200.208.45/emotion-museum/ -- **管理后台**: http://101.200.208.45/emotion-museum-admin/ -- **后端API**: http://101.200.208.45:19089/api -- **WebSocket**: ws://101.200.208.45:19089/ws +### 生产环境 (HTTPS 已启用) +- **用户前端**: https://lifescript.happylifeos.com/emotion-museum/ +- **管理后台**: https://lifescript.happylifeos.com/emotion-museum-admin/ +- **课程页面**: https://lifescript.happylifeos.com/course-of-life/ +- **后端 API**: https://lifescript.happylifeos.com/api/ +- **WebSocket**: wss://lifescript.happylifeos.com/ws +- **健康检查**: https://lifescript.happylifeos.com/health -#### 开发环境 (本地) +**SSL 证书信息**: +- 证书有效期:2026-03-18 至 2026-06-16 +- 自动续期:已配置(每天凌晨 2 点自动检查) +- 证书颁发机构:Let's Encrypt + +### 开发环境 (本地) - **用户前端**: http://localhost:5173 - **管理后台**: http://localhost:5174 -- **后端API**: http://localhost:19089/api +- **后端 API**: http://localhost:19089/api - **WebSocket**: ws://localhost:19089/ws ## 🧪 测试策略 diff --git a/conf/emotion-museum.conf b/conf/emotion-museum.conf index 4798464..122c301 100644 --- a/conf/emotion-museum.conf +++ b/conf/emotion-museum.conf @@ -1,84 +1,122 @@ # Emotion Museum HTTPS Nginx 配置 # 配置路径:/www/server/panel/vhost/nginx/emotion-museum.conf +# 域名:lifescript.happylifeos.com +# HTTP 服务器 - 强制跳转 HTTPS server { - listen 443 ssl; - http2 on; + 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_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; 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; + + # ACME 挑战目录 - 用于 SSL 证书申请和续期 location ^~ /.well-known/acme-challenge/ { root /data/www/acme-challenge; allow all; try_files $uri =404; } + # 根路径 - 用户前端应用 location = / { return 404; } - location /emotion-museum/ { + location / { alias /data/www/emotion-museum/; autoindex off; - # 静态资源直接返回文件,不经过 try_files - location ~* ^/emotion-museum/(assets|static)/ { + + # 处理 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; try_files $uri =404; } - # SPA 路由兜底 - try_files $uri $uri/ /emotion-museum/index.html; - } - - location = /emotion-museum { - rewrite ^(.*)$ $1/ permanent; } + # 管理后台应用路径 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; + } } location = /emotion-museum-admin { rewrite ^(.*)$ $1/ permanent; } - location /course-of-life/ { - alias /data/www/course-of-life/; - autoindex off; - try_files $uri $uri/ /course-of-life/index.html; - } - - location = /course-of-life { - rewrite ^ /course-of-life/ last; - } - - # life-script React 应用 + # Life-Script 应用路径 location /life-script/ { alias /data/www/life-script/; autoindex off; - # 静态资源直接返回文件 - location ~* ^/life-script/(assets|static)/ { + + # 处理 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; - try_files $uri =404; } - # SPA 路由兜底 - try_files $uri $uri/ /life-script/index.html; } location = /life-script { rewrite ^ /life-script/ last; } + # 后端 API 代理 location /api { proxy_pass http://127.0.0.1:19089; proxy_set_header Host $host; @@ -90,6 +128,7 @@ server { proxy_read_timeout 60s; } + # WebSocket 代理 location /ws { proxy_pass http://127.0.0.1:19089; proxy_http_version 1.1; @@ -104,23 +143,21 @@ server { 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 /www/wwwlogs/ssl-access.log; -} - -server { - listen 80; - server_name lifescript.happylifeos.com; - return 301 https://$host$request_uri; + error_log /www/wwwlogs/ssl-error.log; } diff --git a/conf/nginx-emotion-museum-fix.conf b/conf/nginx-emotion-museum-fix.conf new file mode 100644 index 0000000..bc5afe6 --- /dev/null +++ b/conf/nginx-emotion-museum-fix.conf @@ -0,0 +1,161 @@ +# Emotion Museum 前端应用 Nginx 配置 +# 配置路径:/www/server/panel/vhost/nginx/emotion-museum.conf + +server { + listen 80; + server_name lifescript.happylifeos.com; + + # Let's Encrypt ACME 挑战位置(必须在其他 location 之前) + location ^~ /.well-known/acme-challenge/ { + root /data/www/acme-challenge; + allow all; + try_files $uri =404; + } + + # 根路径不提供站点,避免跳转或兜底到其他 server + location = / { + return 404; + } + + # 前端应用路径 + location /emotion-museum/ { + alias /data/www/emotion-museum/; + + # 启用目录索引(可选) + autoindex off; + + # 处理 Vue Router 的 history 模式 + # 所有非文件请求都重定向到 index.html + try_files $uri $uri/ /emotion-museum/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 请求 + location = /emotion-museum { + rewrite ^(.*)$ $1/ permanent; + } + + # 管理后台应用路径 + location /emotion-museum-admin/ { + alias /data/www/emotion-museum-admin/; + + # 启用目录索引(可选) + autoindex off; + + # 处理 Vue Router 的 history 模式 + # 所有非文件请求都重定向到 index.html + 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; + } + + # 体验前端应用路径 (course-web) + location /course-of-life/ { + alias /data/www/course-of-life/; + + # 启用目录索引(可选) + autoindex off; + + # 处理 SPA 的 history 模式 (React Router) + # 所有非文件请求都重定向到 index.html + try_files $uri $uri/ /course-of-life/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; + } + } + + # 处理不带末尾斜杠的 /course-of-life 请求 + location = /course-of-life { + # 不进行 301/302 外部跳转:内部改写到 /course-of-life/ 交给下方 SPA location 处理 + # 这样 URL 仍是 /course-of-life,但返回内容与 /course-of-life/ 完全一致(且不会触发"下载") + rewrite ^ /course-of-life/ 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 /www/wwwlogs/access.log; +} diff --git a/conf/nginx-emotion-museum-ssl.conf b/conf/nginx-emotion-museum-ssl.conf new file mode 100644 index 0000000..4798464 --- /dev/null +++ b/conf/nginx-emotion-museum-ssl.conf @@ -0,0 +1,126 @@ +# Emotion Museum HTTPS Nginx 配置 +# 配置路径:/www/server/panel/vhost/nginx/emotion-museum.conf + +server { + listen 443 ssl; + http2 on; + server_name lifescript.happylifeos.com; + + ssl_certificate /etc/letsencrypt/live/lifescript.happylifeos.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/lifescript.happylifeos.com/privkey.pem; + 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; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + + location ^~ /.well-known/acme-challenge/ { + root /data/www/acme-challenge; + allow all; + try_files $uri =404; + } + + location = / { + return 404; + } + + location /emotion-museum/ { + alias /data/www/emotion-museum/; + autoindex off; + # 静态资源直接返回文件,不经过 try_files + location ~* ^/emotion-museum/(assets|static)/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + expires 1y; + try_files $uri =404; + } + # SPA 路由兜底 + try_files $uri $uri/ /emotion-museum/index.html; + } + + location = /emotion-museum { + rewrite ^(.*)$ $1/ permanent; + } + + location /emotion-museum-admin/ { + alias /data/www/emotion-museum-admin/; + autoindex off; + try_files $uri $uri/ /emotion-museum-admin/index.html; + } + + location = /emotion-museum-admin { + rewrite ^(.*)$ $1/ permanent; + } + + location /course-of-life/ { + alias /data/www/course-of-life/; + autoindex off; + try_files $uri $uri/ /course-of-life/index.html; + } + + location = /course-of-life { + rewrite ^ /course-of-life/ last; + } + + # life-script React 应用 + location /life-script/ { + alias /data/www/life-script/; + autoindex off; + # 静态资源直接返回文件 + location ~* ^/life-script/(assets|static)/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + expires 1y; + try_files $uri =404; + } + # SPA 路由兜底 + try_files $uri $uri/ /life-script/index.html; + } + + location = /life-script { + rewrite ^ /life-script/ last; + } + + 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; + } + + 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; + 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 /www/wwwlogs/ssl-access.log; +} + +server { + listen 80; + server_name lifescript.happylifeos.com; + return 301 https://$host$request_uri; +} diff --git a/deploy-all.sh b/deploy-all.sh index 216a509..4554904 100755 --- a/deploy-all.sh +++ b/deploy-all.sh @@ -229,16 +229,16 @@ if [ "$ALL_SUCCESS" = true ]; then log_section "部署成功!" if [ "$DEPLOY_TYPE" = "backend" ] || [ "$DEPLOY_TYPE" = "all" ]; then - log_info "🔌 后端API地址: http://101.200.208.45:19089/api" - log_info "📊 WebSocket地址: ws://101.200.208.45:19089/ws" + log_info "🔌 后端API地址: https://lifescript.happylifeos.com/api" + log_info "📊 WebSocket地址: wss://lifescript.happylifeos.com/ws" fi if [ "$DEPLOY_TYPE" = "frontend" ] || [ "$DEPLOY_TYPE" = "all" ]; then - log_info "📱 前端访问地址: http://101.200.208.45/emotion-museum/" + log_info "📱 前端访问地址: https://lifescript.happylifeos.com/" fi if [ "$DEPLOY_TYPE" = "admin" ] || [ "$DEPLOY_TYPE" = "all" ]; then - log_info "🔧 管理后台地址: http://101.200.208.45/emotion-museum-admin/" + log_info "🔧 管理后台地址: https://lifescript.happylifeos.com/emotion-museum-admin/" fi echo "" diff --git a/deploy-domain.sh b/deploy-domain.sh new file mode 100644 index 0000000..54c4aeb --- /dev/null +++ b/deploy-domain.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# +# Author: Emotion Museum Team +# Created: 2026-03-18 +# Purpose: 一键部署域名配置(SSL + Nginx + 后端 + 前端) +# +# 使用方法: +# ./deploy-domain.sh +# +# 前置条件: +# - 已配置 SSH 免密登录到服务器 +# - 服务器已安装 certbot 和 nginx +# + +set -e + +# 配置 +SERVER="101.200.208.45" +DOMAIN="lifescript.happylifeos.com" +EMAIL="admin@happylifeos.com" + +# 颜色 +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 -e "\n${BLUE}==== $1 ====${NC}\n"; } + +log_section "情绪博物馆 - 域名部署" + +# 1. 检查 SSH 连接 +log_info "检查 SSH 连接..." +ssh root@$SERVER "echo 'SSH 连接正常'" || { log_error "SSH 连接失败"; exit 1; } + +# 2. 上传 SSL 证书脚本 +log_info "上传 SSL 证书脚本..." +scp tools/deploy-ssl-cert.py root@$SERVER:/tmp/deploy-ssl-cert.py + +# 3. 执行 SSL 证书验证(证书应该已申请) +log_info "验证 SSL 证书..." +ssh root@$SERVER "python3 /tmp/deploy-ssl-cert.py --verify" || log_warn "证书验证失败,请检查" + +# 4. 上传并应用 Nginx 配置 +log_info "上传 Nginx 配置..." +scp conf/nginx-emotion-museum-fix.conf root@$SERVER:/tmp/nginx-fix.conf +ssh root@$SERVER " + # 备份当前配置 + cp /www/server/panel/vhost/nginx/emotion-museum.conf /www/server/panel/vhost/nginx/emotion-museum.conf.bak 2>/dev/null || true + # 应用新配置 + cp /tmp/nginx-fix.conf /www/server/panel/vhost/nginx/emotion-museum.conf + # 验证并重载 + /www/server/nginx/sbin/nginx -t && /www/server/nginx/sbin/nginx -s reload +" +log_info "Nginx 配置已应用" + +# 5. 部署后端 +log_info "部署后端服务..." +cd backend-single && mvn clean package -DskipTests && cd .. +scp backend-single/target/backend-single-1.0.0.jar root@$SERVER:/opt/emotion-museum/backend.jar +ssh root@$SERVER " + systemctl stop emotion-museum-backend 2>/dev/null || true + sleep 2 + # 备份旧版本 + cp /opt/emotion-museum/backend.jar /opt/emotion-museum/backend.jar.bak 2>/dev/null || true + # 后端已在上面 scp 覆盖,重启服务 + systemctl restart emotion-museum-backend 2>/dev/null || (cd /opt/emotion-museum && nohup java -jar backend.jar > /dev/null 2>&1 &) + sleep 3 + echo '后端服务状态:' + systemctl status emotion-museum-backend 2>/dev/null || ps aux | grep backend.jar | grep -v grep +" + +# 6. 部署前端 +log_info "部署前端..." +cd web && npm run build && cd .. +scp -r web/dist/* root@$SERVER:/data/www/emotion-museum/ +log_info "前端部署完成" + +# 7. 部署管理后台 +log_info "部署管理后台..." +cd web-admin && npm run build && cd .. +scp -r web-admin/dist/* root@$SERVER:/data/www/emotion-museum-admin/ +log_info "管理后台部署完成" + +# 8. 验证部署 +log_section "验证部署" +log_info "验证 HTTPS 访问..." +curl -k -s -o /dev/null -w " 前端页面:HTTP %{http_code}\n" https://$DOMAIN/emotion-museum/ +curl -k -s -o /dev/null -w " 管理后台:HTTP %{http_code}\n" https://$DOMAIN/emotion-museum-admin/ +curl -k -s -o /dev/null -w " API 代理:HTTP %{http_code}\n" https://$DOMAIN/api/health +curl -s -o /dev/null -w " HTTP 跳转:HTTP %{http_code}\n" http://$DOMAIN/ + +log_section "部署完成" +log_info "访问地址:" +log_info " 用户前端:https://$DOMAIN/emotion-museum/" +log_info " 管理后台:https://$DOMAIN/emotion-museum-admin/" +log_info " API 地址:https://$DOMAIN/api/" +log_info " WebSocket: wss://$DOMAIN/ws" diff --git a/deploy-to-prod.sh b/deploy-to-prod.sh new file mode 100644 index 0000000..06c119e --- /dev/null +++ b/deploy-to-prod.sh @@ -0,0 +1,252 @@ +#!/bin/bash +# Author: huazhongmin +# Created: 2026-03-17 +# Purpose: 域名一键部署脚本 - 支持 SSL 证书申请、前端、后端、nginx 配置部署 + +# 情绪博物馆 - 域名部署脚本 +# 域名:lifescript.happylifeos.com +# 使用方法:bash deploy-to-prod.sh [ssl|frontend|admin|life-script|backend|nginx|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 "$@" diff --git a/web-admin/deploy.sh b/web-admin/deploy.sh index 3753fa0..29fdfa3 100755 --- a/web-admin/deploy.sh +++ b/web-admin/deploy.sh @@ -55,7 +55,7 @@ if scp dist/index.html "${USERNAME}@${SERVER_IP}:${REMOTE_PATH}/" && \ ssh "${USERNAME}@${SERVER_IP}" "chmod -R 755 ${REMOTE_PATH}" echo "✅ 管理后台部署完成!" - echo "📱 访问地址: http://$SERVER_IP/emotion-museum-admin/" + echo "📱 访问地址: https://lifescript.happylifeos.com/emotion-museum-admin/" echo "🔧 管理后台功能: AI配置管理、用户管理、数据统计等" else diff --git a/web/.env.production b/web/.env.production index 6745462..84fc992 100644 --- a/web/.env.production +++ b/web/.env.production @@ -3,14 +3,14 @@ VITE_APP_ENV=prod VITE_APP_TITLE=情绪博物馆 VITE_APP_VERSION=1.0.0 -# API配置 -VITE_API_BASE_URL=http://101.200.208.45:19089/api -VITE_WS_BASE_URL=ws://101.200.208.45:19089/api -VITE_UPLOAD_URL=http://101.200.208.45:19089/api/upload +# 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系统 +VITE_APP_DESCRIPTION=情绪博物馆 Web 系统 diff --git a/web/deploy.sh b/web/deploy.sh index 5f5ba82..296b353 100755 --- a/web/deploy.sh +++ b/web/deploy.sh @@ -47,7 +47,7 @@ if scp dist/index.html "${USERNAME}@${SERVER_IP}:${REMOTE_PATH}/" && \ fi echo "✅ 部署完成!" - echo "📱 访问地址: http://$SERVER_IP/emotion-museum/" + echo "📱 访问地址: https://lifescript.happylifeos.com/" else echo "❌ 部署失败,请检查:" diff --git a/web/package-lock.json b/web/package-lock.json index 4acf8a1..0d952fd 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -2408,21 +2408,6 @@ "node": "*" } }, - "node_modules/bufferutil": { - "version": "4.0.9", - "resolved": "https://registry.npmmirror.com/bufferutil/-/bufferutil-4.0.9.tgz", - "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", @@ -5616,19 +5601,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", @@ -7409,21 +7381,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",