#!/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