375 lines
12 KiB
Bash
375 lines
12 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# Node.js 服务管理脚本 - 银行端后端(bank-backend)
|
|||
|
|
# 使用方法: ./node_manager.sh [start|stop|restart|status]
|
|||
|
|
|
|||
|
|
# 配置区域
|
|||
|
|
APP_NAME="bank-backend"
|
|||
|
|
ENTRY_FILE="server.js"
|
|||
|
|
APP_PORT="5351"
|
|||
|
|
LOG_DIR="logs"
|
|||
|
|
LOG_FILE="${LOG_DIR}/${APP_NAME}.log"
|
|||
|
|
PID_FILE="pid.${APP_NAME}"
|
|||
|
|
NODE_ENV="production"
|
|||
|
|
|
|||
|
|
# 颜色输出
|
|||
|
|
RED='\033[0;31m'
|
|||
|
|
GREEN='\033[0;32m'
|
|||
|
|
YELLOW='\033[1;33m'
|
|||
|
|
NC='\033[0m' # No Color
|
|||
|
|
|
|||
|
|
# 创建日志目录
|
|||
|
|
if [ ! -d "$LOG_DIR" ]; then
|
|||
|
|
mkdir -p "$LOG_DIR"
|
|||
|
|
echo "已创建日志目录: $LOG_DIR"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查 Node.js 是否安装
|
|||
|
|
if ! command -v node &> /dev/null; then
|
|||
|
|
echo -e "${RED}错误: Node.js 未安装或不在 PATH 中${NC}"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查入口文件是否存在
|
|||
|
|
if [ ! -f "$ENTRY_FILE" ]; then
|
|||
|
|
echo -e "${RED}错误: 入口文件 $ENTRY_FILE 不存在${NC}"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 显示 Node.js 版本信息
|
|||
|
|
NODE_VERSION=$(node --version)
|
|||
|
|
echo -e "${GREEN}Node.js 版本: $NODE_VERSION${NC}"
|
|||
|
|
|
|||
|
|
# 使用 PM2 启动服务(如已安装)
|
|||
|
|
function startPM2() {
|
|||
|
|
if command -v pm2 &> /dev/null; then
|
|||
|
|
echo -e "${YELLOW}检测到已安装 PM2,使用 PM2 管理服务: $APP_NAME${NC}"
|
|||
|
|
# 确保日志目录存在
|
|||
|
|
[ -d "$LOG_DIR" ] || mkdir -p "$LOG_DIR"
|
|||
|
|
pm2 start "$ENTRY_FILE" \
|
|||
|
|
--name "$APP_NAME" \
|
|||
|
|
--cwd "$(pwd)" \
|
|||
|
|
--env "$NODE_ENV" \
|
|||
|
|
--time \
|
|||
|
|
--output "$LOG_DIR/${APP_NAME}.out.log" \
|
|||
|
|
--error "$LOG_DIR/${APP_NAME}.err.log"
|
|||
|
|
|
|||
|
|
pm2 save
|
|||
|
|
echo -e "${GREEN}PM2 已启动并保存进程列表(pm2 save)${NC}"
|
|||
|
|
echo "如需开机自启,请执行(一次性):"
|
|||
|
|
echo "pm2 startup systemd -u $(whoami) --hp $HOME; pm2 save"
|
|||
|
|
echo "查看状态: pm2 status $APP_NAME"
|
|||
|
|
return 0
|
|||
|
|
else
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 停止服务函数
|
|||
|
|
function stopApp() {
|
|||
|
|
echo -e "${YELLOW}正在停止服务: $APP_NAME${NC}"
|
|||
|
|
|
|||
|
|
# 从 PID 文件读取进程 ID
|
|||
|
|
if [ -f "$PID_FILE" ]; then
|
|||
|
|
PID=$(cat "$PID_FILE")
|
|||
|
|
if ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
echo "找到进程 PID: $PID,正在停止..."
|
|||
|
|
kill -TERM $PID
|
|||
|
|
|
|||
|
|
# 等待进程优雅退出
|
|||
|
|
for i in {1..10}; do
|
|||
|
|
if ! ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
echo -e "${GREEN}服务已优雅停止${NC}"
|
|||
|
|
rm -f "$PID_FILE"
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
sleep 1
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# 如果优雅停止失败,强制杀死
|
|||
|
|
echo -e "${YELLOW}优雅停止失败,强制终止进程${NC}"
|
|||
|
|
kill -9 $PID
|
|||
|
|
rm -f "$PID_FILE"
|
|||
|
|
else
|
|||
|
|
echo "PID 文件存在但进程不存在,清理 PID 文件"
|
|||
|
|
rm -f "$PID_FILE"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
# 如果没有 PID 文件,尝试通过端口查找
|
|||
|
|
PID=$(lsof -ti:$APP_PORT 2>/dev/null)
|
|||
|
|
if [ -n "$PID" ]; then
|
|||
|
|
echo "通过端口 $APP_PORT 找到 PID: $PID,正在停止..."
|
|||
|
|
kill -TERM $PID
|
|||
|
|
sleep 2
|
|||
|
|
if ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
kill -9 $PID
|
|||
|
|
fi
|
|||
|
|
echo -e "${GREEN}服务已停止${NC}"
|
|||
|
|
else
|
|||
|
|
echo -e "${YELLOW}未找到运行中的服务: $APP_NAME${NC}"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 启动服务函数
|
|||
|
|
function startApp() {
|
|||
|
|
echo -e "${YELLOW}正在启动服务: $APP_NAME${NC}"
|
|||
|
|
|
|||
|
|
# 优先使用 PM2 管理服务
|
|||
|
|
if startPM2; then
|
|||
|
|
echo -e "${GREEN}服务已通过 PM2 启动并托管,支持崩溃自动重启${NC}"
|
|||
|
|
echo "日志: $LOG_DIR/${APP_NAME}.out.log 和 $LOG_DIR/${APP_NAME}.err.log"
|
|||
|
|
return 0
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查服务是否已经运行
|
|||
|
|
if [ -f "$PID_FILE" ]; then
|
|||
|
|
PID=$(cat "$PID_FILE")
|
|||
|
|
if ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
echo -e "${YELLOW}服务已在运行中 (PID: $PID)${NC}"
|
|||
|
|
return 0
|
|||
|
|
else
|
|||
|
|
echo "PID 文件存在但进程不存在,清理后重新启动"
|
|||
|
|
rm -f "$PID_FILE"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查端口是否被占用
|
|||
|
|
if command -v lsof &> /dev/null; then
|
|||
|
|
PORT_CHECK=$(lsof -ti:$APP_PORT 2>/dev/null)
|
|||
|
|
if [ -n "$PORT_CHECK" ]; then
|
|||
|
|
echo -e "${RED}错误: 端口 $APP_PORT 已被占用 (PID: $PORT_CHECK)${NC}"
|
|||
|
|
echo "请先停止占用端口的进程或使用 restart 命令"
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查 .env 文件是否存在
|
|||
|
|
if [ ! -f ".env" ]; then
|
|||
|
|
echo -e "${YELLOW}警告: .env 文件不存在,将使用默认配置${NC}"
|
|||
|
|
echo "建议从 env.example 复制并配置 .env 文件"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查 node_modules 是否存在
|
|||
|
|
if [ ! -d "node_modules" ]; then
|
|||
|
|
echo -e "${RED}错误: node_modules 目录不存在${NC}"
|
|||
|
|
echo "请先运行: npm install"
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 启动 Node.js 应用
|
|||
|
|
echo "启动命令: NODE_ENV=$NODE_ENV nohup node $ENTRY_FILE > $LOG_FILE 2>&1 &"
|
|||
|
|
NODE_ENV=$NODE_ENV nohup node $ENTRY_FILE > $LOG_FILE 2>&1 &
|
|||
|
|
|
|||
|
|
# 获取新进程的 PID
|
|||
|
|
PID=$!
|
|||
|
|
echo $PID > "$PID_FILE"
|
|||
|
|
|
|||
|
|
# 等待应用启动
|
|||
|
|
echo "等待服务启动..."
|
|||
|
|
sleep 3
|
|||
|
|
|
|||
|
|
# 验证启动是否成功
|
|||
|
|
if ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
echo -e "${GREEN}========================================${NC}"
|
|||
|
|
echo -e "${GREEN}服务启动成功!${NC}"
|
|||
|
|
echo -e "${GREEN}========================================${NC}"
|
|||
|
|
echo "应用名称: $APP_NAME"
|
|||
|
|
echo "进程 ID: $PID"
|
|||
|
|
echo "监听端口: $APP_PORT"
|
|||
|
|
echo "日志文件: $LOG_FILE"
|
|||
|
|
echo "PID 文件: $PID_FILE"
|
|||
|
|
echo "环境变量: NODE_ENV=$NODE_ENV"
|
|||
|
|
echo -e "${GREEN}API 文档: http://localhost:$APP_PORT/api-docs${NC}"
|
|||
|
|
echo ""
|
|||
|
|
|
|||
|
|
# 等待端口监听
|
|||
|
|
echo "检查端口监听状态..."
|
|||
|
|
sleep 2
|
|||
|
|
if command -v lsof &> /dev/null; then
|
|||
|
|
if lsof -ti:$APP_PORT > /dev/null 2>&1; then
|
|||
|
|
echo -e "${GREEN}✓ 端口 $APP_PORT 正在监听${NC}"
|
|||
|
|
else
|
|||
|
|
echo -e "${YELLOW}⚠ 端口 $APP_PORT 尚未监听,请检查日志${NC}"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 显示最近的日志
|
|||
|
|
echo ""
|
|||
|
|
echo "最近的启动日志:"
|
|||
|
|
echo "------------------------"
|
|||
|
|
tail -20 "$LOG_FILE"
|
|||
|
|
else
|
|||
|
|
echo -e "${RED}========================================${NC}"
|
|||
|
|
echo -e "${RED}服务启动失败!${NC}"
|
|||
|
|
echo -e "${RED}========================================${NC}"
|
|||
|
|
echo "请检查日志文件: $LOG_FILE"
|
|||
|
|
echo ""
|
|||
|
|
echo "最近的错误日志:"
|
|||
|
|
echo "------------------------"
|
|||
|
|
tail -30 "$LOG_FILE"
|
|||
|
|
rm -f "$PID_FILE"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 重启服务函数
|
|||
|
|
function restartApp() {
|
|||
|
|
echo -e "${YELLOW}正在重启服务: $APP_NAME${NC}"
|
|||
|
|
stopApp
|
|||
|
|
sleep 3
|
|||
|
|
startApp
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 状态检查函数
|
|||
|
|
function statusApp() {
|
|||
|
|
echo -e "${YELLOW}检查服务状态: $APP_NAME${NC}"
|
|||
|
|
echo "========================================"
|
|||
|
|
|
|||
|
|
# 如果使用 PM2 托管,优先展示 PM2 状态
|
|||
|
|
if command -v pm2 &> /dev/null; then
|
|||
|
|
if pm2 describe "$APP_NAME" > /dev/null 2>&1; then
|
|||
|
|
PM2_STATE=$(pm2 info "$APP_NAME" | grep -i "status" | awk -F":" '{print $2}' | xargs)
|
|||
|
|
echo -e "PM2 状态: ${GREEN}${PM2_STATE}${NC}"
|
|||
|
|
pm2 status "$APP_NAME"
|
|||
|
|
echo ""
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [ -f "$PID_FILE" ]; then
|
|||
|
|
PID=$(cat "$PID_FILE")
|
|||
|
|
if ps -p $PID > /dev/null 2>&1; then
|
|||
|
|
echo -e "${GREEN}✓ 服务正在运行${NC}"
|
|||
|
|
echo "----------------------------------------"
|
|||
|
|
echo "进程 ID: $PID"
|
|||
|
|
echo "应用名称: $APP_NAME"
|
|||
|
|
echo "监听端口: $APP_PORT"
|
|||
|
|
|
|||
|
|
# 启动时间
|
|||
|
|
if command -v ps &> /dev/null; then
|
|||
|
|
START_TIME=$(ps -o lstart= -p $PID 2>/dev/null)
|
|||
|
|
if [ -n "$START_TIME" ]; then
|
|||
|
|
echo "启动时间: $START_TIME"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 内存使用
|
|||
|
|
MEM_USAGE=$(ps -o rss= -p $PID 2>/dev/null | awk '{printf "%.2f MB", $1/1024}')
|
|||
|
|
if [ -n "$MEM_USAGE" ]; then
|
|||
|
|
echo "内存使用: $MEM_USAGE"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# CPU使用率
|
|||
|
|
CPU_USAGE=$(ps -o %cpu= -p $PID 2>/dev/null)
|
|||
|
|
if [ -n "$CPU_USAGE" ]; then
|
|||
|
|
echo "CPU 使用: ${CPU_USAGE}%"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 检查端口监听状态
|
|||
|
|
if command -v lsof &> /dev/null; then
|
|||
|
|
PORT_INFO=$(lsof -ti:$APP_PORT 2>/dev/null)
|
|||
|
|
if [ -n "$PORT_INFO" ]; then
|
|||
|
|
echo -e "端口状态: ${GREEN}监听中 ($APP_PORT)${NC}"
|
|||
|
|
else
|
|||
|
|
echo -e "端口状态: ${RED}未监听${NC}"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# 日志文件信息
|
|||
|
|
if [ -f "$LOG_FILE" ]; then
|
|||
|
|
LOG_SIZE=$(du -h "$LOG_FILE" | cut -f1)
|
|||
|
|
echo "日志大小: $LOG_SIZE"
|
|||
|
|
echo "日志文件: $LOG_FILE"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo ""
|
|||
|
|
echo "最近的日志 (最后10行):"
|
|||
|
|
echo "----------------------------------------"
|
|||
|
|
tail -10 "$LOG_FILE" 2>/dev/null || echo "无法读取日志文件"
|
|||
|
|
|
|||
|
|
else
|
|||
|
|
echo -e "${RED}✗ 服务未运行 (PID 文件存在但进程不存在)${NC}"
|
|||
|
|
echo "建议清理 PID 文件: rm -f $PID_FILE"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
# 通过端口检查
|
|||
|
|
if command -v lsof &> /dev/null; then
|
|||
|
|
PID=$(lsof -ti:$APP_PORT 2>/dev/null)
|
|||
|
|
if [ -n "$PID" ]; then
|
|||
|
|
echo -e "${YELLOW}⚠ 服务正在运行但未通过脚本管理${NC}"
|
|||
|
|
echo "进程 ID: $PID"
|
|||
|
|
echo "监听端口: $APP_PORT"
|
|||
|
|
echo "建议使用: ./node_manager.sh stop 停止服务后重新启动"
|
|||
|
|
else
|
|||
|
|
echo -e "${RED}✗ 服务未运行${NC}"
|
|||
|
|
echo "使用以下命令启动: ./node_manager.sh start"
|
|||
|
|
fi
|
|||
|
|
else
|
|||
|
|
echo -e "${RED}✗ 服务未运行 (PID 文件不存在)${NC}"
|
|||
|
|
echo "使用以下命令启动: ./node_manager.sh start"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
echo "========================================"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 查看日志函数
|
|||
|
|
function viewLogs() {
|
|||
|
|
if [ -f "$LOG_FILE" ]; then
|
|||
|
|
echo -e "${YELLOW}实时查看日志 (Ctrl+C 退出):${NC}"
|
|||
|
|
tail -f "$LOG_FILE"
|
|||
|
|
else
|
|||
|
|
echo -e "${RED}日志文件不存在: $LOG_FILE${NC}"
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 主逻辑
|
|||
|
|
case "$1" in
|
|||
|
|
start)
|
|||
|
|
startApp
|
|||
|
|
;;
|
|||
|
|
stop)
|
|||
|
|
stopApp
|
|||
|
|
;;
|
|||
|
|
restart)
|
|||
|
|
restartApp
|
|||
|
|
;;
|
|||
|
|
status)
|
|||
|
|
statusApp
|
|||
|
|
;;
|
|||
|
|
logs)
|
|||
|
|
viewLogs
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
echo "========================================"
|
|||
|
|
echo "银行端后端(bank-backend) - 服务管理脚本"
|
|||
|
|
echo "========================================"
|
|||
|
|
echo "使用方法: $0 {start|stop|restart|status|logs}"
|
|||
|
|
echo ""
|
|||
|
|
echo "命令说明:"
|
|||
|
|
echo " start - 启动服务"
|
|||
|
|
echo " stop - 停止服务"
|
|||
|
|
echo " restart - 重启服务"
|
|||
|
|
echo " status - 查看服务状态"
|
|||
|
|
echo " logs - 实时查看日志"
|
|||
|
|
echo ""
|
|||
|
|
echo "增强说明:"
|
|||
|
|
echo " 已优先使用 PM2 管理服务(如安装了 pm2),自动重启更可靠"
|
|||
|
|
echo " 配置 PM2 开机自启: pm2 startup systemd -u $(whoami) --hp $HOME; pm2 save"
|
|||
|
|
echo ""
|
|||
|
|
echo "配置信息:"
|
|||
|
|
echo " 应用名称: $APP_NAME"
|
|||
|
|
echo " 入口文件: $ENTRY_FILE"
|
|||
|
|
echo " 监听端口: $APP_PORT"
|
|||
|
|
echo " 日志文件: $LOG_FILE"
|
|||
|
|
echo ""
|
|||
|
|
echo "示例:"
|
|||
|
|
echo " $0 start # 启动服务"
|
|||
|
|
echo " $0 status # 查看状态"
|
|||
|
|
echo " $0 logs # 查看日志"
|
|||
|
|
exit 1
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
|
|||
|
|
exit 0
|
|||
|
|
|