Files
app_03_pregnancy/backend/serv.sh

396 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Unicorn 服务管理脚本
# 优化版本 - 支持完整的服务管理功能,配置完全从 .env 文件读取
set -e
# 颜色定义
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 "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
is_true() {
local value="$1"
value=$(echo "$value" | tr '[:upper:]' '[:lower:]')
case "$value" in
true|1|yes|y|on) return 0 ;;
*) return 1 ;;
esac
}
# 加载 .env 文件
load_env() {
local env_file=".env"
if [ -f "$env_file" ]; then
set -a
source "$env_file"
set +a
log_info "已加载 $env_file 文件中的环境变量"
else
log_warning "$env_file 文件不存在,使用默认值或环境变量"
fi
}
# 从环境变量获取配置,提供默认值
init_config() {
# 应用配置
APP_NAME=${APP_NAME:-"app_name"}
APP_MODULE=${APP_MODULE:-"app.main:app"}
APP_DIR=${APP_DIR:-"$(pwd)"}
# 服务器配置
APP_HOST=${APP_HOST:-${HOST:-"0.0.0.0"}}
APP_PORT=${APP_PORT:-${PORT:-8000}}
APP_WORKERS=${APP_WORKERS:-1}
APP_LOG_LEVEL=${APP_LOG_LEVEL:-"info"}
DEV_MODE=${DEV_MODE:-"0"}
# PID 文件路径
PID_FILE="${APP_DIR}/.${APP_NAME}.pid"
# 日志文件路径
LOG_DIR="${APP_DIR}/logs"
# 获取当前日期用于日志文件分割
CURRENT_DATE=$(date +%Y-%m-%d)
LOG_FILE="${LOG_DIR}/${APP_NAME}-${CURRENT_DATE}.log"
log_info "应用名称: $APP_NAME"
log_info "应用模块: $APP_MODULE"
log_info "应用目录: $APP_DIR"
log_info "服务器地址: $APP_HOST"
log_info "服务器端口: $APP_PORT"
log_info "工作进程数: $APP_WORKERS"
log_info "日志级别: $APP_LOG_LEVEL"
log_info "开发模式: $([[ $DEV_MODE =~ ^(1|true|yes)$ ]] && echo '已启用' || echo '已禁用')"
log_info "日志文件: $LOG_FILE"
}
# 检查是否在正确的目录
check_directory() {
local target_dir="${APP_DIR}"
if [ "$(pwd)" != "$target_dir" ]; then
log_warning "当前目录不是项目根目录,切换到 $target_dir"
cd "$target_dir" || {
log_error "无法切换到项目目录: $target_dir"
exit 1
}
fi
}
# 检查 uv 是否可用
check_uv() {
if ! command -v uv &> /dev/null; then
log_error "uv 命令未找到,请确保 uv 已安装"
exit 1
fi
}
# 检查应用模块是否存在
check_app_module() {
local module_path=$(echo "$APP_MODULE" | sed 's/:.*//' | sed 's/\./\//g').py
if [ ! -f "$module_path" ]; then
log_error "应用模块不存在: $module_path"
exit 1
fi
}
# 创建日志目录
setup_logs() {
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
log_info "创建日志目录: $LOG_DIR"
fi
}
# 启动 Unicorn 服务器
start_server() {
local enable_reload=false
local reload_from_arg=false
# 检查是否传递了 --reload 参数
for arg in "$@"; do
if [ "$arg" = "--reload" ]; then
enable_reload=true
reload_from_arg=true
break
fi
done
if [ "$reload_from_arg" = false ]; then
if is_true "${APP_DEBUG:-$DEBUG}"; then
enable_reload=true
fi
fi
log_info "正在启动 $APP_NAME..."
# 检查是否已经在运行
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
log_warning "$APP_NAME 已经在运行 (PID: $pid)"
return 0
else
log_warning "发现旧的 PID 文件,清理中..."
rm -f "$PID_FILE"
fi
fi
# 构建启动命令
local uvicorn_cmd="uv run uvicorn \
$APP_MODULE \
--host $APP_HOST \
--port $APP_PORT \
--workers $APP_WORKERS \
--log-level $APP_LOG_LEVEL \
--access-log"
# 根据参数添加 --reload
if [ "$enable_reload" = true ]; then
uvicorn_cmd="$uvicorn_cmd --reload"
log_info "启用热重载模式"
else
log_info "禁用热重载模式(生产环境推荐)"
fi
# 启动服务器(使用 >> 追加日志,相同日期日志不会丢失)
nohup $uvicorn_cmd >> "$LOG_FILE" 2>&1 &
local server_pid=$!
echo $server_pid > "$PID_FILE"
sleep 2
if kill -0 "$server_pid" 2>/dev/null; then
log_success "$APP_NAME 已启动 (PID: $server_pid)"
log_info "日志文件: $LOG_FILE"
log_info "访问地址: http://$APP_HOST:$APP_PORT"
log_info "热重载: $([ "$enable_reload" = true ] && echo "已启用" || echo "已禁用")"
else
log_error "$APP_NAME 启动失败,请检查日志文件: $LOG_FILE"
rm -f "$PID_FILE"
exit 1
fi
}
# 停止 Unicorn 服务器
stop_server() {
log_info "正在停止 $APP_NAME..."
if [ ! -f "$PID_FILE" ]; then
log_warning "PID 文件不存在,$APP_NAME 可能未运行"
return 0
fi
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
kill -TERM "$pid"
sleep 3
if kill -0 "$pid" 2>/dev/null; then
log_warning "正常停止失败,尝试强制停止..."
kill -KILL "$pid"
sleep 1
fi
if kill -0 "$pid" 2>/dev/null; then
log_error "无法停止进程 $pid"
return 1
else
rm -f "$PID_FILE"
log_success "$APP_NAME 已停止"
fi
else
log_warning "进程 $pid 不存在,清理 PID 文件"
rm -f "$PID_FILE"
fi
}
# 重启 Unicorn 服务器
restart_server() {
log_info "正在重启 $APP_NAME..."
stop_server
sleep 2
# 传递所有参数给 start_server(包括可能的 --reload)
start_server "$@"
}
# 查看服务器状态
status_server() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
log_success "$APP_NAME 正在运行 (PID: $pid)"
log_info "端口: $APP_PORT"
log_info "工作进程: $APP_WORKERS"
return 0
else
log_warning "PID 文件存在但进程未运行,清理中..."
rm -f "$PID_FILE"
fi
fi
log_info "$APP_NAME 未运行"
return 1
}
# 查看服务器日志
tail_logs() {
local target_log_file="$LOG_FILE"
# 检查是否有日期参数
if [ $# -gt 0 ]; then
local date_param="$1"
# 验证日期格式是否为 YYYY-MM-DD
if [[ "$date_param" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
target_log_file="${LOG_DIR}/${APP_NAME}-${date_param}.log"
log_info "查看指定日期日志: $date_param"
else
log_warning "日期参数格式错误,使用默认日期日志"
fi
fi
if [ -f "$target_log_file" ]; then
log_info "显示日志文件最后 50 行: $target_log_file"
echo "=========================================="
tail -50 "$target_log_file"
echo "=========================================="
# 显示文件基本信息
local file_size=$(du -h "$target_log_file" | cut -f1)
local line_count=$(wc -l < "$target_log_file")
log_info "文件大小: $file_size, 行数: $line_count"
else
# 尝试查找最新的日志文件
local latest_log=$(ls -t "${LOG_DIR}/${APP_NAME}-"*.log 2>/dev/null | head -1)
if [ -f "$latest_log" ]; then
log_warning "指定日志文件不存在,显示最新日志文件: $latest_log"
log_info "显示日志文件最后 50 行: $latest_log"
echo "=========================================="
tail -50 "$latest_log"
echo "=========================================="
else
log_warning "日志文件不存在: $target_log_file"
log_info "可用的日志文件:"
ls -la "${LOG_DIR}/"*.log 2>/dev/null || echo "无日志文件"
fi
fi
}
# 显示帮助信息
show_help() {
echo "用法: $0 {start|stop|restart|status|logs|help} [--reload] [日期]"
echo ""
echo "服务管理命令:"
echo " start [--reload] 启动 $APP_NAME 服务器(默认禁用热重载)"
echo " stop 停止 $APP_NAME 服务器"
echo " restart [--reload] 重启 $APP_NAME 服务器"
echo " status 查看 $APP_NAME 服务器状态"
echo " logs [YYYY-MM-DD] 查看 $APP_NAME 服务器日志(可指定日期)"
echo " help 显示此帮助信息"
echo ""
echo "可选参数:"
echo " --reload 启用热重载模式(开发环境使用)"
echo " 日期参数 格式: YYYY-MM-DD,用于查看特定日期日志"
echo ""
echo "环境变量配置:"
echo " 所有配置都从 .env 文件读取,包括:"
echo " APP_NAME, APP_MODULE, HOST, PORT, DEBUG, APP_HOST, APP_PORT, APP_WORKERS, APP_LOG_LEVEL 等"
echo ""
echo "日志功能:"
echo " • 日志按日期分割: ${APP_NAME}-YYYY-MM-DD.log"
echo " • 相同日期日志自动追加"
echo " • 支持查看特定日期日志"
echo ""
echo "示例:"
echo " $0 start # 启动服务器(生产环境推荐)"
echo " $0 start --reload # 启动服务器并启用热重载(开发环境)"
echo " $0 restart --reload # 重启服务器并启用热重载"
echo " $0 status # 查看服务器状态"
echo " $0 logs # 查看今天日志"
echo " $0 logs 2025-12-14 # 查看指定日期日志"
}
# 主函数
main() {
# 加载环境变量
load_env
# 初始化配置
init_config
# 执行检查
check_directory
check_uv
check_app_module
setup_logs
# 处理命令行参数
case "$1" in
start)
# 传递剩余参数给 start_server
shift
start_server "$@"
;;
stop)
stop_server
;;
restart)
# 重启时也支持 --reload 参数
shift
restart_server "$@"
;;
status)
status_server
;;
logs)
# 传递可能的日期参数给 tail_logs
shift
tail_logs "$@"
;;
help|--help|-h)
show_help
;;
*)
if [ -z "$1" ]; then
# 默认行为:启动服务器(向后兼容)
log_warning "未指定命令,默认执行启动操作"
log_warning "建议使用: $0 start"
start_server
else
log_error "未知命令: $1"
show_help
exit 1
fi
;;
esac
}
# 脚本入口
main "$@"