feat: 添加 FastAPI 项目管理脚本

- 新增 run.sh 交互式管理脚本,提供以下功能:
  - 启动开发服务器(自动检查并创建数据库)
  - 生成和应用数据库迁移
  - 重置迁移记录
  - 清理和删除数据库
  - 执行 SQL 初始化脚本
- 支持从 .env.dev 文件读取数据库配置
- 提供彩色输出和友好的交互界面
- 包含安全确认机制防止误操作
This commit is contained in:
coderxslee
2026-04-20 22:05:40 +08:00
parent c32823138d
commit 698418b3e5
Executable
+378
View File
@@ -0,0 +1,378 @@
#!/bin/bash
set -e
# 固定脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# ========== 彩色输出定义 ==========
if [[ -t 1 ]]; then
tty_red="\033[0;31m"
tty_green="\033[0;32m"
tty_yellow="\033[0;33m"
tty_blue="\033[0;34m"
tty_cyan="\033[0;36m"
tty_purple="\033[0;35m"
tty_bold="\033[1m"
tty_reset="\033[0m"
else
tty_red="" tty_green="" tty_yellow="" tty_blue="" tty_cyan="" tty_purple="" tty_bold="" tty_reset=""
fi
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[38;5;226m[!]'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
LIGHT_GRAY='\033[0;37m'
PURPLE='\033[0;35m'
BOLD='\033[1m'
RESET='\033[0m'
# ========== 彩色输出函数 ==========
function info() {
echo -e "${tty_green}✅ $1${tty_reset}"
}
function warn() {
echo -e "${tty_yellow}⚠️ $1${tty_reset}"
}
function error() {
echo -e "${tty_red}❌ $1${tty_reset}"
}
function pause() {
read -n1 -r -p "按任意键继续..." key
}
# 判断执行是否成功
JudgeSuccess() {
if [ $? -ne 0 ]; then
error "步骤失败: $1"
exit 1
else
info "步骤成功: $1"
fi
}
# 分割线输出函数
print_separator() {
printf "${LIGHT_GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"
}
show_banner() {
echo ""
echo -e "╭──────────────────────────────────────────────╮"
echo -e "│ \033[1;34m👋 欢迎使用 FastAPI 初始化脚本\033[0m │"
echo -e "╰──────────────────────────────────────────────╯"
echo "版本: 1.0.0"
echo "作者:coderxslee"
echo ""
}
# ========== 从 .env.dev 文件中读取数据库配置 ==========
function load_db_config() {
local env_file="$SCRIPT_DIR/env/.env.dev"
if [ ! -f "$env_file" ]; then
error "未找到 $env_file 文件"
return 1
fi
# 读取数据库配置(忽略注释和空行)
# 使用 sed 去掉注释部分,然后提取值
DATABASE_HOST=$(grep "^DATABASE_HOST" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
DATABASE_PORT=$(grep "^DATABASE_PORT" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
DATABASE_USER=$(grep "^DATABASE_USER" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
DATABASE_PASSWORD=$(grep "^DATABASE_PASSWORD" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
DATABASE_NAME=$(grep "^DATABASE_NAME" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
DATABASE_TYPE=$(grep "^DATABASE_TYPE" "$env_file" | cut -d'=' -f2 | sed 's/#.*//' | xargs)
# 验证必要的配置
if [[ -z "$DATABASE_HOST" ]] || [[ -z "$DATABASE_PORT" ]] || [[ -z "$DATABASE_USER" ]] || [[ -z "$DATABASE_NAME" ]]; then
error "数据库配置不完整,请检查 $env_file 文件"
return 1
fi
return 0
}
# ========== FastAPI 功能函数 ==========
# 1. 启动开发服务器
function start_dev_server() {
print_separator
echo -e "${tty_cyan}🚀 启动(uv run dev)...${tty_reset}"
load_db_config || return 1
echo -e "${tty_blue}🗄️ 检查并创建数据库...${tty_reset}"
# 检查数据库是否存在
DB_EXISTS=$(PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "postgres" -tAc "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME';" 2>/dev/null || echo "")
if [ "$DB_EXISTS" != "1" ]; then
warn "数据库 '$DATABASE_NAME' 不存在,正在创建..."
CREATE_DB_OUTPUT=$(PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "postgres" -c "CREATE DATABASE \"$DATABASE_NAME\" WITH ENCODING 'UTF8' LC_COLLATE 'C.UTF-8' LC_CTYPE 'C.UTF-8' TEMPLATE template0 OWNER $DATABASE_USER;" 2>&1)
if [ $? -ne 0 ]; then
error "数据库创建失败"
echo -e "${tty_red}错误详情:${tty_reset}"
echo -e "${tty_red}$CREATE_DB_OUTPUT${tty_reset}"
return 1
fi
info "数据库 '$DATABASE_NAME' 创建成功 (UTF8 编码, C.UTF-8 排序规则)"
else
info "数据库 '$DATABASE_NAME' 已存在"
fi
print_separator
cd "$SCRIPT_DIR"
uv run main.py run --env=dev
JudgeSuccess "开发服务器启动"
echo -e "${tty_green}🎉 开发服务器启动完成!${tty_reset}"
print_separator
}
# 2. 生成迁移文件
function create_migration() {
print_separator
echo -e "${tty_cyan}📝 生成迁移文件(模型变更后)...${tty_reset}"
cd "$SCRIPT_DIR"
uv run main.py revision --env=dev
JudgeSuccess "迁移文件生成"
echo -e "${tty_green}🎉 迁移文件生成完成!${tty_reset}"
print_separator
}
# 3. 应用迁移
function apply_migration() {
print_separator
echo -e "${tty_cyan}⚡ 应用迁移...${tty_reset}"
cd "$SCRIPT_DIR"
uv run main.py upgrade --env=dev
JudgeSuccess "迁移应用"
echo -e "${tty_green}🎉 迁移应用完成!${tty_reset}"
print_separator
}
# 4. 重置数据库中的迁移记录
function reset_migration_records() {
print_separator
echo -e "${tty_cyan}🔄 重置数据库中的迁移记录...${tty_reset}"
echo -e "${tty_yellow}⚠️ 警告:此操作将重置数据库中的迁移记录!${tty_reset}"
read -p "确认继续吗?(y/N): " confirm
if [[ $confirm != [yY] ]]; then
echo -e "${tty_yellow}操作已取消${tty_reset}"
return
fi
load_db_config || return 1
cd "$SCRIPT_DIR"
echo -e "${tty_blue}🔄 正在重置迁移记录...${tty_reset}"
# 使用 psql 连接到数据库并重置迁移记录
PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -c "DELETE FROM alembic_version;" 2>/dev/null
JudgeSuccess "迁移记录重置"
echo -e "${tty_green}🎉 迁移记录重置完成!${tty_reset}"
print_separator
}
# 5. 清理数据库(删除所有表)
function clean_database() {
print_separator
echo -e "${tty_cyan}🗄️ 清理数据库(删除所有表)...${tty_reset}"
echo -e "${tty_yellow}⚠️ 警告:此操作将删除数据库中的所有数据!${tty_reset}"
read -p "确认继续吗?(y/N): " confirm
if [[ $confirm != [yY] ]]; then
echo -e "${tty_yellow}操作已取消${tty_reset}"
return
fi
load_db_config || return 1
cd "$SCRIPT_DIR"
echo -e "${tty_blue}🗄️ 清理数据库,删除所有现有的表...${tty_reset}"
# 使用 psql 连接到数据库并删除所有表
PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" 2>/dev/null
JudgeSuccess "数据库清理"
echo -e "${tty_green}🎉 数据库清理完成!${tty_reset}"
print_separator
}
# 6. 删除数据库
function drop_database() {
print_separator
echo -e "${tty_cyan}🗑️ 删除数据库...${tty_reset}"
# 获取数据库名称
echo -e "${tty_blue}📝 请输入要删除的数据库名称:${tty_reset}"
read -p "数据库名称: " db_name
# 验证数据库名称不为空
if [[ -z "$db_name" ]]; then
error "数据库名称不能为空!"
return
fi
# 显示警告信息
echo -e "${tty_red}⚠️ 警告:此操作将永久删除数据库 '$db_name' 及其所有数据!${tty_reset}"
echo -e "${tty_yellow}此操作不可撤销!${tty_reset}"
# 第一次确认
read -p "确认要删除数据库 '$db_name' 吗?(y/N): " confirm1
if [[ $confirm1 != [yY] ]]; then
echo -e "${tty_yellow}操作已取消${tty_reset}"
return
fi
# 第二次确认
echo -e "${tty_red}⚠️ 最后确认:您真的要删除数据库 '$db_name' 吗?${tty_reset}"
read -p "请输入 'DELETE' 来确认删除: " confirm2
if [[ $confirm2 != "DELETE" && $confirm2 != "delete" ]]; then
echo -e "${tty_yellow}操作已取消${tty_reset}"
return
fi
load_db_config || return 1
cd "$SCRIPT_DIR"
echo -e "${tty_blue}🗑️ 正在删除数据库 '$db_name'...${tty_reset}"
# 先断开所有连接到该数据库的连接
PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$db_name' AND pid <> pg_backend_pid();" 2>/dev/null
# 删除数据库
PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "postgres" -c "DROP DATABASE IF EXISTS \"$db_name\";" 2>/dev/null
JudgeSuccess "数据库删除"
echo -e "${tty_green}🎉 数据库 '$db_name' 删除完成!${tty_reset}"
print_separator
}
# 7. 初始化数据(执行 sql 目录下脚本)
function init_sql_data() {
print_separator
echo -e "${tty_cyan}🧰 初始化数据...${tty_reset}"
local sql_dir
sql_dir="$REPO_ROOT/backend/sql/postgres/init_data"
if [ ! -d "$sql_dir" ]; then
error "未找到 SQL 目录: $sql_dir"
return 1
fi
local sql_files=()
while IFS= read -r file; do
sql_files+=("$file")
done < <(find "$sql_dir" -maxdepth 1 -type f -name "*.sql" 2>/dev/null | sort)
if [ ${#sql_files[@]} -eq 0 ]; then
warn "SQL 目录下没有可执行的 .sql 文件: $sql_dir"
return 0
fi
echo -e "${tty_purple}可用 SQL 文件:${tty_reset}"
echo -e "${tty_yellow}0. 执行全部 SQL 文件${tty_reset}"
local i
for i in "${!sql_files[@]}"; do
echo "$((i + 1)). $(basename "${sql_files[$i]}")"
done
local choice
read -p "请选择要初始化的 SQL(输入序号): " choice
if ! [[ "$choice" =~ ^[0-9]+$ ]]; then
error "输入无效,请输入数字序号"
return 1
fi
load_db_config || return 1
if [ "$choice" -eq 0 ]; then
echo -e "${tty_blue}🚀 开始执行全部 SQL 文件...${tty_reset}"
local file
for file in "${sql_files[@]}"; do
echo -e "${tty_blue}执行: $(basename "$file")${tty_reset}"
if PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -f "$file"; then
info "执行成功: $(basename "$file")"
else
error "执行失败: $(basename "$file")"
return 1
fi
done
info "全部 SQL 文件执行完成"
else
local index=$((choice - 1))
if [ "$index" -lt 0 ] || [ "$index" -ge ${#sql_files[@]} ]; then
error "序号超出范围"
return 1
fi
local selected_file="${sql_files[$index]}"
echo -e "${tty_blue}执行: $(basename "$selected_file")${tty_reset}"
if PGPASSWORD="$DATABASE_PASSWORD" psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USER" -d "$DATABASE_NAME" -f "$selected_file"; then
info "执行成功: $(basename "$selected_file")"
info "SQL 初始化完成"
else
error "执行失败: $(basename "$selected_file")"
return 1
fi
fi
print_separator
}
# 主菜单
function main_menu() {
clear
show_banner
echo -e "\033[1;34m📦 请选择要执行的操作:\033[0m"
echo ""
echo -e "\033[1;33m1. 🚀 启动(uv run dev)\033[0m"
echo -e "\033[1;32m2. 📝 生成迁移文件(模型变更后)\033[0m"
echo -e "\033[1;36m3. ⚡ 应用迁移\033[0m"
echo -e "\033[1;36m4. 🔄 重置数据库中的迁移记录\033[0m"
echo -e "\033[1;31m5. 🗄️ 清理数据库(删除所有表)\033[0m"
echo -e "\033[1;31m6. 🗑️ 删除数据库\033[0m"
echo -e "\033[1;36m7. 🧰 初始化数据(执行 sql 脚本)\033[0m"
echo -e "\033[1;31m0. ❌ 退出\033[0m"
echo ""
read -p "请选择你要执行的操作: " option
case $option in
1) start_dev_server && pause ;;
2) create_migration && pause ;;
3) apply_migration && pause ;;
4) reset_migration_records && pause ;;
5) clean_database && pause ;;
6) drop_database && pause ;;
7) init_sql_data && pause ;;
0) exit 0 ;;
*) error "未知选项: $option" && pause ;;
esac
}
# 非交互模式入口
if [[ "$1" == "--start-dev" ]]; then
start_dev_server
exit $?
fi
# 默认进入交互菜单
main_menu