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
|
||
|