From 9d76e178de19666519fe952145c170f5837935c8 Mon Sep 17 00:00:00 2001 From: En Date: Wed, 25 Mar 2026 18:05:02 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=92=8C=E6=95=B0=E6=8D=AE=E5=BA=93=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 application-dev/test/prod.yml 环境配置 - 更新 V35 清理旧套餐表的迁移脚本 - 添加部署相关文档和脚本 - 移除 CLEAN_V10_FAILED.sql 临时文件 --- .env.prod.template | 36 ++ DEPLOY_BAOTA.md | 310 +++++++++++++ README-BAOTA-DEPLOY.md | 170 ++++++++ deploy-backend.sh | 191 ++++++++ deploy-frontend.sh | 120 ++++++ deploy-to-baota.sh | 408 ++++++++++++++++++ deploy.sh | 161 +++++++ nginx-baota.conf | 123 ++++++ .../src/main/resources/application-dev.yml | 50 ++- .../src/main/resources/application-prod.yml | 63 ++- .../src/main/resources/application-test.yml | 50 ++- .../db/migration/CLEAN_V10_FAILED.sql | 12 - .../db/migration/README_DEPLOYMENT.md | 141 ++++++ .../V35__cleanup_old_package_table.sql | 4 - 14 files changed, 1739 insertions(+), 100 deletions(-) create mode 100644 .env.prod.template create mode 100644 DEPLOY_BAOTA.md create mode 100644 README-BAOTA-DEPLOY.md create mode 100644 deploy-backend.sh create mode 100644 deploy-frontend.sh create mode 100644 deploy-to-baota.sh create mode 100644 deploy.sh create mode 100644 nginx-baota.conf delete mode 100644 reading-platform-java/src/main/resources/db/migration/CLEAN_V10_FAILED.sql create mode 100644 reading-platform-java/src/main/resources/db/migration/README_DEPLOYMENT.md diff --git a/.env.prod.template b/.env.prod.template new file mode 100644 index 0000000..bc7450f --- /dev/null +++ b/.env.prod.template @@ -0,0 +1,36 @@ +# 生产环境配置模板 +# 复制此文件为 .env.prod 并填入实际值 + +#============================== +# 数据库配置 +#============================== +DB_HOST=localhost +DB_PORT=3306 +DB_USERNAME=reading_platform +DB_PASSWORD=你的数据库密码 + +#============================== +# Redis 配置 +#============================== +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD= + +#============================== +# JWT 配置(生产环境必须修改) +#============================== +JWT_SECRET=你的-production-jwt-secret-至少-32-字符 +JWT_EXPIRATION=86400000 + +#============================== +# 阿里云 OSS 配置 +#============================== +OSS_ENDPOINT=oss-cn-shenzhen.aliyuncs.com +OSS_ACCESS_KEY_ID=LTAI5tKZhPofbThbSzDSiWoK +OSS_ACCESS_KEY_SECRET=FtcsC7oQX3T0NaChaa9FYq2aoysQFM +OSS_BUCKET_NAME=lesingle-kid-course + +#============================== +# 服务器配置 +#============================== +SERVER_PORT=8480 diff --git a/DEPLOY_BAOTA.md b/DEPLOY_BAOTA.md new file mode 100644 index 0000000..304308b --- /dev/null +++ b/DEPLOY_BAOTA.md @@ -0,0 +1,310 @@ +# 宝塔面板部署指南 + +## 快速开始 + +### 方式一:使用部署脚本(推荐) + +```bash +# 1. 配置环境变量 +export REMOTE_HOST=你的服务器 IP +export REMOTE_USER=root + +# 2. 部署后端 +./deploy-backend.sh + +# 3. 部署前端 +./deploy-frontend.sh +``` + +### 方式二:手动部署 + +--- + +## 第一步:在宝塔安装基础服务 + +1. 登录宝塔面板(`http://你的IP:8888`) +2. 安装 LNMP 套件: + - Nginx 1.24+ + - MySQL 8.0+ + - PHP 纯静态(前端不需要 PHP) + - Redis 7.x + +3. 安装 JDK 17: + - 软件商店 → 搜索 "Java" + - 安装 JDK 17 版本 + +--- + +## 第二步:创建数据库 + +1. 宝塔面板 → 数据库 → 添加 +2. 填写信息: + - 数据库名:`lesingle-edu-reading-platform` + - 用户名:`reading_platform` + - 密码:设置一个强密码(保存好) + - 权限:全部 + +3. 记录数据库信息,后面要用 + +--- + +## 第三步:部署后端 + +### 3.1 上传 JAR 包 + +```bash +# 本地构建 +cd /f/LesingleProject/lesingle-kindergarten-course/kindergarten_java/reading-platform-java +mvn clean package -DskipTests + +# 上传到服务器 +scp target/reading-platform-1.0.0.jar root@你的 IP:/www/wwwroot/reading-platform/app.jar +``` + +### 3.2 创建启动脚本 + +在服务器上创建 `/www/wwwroot/reading-platform/start.sh`: + +```bash +#!/bin/bash +export SPRING_PROFILES_ACTIVE=prod +export SERVER_PORT=8480 +export DB_HOST=localhost +export DB_PASSWORD=你的数据库密码 +export JWT_SECRET=你的 JWT 密钥 + +nohup java -jar -Xms512m -Xmx1024m /www/wwwroot/reading-platform/app.jar \ + --spring.profiles.active=prod > /www/wwwroot/reading-platform/logs/app.log 2>&1 & +echo $! > /www/wwwroot/reading-platform/app.pid +``` + +```bash +chmod +x /www/wwwroot/reading-platform/start.sh +``` + +### 3.3 创建环境变量文件 + +`/www/wwwroot/reading-platform/.env`: + +```bash +# 数据库 +DB_HOST=localhost +DB_PASSWORD=你的数据库密码 + +# JWT +JWT_SECRET=你的-production-jwt-secret-至少-32-字符 +JWT_EXPIRATION=86400000 + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 + +# OSS +OSS_ACCESS_KEY_ID=LTAI5tKZhPofbThbSzDSiWoK +OSS_ACCESS_KEY_SECRET=FtcsC7oQX3T0NaChaa9FYq2aoysQFM +OSS_BUCKET_NAME=lesingle-kid-course +``` + +### 3.4 启动后端 + +```bash +cd /www/wwwroot/reading-platform +./start.sh + +# 查看日志 +tail -f logs/app.log +``` + +--- + +## 第四步:部署前端 + +### 4.1 本地构建 + +```bash +cd /f/LesingleProject/lesingle-kindergarten-course/kindergarten_java/reading-platform-frontend +npm run build +``` + +### 4.2 上传到服务器 + +```bash +# 上传到宝塔站点目录 +scp -r dist/* root@你的 IP:/www/wwwroot/你的域名/ +``` + +--- + +## 第五步:配置 Nginx 反向代理 + +1. 宝塔面板 → 网站 → 添加站点 +2. 填写域名,根目录设为 `/www/wwwroot/你的域名` +3. 确定后,点击网站 → 设置 → 配置文件 +4. 添加以下配置: + +```nginx +# 前端页面 +location / { + try_files $uri $uri/ /index.html; +} + +# 后端 API 反向代理 +location /api { + proxy_pass http://localhost:8480; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +} + +# 文件上传代理 +location /uploads { + proxy_pass http://localhost:8480; +} +``` + +5. 保存配置 + +--- + +## 第六步:配置防火墙 + +1. 宝塔面板 → 安全 → 放行端口 +2. 放行以下端口: + - 80(HTTP) + - 443(HTTPS,如果用 SSL) + - 8480(后端 API,可选,如果通过 Nginx 代理就不需要) + +--- + +## 第七步:验证部署 + +```bash +# 检查后端健康状态 +curl http://localhost:8480/actuator/health + +# 访问前端 +curl http://localhost + +# 测试 API +curl http://localhost:8480/api/v1 +``` + +--- + +## 第八步:申请 SSL 证书(可选) + +1. 宝塔面板 → 网站 → 点击对应网站 → SSL +2. 选择 "Let's Encrypt" 免费证书 +3. 填写邮箱,点击申请 +4. 申请成功后,开启"强制 HTTPS" + +--- + +## 管理命令 + +### 后端管理 + +```bash +# 启动 +cd /www/wwwroot/reading-platform && ./start.sh + +# 停止 +cd /www/wwwroot/reading-platform && ./stop.sh + +# 重启 +cd /www/wwwroot/reading-platform && ./stop.sh && ./start.sh + +# 查看日志 +tail -f /www/wwwroot/reading-platform/logs/app.log + +# 查看进程 +ps aux | grep reading-platform +``` + +### 前端更新 + +```bash +# 本地重新构建并上传 +cd reading-platform-frontend +npm run build +scp -r dist/* root@你的 IP:/www/wwwroot/你的域名/ +``` + +--- + +## 常见问题 + +### 1. 后端启动失败 + +检查日志: +```bash +tail -100 /www/wwwroot/reading-platform/logs/app.log +``` + +常见原因: +- JDK 版本不对(必须是 17) +- 数据库连接失败 +- 端口被占用 + +### 2. 前端页面空白 + +- 检查 Nginx 配置是否正确 +- 打开浏览器 Console 查看错误 +- 检查 API 请求是否 404 + +### 3. API 请求失败 + +- 检查 Nginx 反向代理配置 +- 确认后端服务是否运行 +- 检查防火墙端口 + +### 4. 文件上传失败 + +- 检查 OSS 配置 +- 检查 Nginx 上传大小限制 +- 查看后端日志 + +--- + +## 性能优化 + +### JVM 优化参数 + +```bash +java -jar \ + -Xms1g \ + -Xmx2g \ + -XX:+UseG1GC \ + -XX:MaxGCPauseMillis=200 \ + -XX:+HeapDumpOnOutOfMemoryError \ + app.jar +``` + +### Nginx 优化 + +```nginx +# 开启 gzip 压缩 +gzip on; +gzip_types text/plain text/css application/json application/javascript; + +# 上传大小限制 +client_max_body_size 100M; +``` + +--- + +## 备份与恢复 + +### 数据库备份 + +```bash +# 宝塔面板 → 数据库 → 备份 +# 或命令行 +mysqldump -u reading_platform -p lesingle-edu-reading-platform > backup.sql +``` + +### 文件备份 + +```bash +tar -czf backup-$(date +%Y%m%d).tar.gz /www/wwwroot/reading-platform +``` diff --git a/README-BAOTA-DEPLOY.md b/README-BAOTA-DEPLOY.md new file mode 100644 index 0000000..101e346 --- /dev/null +++ b/README-BAOTA-DEPLOY.md @@ -0,0 +1,170 @@ +# 宝塔部署快速配置指南 + +## 第一次部署前,请在宝塔面板完成以下操作: + +### 1. 安装基础服务 + +登录宝塔面板 → 软件商店 → 安装以下软件: + +| 软件 | 版本 | 说明 | +|------|------|------| +| Nginx | 1.24+ | Web 服务器 | +| MySQL | 8.0+ | 数据库 | +| Redis | 7.x | 缓存 | +| Java | JDK 17 | 运行环境 | + +### 2. 创建数据库 + +宝塔面板 → 数据库 → 添加: + +``` +数据库名:lesingle-edu-reading-platform +用户名:reading_platform +密码:[设置一个强密码,记下来] +权限:全部 +``` + +### 3. 配置环境变量 + +复制配置文件模板: + +```bash +cp .env.prod.template .env.prod +``` + +编辑 `.env.prod`,填入实际值: + +```bash +# 数据库密码(从宝塔获取) +DB_PASSWORD=你的数据库密码 + +# JWT 密钥(必须修改) +JWT_SECRET=你的-production-jwt-secret-至少-32-字符 + +# Redis 密码(如果宝塔设置了) +REDIS_PASSWORD= +``` + +### 4. 修改部署脚本配置 + +编辑 `deploy-backend.sh`,找到环境变量部分,填入你的配置: + +```bash +# 在启动脚本中添加环境变量 +export DB_PASSWORD=你的数据库密码 +export JWT_SECRET=你的 JWT 密钥 +``` + +或者在服务器上创建 `/www/wwwroot/reading-platform/.env` 文件。 + +--- + +## 一键部署 + +```bash +# 方式 1:使用主部署脚本 +./deploy.sh root@你的服务器 IP + +# 方式 2:分别部署 +export REMOTE_HOST=你的服务器 IP +./deploy-backend.sh # 部署后端 +./deploy-frontend.sh # 部署前端 +``` + +--- + +## 部署后配置 + +### 1. 配置 Nginx 反向代理 + +宝塔面板 → 网站 → 你的网站 → 设置 → 配置文件: + +粘贴 `nginx-baota.conf` 中的配置。 + +### 2. 验证部署 + +```bash +# SSH 登录服务器 +ssh root@你的服务器 IP + +# 检查后端健康状态 +curl http://localhost:8480/actuator/health + +# 查看后端日志 +tail -f /www/wwwroot/reading-platform/logs/app.log +``` + +### 3. 访问应用 + +- 前端:http://你的服务器 IP +- API 文档:http://你的服务器 IP:8480/swagger-ui.html + +--- + +## 常用命令 + +### 后端管理 + +```bash +# 登录服务器 +ssh root@你的服务器 IP + +# 进入目录 +cd /www/wwwroot/reading-platform + +# 启动 +./start.sh + +# 停止 +./stop.sh + +# 查看日志 +tail -f logs/app.log + +# 查看进程 +ps aux | grep app.jar +``` + +### 前端更新 + +```bash +# 本地重新构建并部署 +./deploy-frontend.sh root@你的服务器 IP +``` + +--- + +## 问题排查 + +### 后端启动失败 + +```bash +# 查看详细日志 +tail -100 /www/wwwroot/reading-platform/logs/app.log + +# 检查 Java 版本 +java -version + +# 检查端口占用 +netstat -tlnp | grep 8480 +``` + +### 前端页面空白 + +1. 打开浏览器 Console(F12) +2. 查看 Network 标签,检查 API 请求 +3. 确认 Nginx 反向代理配置正确 + +### 数据库连接失败 + +1. 确认数据库已创建 +2. 确认数据库密码正确 +3. 确认 MySQL 服务运行中 + +--- + +## 需要帮助? + +- 详细部署文档:`DEPLOY_BAOTA.md` +- Nginx 配置:`nginx-baota.conf` +- 环境变量模板:`.env.prod.template` diff --git a/deploy-backend.sh b/deploy-backend.sh new file mode 100644 index 0000000..d16eee9 --- /dev/null +++ b/deploy-backend.sh @@ -0,0 +1,191 @@ +#!/bin/bash + +#=============================================================================== +# 后端生产环境部署脚本 +# +# 使用方法: +# ./deploy-backend.sh user@host +# 或 +# export REMOTE_USER=root +# export REMOTE_HOST=8.148.151.56 +# ./deploy-backend.sh +#=============================================================================== + +set -e + +# 配置 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BACKEND_DIR="$SCRIPT_DIR/reading-platform-java" +REMOTE_BASE_DIR="/www/wwwroot/reading-platform" + +# SSH 配置 +REMOTE_USER="${REMOTE_USER:-root}" +REMOTE_HOST="${REMOTE_HOST:-}" +REMOTE_PORT="${REMOTE_PORT:-22}" + +# 颜色 +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_step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +# 检查参数 +if [ -n "$1" ]; then + if [[ "$1" == *"@"* ]]; then + REMOTE_USER=$(echo "$1" | cut -d'@' -f1) + REMOTE_HOST=$(echo "$1" | cut -d'@' -f2) + fi +fi + +if [ -z "$REMOTE_HOST" ]; then + log_error "请提供远程主机:$0 user@host 或设置 REMOTE_HOST 环境变量" + exit 1 +fi + +echo "" +echo "==============================================" +echo " 后端部署到宝塔" +echo "==============================================" +echo "" +echo " 远程主机:$REMOTE_USER@$REMOTE_HOST" +echo " 部署目录:$REMOTE_BASE_DIR" +echo "" + +# 步骤 1: 构建 JAR +log_step "构建后端..." +cd "$BACKEND_DIR" + +if ! command -v mvn &> /dev/null; then + log_error "未找到 Maven" + exit 1 +fi + +# 检查 Java 版本 +java_ver=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1) +if [ "$java_ver" != "17" ]; then + log_warn "当前 Java 版本:$java_ver,建议使用 JDK 17" +fi + +mvn clean package -DskipTests + +JAR_FILE="$BACKEND_DIR/target/reading-platform-1.0.0.jar" +if [ ! -f "$JAR_FILE" ]; then + log_error "构建失败,未找到 JAR 包" + exit 1 +fi +log_info "构建成功:$JAR_FILE" + +# 步骤 2: 上传到服务器 +log_step "上传到远程服务器..." + +# 创建目录 +ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" << 'ENDSSH' + mkdir -p /www/wwwroot/reading-platform + mkdir -p /www/wwwroot/reading-platform/logs +ENDSSH + +# 上传 JAR +scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + "$JAR_FILE" \ + "$REMOTE_USER@$REMOTE_HOST:$REMOTE_BASE_DIR/app.jar" + +# 步骤 3: 创建启动脚本 +log_step "创建启动脚本..." + +cat > /tmp/baota-start.sh << 'EOF' +#!/bin/bash +APP_DIR="/www/wwwroot/reading-platform" +JAR_FILE="$APP_DIR/app.jar" +PID_FILE="$APP_DIR/app.pid" +LOG_FILE="$APP_DIR/logs/application.log" + +# 环境变量 +export SPRING_PROFILES_ACTIVE=prod +export SERVER_PORT=8480 + +# 停止旧进程 +if [ -f "$PID_FILE" ]; then + OLD_PID=$(cat "$PID_FILE") + if kill -0 "$OLD_PID" 2>/dev/null; then + echo "停止旧进程 (PID: $OLD_PID)..." + kill "$OLD_PID" + sleep 3 + fi + rm -f "$PID_FILE" +fi +pkill -f "app.jar" 2>/dev/null || true + +# 启动 +echo "启动应用..." +nohup java -jar \ + -Xms512m \ + -Xmx1024m \ + -XX:+UseG1GC \ + "$JAR_FILE" \ + --spring.profiles.active=prod \ + > "$LOG_FILE" 2>&1 & + +NEW_PID=$! +echo $NEW_PID > "$PID_FILE" +echo "已启动 (PID: $NEW_PID)" + +# 等待启动 +for i in {1..30}; do + if curl -s http://localhost:8480/actuator/health 2>/dev/null; then + echo "启动成功!" + exit 0 + fi + sleep 2 +done +echo "启动超时,检查日志:tail -100 $LOG_FILE" +exit 1 +EOF + +scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + /tmp/baota-start.sh \ + "$REMOTE_USER@$REMOTE_HOST:$REMOTE_BASE_DIR/start.sh" + +# 步骤 4: 创建停止脚本 +cat > /tmp/baota-stop.sh << 'EOF' +#!/bin/bash +PID_FILE="/www/wwwroot/reading-platform/app.pid" +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + kill "$PID" + echo "已停止 (PID: $PID)" + fi + rm -f "$PID_FILE" +fi +pkill -f "app.jar" 2>/dev/null || true +echo "已停止" +EOF + +scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + /tmp/baota-stop.sh \ + "$REMOTE_USER@$REMOTE_HOST:$REMOTE_BASE_DIR/stop.sh" + +# 设置权限 +ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" \ + "chmod +x $REMOTE_BASE_DIR/start.sh $REMOTE_BASE_DIR/stop.sh" + +log_info "部署完成" + +echo "" +echo "==============================================" +echo " 后端部署完成!" +echo "==============================================" +echo "" +echo " 远程登录:ssh $REMOTE_USER@$REMOTE_HOST" +echo " 启动服务:cd $REMOTE_BASE_DIR && ./start.sh" +echo " 停止服务:cd $REMOTE_BASE_DIR && ./stop.sh" +echo " 查看日志:tail -f $REMOTE_BASE_DIR/logs/application.log" +echo "" +echo " 健康检查:curl http://localhost:8480/actuator/health" +echo "" diff --git a/deploy-frontend.sh b/deploy-frontend.sh new file mode 100644 index 0000000..4881b51 --- /dev/null +++ b/deploy-frontend.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +#=============================================================================== +# 前端生产环境构建并部署脚本 +# +# 使用方法: +# ./deploy-frontend.sh user@host +# 或 +# export REMOTE_USER=root +# export REMOTE_HOST=8.148.151.56 +# ./deploy-frontend.sh +#=============================================================================== + +set -e + +# 配置 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +FRONTEND_DIR="$SCRIPT_DIR/reading-platform-frontend" +REMOTE_DIR="/www/wwwroot/reading.ycapp.cn" + +# SSH 配置(可通过环境变量覆盖) +REMOTE_USER="${REMOTE_USER:-root}" +REMOTE_HOST="${REMOTE_HOST:-}" +REMOTE_PORT="${REMOTE_PORT:-22}" + +# 颜色定义 +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_step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +# 检查参数 +if [ -n "$1" ]; then + if [[ "$1" == *"@"* ]]; then + REMOTE_USER=$(echo "$1" | cut -d'@' -f1) + REMOTE_HOST=$(echo "$1" | cut -d'@' -f2) + fi +fi + +if [ -z "$REMOTE_HOST" ]; then + log_error "请提供远程主机:$0 user@host 或设置 REMOTE_HOST 环境变量" + exit 1 +fi + +echo "" +echo "==============================================" +echo " 前端部署到宝塔" +echo "==============================================" +echo "" +echo " 远程主机:$REMOTE_USER@$REMOTE_HOST" +echo " 部署目录:$REMOTE_DIR" +echo "" + +cd "$FRONTEND_DIR" + +# 步骤 1: 检查 Node.js +log_step "检查环境..." +if ! command -v node &> /dev/null; then + log_error "未找到 Node.js" + exit 1 +fi +node_version=$(node -v) +npm_version=$(npm -v) +log_info "Node.js: $node_version, npm: $npm_version" + +# 步骤 2: 安装依赖 +log_step "安装依赖..." +if [ ! -d "node_modules" ]; then + npm install +else + log_info "node_modules 已存在,跳过依赖安装" +fi + +# 步骤 3: 构建 +log_step "构建前端..." +npm run build + +if [ ! -d "dist" ]; then + log_error "构建失败,未找到 dist 目录" + exit 1 +fi +log_info "构建成功" + +# 步骤 4: 上传到服务器 +log_step "上传到远程服务器..." + +# 创建远程目录 +ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" \ + "mkdir -p $REMOTE_DIR" + +# 上传文件 +scp -o StrictHostKeyChecking=no -r -P "$REMOTE_PORT" \ + dist/ \ + "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/" + +log_info "上传完成" + +# 步骤 5: 设置权限 +log_step "设置文件权限..." +ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" << 'ENDSSH' + chown -R www:www /www/wwwroot/reading.ycapp.cn + chmod -R 755 /www/wwwroot/reading.ycapp.cn +ENDSSH + +echo "" +echo "==============================================" +echo " 前端部署完成!" +echo "==============================================" +echo "" +echo " 访问地址:http://${REMOTE_HOST}" +echo "" +echo " 下一步:在宝塔面板配置 Nginx 反向代理" +echo " /api -> http://localhost:8480" +echo "" diff --git a/deploy-to-baota.sh b/deploy-to-baota.sh new file mode 100644 index 0000000..b9fee4d --- /dev/null +++ b/deploy-to-baota.sh @@ -0,0 +1,408 @@ +#!/bin/bash + +#=============================================================================== +# 幼儿阅读教学服务平台 - 一键部署到宝塔面板 +# +# 使用前请配置: +# 1. 远程服务器信息(IP、用户名、密码/密钥) +# 2. 宝塔面板信息(面板地址、API 密钥) +# 3. 数据库信息 +#=============================================================================== + +set -e + +#============================== 配置区域 ============================== + +# 远程服务器配置 +REMOTE_HOST="8.148.151.56" # 服务器 IP +REMOTE_USER="root" # SSH 用户名 +REMOTE_PORT="22" # SSH 端口 +# 推荐使用 SSH 密钥,如无密钥将使用密码认证 +REMOTE_PASSWORD="" # SSH 密码(可选) +SSH_KEY_FILE="~/.ssh/id_rsa" # SSH 密钥文件 + +# 部署路径配置 +REMOTE_BASE_DIR="/www/wwwroot/reading-platform" # 后端部署目录 +REMOTE_FRONTEND_DIR="/www/wwwroot/reading.ycapp.cn" # 前端部署目录(宝塔站点目录) + +# 宝塔面板配置 +BAOTA_PANEL_URL="http://${REMOTE_HOST}:8888" # 宝塔面板地址 +BAOTA_API_KEY="" # 宝塔 API 密钥(在宝塔面板 → 设置 → API 获取) +BAOTA_SECRET_KEY="" # 宝塔 API 密钥 + +# 数据库配置(宝塔中创建的数据库) +DB_HOST="localhost" +DB_PORT="3306" +DB_NAME="lesingle-edu-reading-platform" +DB_USER="reading_platform" +DB_PASSWORD="" # 数据库密码 + +# JWT 配置(生产环境必须修改) +JWT_SECRET="your-production-jwt-secret-change-this" +JWT_EXPIRATION="86400000" + +# Redis 配置 +REDIS_HOST="localhost" +REDIS_PORT="6379" +REDIS_PASSWORD="" + +# OSS 配置(生产环境) +OSS_ACCESS_KEY_ID="LTAI5tKZhPofbThbSzDSiWoK" +OSS_ACCESS_KEY_SECRET="FtcsC7oQX3T0NaChaa9FYq2aoysQFM" +OSS_BUCKET_NAME="lesingle-kid-course" +OSS_ENDPOINT="oss-cn-shenzhen.aliyuncs.com" + +#============================== 脚本主体 ============================== + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 日志函数 +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_step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +# 检查配置 +check_config() { + log_step "检查配置..." + + if [ -z "$REMOTE_HOST" ]; then + log_error "请配置 REMOTE_HOST(服务器 IP)" + exit 1 + fi + + # 检查本地是否有 JAR 包 + JAR_FILE="reading-platform-java/target/reading-platform-1.0.0.jar" + if [ ! -f "$JAR_FILE" ]; then + log_warn "未找到 JAR 包,将先执行构建..." + BUILD_NEEDED=true + fi + + # 检查前端是否有 dist + if [ ! -d "reading-platform-frontend/dist" ]; then + log_warn "未找到前端构建文件,将先执行构建..." + BUILD_FRONTEND_NEEDED=true + fi +} + +# 构建后端 +build_backend() { + if [ "$BUILD_NEEDED" != "true" ]; then + return + fi + + log_step "构建后端服务..." + cd "reading-platform-java" + + # 检查 JDK 版本 + if command -v java &> /dev/null; then + JAVA_VERSION=$(java -version 2>&1 | head -n 1 | cut -d'"' -f2 | cut -d'.' -f1) + if [ "$JAVA_VERSION" != "17" ]; then + log_warn "当前 Java 版本为 $JAVA_VERSION,建议 JDK 17" + fi + fi + + # Maven 构建 + if command -v mvn &> /dev/null; then + mvn clean package -DskipTests + else + log_error "未找到 Maven,请先安装" + exit 1 + fi + + cd .. + + if [ -f "$JAR_FILE" ]; then + log_info "后端构建成功:$JAR_FILE" + else + log_error "后端构建失败" + exit 1 + fi +} + +# 构建前端 +build_frontend() { + if [ "$BUILD_FRONTEND_NEEDED" != "true" ]; then + return + fi + + log_step "构建前端..." + cd "reading-platform-frontend" + + # 检查 Node.js + if ! command -v node &> /dev/null; then + log_error "未找到 Node.js,请先安装" + exit 1 + fi + + # 安装依赖(如果 node_modules 不存在) + if [ ! -d "node_modules" ]; then + log_info "安装前端依赖..." + npm install + fi + + # 构建 + npm run build + + cd .. + + if [ -d "reading-platform-frontend/dist" ]; then + log_info "前端构建成功" + else + log_error "前端构建失败" + exit 1 + fi +} + +# SSH 上传文件 +upload_files() { + log_step "上传文件到远程服务器..." + + # 创建 SSH 命令别名(根据是否有密码) + if [ -n "$REMOTE_PASSWORD" ]; then + SSH_PASS="sshpass -p '$REMOTE_PASSWORD'" + SCP_PASS="sshpass -p '$REMOTE_PASSWORD'" + else + SSH_PASS="" + SCP_PASS="" + fi + + # 测试 SSH 连接 + log_info "测试 SSH 连接..." + if ! ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" "echo '连接成功'" 2>/dev/null; then + log_error "SSH 连接失败,请检查服务器配置" + exit 1 + fi + log_info "SSH 连接成功" + + # 创建远程目录 + log_info "创建远程目录..." + ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" << 'ENDSSH' + mkdir -p /www/wwwroot/reading-platform + mkdir -p /www/wwwroot/reading-platform/logs +ENDSSH + + # 上传 JAR 包 + log_info "上传后端 JAR 包..." + scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + "reading-platform-java/target/reading-platform-1.0.0.jar" \ + "$REMOTE_USER@$REMOTE_HOST:/www/wwwroot/reading-platform/app.jar" + + # 上传启动脚本 + log_info "上传启动脚本..." + cat > /tmp/deploy-start.sh << 'EOF' +#!/bin/bash +# 阅读平台后端启动脚本 + +APP_DIR="/www/wwwroot/reading-platform" +JAR_FILE="$APP_DIR/app.jar" +PID_FILE="$APP_DIR/app.pid" +LOG_FILE="$APP_DIR/logs/application.log" + +# 环境变量 +export SPRING_PROFILES_ACTIVE=prod +export SERVER_PORT=8480 +export DB_HOST=${DB_HOST:-localhost} +export DB_PASSWORD=${DB_PASSWORD} +export JWT_SECRET=${JWT_SECRET} + +# 检查是否已在运行 +if [ -f "$PID_FILE" ]; then + OLD_PID=$(cat "$PID_FILE") + if kill -0 "$OLD_PID" 2>/dev/null; then + echo "停止旧进程 (PID: $OLD_PID)..." + kill "$OLD_PID" + sleep 3 + fi + rm -f "$PID_FILE" +fi + +# 启动应用 +echo "启动阅读平台后端..." +nohup java -jar \ + -Xms512m \ + -Xmx1024m \ + -XX:+UseG1GC \ + -XX:MaxGCPauseMillis=200 \ + -XX:+HeapDumpOnOutOfMemoryError \ + -XX:HeapDumpPath="$APP_DIR/logs/heapdump.hprof" \ + "$JAR_FILE" \ + --spring.profiles.active=prod \ + > "$LOG_FILE" 2>&1 & + +NEW_PID=$! +echo $NEW_PID > "$PID_FILE" +echo "应用已启动 (PID: $NEW_PID)" +echo "日志文件:$LOG_FILE" + +# 等待启动 +echo "等待应用启动..." +for i in {1..60}; do + if curl -s http://localhost:8480/actuator/health > /dev/null 2>&1; then + echo "应用启动成功!" + exit 0 + fi + sleep 2 +done + +echo "应用启动超时,请检查日志" +tail -100 "$LOG_FILE" +exit 1 +EOF + + scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + /tmp/deploy-start.sh \ + "$REMOTE_USER@$REMOTE_HOST:/www/wwwroot/reading-platform/start.sh" + + # 上传停止脚本 + cat > /tmp/deploy-stop.sh << 'EOF' +#!/bin/bash +PID_FILE="/www/wwwroot/reading-platform/app.pid" +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "停止应用 (PID: $PID)..." + kill "$PID" + sleep 3 + fi + rm -f "$PID_FILE" +fi +# 确保进程已停止 +pkill -f "reading-platform-1.0.0.jar" 2>/dev/null || true +echo "应用已停止" +EOF + + scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + /tmp/deploy-stop.sh \ + "$REMOTE_USER@$REMOTE_HOST:/www/wwwroot/reading-platform/stop.sh" + + # 设置执行权限 + ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" \ + "chmod +x /www/wwwroot/reading-platform/start.sh /www/wwwroot/reading-platform/stop.sh" + + log_info "文件上传完成" +} + +# 上传前端文件 +upload_frontend() { + log_step "上传前端文件..." + + # 确保前端目录存在 + ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" \ + "mkdir -p $REMOTE_FRONTEND_DIR" + + # 上传 dist 目录 + log_info "上传前端构建文件..." + scp -o StrictHostKeyChecking=no -r -P "$REMOTE_PORT" \ + "reading-platform-frontend/dist/" \ + "$REMOTE_USER@$REMOTE_HOST:$REMOTE_FRONTEND_DIR/" + + log_info "前端文件上传完成" +} + +# 创建配置文件 +create_config() { + log_step "创建生产环境配置..." + + # 创建环境变量文件 + cat > /tmp/remote-env << EOF +# 数据库配置 +DB_HOST=$DB_HOST +DB_PASSWORD=$DB_PASSWORD + +# JWT 配置 +JWT_SECRET=$JWT_SECRET +JWT_EXPIRATION=$JWT_EXPIRATION + +# Redis 配置 +REDIS_HOST=$REDIS_HOST +REDIS_PORT=$REDIS_PORT +REDIS_PASSWORD=$REDIS_PASSWORD + +# OSS 配置 +OSS_ACCESS_KEY_ID=$OSS_ACCESS_KEY_ID +OSS_ACCESS_KEY_SECRET=$OSS_ACCESS_KEY_SECRET +OSS_BUCKET_NAME=$OSS_BUCKET_NAME +OSS_ENDPOINT=$OSS_ENDPOINT +EOF + + scp -o StrictHostKeyChecking=no -P "$REMOTE_PORT" \ + /tmp/remote-env \ + "$REMOTE_USER@$REMOTE_HOST:/www/wwwroot/reading-platform/.env" + + log_info "配置文件创建完成" +} + +# 远程部署 +remote_deploy() { + log_step "执行远程部署..." + + ssh -o StrictHostKeyChecking=no -p "$REMOTE_PORT" "$REMOTE_USER@$REMOTE_HOST" << 'ENDSSH' + cd /www/wwwroot/reading-platform + + # 停止旧服务 + echo "停止旧服务..." + bash stop.sh + + # 启动新服务 + echo "启动新服务..." + bash start.sh +ENDSSH + + log_info "远程部署完成" +} + +# 显示部署信息 +show_deploy_info() { + echo "" + echo "==============================================" + echo " 部署完成!" + echo "==============================================" + echo "" + echo "后端 API 地址:http://${REMOTE_HOST}:8480" + echo "前端访问地址:http://${REMOTE_HOST}" + echo "API 文档地址:http://${REMOTE_HOST}:8480/swagger-ui.html" + echo "" + echo "远程目录:" + echo " 后端:/www/wwwroot/reading-platform" + echo " 前端:$REMOTE_FRONTEND_DIR" + echo "" + echo "管理命令:" + echo " ssh $REMOTE_USER@$REMOTE_HOST" + echo " cd /www/wwwroot/reading-platform" + echo " ./start.sh # 启动" + echo " ./stop.sh # 停止" + echo " ./restart.sh # 重启" + echo "" + echo "日志查看:" + echo " tail -f /www/wwwroot/reading-platform/logs/application.log" + echo "" + echo "==============================================" +} + +# 主函数 +main() { + echo "" + echo "==============================================" + echo " 幼儿阅读教学服务平台" + echo " 一键部署到宝塔面板" + echo "==============================================" + echo "" + + check_config + build_backend + build_frontend + upload_files + upload_frontend + create_config + remote_deploy + show_deploy_info +} + +# 执行 +main "$@" diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..a89833e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,161 @@ +#!/bin/bash + +#=============================================================================== +# 幼儿阅读教学服务平台 - 一键部署到宝塔面板 +# +# 使用方法: +# ./deploy.sh your-server-ip +# 或 +# ./deploy.sh root@your-server-ip +#=============================================================================== + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 颜色 +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_step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +# 检查参数 +if [ -z "$1" ]; then + echo "" + echo "==============================================" + echo " 幼儿阅读教学服务平台" + echo " 一键部署到宝塔面板" + echo "==============================================" + echo "" + echo "使用方法:" + echo " $0 your-server-ip" + echo " $0 root@your-server-ip" + echo "" + echo "示例:" + echo " $0 8.148.151.56" + echo " $0 root@8.148.151.56" + echo "" + exit 1 +fi + +# 解析参数 +REMOTE_HOST="$1" +REMOTE_USER="root" +if [[ "$1" == *"@"* ]]; then + REMOTE_USER=$(echo "$1" | cut -d'@' -f1) + REMOTE_HOST=$(echo "$1" | cut -d'@' -f2) +fi + +echo "" +echo "==============================================" +echo " 一键部署到宝塔面板" +echo "==============================================" +echo "" +echo " 服务器:$REMOTE_USER@$REMOTE_HOST" +echo "" + +# 检查 SSH 连接 +log_step "检查 SSH 连接..." +if ! ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" "echo '连接成功'" 2>/dev/null; then + log_error "SSH 连接失败,请检查:" + echo " 1. 服务器 IP 是否正确" + echo " 2. SSH 是否启用(默认端口 22)" + echo " 3. 密码/密钥是否正确" + echo "" + echo " 如果是密码登录,请先执行:" + echo " ssh-copy-id $REMOTE_USER@$REMOTE_HOST" + exit 1 +fi +log_info "SSH 连接成功" + +# 检查远程服务器 +log_step "检查远程服务器..." +ssh "$REMOTE_USER@$REMOTE_HOST" << 'ENDSSH' + # 检查是否安装了宝塔 + if [ -d "/www/server/panel" ]; then + echo "宝塔面板:已安装" + else + echo "警告:未检测到宝塔面板" + fi + + # 检查 JDK + if command -v java &> /dev/null; then + java_version=$(java -version 2>&1 | head -n 1) + echo "Java: $java_version" + else + echo "警告:未安装 Java" + fi + + # 检查 MySQL + if command -v mysql &> /dev/null; then + echo "MySQL: 已安装" + else + echo "警告:未安装 MySQL" + fi + + # 检查 Nginx + if command -v nginx &> /dev/null; then + echo "Nginx: 已安装" + fi + + # 检查磁盘空间 + disk_used=$(df -h / | tail -1 | awk '{print $5}') + echo "磁盘使用:$disk_used" +ENDSSH + +echo "" +echo "==============================================" +echo " 部署前检查完成" +echo "==============================================" +echo "" +echo " 请确认:" +echo " 1. 宝塔面板已安装并可以访问" +echo " 2. 已在宝塔中安装 MySQL、Redis、Nginx" +echo " 3. 已创建数据库和用户(见 DEPLOY_BAOTA.md)" +echo " 4. JDK 17 已安装(或通过宝塔安装)" +echo "" +read -p "是否继续部署?(y/n) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 +fi + +# 部署后端 +log_step "部署后端..." +export REMOTE_USER +export REMOTE_HOST +bash "$SCRIPT_DIR/deploy-backend.sh" + +# 部署前端 +log_step "部署前端..." +bash "$SCRIPT_DIR/deploy-frontend.sh" + +echo "" +echo "==============================================" +echo " 部署完成!" +echo "==============================================" +echo "" +echo " 下一步操作:" +echo "" +echo " 1. 在宝塔面板配置 Nginx 反向代理" +echo " 网站 → 设置 → 配置文件" +echo " 参考 nginx-baota.conf 中的配置" +echo "" +echo " 2. 验证后端服务" +echo " ssh $REMOTE_USER@$REMOTE_HOST" +echo " curl http://localhost:8480/actuator/health" +echo "" +echo " 3. 访问前端页面" +echo " http://$REMOTE_HOST" +echo "" +echo " 4. 查看日志" +echo " 后端:tail -f /www/wwwroot/reading-platform/logs/app.log" +echo " Nginx: tail -f /www/wwwlogs/你的域名.error.log" +echo "" +echo "==============================================" diff --git a/nginx-baota.conf b/nginx-baota.conf new file mode 100644 index 0000000..0c35412 --- /dev/null +++ b/nginx-baota.conf @@ -0,0 +1,123 @@ +# Nginx 配置 - 幼儿阅读教学服务平台 +# 在宝塔面板中配置此反向代理 + +#============================== +# 在宝塔面板的操作步骤: +# 1. 网站 → 添加站点 → 创建站点 +# 2. 域名:输入你的域名(如 reading.ycapp.cn) +# 3. 根目录:/www/wwwroot/reading.ycapp.cn +# 4. 数据库:PHP 纯静态(前端已构建好) +# 5. 确定后,点击网站 → 设置 → 配置文件 +# 6. 将以下配置粘贴到配置文件中 +#============================== + +server { + listen 80; + server_name reading.ycapp.cn; # 修改为你的域名 + + # 前端静态文件根目录 + root /www/wwwroot/reading.ycapp.cn; + index index.html; + + # 前端页面路由(Vue Router history 模式支持) + location / { + try_files $uri $uri/ /index.html; + } + + # 后端 API 反向代理 + location /api { + proxy_pass http://localhost:8480; + 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; + + # 跨域支持(如果需要) + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; + + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'Content-Type, Authorization'; + add_header Content-Type 'text/plain; charset=utf-8'; + add_header Content-Length 0; + return 204; + } + } + + # 文件上传代理 + location /uploads { + proxy_pass http://localhost:8480; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + # 静态资源缓存 + location ~* \.(jpg|jpeg|png|gif|svg|ico|css|js|woff|woff2|ttf|eot)$ { + expires 30d; + add_header Cache-Control "public, immutable"; + } + + # 禁止访问隐藏文件 + location ~ /\. { + deny all; + } + + # 安全头 + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; +} + +#============================== +# HTTPS 配置(启用 SSL 后使用此配置) +#============================== +# 在宝塔面板 → 网站 → SSL → Let's Encrypt 免费申请证书 +# 申请成功后会自动生成以下配置 + +# server { +# listen 443 ssl http2; +# server_name reading.ycapp.cn; +# ssl_certificate /www/server/panel/vhost/certs/reading.ycapp.cn/fullchain.pem; +# ssl_certificate_key /www/server/panel/vhost/certs/reading.ycapp.cn/privkey.pem; +# +# # SSL 优化 +# ssl_protocols TLSv1.2 TLSv1.3; +# ssl_ciphers HIGH:!aNULL:!MD5; +# ssl_prefer_server_ciphers on; +# ssl_session_cache shared:SSL:10m; +# ssl_session_timeout 10m; +# +# root /www/wwwroot/reading.ycapp.cn; +# index index.html; +# +# location / { +# try_files $uri $uri/ /index.html; +# } +# +# location /api { +# proxy_pass http://localhost:8480; +# 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; +# } +# +# location /uploads { +# proxy_pass http://localhost:8480; +# } +# } +# +# # HTTP 自动跳转 HTTPS +# server { +# listen 80; +# server_name reading.ycapp.cn; +# return 301 https://$server_name$request_uri; +# } diff --git a/reading-platform-java/src/main/resources/application-dev.yml b/reading-platform-java/src/main/resources/application-dev.yml index af6ddb5..f9b3622 100644 --- a/reading-platform-java/src/main/resources/application-dev.yml +++ b/reading-platform-java/src/main/resources/application-dev.yml @@ -11,6 +11,29 @@ spring: username: ${DB_USERNAME:reading_platform} password: ${DB_PASSWORD:reading_platform_pwd} type: com.alibaba.druid.pool.DruidDataSource + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + validation-query: SELECT 1 + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: admin + web-stat-filter: + enabled: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" data: redis: # host: ${REDIS_HOST:8.148.151.56} @@ -36,32 +59,7 @@ spring: clean-on-validation-error: true out-of-order: true -# Druid 连接池配置(开发环境) -druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000ms - time-between-eviction-runs-millis: 60000ms - min-evictable-idle-time-millis: 300000ms - validation-query: SELECT 1 - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - stat-view-servlet: - enabled: true - url-pattern: /druid/* - reset-enable: false - login-username: admin - login-password: admin - web-stat-filter: - enabled: true - url-pattern: /* - exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" - -# MyBatis-Plus 配置(开发环境 - 开启 SQL 日志) +# MyBatis-Plus 配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/reading-platform-java/src/main/resources/application-prod.yml b/reading-platform-java/src/main/resources/application-prod.yml index 5496bc2..4c0e514 100644 --- a/reading-platform-java/src/main/resources/application-prod.yml +++ b/reading-platform-java/src/main/resources/application-prod.yml @@ -6,17 +6,40 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/reading_platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true&allowPublicKeyRetrieval=true - username: ${DB_USERNAME} - password: ${DB_PASSWORD} + url: jdbc:mysql://${DB_HOST:47.106.92.245}:${DB_PORT:3306}/lesingle-edu-reading-platform?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=true&allowPublicKeyRetrieval=true + username: ${DB_USERNAME:reading_platform} + password: ${DB_PASSWORD:WawfXxrMRENydksk} type: com.alibaba.druid.pool.DruidDataSource + druid: + initial-size: 10 + min-idle: 10 + max-active: 50 + max-wait: 30000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + validation-query: SELECT 1 + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 50 + stat-view-servlet: + enabled: false + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: admin + web-stat-filter: + enabled: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" data: redis: - host: ${REDIS_HOST:localhost} + host: ${REDIS_HOST:127.0.0.1} port: ${REDIS_PORT:6379} - password: ${REDIS_PASSWORD} - database: 0 + password: ${REDIS_PASSWORD:laomo_redis123} + database: 7 timeout: 10000ms lettuce: pool: @@ -30,33 +53,9 @@ spring: locations: classpath:db/migration clean-disabled: true validate-on-migrate: true + skip-patterns: V7_,V8_,V10_,V43_ -# Druid 连接池配置(生产环境) -druid: - initial-size: 10 - min-idle: 10 - max-active: 50 - max-wait: 30000ms - time-between-eviction-runs-millis: 60000ms - min-evictable-idle-time-millis: 300000ms - validation-query: SELECT 1 - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 50 - stat-view-servlet: - enabled: false - url-pattern: /druid/* - reset-enable: false - login-username: admin - login-password: admin - web-stat-filter: - enabled: true - url-pattern: /* - exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" - -# MyBatis-Plus 配置(生产环境 - 关闭 SQL 日志) +# MyBatis-Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true diff --git a/reading-platform-java/src/main/resources/application-test.yml b/reading-platform-java/src/main/resources/application-test.yml index 0b8aac6..bb7ee62 100644 --- a/reading-platform-java/src/main/resources/application-test.yml +++ b/reading-platform-java/src/main/resources/application-test.yml @@ -10,6 +10,29 @@ spring: username: ${DB_USERNAME:reading_platform} password: ${DB_PASSWORD:reading_platform_pwd} type: com.alibaba.druid.pool.DruidDataSource + druid: + initial-size: 5 + min-idle: 5 + max-active: 20 + max-wait: 60000 + time-between-eviction-runs-millis: 60000 + min-evictable-idle-time-millis: 300000 + validation-query: SELECT 1 + test-while-idle: true + test-on-borrow: false + test-on-return: false + pool-prepared-statements: true + max-pool-prepared-statement-per-connection-size: 20 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: false + login-username: admin + login-password: admin + web-stat-filter: + enabled: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" data: redis: @@ -31,32 +54,7 @@ spring: clean-disabled: true validate-on-migrate: true -# Druid 连接池配置(测试环境) -druid: - initial-size: 5 - min-idle: 5 - max-active: 20 - max-wait: 60000ms - time-between-eviction-runs-millis: 60000ms - min-evictable-idle-time-millis: 300000ms - validation-query: SELECT 1 - test-while-idle: true - test-on-borrow: false - test-on-return: false - pool-prepared-statements: true - max-pool-prepared-statement-per-connection-size: 20 - stat-view-servlet: - enabled: true - url-pattern: /druid/* - reset-enable: false - login-username: admin - login-password: admin - web-stat-filter: - enabled: true - url-pattern: /* - exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" - -# MyBatis-Plus 配置(测试环境) +# MyBatis-Plus 配置 mybatis-plus: configuration: map-underscore-to-camel-case: true diff --git a/reading-platform-java/src/main/resources/db/migration/CLEAN_V10_FAILED.sql b/reading-platform-java/src/main/resources/db/migration/CLEAN_V10_FAILED.sql deleted file mode 100644 index 0084412..0000000 --- a/reading-platform-java/src/main/resources/db/migration/CLEAN_V10_FAILED.sql +++ /dev/null @@ -1,12 +0,0 @@ --- ===================================================== --- 清理 V10 失败的 Flyway 迁移记录 --- 使用方法:在 MySQL 客户端中执行此脚本 --- ===================================================== - --- 删除 V10 失败记录 -DELETE FROM flyway_schema_history WHERE version = '10'; - --- 验证清理结果 -SELECT * FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5; - --- 确认后,重启应用,Flyway 将重新执行 V10 迁移 diff --git a/reading-platform-java/src/main/resources/db/migration/README_DEPLOYMENT.md b/reading-platform-java/src/main/resources/db/migration/README_DEPLOYMENT.md new file mode 100644 index 0000000..eb0a413 --- /dev/null +++ b/reading-platform-java/src/main/resources/db/migration/README_DEPLOYMENT.md @@ -0,0 +1,141 @@ +# Flyway 迁移脚本部署说明 + +## 正式环境部署指南 + +### 1. Flyway 配置确认 + +生产环境配置 `application-prod.yml` 已正确设置: + +```yaml +spring: + flyway: + enabled: true # ✅ 必须设置为 true + locations: classpath:db/migration + clean-disabled: true # ✅ 禁止清理数据库 + validate-on-migrate: true # ✅ 迁移前验证 + skip-patterns: V7_,V8_,V10_,V43_ # ✅ 跳过测试数据脚本 +``` + +### 2. 测试数据脚本处理 + +生产环境通过 `skip-patterns` 配置跳过以下测试数据脚本: + +| 脚本文件 | 内容 | 状态 | +|---------|------|------| +| `V7__add_test_data.sql` | 测试课程、套餐、教师、家长、学生数据 | ⚠️ 生产环境跳过 | +| `V8__add_tenant_package_test_data.sql` | 测试租户套餐数据 | ⚠️ 生产环境跳过 | +| `V10__add_relation_test_data.sql` | 测试关联数据(课程资源、活动、任务等) | ⚠️ 生产环境跳过 | +| `V43__add_student_class_history_data.sql` | 测试学生班级关联数据 | ⚠️ 生产环境跳过 | + +**注意**: +- 开发和测试环境**不受影响**,会继续执行所有脚本 +- 生产环境通过 `skip-patterns` 配置自动跳过带 `V7_`、`V8_`、`V10_`、`V43_` 前缀的脚本 + +### 3. 正式环境迁移执行顺序 + +Flyway 将按以下顺序执行迁移脚本(生产环境跳过 V7, V8, V10, V43): + +``` +V1 → V2 → V3 → V4 → V5 → V6 → V9 → V11 → V12 → V13 → V14 → V15 → V16 +→ V26 → V27 → V28 → V29 → V31 → V32 → V33 → V34 → V35 → V36 → V37 +→ V38 → V39 → V40 → V41 → V42 → V44 → V45 → V46 → V47 → V48 → V49 → V50 +``` + +**跳过的脚本**:V7, V8, V10, V43(测试数据) + +### 4. 部署步骤 + +#### 步骤 1:准备数据库 + +```sql +-- 创建数据库(如未创建) +CREATE DATABASE IF NOT EXISTS `lesingle-edu-reading-platform` +DEFAULT CHARACTER SET utf8mb4 +COLLATE utf8mb4_unicode_ci; + +-- 创建数据库用户(推荐使用专用用户) +CREATE USER 'reading_platform'@'%' IDENTIFIED BY '强密码'; +GRANT ALL PRIVILEGES ON `lesingle-edu-reading-platform`.* TO 'reading_platform'@'%'; +FLUSH PRIVILEGES; +``` + +#### 步骤 2:配置环境变量 + +```bash +# 数据库配置 +export DB_HOST=你的数据库主机 +export DB_PORT=3306 +export DB_USERNAME=reading_platform +export DB_PASSWORD=强密码 + +# Redis 配置 +export REDIS_HOST=你的 Redis 主机 +export REDIS_PORT=6379 + +# JWT 密钥(必须设置) +export JWT_SECRET=你的 JWT 密钥(建议 32 位以上随机字符串) + +# OSS 配置(可选,使用默认值或设置) +export OSS_ENDPOINT=oss-cn-shenzhen.aliyuncs.com +export OSS_ACCESS_KEY_ID=你的 AccessKey +export OSS_ACCESS_KEY_SECRET=你的 AccessKey 密钥 +export OSS_BUCKET_NAME=lesingle-kid-course +``` + +#### 步骤 3:启动应用 + +```bash +# 使用生产环境配置启动 +java -jar reading-platform.jar --spring.profiles.active=prod +``` + +#### 步骤 4:验证迁移 + +启动后检查日志,确认 Flyway 迁移成功: + +``` +Flyway migration completed successfully +Applied X migrations to database +``` + +检查数据库中的迁移历史表: + +```sql +USE `lesingle-edu-reading-platform`; +SELECT * FROM flyway_schema_history ORDER BY installed_rank; +``` + +### 5. 初始化超管账号 + +迁移完成后,数据库中没有超管账号,需要通过超管端创建第一个超管账号。 + +### 6. 注意事项 + +1. **测试数据脚本跳过** - 生产环境通过 `skip-patterns` 配置跳过 V7, V8, V10, V43 +2. **开发和测试环境不受影响** - 其他环境继续执行所有脚本(包括测试数据) +3. **JWT 密钥必须设置** - 生产环境必须通过环境变量设置 JWT_SECRET +4. **数据库权限最小化** - 建议使用专用数据库用户,仅授予必要权限 +5. **备份数据库** - 部署前建议备份数据库 +6. **Flyway 历史记录** - `flyway_schema_history` 表记录了所有迁移历史,不要手动修改 + +### 7. 迁移脚本清单 + +| 版本号 | 脚本文件 | 描述 | +| --- | --- | --- | +| V1 | `V1__init_schema.sql` | 初始化基础表结构 | +| V2 | `V2__add_course_tables.sql` | 添加课程相关表 | +| V3 | `V3__add_business_tables.sql` | 添加业务表 | +| V4 | `V4__add_resource_tables.sql` | 添加资源表 | +| V5 | `V5__fix_password.sql` | 修复密码字段 | +| V6 | `V6__fix_status.sql` | 修复状态字段 | +| V9 | `V9__fix_resource_and_tenant_tables.sql` | 修复资源和租户表 | +| V11-V16 | `V11-V16__*.sql` | 自增 ID、字段修复 | +| V26-V29 | `V26-V29__*.sql` | 添加集体课型、排课引用数据等 | +| V31-V35 | `V31-V35__*.sql` | 删除废弃字段、套餐结构重构 | +| V36-V42 | `V36-V42__*.sql` | 重命名表、清理旧表、优化关联表 | +| V44-V50 | `V44-V50__*.sql` | 添加阅读任务功能、主题色、操作日志扩展 | + +--- + +**最后更新**: 2026-03-25 +**适用版本**: 生产环境部署 diff --git a/reading-platform-java/src/main/resources/db/migration/V35__cleanup_old_package_table.sql b/reading-platform-java/src/main/resources/db/migration/V35__cleanup_old_package_table.sql index e11b3a7..af2580d 100644 --- a/reading-platform-java/src/main/resources/db/migration/V35__cleanup_old_package_table.sql +++ b/reading-platform-java/src/main/resources/db/migration/V35__cleanup_old_package_table.sql @@ -15,10 +15,6 @@ SET FOREIGN_KEY_CHECKS = 0; SELECT '=== 检查 course_package 表中需要迁移的数据 ===' as info; --- 如果有需要保留的数据,可以先备份 -CREATE TABLE IF NOT EXISTS course_package_backup AS -SELECT * FROM course_package WHERE 1=0; - -- ===================================================== -- 第二步:删除旧的关联表 -- =====================================================