2026-09-14 12:53:54 +08:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
|
# 铁路培训实训教学平台 - 全自动平滑部署与自检脚本
|
|
|
|
|
|
# 路径规范:/data2/wzb/deploy
|
|
|
|
|
|
# ==============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
DEPLOY_ROOT="/data2/wzb/deploy"
|
|
|
|
|
|
BACKEND_DIR="$DEPLOY_ROOT/backend"
|
|
|
|
|
|
FRONTEND_DIR="$DEPLOY_ROOT/frontend"
|
|
|
|
|
|
LOG_DIR="$DEPLOY_ROOT/logs"
|
|
|
|
|
|
PID_FILE="$BACKEND_DIR/app.pid"
|
|
|
|
|
|
JAR_PATH="$BACKEND_DIR/ruoyi-admin.jar"
|
|
|
|
|
|
LOG_PATH="$LOG_DIR/backend.log"
|
|
|
|
|
|
JAVA_BIN="/data2/wzb/tools/jdk8/bin/java"
|
|
|
|
|
|
|
|
|
|
|
|
# 服务运行端口与数据库参数
|
|
|
|
|
|
SERVER_PORT=8092
|
|
|
|
|
|
DB_URL="jdbc:kingbase8://192.168.139.218:4321/training?currentSchema=public,sys_catalog&useUnicode=true&characterEncoding=utf8"
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "$BACKEND_DIR" "$FRONTEND_DIR" "$LOG_DIR"
|
|
|
|
|
|
|
2026-09-14 13:32:26 +08:00
|
|
|
|
echo "=== 0. 检查运行时安全配置 ==="
|
|
|
|
|
|
|
2026-09-15 11:22:38 +08:00
|
|
|
|
MISSING_CONFIG=0
|
2026-09-14 13:32:26 +08:00
|
|
|
|
if [ -z "${TRAIN_STUDENT_IDENTITY_ENCRYPTION_KEY:-}" ]; then
|
|
|
|
|
|
echo "错误:缺少 TRAIN_STUDENT_IDENTITY_ENCRYPTION_KEY"
|
2026-09-15 11:22:38 +08:00
|
|
|
|
MISSING_CONFIG=1
|
2026-09-14 13:32:26 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "${TRAIN_STUDENT_IDENTITY_HASH_KEY:-}" ]; then
|
|
|
|
|
|
echo "错误:缺少 TRAIN_STUDENT_IDENTITY_HASH_KEY"
|
2026-09-15 11:22:38 +08:00
|
|
|
|
MISSING_CONFIG=1
|
2026-09-14 13:32:26 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-14 14:13:57 +08:00
|
|
|
|
if [ -z "${SPRING_REDIS_PASSWORD:-}" ]; then
|
|
|
|
|
|
echo "错误:缺少 SPRING_REDIS_PASSWORD"
|
2026-09-15 11:22:38 +08:00
|
|
|
|
MISSING_CONFIG=1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$MISSING_CONFIG" -ne 0 ]; then
|
|
|
|
|
|
echo "运行时安全配置检查失败"
|
2026-09-14 14:13:57 +08:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-15 11:22:38 +08:00
|
|
|
|
echo "required runtime configuration present."
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
2026-09-14 12:53:54 +08:00
|
|
|
|
echo "=== 1. 检查并停止当前用户的旧后端进程 ==="
|
|
|
|
|
|
TARGET_PID=""
|
|
|
|
|
|
if [ -f "$PID_FILE" ]; then
|
|
|
|
|
|
SAVED_PID=$(cat "$PID_FILE")
|
|
|
|
|
|
if kill -0 "$SAVED_PID" 2>/dev/null; then
|
|
|
|
|
|
TARGET_PID="$SAVED_PID"
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -z "$TARGET_PID" ]; then
|
|
|
|
|
|
TARGET_PID=$(pgrep -u $(whoami) -f "$JAR_PATH" | head -n 1)
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ -n "$TARGET_PID" ]; then
|
|
|
|
|
|
echo "正在停止旧进程 PID: $TARGET_PID ..."
|
|
|
|
|
|
kill -15 $TARGET_PID 2>/dev/null
|
|
|
|
|
|
sleep 3
|
|
|
|
|
|
if kill -0 $TARGET_PID 2>/dev/null; then
|
|
|
|
|
|
echo "强制终止 PID: $TARGET_PID"
|
|
|
|
|
|
kill -9 $TARGET_PID 2>/dev/null
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
rm -f "$PID_FILE"
|
|
|
|
|
|
|
|
|
|
|
|
echo "=== 2. 启动新的后端服务(端口: $SERVER_PORT) ==="
|
|
|
|
|
|
if [ ! -f "$JAR_PATH" ]; then
|
|
|
|
|
|
echo "错误:未找到 $JAR_PATH"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
nohup $JAVA_BIN -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m \
|
|
|
|
|
|
-Dfile.encoding=UTF-8 \
|
|
|
|
|
|
-jar "$JAR_PATH" \
|
|
|
|
|
|
--server.port=$SERVER_PORT \
|
2026-09-17 10:50:58 +08:00
|
|
|
|
--spring.datasource.druid.master.url="$DB_URL" \
|
|
|
|
|
|
--spring.datasource.druid.master.username="${DB_USERNAME:-kingbase}" \
|
|
|
|
|
|
--spring.datasource.druid.master.password="${DB_PASSWORD:-sict123456}" > "$LOG_PATH" 2>&1 &
|
2026-09-14 12:53:54 +08:00
|
|
|
|
|
|
|
|
|
|
NEW_PID=$!
|
|
|
|
|
|
echo $NEW_PID > "$PID_FILE"
|
|
|
|
|
|
echo "后端已在后台启动,PID: $NEW_PID"
|
|
|
|
|
|
echo "实时日志: $LOG_PATH"
|
|
|
|
|
|
|
|
|
|
|
|
echo "等待服务启动与健康自检..."
|
2026-09-14 13:32:26 +08:00
|
|
|
|
HEALTH_OK=0
|
|
|
|
|
|
|
2026-09-14 12:53:54 +08:00
|
|
|
|
for i in {1..35}; do
|
|
|
|
|
|
RES=$(curl -s "http://127.0.0.1:${SERVER_PORT}/captchaImage" 2>/dev/null)
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
2026-09-14 12:53:54 +08:00
|
|
|
|
if echo "$RES" | grep -q '"code":200'; then
|
2026-09-14 13:32:26 +08:00
|
|
|
|
HEALTH_OK=1
|
2026-09-14 12:53:54 +08:00
|
|
|
|
echo "后端健康检查通过!验证码接口返回正常!服务已在端口 $SERVER_PORT 成功运行!"
|
2026-09-14 13:02:41 +08:00
|
|
|
|
break
|
2026-09-14 12:53:54 +08:00
|
|
|
|
fi
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
|
|
|
|
|
if ! kill -0 "$NEW_PID" 2>/dev/null; then
|
|
|
|
|
|
echo "错误:后端进程在启动阶段提前退出,请检查 $LOG_PATH"
|
2026-09-17 10:50:58 +08:00
|
|
|
|
cat "$LOG_PATH" | tail -n 50 || true
|
2026-09-14 13:32:26 +08:00
|
|
|
|
rm -f "$PID_FILE"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-14 12:53:54 +08:00
|
|
|
|
sleep 2
|
|
|
|
|
|
done
|
|
|
|
|
|
|
2026-09-15 11:22:38 +08:00
|
|
|
|
if [ "$HEALTH_OK" != "1" ]; then
|
|
|
|
|
|
echo "Backend health check failed: 后端健康检查失败或超时,请检查 $LOG_PATH"
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
|
|
|
|
|
if kill -0 "$NEW_PID" 2>/dev/null; then
|
|
|
|
|
|
kill -15 "$NEW_PID" 2>/dev/null || true
|
|
|
|
|
|
sleep 2
|
|
|
|
|
|
|
|
|
|
|
|
if kill -0 "$NEW_PID" 2>/dev/null; then
|
|
|
|
|
|
kill -9 "$NEW_PID" 2>/dev/null || true
|
|
|
|
|
|
fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
rm -f "$PID_FILE"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-14 13:02:41 +08:00
|
|
|
|
echo "=== 3. 检查并启动/重载前端 Web 服务 (Nginx 8082) ==="
|
|
|
|
|
|
NGINX_DIR="$DEPLOY_ROOT/nginx"
|
|
|
|
|
|
NGINX_CONF="$NGINX_DIR/nginx.conf"
|
|
|
|
|
|
mkdir -p "$NGINX_DIR/temp"
|
|
|
|
|
|
|
|
|
|
|
|
if [ ! -f "$NGINX_CONF" ]; then
|
|
|
|
|
|
cat << 'NGINX_EOF' > "$NGINX_CONF"
|
|
|
|
|
|
pid /data2/wzb/deploy/nginx/nginx.pid;
|
|
|
|
|
|
error_log /data2/wzb/deploy/nginx/error.log;
|
|
|
|
|
|
|
|
|
|
|
|
events {
|
|
|
|
|
|
worker_connections 1024;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
http {
|
|
|
|
|
|
include /etc/nginx/mime.types;
|
|
|
|
|
|
default_type application/octet-stream;
|
|
|
|
|
|
|
|
|
|
|
|
client_body_temp_path /data2/wzb/deploy/nginx/temp/client_body;
|
|
|
|
|
|
proxy_temp_path /data2/wzb/deploy/nginx/temp/proxy;
|
|
|
|
|
|
fastcgi_temp_path /data2/wzb/deploy/nginx/temp/fastcgi;
|
|
|
|
|
|
uwsgi_temp_path /data2/wzb/deploy/nginx/temp/uwsgi;
|
|
|
|
|
|
scgi_temp_path /data2/wzb/deploy/nginx/temp/scgi;
|
|
|
|
|
|
|
|
|
|
|
|
access_log /data2/wzb/deploy/nginx/access.log;
|
|
|
|
|
|
|
|
|
|
|
|
sendfile on;
|
|
|
|
|
|
keepalive_timeout 65;
|
|
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
|
listen 8082;
|
|
|
|
|
|
server_name localhost;
|
|
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
|
root /data2/wzb/deploy/frontend;
|
|
|
|
|
|
try_files $uri $uri/ /index.html;
|
|
|
|
|
|
index index.html;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
location /prod-api/ {
|
|
|
|
|
|
proxy_set_header Host $http_host;
|
|
|
|
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
|
|
|
|
proxy_set_header REMOTE-HOST $remote_addr;
|
|
|
|
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
|
|
|
|
proxy_pass http://127.0.0.1:8092/;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
NGINX_EOF
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-15 11:22:38 +08:00
|
|
|
|
echo "验证 Nginx 配置文件 (Nginx Gate)..."
|
|
|
|
|
|
if ! /usr/sbin/nginx -t -c "$NGINX_CONF"; then
|
|
|
|
|
|
echo "错误:Nginx 配置文件测试失败,禁止启动或重载"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-14 13:32:26 +08:00
|
|
|
|
if pgrep -u "$(whoami)" -f "nginx: master" >/dev/null; then
|
2026-09-14 13:02:41 +08:00
|
|
|
|
echo "平滑重载前端 Nginx 服务..."
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
|
|
|
|
|
if ! /usr/sbin/nginx -c "$NGINX_CONF" -s reload; then
|
|
|
|
|
|
echo "错误:Nginx 平滑重载失败"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2026-09-14 13:02:41 +08:00
|
|
|
|
else
|
|
|
|
|
|
echo "启动前端 Nginx 服务..."
|
2026-09-14 13:32:26 +08:00
|
|
|
|
|
|
|
|
|
|
if ! /usr/sbin/nginx -c "$NGINX_CONF"; then
|
|
|
|
|
|
echo "错误:Nginx 启动失败"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2026-09-14 13:02:41 +08:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-09-15 11:22:38 +08:00
|
|
|
|
echo "=== 4. 执行生产环境自动化冒烟测试 (Production Smoke Gate) ==="
|
|
|
|
|
|
SMOKE_PASS=1
|
|
|
|
|
|
|
|
|
|
|
|
# A. 验证前端应用首页
|
|
|
|
|
|
HTTP_STATUS_A=$(curl -o /dev/null -s -w "%{http_code}" "http://127.0.0.1:8082/")
|
|
|
|
|
|
if [ "$HTTP_STATUS_A" = "200" ]; then
|
|
|
|
|
|
echo "[Smoke PASS] A: http://127.0.0.1:8082/ -> HTTP $HTTP_STATUS_A"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[Smoke FAIL] A: http://127.0.0.1:8082/ -> HTTP $HTTP_STATUS_A (预期 200)"
|
|
|
|
|
|
SMOKE_PASS=0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# B. 验证后端 API 反向代理及业务状态
|
|
|
|
|
|
RES_B=$(curl -s "http://127.0.0.1:8082/prod-api/captchaImage" 2>/dev/null)
|
|
|
|
|
|
HTTP_STATUS_B=$(curl -o /dev/null -s -w "%{http_code}" "http://127.0.0.1:8082/prod-api/captchaImage")
|
|
|
|
|
|
if [ "$HTTP_STATUS_B" = "200" ] && echo "$RES_B" | grep -q '"code":200'; then
|
|
|
|
|
|
echo "[Smoke PASS] B: http://127.0.0.1:8082/prod-api/captchaImage -> HTTP $HTTP_STATUS_B 且 code:200"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[Smoke FAIL] B: http://127.0.0.1:8082/prod-api/captchaImage -> HTTP $HTTP_STATUS_B 业务响应异常"
|
|
|
|
|
|
SMOKE_PASS=0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# C. 验证 Human Documentation Portal 首页
|
|
|
|
|
|
HTTP_STATUS_C=$(curl -o /dev/null -s -w "%{http_code}" "http://127.0.0.1:8082/docs/visual/index.html")
|
|
|
|
|
|
if [ "$HTTP_STATUS_C" = "200" ]; then
|
|
|
|
|
|
echo "[Smoke PASS] C: http://127.0.0.1:8082/docs/visual/index.html -> HTTP $HTTP_STATUS_C"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[Smoke FAIL] C: http://127.0.0.1:8082/docs/visual/index.html -> HTTP $HTTP_STATUS_C (预期 200)"
|
|
|
|
|
|
SMOKE_PASS=0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
# D. 验证文档静态资源 docs.css 与 docs.js
|
|
|
|
|
|
HTTP_STATUS_CSS=$(curl -o /dev/null -s -w "%{http_code}" "http://127.0.0.1:8082/docs/visual/assets/docs.css")
|
|
|
|
|
|
HTTP_STATUS_JS=$(curl -o /dev/null -s -w "%{http_code}" "http://127.0.0.1:8082/docs/visual/assets/docs.js")
|
|
|
|
|
|
if [ "$HTTP_STATUS_CSS" = "200" ] && [ "$HTTP_STATUS_JS" = "200" ]; then
|
|
|
|
|
|
echo "[Smoke PASS] D: docs.css (HTTP $HTTP_STATUS_CSS) & docs.js (HTTP $HTTP_JS) -> HTTP 200"
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "[Smoke FAIL] D: docs.css (HTTP $HTTP_STATUS_CSS), docs.js (HTTP $HTTP_JS) (预期 200)"
|
|
|
|
|
|
SMOKE_PASS=0
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [ "$SMOKE_PASS" != "1" ]; then
|
|
|
|
|
|
echo "错误:生产环境冒烟测试未全部通过,部署失败!"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
echo "=== 生产环境冒烟测试全部通过 (Production Smoke Gate PASSED) ==="
|
|
|
|
|
|
echo "APPLICATION_URL=http://192.168.139.230:8082/"
|
|
|
|
|
|
echo "DOCUMENTATION_URL=http://192.168.139.230:8082/docs/visual/index.html"
|
2026-09-14 13:02:41 +08:00
|
|
|
|
echo "=== 部署全部就绪!前端访问地址: http://192.168.139.230:8082 ==="
|