feat: 服务端集成自动打包流程

This commit is contained in:
shen
2026-07-17 22:34:04 +08:00
parent a2437a039d
commit d1f070b251
221 changed files with 7284 additions and 447 deletions
+75 -44
View File
@@ -33,6 +33,36 @@ info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
# 在 PID 文件丢失或进程 PID 被复用时,仍能安全识别本服务。
is_server_process() {
local pid="$1"
local command
command=$(ps -p "$pid" -o command= 2>/dev/null || true)
[[ "$command" == *"uvicorn backend.main:app"* ]]
}
get_server_pid() {
local pid
if [ -f "$PID_FILE" ]; then
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null && is_server_process "$pid"; then
echo "$pid"
return 0
fi
rm -f "$PID_FILE"
fi
# 仅认领监听当前配置端口、且命令明确属于本项目的 uvicorn 进程。
for pid in $(lsof -tiTCP:"$BACKEND_PORT" -sTCP:LISTEN 2>/dev/null); do
if is_server_process "$pid"; then
echo "$pid" > "$PID_FILE"
echo "$pid"
return 0
fi
done
return 1
}
# 日志同时写文件和终端
log_to_file() {
local level="$1"; shift
@@ -52,8 +82,9 @@ check_env() {
exit 1
fi
if ! command -v node &>/dev/null; then
error "未找到 node"
# 运行已构建的前端不需要 Node;仅在缺少静态资源时才需要它重新构建。
if [ ! -d "$SCRIPT_DIR/backend/static" ] && ! command -v node &>/dev/null; then
error "未找到 node,且前端尚未构建"
exit 1
fi
@@ -65,12 +96,16 @@ check_env() {
# ========== Python 虚拟环境 ==========
setup_venv() {
# 项目目录移动后,venv 内 pip 的 shebang 会指向旧路径,需要重建。
if [ -d "$VENV_DIR" ] && ! "$VENV_DIR/bin/pip" --version >/dev/null 2>&1; then
warn "检测到失效的 Python 虚拟环境,正在重建..."
rm -rf "$VENV_DIR"
fi
if [ ! -d "$VENV_DIR" ]; then
info "创建 Python 虚拟环境..."
python3 -m venv "$VENV_DIR"
fi
source "$VENV_DIR/bin/activate"
pip install -r requirements.txt -q
"$VENV_DIR/bin/python3" -m pip install -r requirements.txt -q
}
# ========== 构建前端 ==========
@@ -107,14 +142,11 @@ do_build() {
do_start() {
check_env
if [ -f "$PID_FILE" ]; then
local pid
pid=$(cat "$PID_FILE")
if kill -0 "$pid" 2>/dev/null; then
warn "服务已在运行 (PID: $pid)"
return 0
fi
rm -f "$PID_FILE"
local existing_pid
existing_pid=$(get_server_pid || true)
if [ -n "$existing_pid" ]; then
warn "服务已在运行 (PID: $existing_pid)"
return 0
fi
# 确保前端已构建
@@ -128,7 +160,7 @@ do_start() {
mkdir -p "$LOG_DIR"
info "启动后端服务 (端口: $BACKEND_PORT)..."
nohup python3 -m uvicorn backend.main:app \
nohup "$VENV_DIR/bin/python3" -m uvicorn backend.main:app \
--host 0.0.0.0 \
--port "$BACKEND_PORT" \
--workers 1 \
@@ -138,8 +170,11 @@ do_start() {
sleep 1
if kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
local access_host
access_host=$(ipconfig getifaddr en0 2>/dev/null || true)
access_host="${access_host:-localhost}"
info "服务已启动 (PID: $(cat "$PID_FILE"))"
info "访问地址: http://$(hostname -I 2>/dev/null | awk '{print $1}' || echo 'localhost'):$BACKEND_PORT"
info "访问地址: http://$access_host:$BACKEND_PORT"
else
error "启动失败,请查看日志: $LOG_DIR/server.log"
rm -f "$PID_FILE"
@@ -149,40 +184,37 @@ do_start() {
# ========== 停止服务 ==========
do_stop() {
if [ ! -f "$PID_FILE" ]; then
local pid
pid=$(get_server_pid || true)
if [ -z "$pid" ]; then
warn "服务未在运行"
return 0
fi
local pid
pid=$(cat "$PID_FILE")
info "停止服务 (PID: $pid)..."
kill "$pid"
# 等待进程退出
for i in $(seq 1 10); do
kill -0 "$pid" 2>/dev/null || break
sleep 0.5
done
# 还没退出就强杀
if kill -0 "$pid" 2>/dev/null; then
info "停止服务 (PID: $pid)..."
kill "$pid"
# 等待进程退出
for i in $(seq 1 10); do
kill -0 "$pid" 2>/dev/null || break
sleep 0.5
done
# 还没退出就强杀
if kill -0 "$pid" 2>/dev/null; then
warn "进程未响应,强制终止..."
kill -9 "$pid"
fi
info "服务已停止"
else
warn "进程已不存在"
warn "进程未响应,强制终止..."
kill -9 "$pid"
fi
info "服务已停止"
rm -f "$PID_FILE"
}
# ========== 查看状态 ==========
do_status() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
info "服务运行中 (PID: $(cat "$PID_FILE")), 端口: $BACKEND_PORT"
local pid
pid=$(get_server_pid || true)
if [ -n "$pid" ]; then
info "服务运行中 (PID: $pid), 端口: $BACKEND_PORT"
else
warn "服务未运行"
rm -f "$PID_FILE" 2>/dev/null
fi
if [ -f "$WATCHDOG_PID_FILE" ] && kill -0 "$(cat "$WATCHDOG_PID_FILE")" 2>/dev/null; then
@@ -196,7 +228,9 @@ do_status() {
# ========== 健康检查 ==========
check_health() {
# 检查进程是否存在
if [ ! -f "$PID_FILE" ] || ! kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
local pid
pid=$(get_server_pid || true)
if [ -z "$pid" ]; then
echo "process_down"
return
fi
@@ -345,9 +379,9 @@ do_watchdog_loop() {
fi
# 重新启动
source "$VENV_DIR/bin/activate" 2>/dev/null
setup_venv
mkdir -p "$LOG_DIR"
nohup python3 -m uvicorn backend.main:app \
nohup "$VENV_DIR/bin/python3" -m uvicorn backend.main:app \
--host 0.0.0.0 \
--port "$BACKEND_PORT" \
--workers 1 \
@@ -392,11 +426,8 @@ do_test() {
local failed=0
# 确保依赖已安装
if [ ! -d "$VENV_DIR" ]; then
setup_venv
else
source "$VENV_DIR/bin/activate"
fi
setup_venv
"$VENV_DIR/bin/python3" -m pip install -r requirements-dev.txt -q
if [ ! -d "frontend/node_modules" ]; then
cd frontend && npm install --silent && cd ..
@@ -405,7 +436,7 @@ do_test() {
# 后端测试
info "运行后端测试..."
echo "────────────────────────────────────────"
python3 -m pytest tests/ -v "$@" || failed=1
"$VENV_DIR/bin/python3" -m pytest tests/ -v "$@" || failed=1
echo ""
# 前端测试