diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b506438 --- /dev/null +++ b/Makefile @@ -0,0 +1,206 @@ +# ============================================================ +# PIS Project Startup Script - Makefile +# PIS 项目启动脚本 +# ============================================================ +# 使用方法: +# make help 查看所有可用命令 +# make dev 启动开发环境(前端 + 后端 + Celery) +# make dev-backend 仅启动后端 +# make dev-frontend 仅启动前端 +# make dev-celery 仅启动 Celery +# make stop 停止所有服务 +# make status 查看服务状态 +# make logs 查看所有日志 +# make logs-backend 查看后端日志 +# make logs-frontend 查看前端日志 +# make logs-celery 查看 Celery 日志 +# make restart 重启所有服务 +# make restart-celery 重启 Celery +# ============================================================ + +# 默认环境 +ENV ?= dev + +# 路径配置 +PROJECT_ROOT := $(shell pwd) +BACKEND_DIR := $(PROJECT_ROOT)/backend +FRONTEND_DIR := $(PROJECT_ROOT)/web +SCRIPTS_DIR := $(PROJECT_ROOT)/scripts +LOG_DIR := $(BACKEND_DIR)/logs + +# PID 文件目录 +PID_DIR := $(PROJECT_ROOT)/.pids + +# 后端服务端口 +BACKEND_PORT := 8000 +FRONTEND_PORT := 8080 + +# 帮助信息 +.PHONY: help +help: + @echo "" + @echo "========================================" + @echo " PIS 项目启动脚本 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 启动命令:" + @echo " make dev 启动开发环境(前端+后端+Celery)" + @echo " make dev-backend 仅启动后端服务" + @echo " make dev-frontend 仅启动前端服务" + @echo " make dev-celery 仅启动 Celery" + @echo "" + @echo " 停止/重启:" + @echo " make stop 停止所有服务" + @echo " make restart 重启所有服务" + @echo " make restart-celery 重启 Celery" + @echo "" + @echo " 日志查看:" + @echo " make logs 查看所有日志" + @echo " make logs-backend 查看后端日志" + @echo " make logs-frontend 查看前端日志" + @echo " make logs-celery 查看 Celery 日志" + @echo "" + @echo " 状态查看:" + @echo " make status 查看所有服务状态" + @echo "" + @echo " 环境切换:" + @echo " ENV=prod make dev 使用生产环境配置启动" + @echo "" + @echo "========================================" + +# ============================================================ +# 创建必要的目录 +# ============================================================ +$(PID_DIR): + @mkdir -p $(PID_DIR) + +$(LOG_DIR): + @mkdir -p $(LOG_DIR) + +# ============================================================ +# 启动开发环境 - 全部服务 +# ============================================================ +.PHONY: dev +dev: $(PID_DIR) $(LOG_DIR) + @echo "========================================" + @echo " 启动开发环境 (ENV=$(ENV))" + @echo "========================================" + @echo "" + @echo " 后端: http://localhost:$(BACKEND_PORT)" + @echo " 前端: http://localhost:$(FRONTEND_PORT)" + @echo " Celery: Worker + Beat" + @echo "" + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 启动后端 +# ============================================================ +.PHONY: dev-backend +dev-backend: $(PID_DIR) $(LOG_DIR) + @echo "启动后端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-backend.sh $(ENV) + +# ============================================================ +# 启动前端 +# ============================================================ +.PHONY: dev-frontend +dev-frontend: + @echo "启动前端服务 (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-frontend.sh $(ENV) + +# ============================================================ +# 启动 Celery +# ============================================================ +.PHONY: dev-celery +dev-celery: $(PID_DIR) $(LOG_DIR) + @echo "启动 Celery (ENV=$(ENV))..." + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 停止所有服务 +# ============================================================ +.PHONY: stop +stop: + @echo "停止所有服务..." + @bash $(SCRIPTS_DIR)/stop-all.sh + @echo "所有服务已停止" + +# ============================================================ +# 重启所有服务 +# ============================================================ +.PHONY: restart +restart: + @echo "重启所有服务..." + @$(MAKE) stop + @sleep 2 + @$(MAKE) dev + +# ============================================================ +# 重启 Celery +# ============================================================ +.PHONY: restart-celery +restart-celery: + @bash $(SCRIPTS_DIR)/stop-celery.sh + @sleep 1 + @bash $(SCRIPTS_DIR)/start-celery.sh $(ENV) + +# ============================================================ +# 查看日志 +# ============================================================ +.PHONY: logs +logs: + @echo "========================================" + @echo " 查看所有日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh all + +.PHONY: logs-backend +logs-backend: + @echo "========================================" + @echo " 查看后端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh backend + +.PHONY: logs-frontend +logs-frontend: + @echo "========================================" + @echo " 查看前端日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh frontend + +.PHONY: logs-celery +logs-celery: + @echo "========================================" + @echo " 查看 Celery 日志 (Ctrl+C 退出)" + @echo "========================================" + @bash $(SCRIPTS_DIR)/view-logs.sh celery + +# ============================================================ +# 查看服务状态 +# ============================================================ +.PHONY: status +status: + @echo "========================================" + @echo " 服务状态检查" + @echo "========================================" + @bash $(SCRIPTS_DIR)/check-status.sh + +# ============================================================ +# 清理日志 +# ============================================================ +.PHONY: clean-logs +clean-logs: + @echo "清理日志文件..." + @find $(LOG_DIR) -type f -name "*.log" -exec rm -f {} \; 2>/dev/null || true + @echo "日志已清理" + +# ============================================================ +# 清理 PID 文件(强制停止) +# ============================================================ +.PHONY: clean-pids +clean-pids: + @echo "清理 PID 文件..." + @rm -f $(PID_DIR)/*.pid 2>/dev/null || true + @echo "PID 文件已清理" diff --git a/docs/scripts-documentation.md b/docs/scripts-documentation.md new file mode 100644 index 0000000..198fa24 --- /dev/null +++ b/docs/scripts-documentation.md @@ -0,0 +1,257 @@ +# 脚本文档 + +PIS 项目跨平台服务管理脚本。 + +## 概述 + +| 脚本 | Windows (PowerShell) | Unix/Linux/macOS (Bash) | +|------|---------------------|------------------------| +| 启动后端 | `start-backend.ps1` | `start-backend.sh` | +| 启动前端 | `start-frontend.ps1` | `start-frontend.sh` | +| 启动 Celery | `start-celery.ps1` | `start-celery.sh` | +| 停止所有 | `stop-all.ps1` | `stop-all.sh` | +| 停止 Celery | `stop-celery.ps1` | `stop-celery.sh` | +| 查看日志 | `view-logs.ps1` | `view-logs.sh` | +| 检查状态 | `check-status.ps1` | `check-status.sh` | + +## 快速开始 + +### Windows (PowerShell) + +```powershell +# 启动所有服务 +.\make.ps1 dev + +# 停止所有服务 +.\make.ps1 stop + +# 查看日志 +.\make.ps1 logs + +# 检查状态 +.\make.ps1 status +``` + +### Unix/Linux/macOS (Bash) + +```bash +# 启动所有服务 +make dev + +# 停止所有服务 +make stop + +# 查看日志 +make logs + +# 检查状态 +make status +``` + +## 使用方法 + +### make.ps1 命令 (Windows PowerShell) + +| 命令 | 说明 | +|------|------| +| `.\make.ps1 help` | 显示所有可用命令 | +| `.\make.ps1 dev` | 启动开发环境(前端 + 后端 + Celery) | +| `.\make.ps1 dev-backend` | 仅启动后端 | +| `.\make.ps1 dev-frontend` | 仅启动前端 | +| `.\make.ps1 dev-celery` | 仅启动 Celery | +| `.\make.ps1 stop` | 停止所有服务 | +| `.\make.ps1 restart` | 重启所有服务 | +| `.\make.ps1 restart-celery` | 仅重启 Celery | +| `.\make.ps1 logs` | 查看所有日志 | +| `.\make.ps1 logs-backend` | 查看后端日志 | +| `.\make.ps1 logs-frontend` | 查看前端日志 | +| `.\make.ps1 logs-celery` | 查看 Celery 日志 | +| `.\make.ps1 status` | 检查服务状态 | +| `.\make.ps1 clean-logs` | 清理日志文件 | +| `.\make.ps1 clean-pids` | 清理 PID 文件 | + +### make 命令 (Unix/Linux/macOS Bash) + +| 命令 | 说明 | +|------|------| +| `make help` | 显示所有可用命令 | +| `make dev` | 启动开发环境(前端 + 后端 + Celery) | +| `make dev-backend` | 仅启动后端 | +| `make dev-frontend` | 仅启动前端 | +| `make dev-celery` | 仅启动 Celery | +| `make stop` | 停止所有服务 | +| `make restart` | 重启所有服务 | +| `make restart-celery` | 仅重启 Celery | +| `make logs` | 查看所有日志 | +| `make logs-backend` | 查看后端日志 | +| `make logs-frontend` | 查看前端日志 | +| `make logs-celery` | 查看 Celery 日志 | +| `make status` | 检查服务状态 | +| `make clean-logs` | 清理日志文件 | +| `make clean-pids` | 清理 PID 文件 | + +## 直接调用脚本 + +### Windows PowerShell + +```powershell +# 启动后端 +.\scripts\start-backend.ps1 [dev|prod] + +# 启动前端 +.\scripts\start-frontend.ps1 [dev|prod] + +# 启动 Celery +.\scripts\start-celery.ps1 [dev|prod] + +# 停止所有服务 +.\scripts\stop-all.ps1 + +# 仅停止 Celery +.\scripts\stop-celery.ps1 + +# 查看日志 +.\scripts\view-logs.ps1 [backend|frontend|celery|all] + +# 检查状态 +.\scripts\check-status.ps1 +``` + +### Unix/Linux/macOS Bash + +```bash +# 启动后端 +./scripts/start-backend.sh [dev|prod] + +# 启动前端 +./scripts/start-frontend.sh [dev|prod] + +# 启动 Celery +./scripts/start-celery.sh [dev|prod] + +# 停止所有服务 +./scripts/stop-all.sh + +# 仅停止 Celery +./scripts/stop-celery.sh + +# 查看日志 +./scripts/view-logs.sh [backend|frontend|celery|all] + +# 检查状态 +./scripts/check-status.sh +``` + +## 环境参数 + +- `dev`(默认):开发模式,带热重载 +- `prod`:生产模式,使用优化构建 + +### Windows PowerShell +```powershell +.\make.ps1 dev -ENV prod +``` + +### Unix/Linux/macOS +```bash +ENV=prod make dev +``` + +## 端口 + +| 服务 | 端口 | +|------|------| +| 后端 (Django) | 8000 | +| 前端 (Vue) | 8080 | + +## 日志文件 + +日志存储在 `backend/logs/`: + +- `backend.log` - 后端应用日志 +- `frontend.log` - 前端应用日志 +- `celery_worker.log` - Celery Worker 日志 +- `celery_beat.log` - Celery Beat 调度器日志 + +## PID 文件 + +PID 文件存储在 `.pids/`: + +- `backend.pid` +- `frontend.pid` +- `celery_worker.pid` +- `celery_beat.pid` + +## 跨平台兼容性 + +### Windows PowerShell +- 支持 Windows PowerShell 5.1 及 PowerShell 7+ +- 使用 `System.Net.Sockets.TcpClient` 检查端口 +- 使用 WMI 查询检测进程(兼容 PS 5.1 和 PS 7+) +- 所有输出使用英文(避免编码问题) + +### Unix/Linux/macOS +- 需要 Bash 4.0+ +- 使用 `nc`(netcat)检查端口(如果可用) +- 使用 `pgrep` 和 `pkill` 管理进程 +- 输出使用中文 + +## 依赖要求 + +### 后端 +- Python 3.10+ +- Django 4.2.14 +- uvicorn(开发)或 gunicorn(生产) +- Celery + Redis broker + +### 前端 +- Node.js 18+ +- Yarn 或 npm + +### Celery +- Redis 服务运行中 +- django-celery-beat + +## 故障排除 + +### 后端无法启动 +1. 检查端口 8000 是否被占用 +2. 检查 Python 依赖是否安装 +3. 检查 Django 配置:确保 `backend/conf/env.py` 已配置 + +### 前端无法启动 +1. 检查 Node.js 是否安装 +2. 安装依赖:`cd web && yarn install` +3. 检查端口 8080 是否被占用 + +### Celery 无法启动 +1. 检查 Redis 是否运行 +2. 执行数据库迁移:`python manage.py migrate` +3. 查看 Celery 日志 + +### 编码问题 +如果 PowerShell 显示乱码,确保使用 UTF-8 with BOM 编码保存的脚本文件。 + +## 脚本架构 + +``` +scripts/ +├── start-backend.ps1/.sh # 启动 Django 后端服务 +├── start-frontend.ps1/.sh # 启动 Vue 前端开发服务器 +├── start-celery.ps1/.sh # 启动 Celery worker 和 beat +├── stop-all.ps1/.sh # 停止所有服务 +├── stop-celery.ps1/.sh # 仅停止 Celery 服务 +├── view-logs.ps1/.sh # 查看和跟踪日志文件 +├── check-status.ps1/.sh # 检查服务运行状态 +├── Makefile # Unix/Linux/macOS 编排 +└── make.ps1 # Windows PowerShell 编排 +``` + +## 注意事项 + +- 所有脚本都是幂等的 - 多次运行是安全的 +- 脚本会检测服务是否已运行,已运行则跳过启动 +- 使用 PID 文件实现优雅关闭 +- 停止时会清理残留进程 +- PowerShell 脚本输出使用英文,避免编码问题 +- Bash 脚本输出使用中文 diff --git a/make.ps1 b/make.ps1 new file mode 100644 index 0000000..bd20567 --- /dev/null +++ b/make.ps1 @@ -0,0 +1,202 @@ +# ============================================================ +# PIS Project Startup Script - Windows PowerShell Makefile +# ============================================================ +# Usage: +# .\make.ps1 help Show all commands +# .\make.ps1 dev Start dev environment (frontend + backend + Celery) +# .\make.ps1 dev-backend Start backend only +# .\make.ps1 dev-frontend Start frontend only +# .\make.ps1 dev-celery Start Celery only +# .\make.ps1 stop Stop all services +# .\make.ps1 status Check service status +# .\make.ps1 logs View all logs +# .\make.ps1 logs-backend View backend logs +# .\make.ps1 logs-frontend View frontend logs +# .\make.ps1 logs-celery View Celery logs +# .\make.ps1 restart Restart all services +# .\make.ps1 restart-celery Restart Celery +# ============================================================ + +param( + [string]$Target = "help", + [string]$ENV = "dev" +) + +$ErrorActionPreference = "Continue" +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = $ScriptDir +$ScriptsDir = Join-Path $ProjectRoot "scripts" + +# Path configuration +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $ProjectRoot "backend\logs" + +function Ensure-Directories { + if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } + if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } +} + +function Run-Script { + param([string]$ScriptName) + $scriptPath = Join-Path $ScriptsDir "$ScriptName.ps1" + if (Test-Path $scriptPath) { + & $scriptPath $ENV + } else { + # fallback to bash if ps1 doesn't exist + $bashPath = Join-Path $ScriptsDir "$ScriptName.sh" + if (Test-Path $bashPath) { + bash $bashPath $ENV + } else { + Write-Host "[ERROR] Script not found: $ScriptName" + exit 1 + } + } +} + +switch ($Target.ToLower()) { + "help" { + Write-Host "" + Write-Host "========================================" + Write-Host " PIS Project Startup Script (ENV=$ENV)" + Write-Host "========================================" + Write-Host "" + Write-Host " Startup Commands:" + Write-Host " .\make.ps1 dev Start dev environment (frontend+backend+Celery)" + Write-Host " .\make.ps1 dev-backend Start backend only" + Write-Host " .\make.ps1 dev-frontend Start frontend only" + Write-Host " .\make.ps1 dev-celery Start Celery only" + Write-Host "" + Write-Host " Stop/Restart:" + Write-Host " .\make.ps1 stop Stop all services" + Write-Host " .\make.ps1 restart Restart all services" + Write-Host " .\make.ps1 restart-celery Restart Celery" + Write-Host "" + Write-Host " View Logs:" + Write-Host " .\make.ps1 logs View all logs" + Write-Host " .\make.ps1 logs-backend View backend logs" + Write-Host " .\make.ps1 logs-frontend View frontend logs" + Write-Host " .\make.ps1 logs-celery View Celery logs" + Write-Host "" + Write-Host " Status:" + Write-Host " .\make.ps1 status Check service status" + Write-Host "" + Write-Host " Environment:" + Write-Host " .\make.ps1 -ENV prod dev Use production environment" + Write-Host "" + Write-Host "========================================" + } + + "dev" { + Ensure-Directories + Write-Host "========================================" + Write-Host " Starting Dev Environment (ENV=$ENV)" + Write-Host "========================================" + Write-Host "" + Write-Host " Backend: http://localhost:8000" + Write-Host " Frontend: http://localhost:8080" + Write-Host " Celery: Worker + Beat" + Write-Host "" + Run-Script "start-backend" + Run-Script "start-frontend" + Run-Script "start-celery" + } + + "dev-backend" { + Ensure-Directories + Write-Host "Starting backend service (ENV=$ENV)..." + Run-Script "start-backend" + } + + "dev-frontend" { + Write-Host "Starting frontend service (ENV=$ENV)..." + Run-Script "start-frontend" + } + + "dev-celery" { + Ensure-Directories + Write-Host "Starting Celery (ENV=$ENV)..." + Run-Script "start-celery" + } + + "stop" { + Write-Host "Stopping all services..." + Run-Script "stop-all" + Write-Host "All services stopped" + } + + "restart" { + Write-Host "Restarting all services..." + Run-Script "stop-all" + Start-Sleep -Seconds 2 + Ensure-Directories + Run-Script "start-backend" + Run-Script "start-frontend" + Run-Script "start-celery" + } + + "restart-celery" { + Run-Script "stop-celery" + Start-Sleep -Seconds 1 + Ensure-Directories + Run-Script "start-celery" + } + + "logs" { + Write-Host "========================================" + Write-Host " Viewing All Logs (Ctrl+C to exit)" + Write-Host "========================================" + Run-Script "view-logs" + } + + "logs-backend" { + Write-Host "========================================" + Write-Host " Viewing Backend Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") backend + } + + "logs-frontend" { + Write-Host "========================================" + Write-Host " Viewing Frontend Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") frontend + } + + "logs-celery" { + Write-Host "========================================" + Write-Host " Viewing Celery Logs (Ctrl+C to exit)" + Write-Host "========================================" + & (Join-Path $ScriptsDir "view-logs.ps1") celery + } + + "status" { + Write-Host "========================================" + Write-Host " Service Status Check" + Write-Host "========================================" + Run-Script "check-status" + } + + "clean-logs" { + Write-Host "Cleaning log files..." + if (Test-Path $LOG_DIR) { + Get-ChildItem $LOG_DIR -Filter "*.log" | Remove-Item -Force -ErrorAction SilentlyContinue + } + Write-Host "Logs cleaned" + } + + "clean-pids" { + Write-Host "Cleaning PID files..." + if (Test-Path $PID_DIR) { + Get-ChildItem $PID_DIR -Filter "*.pid" | Remove-Item -Force -ErrorAction SilentlyContinue + } + Write-Host "PID files cleaned" + } + + default { + Write-Host "[ERROR] Unknown command: $Target" + Write-Host "Run '.\make.ps1 help' to see available commands" + exit 1 + } +} + +exit 0 diff --git a/scripts/check-status.ps1 b/scripts/check-status.ps1 new file mode 100644 index 0000000..52fdb1d --- /dev/null +++ b/scripts/check-status.ps1 @@ -0,0 +1,103 @@ +# ============================================================ +# Check Service Status (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Test-ServicePort { + param([int]$Port) + try { + $tcpClient = New-Object System.Net.Sockets.TcpClient + $tcpClient.Connect("localhost", $Port) + $tcpClient.Close() + return $true + } catch { + return $false + } +} + +function Check-Service { + param([string]$Name, [int]$Port = 0) + + Write-Host -NoNewline " $Name`: " + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "Running (PID: $pid)" + return 0 + } + } + } + + # Check process name - PowerShell 5.1 compatible using WMI + $patterns = @{ + "backend" = @("uvicorn", "gunicorn", "python") + "frontend" = @("node", "npm") + "celery_worker" = @("celery") + "celery_beat" = @("celery") + } + + if ($patterns.ContainsKey($Name)) { + foreach ($pattern in $patterns[$Name]) { + # Use WMI for reliable cross-version process matching + $wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%$pattern%'" + $procs = Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($procs) { + Write-Host "Running (PID: $($procs.ProcessId))" + return 0 + } + } + } + + # Check port + if ($Port -gt 0) { + if (Test-ServicePort -Port $Port) { + Write-Host "Running (Port: $Port)" + return 0 + } + } + + Write-Host "Not running" + return 1 +} + +Write-Host "" +Write-Host "========================================" +Write-Host " Service Status Check" +Write-Host "========================================" +Write-Host "" + +Check-Service "backend" 8000 +Check-Service "frontend" 8080 +Check-Service "celery_worker" +Check-Service "celery_beat" +Write-Host "" + +# Port check +Write-Host "Port Check:" + +Write-Host -NoNewline " 8000 (backend): " +if (Test-ServicePort -Port 8000) { + Write-Host "Open" +} else { + Write-Host "Closed" +} + +Write-Host -NoNewline " 8080 (frontend): " +if (Test-ServicePort -Port 8080) { + Write-Host "Open" +} else { + Write-Host "Closed" +} + +Write-Host "" +exit 0 diff --git a/scripts/check-status.sh b/scripts/check-status.sh new file mode 100644 index 0000000..afc91df --- /dev/null +++ b/scripts/check-status.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# ============================================================ +# Check Service Status +# 使用方法: ./check-status.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +check_service() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + local port=$2 + + echo -n " $name: " + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "运行中 (PID: $pid)" + return 0 + fi + fi + + # 检查进程名 + local pids=$(pgrep -f "$name" 2>/dev/null | head -1 || true) + if [ -n "$pids" ]; then + echo "运行中 (PID: $pids)" + return 0 + fi + + # 检查端口 + if [ -n "$port" ]; then + if nc -z localhost "$port" 2>/dev/null; then + echo "运行中 (端口: $port)" + return 0 + fi + fi + + echo "未运行" + return 1 +} + +echo "" +check_service "backend" "8000" +check_service "frontend" "8080" +check_service "celery_worker" "" +check_service "celery_beat" "" +echo "" + +# 端口检查 +echo "端口检查:" +echo -n " 8000 (后端): " +nc -z localhost 8000 2>/dev/null && echo "开放" || echo "未开放" + +echo -n " 8080 (前端): " +nc -z localhost 8080 2>/dev/null && echo "开放" || echo "未开放" + +echo "" diff --git a/scripts/start-backend.ps1 b/scripts/start-backend.ps1 new file mode 100644 index 0000000..f0ff906 --- /dev/null +++ b/scripts/start-backend.ps1 @@ -0,0 +1,78 @@ +# ============================================================ +# Start Backend Service (Windows PowerShell) +# Usage: .\start-backend.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $BackendDir "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$PID_FILE = Join-Path $PID_DIR "backend.pid" +$LOG_FILE = Join-Path $LOG_DIR "backend.log" + +# Check if already running +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + Write-Host "[Backend] Already running (PID: $PID), skipping" + exit 0 + } else { + Write-Host "[Backend] Cleaning stale PID file" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +Set-Location $BackendDir + +# Check dependencies +try { + python -c "import uvicorn" 2>$null + if ($LASTEXITCODE -ne 0) { throw "uvicorn not found" } +} catch { + Write-Host "[Error] uvicorn not installed. Run: cd backend && pip install uvicorn" + exit 1 +} + +# Set environment +$env:DJANGO_SETTINGS_MODULE = "application.settings" + +Write-Host "[Backend] Starting service (ENV=$ENV)..." + +# Select startup method based on environment +if ($ENV -eq "prod") { + Write-Host "[Backend] Using gunicorn (production mode)" + Start-Process -FilePath "python" -ArgumentList "-m gunicorn application.asgi:application -c gunicorn_conf.py --daemon -p $PID_FILE" -WorkingDirectory $BackendDir -RedirectStandardOutput $LOG_FILE -RedirectStandardError $LOG_FILE -WindowStyle Hidden +} else { + Write-Host "[Backend] Using uvicorn (development mode with hot reload)" + $proc = Start-Process -FilePath "python" -ArgumentList "main.py" -WorkingDirectory $BackendDir -PassThru -NoNewWindow + $proc.Id | Set-Content $PID_FILE +} + +# Wait for startup +Start-Sleep -Seconds 3 + +# Check if started successfully +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE + if (Get-Process -Id $PID -ErrorAction SilentlyContinue) { + Write-Host "[Backend] Started successfully (PID: $PID)" + Write-Host "[Backend] Log file: $LOG_FILE" + } else { + Write-Host "[Backend] Failed to start, check log: $LOG_FILE" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} else { + Write-Host "[Backend] Failed to start, check log: $LOG_FILE" +} + +exit 0 diff --git a/scripts/start-backend.sh b/scripts/start-backend.sh new file mode 100644 index 0000000..b8036ee --- /dev/null +++ b/scripts/start-backend.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# ============================================================ +# Start Backend Service +# 使用方法: ./start-backend.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$BACKEND_DIR/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +PID_FILE="$PID_DIR/backend.pid" +LOG_FILE="$LOG_DIR/backend.log" + +# 检查是否已运行 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[后端] 已运行 (PID: $PID),跳过启动" + exit 0 + else + echo "[后端] 清理旧的 PID 文件" + rm -f "$PID_FILE" + fi +fi + +cd "$BACKEND_DIR" + +# 检查依赖 +if ! python -c "import uvicorn" 2>/dev/null; then + echo "[错误] 未安装 uvicorn,请先: cd backend && pip install uvicorn" + exit 1 +fi + +# 设置环境变量 +export DJANGO_SETTINGS_MODULE="application.settings" + +echo "[后端] 启动服务 (ENV=$ENV)..." + +# 根据环境选择启动方式 +if [ "$ENV" = "prod" ]; then + # 生产环境使用 gunicorn + echo "[后端] 使用 gunicorn 启动 (生产模式)" + nohup python -m gunicorn application.asgi:application \ + -c gunicorn_conf.py \ + --daemon \ + -p "$PID_FILE" \ + >> "$LOG_FILE" 2>&1 +else + # 开发环境使用 uvicorn (带 reload) + echo "[后端] 使用 uvicorn 启动 (开发模式,带热重载)" + nohup python main.py \ + >> "$LOG_FILE" 2>&1 & + + echo $! > "$PID_FILE" +fi + +# 等待启动 +sleep 3 + +# 检查是否成功启动 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[后端] 启动成功 (PID: $PID)" + echo "[后端] 日志文件: $LOG_FILE" + else + echo "[后端] 启动失败,请检查日志: $LOG_FILE" + rm -f "$PID_FILE" + fi +else + # uvicorn 模式 + sleep 2 + if pgrep -f "uvicorn.*application.asgi" > "$PID_FILE" 2>/dev/null; then + echo "[后端] 启动成功 (PID: $(cat $PID_FILE))" + echo "[后端] 日志文件: $LOG_FILE" + else + echo "[后端] 启动失败,请检查日志: $LOG_FILE" + fi +fi diff --git a/scripts/start-celery.ps1 b/scripts/start-celery.ps1 new file mode 100644 index 0000000..c403e20 --- /dev/null +++ b/scripts/start-celery.ps1 @@ -0,0 +1,92 @@ +# ============================================================ +# Start Celery Services (Windows PowerShell) +# Usage: .\start-celery.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $BackendDir "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$WORKER_PID_FILE = Join-Path $PID_DIR "celery_worker.pid" +$BEAT_PID_FILE = Join-Path $PID_DIR "celery_beat.pid" +$WORKER_LOG = Join-Path $LOG_DIR "celery_worker.log" +$BEAT_LOG = Join-Path $LOG_DIR "celery_beat.log" + +Set-Location $BackendDir + +# Set Django environment +$env:DJANGO_SETTINGS_MODULE = "application.settings" + +# Check if Worker is already running +$workerRunning = $false +if (Test-Path $WORKER_PID_FILE) { + $PID = Get-Content $WORKER_PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + $workerRunning = $true + Write-Host "[Celery] Worker already running (PID: $PID), skipping" + } +} + +if (-not $workerRunning) { + Write-Host "[Celery] Starting Worker..." + + # Check if celery is installed + try { + python -c "import celery" 2>$null + if ($LASTEXITCODE -ne 0) { throw "celery not found" } + } catch { + Write-Host "[Error] Celery not installed" + exit 1 + } + + # Start Celery Worker + $proc = Start-Process -FilePath "celery" -ArgumentList "-A application worker -l info --logfile=$WORKER_LOG --pidfile=$WORKER_PID_FILE" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden + $proc.Id | Set-Content $WORKER_PID_FILE + Write-Host "[Celery] Worker started (PID: $($proc.Id))" +} + +# Check Beat +$beatRunning = $false +if (Test-Path $BEAT_PID_FILE) { + $PID = Get-Content $BEAT_PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + $beatRunning = $true + Write-Host "[Celery Beat] Already running (PID: $PID), skipping" + } +} + +if (-not $beatRunning) { + Write-Host "[Celery Beat] Starting scheduler..." + + # Start Celery Beat + $proc = Start-Process -FilePath "celery" -ArgumentList "-A application beat -l info --logfile=$BEAT_LOG --pidfile=$BEAT_PID_FILE --scheduler django_celery_beat.schedulers:DatabaseScheduler" -WorkingDirectory $BackendDir -PassThru -NoNewWindow -WindowStyle Hidden + $proc.Id | Set-Content $BEAT_PID_FILE + Write-Host "[Celery Beat] Started (PID: $($proc.Id))" +} + +# Wait for startup +Start-Sleep -Seconds 3 + +# Show status +if (Test-Path $WORKER_PID_FILE) { + Write-Host "[Celery] Worker PID: $(Get-Content $WORKER_PID_FILE)" + Write-Host "[Celery] Worker log: $WORKER_LOG" +} + +if (Test-Path $BEAT_PID_FILE) { + Write-Host "[Celery Beat] PID: $(Get-Content $BEAT_PID_FILE)" + Write-Host "[Celery Beat] log: $BEAT_LOG" +} + +Write-Host "[Celery] Startup complete" +exit 0 diff --git a/scripts/start-celery.sh b/scripts/start-celery.sh new file mode 100644 index 0000000..3e42ae6 --- /dev/null +++ b/scripts/start-celery.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# ============================================================ +# Start Celery Services +# 使用方法: ./start-celery.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$BACKEND_DIR/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +WORKER_PID_FILE="$PID_DIR/celery_worker.pid" +BEAT_PID_FILE="$PID_DIR/celery_beat.pid" +WORKER_LOG="$LOG_DIR/celery_worker.log" +BEAT_LOG="$LOG_DIR/celery_beat.log" + +cd "$BACKEND_DIR" + +# 设置 Django 环境 +export DJANGO_SETTINGS_MODULE="application.settings" + +# 检查是否已运行 +check_celery() { + if [ -f "$WORKER_PID_FILE" ]; then + PID=$(cat "$WORKER_PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + return 0 + fi + fi + return 1 +} + +if check_celery; then + echo "[Celery] Worker 已运行,跳过启动" +else + echo "[Celery] 启动 Worker..." + + # 检查 celery 是否安装 + if ! python -c "import celery" 2>/dev/null; then + echo "[错误] Celery 未安装" + exit 1 + fi + + # 启动 Celery Worker + nohup celery -A application worker \ + -l info \ + --logfile="$WORKER_LOG" \ + --pidfile="$WORKER_PID_FILE" \ + >> "$WORKER_LOG" 2>&1 & + + echo "[Celery] Worker 启动命令已执行" +fi + +# 检查 Beat +if [ -f "$BEAT_PID_FILE" ]; then + PID=$(cat "$BEAT_PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[Celery Beat] 已运行 (PID: $PID),跳过启动" + else + rm -f "$BEAT_PID_FILE" + fi +else + echo "[Celery Beat] 启动调度器..." + + # 启动 Celery Beat + nohup celery -A application beat \ + -l info \ + --logfile="$BEAT_LOG" \ + --pidfile="$BEAT_PID_FILE" \ + --scheduler django_celery_beat.schedulers:DatabaseScheduler \ + >> "$BEAT_LOG" 2>&1 & + + echo "[Celery Beat] 启动命令已执行" +fi + +# 等待检查 +sleep 3 + +# 显示状态 +if [ -f "$WORKER_PID_FILE" ]; then + echo "[Celery] Worker PID: $(cat $WORKER_PID_FILE)" + echo "[Celery] Worker 日志: $WORKER_LOG" +fi + +if [ -f "$BEAT_PID_FILE" ]; then + echo "[Celery Beat] PID: $(cat $BEAT_PID_FILE)" + echo "[Celery Beat] 日志: $BEAT_LOG" +fi + +echo "[Celery] 启动完成" diff --git a/scripts/start-frontend.ps1 b/scripts/start-frontend.ps1 new file mode 100644 index 0000000..1dadf82 --- /dev/null +++ b/scripts/start-frontend.ps1 @@ -0,0 +1,98 @@ +# ============================================================ +# Start Frontend Service (Windows PowerShell) +# Usage: .\start-frontend.ps1 [dev|prod] +# ============================================================ + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$FrontendDir = Join-Path $ProjectRoot "web" +$PID_DIR = Join-Path $ProjectRoot ".pids" +$LOG_DIR = Join-Path $ProjectRoot "backend" +$LOG_DIR = Join-Path $LOG_DIR "logs" + +$ENV = if ($args[0]) { $args[0] } else { "dev" } + +# Create directories +if (!(Test-Path $PID_DIR)) { New-Item -ItemType Directory -Path $PID_DIR -Force | Out-Null } +if (!(Test-Path $LOG_DIR)) { New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null } + +$PID_FILE = Join-Path $PID_DIR "frontend.pid" +$LOG_FILE = Join-Path $LOG_DIR "frontend.log" + +# Check if already running +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE -ErrorAction SilentlyContinue + if ($PID -and (Get-Process -Id $PID -ErrorAction SilentlyContinue)) { + Write-Host "[Frontend] Already running (PID: $PID), skipping" + exit 0 + } else { + Write-Host "[Frontend] Cleaning stale PID file" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +Set-Location $FrontendDir + +# Check if yarn is available +$yarnCmd = Get-Command yarn -ErrorAction SilentlyContinue +if (!$yarnCmd) { + Write-Host "[Frontend] yarn not found, trying npm..." + $npmCmd = Get-Command npm -ErrorAction SilentlyContinue + if (!$npmCmd) { + Write-Host "[Error] npm not installed" + exit 1 + } + $useNpm = $true +} else { + $useNpm = $false +} + +# Check dependencies +if (!(Test-Path "node_modules")) { + Write-Host "[Frontend] Dependencies not installed, installing..." + if ($useNpm) { + npm install + } else { + yarn install + } +} + +Write-Host "[Frontend] Starting service (ENV=$ENV)..." + +# Select startup method based on environment +if ($ENV -eq "prod") { + Write-Host "[Frontend] Building production version..." + if ($useNpm) { + npm run build + } else { + yarn build + } + Write-Host "[Frontend] Production build complete, use static server" +} else { + if ($useNpm) { + $proc = Start-Process -FilePath "npm" -ArgumentList "run dev" -WorkingDirectory $FrontendDir -PassThru -NoNewWindow + } else { + $proc = Start-Process -FilePath "yarn" -ArgumentList "dev" -WorkingDirectory $FrontendDir -PassThru -NoNewWindow + } + $proc.Id | Set-Content $PID_FILE +} + +# Wait for startup +Start-Sleep -Seconds 5 + +# Check if started successfully +if (Test-Path $PID_FILE) { + $PID = Get-Content $PID_FILE + if (Get-Process -Id $PID -ErrorAction SilentlyContinue) { + Write-Host "[Frontend] Started successfully (PID: $PID)" + Write-Host "[Frontend] Access: http://localhost:8080" + Write-Host "[Frontend] Log file: $LOG_FILE" + } else { + Write-Host "[Frontend] Failed to start, check log: $LOG_FILE" + Remove-Item $PID_FILE -Force -ErrorAction SilentlyContinue + } +} + +exit 0 diff --git a/scripts/start-frontend.sh b/scripts/start-frontend.sh new file mode 100644 index 0000000..fec6ccb --- /dev/null +++ b/scripts/start-frontend.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# ============================================================ +# Start Frontend Service +# 使用方法: ./start-frontend.sh [dev|prod] +# ============================================================ + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +FRONTEND_DIR="$PROJECT_ROOT/web" +PID_DIR="$PROJECT_ROOT/.pids" +LOG_DIR="$PROJECT_ROOT/backend/logs" + +ENV=${1:-dev} + +# 创建必要的目录 +mkdir -p "$PID_DIR" +mkdir -p "$LOG_DIR" + +PID_FILE="$PID_DIR/frontend.pid" +LOG_FILE="$LOG_DIR/frontend.log" + +# 检查是否已运行 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[前端] 已运行 (PID: $PID),跳过启动" + exit 0 + else + echo "[前端] 清理旧的 PID 文件" + rm -f "$PID_FILE" + fi +fi + +cd "$FRONTEND_DIR" + +# 检查依赖 +if [ ! -d "node_modules" ]; then + echo "[前端] 依赖未安装,正在安装..." + yarn install +fi + +echo "[前端] 启动服务 (ENV=$ENV)..." + +# 根据环境选择启动方式 +if [ "$ENV" = "prod" ]; then + echo "[前端] 构建生产版本..." + yarn build + # 生产环境可以用 nginx 或静态服务器 served + echo "[前端] 生产构建完成,请使用静态服务器 serving" +else + # 开发环境 + nohup yarn dev >> "$LOG_FILE" 2>&1 & + echo $! > "$PID_FILE" +fi + +# 等待启动 +sleep 5 + +# 检查是否成功启动 +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo "[前端] 启动成功 (PID: $PID)" + echo "[前端] 访问地址: http://localhost:8080" + echo "[前端] 日志文件: $LOG_FILE" + else + echo "[前端] 启动失败,请检查日志: $LOG_FILE" + rm -f "$PID_FILE" + fi +fi diff --git a/scripts/stop-all.ps1 b/scripts/stop-all.ps1 new file mode 100644 index 0000000..8505327 --- /dev/null +++ b/scripts/stop-all.ps1 @@ -0,0 +1,76 @@ +# ============================================================ +# Stop All Services (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Stop-ServiceByName { + param([string]$Name, [string]$Pattern) + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "[$Name] Stopping service (PID: $pid)..." + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + } + Write-Host "[$Name] Stopped" + } + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } + + # Try to find by process name + $wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%$Pattern%' OR CommandLine LIKE '%$Pattern%'" + $procs = Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue + if ($procs) { + foreach ($p in $procs) { + Write-Host "[$Name] Found by pattern PID: $($p.ProcessId), stopping..." + Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue + } + } +} + +# Stop each service +Stop-ServiceByName "backend" "uvicorn" +Stop-ServiceByName "frontend" "node" +Stop-ServiceByName "celery_worker" "celery" +Stop-ServiceByName "celery_beat" "celery" + +# Additional cleanup +Write-Host "[Cleanup] Checking for residual processes..." + +# Cleanup uvicorn +$wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%uvicorn%'" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping uvicorn (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +# Cleanup gunicorn +$wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%gunicorn%'" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping gunicorn (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +# Cleanup node/vite +$wmiQuery = "SELECT ProcessId, Name, CommandLine FROM Win32_Process WHERE Name LIKE '%node%' AND (CommandLine LIKE '%vite%' OR CommandLine LIKE '%dev%')" +Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping vite (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue +} + +Write-Host "[Done] All services stopped" +exit 0 diff --git a/scripts/stop-all.sh b/scripts/stop-all.sh new file mode 100644 index 0000000..7636c6a --- /dev/null +++ b/scripts/stop-all.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# ============================================================ +# Stop All Services +# 使用方法: ./stop-all.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +stop_service() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "[$name] 停止服务 (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + # 强制停止 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + echo "[$name] 已停止" + else + echo "[$name] 未运行" + fi + rm -f "$pid_file" + else + # 尝试通过进程名查找 + local pids=$(pgrep -f "$name" 2>/dev/null || true) + if [ -n "$pids" ]; then + echo "[$name] 通过进程名找到 PID: $pids,停止..." + echo "$pids" | xargs kill 2>/dev/null || true + fi + fi +} + +# 停止各个服务 +stop_service "backend" +stop_service "frontend" +stop_service "celery_worker" +stop_service "celery_beat" + +# 额外清理残留进程 +echo "[清理] 检查残留进程..." + +# 清理 uvicorn +pkill -f "uvicorn.*application.asgi" 2>/dev/null || true +pkill -f "gunicorn.*application.asgi" 2>/dev/null || true + +# 清理 celery +pkill -f "celery.*worker" 2>/dev/null || true +pkill -f "celery.*beat" 2>/dev/null || true + +# 清理 vite +pkill -f "vite" 2>/dev/null || true + +echo "[完成] 所有服务已停止" diff --git a/scripts/stop-celery.ps1 b/scripts/stop-celery.ps1 new file mode 100644 index 0000000..944226b --- /dev/null +++ b/scripts/stop-celery.ps1 @@ -0,0 +1,45 @@ +# ============================================================ +# Stop Celery Services (Windows PowerShell) +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$PID_DIR = Join-Path $ProjectRoot ".pids" + +function Stop-CeleryService { + param([string]$Name) + + $pidFile = Join-Path $PID_DIR "$Name.pid" + + if (Test-Path $pidFile) { + $pid = Get-Content $pidFile -ErrorAction SilentlyContinue + if ($pid) { + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Write-Host "[$Name] Stopping (PID: $pid)..." + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 + $proc = Get-Process -Id $pid -ErrorAction SilentlyContinue + if ($proc) { + Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue + } + } + } + Remove-Item $pidFile -Force -ErrorAction SilentlyContinue + } + + # Cleanup residual + $wmiQuery = "SELECT ProcessId, Name FROM Win32_Process WHERE Name LIKE '%celery%'" + Get-WmiObject -Query $wmiQuery -ErrorAction SilentlyContinue | ForEach-Object { + Write-Host "[Cleanup] Stopping celery (PID: $($_.ProcessId))" + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue + } +} + +Stop-CeleryService "celery_worker" +Stop-CeleryService "celery_beat" + +Write-Host "[Done] Celery stopped" +exit 0 diff --git a/scripts/stop-celery.sh b/scripts/stop-celery.sh new file mode 100644 index 0000000..207ab21 --- /dev/null +++ b/scripts/stop-celery.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# ============================================================ +# Stop Celery Services +# 使用方法: ./stop-celery.sh +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PID_DIR="$PROJECT_ROOT/.pids" + +stop_celery() { + local name=$1 + local pid_file="$PID_DIR/$name.pid" + local pattern="$2" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if kill -0 "$pid" 2>/dev/null; then + echo "[$name] 停止 (PID: $pid)..." + kill "$pid" 2>/dev/null || true + sleep 1 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + fi + rm -f "$pid_file" + fi + + # 清理残留 + pkill -f "celery.*$pattern" 2>/dev/null || true +} + +stop_celery "celery_worker" "worker" +stop_celery "celery_beat" "beat" + +echo "[完成] Celery 已停止" diff --git a/scripts/view-logs.ps1 b/scripts/view-logs.ps1 new file mode 100644 index 0000000..3542290 --- /dev/null +++ b/scripts/view-logs.ps1 @@ -0,0 +1,85 @@ +# ============================================================ +# View Logs (Windows PowerShell) +# Usage: .\view-logs.ps1 [backend|frontend|celery|all] +# ============================================================ + +$ErrorActionPreference = "Continue" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$ProjectRoot = Split-Path -Parent $ScriptDir +$BackendDir = Join-Path $ProjectRoot "backend" +$LOG_DIR = Join-Path $BackendDir "logs" + +$TARGET = if ($args[0]) { $args[0] } else { "all" } + +# Ensure log directory exists +if (!(Test-Path $LOG_DIR)) { + New-Item -ItemType Directory -Path $LOG_DIR -Force | Out-Null +} + +# Create empty log files if they don't exist +$logFiles = @( + (Join-Path $LOG_DIR "backend.log"), + (Join-Path $LOG_DIR "frontend.log"), + (Join-Path $LOG_DIR "celery_worker.log"), + (Join-Path $LOG_DIR "celery_beat.log") +) +foreach ($log in $logFiles) { + if (!(Test-Path $log)) { New-Item -ItemType File -Path $log -Force | Out-Null } +} + +switch ($TARGET) { + "backend" { + Write-Host "Viewing backend logs: $LOG_DIR\backend.log" + Get-Content (Join-Path $LOG_DIR "backend.log") -Wait -Tail 20 + } + "frontend" { + Write-Host "Viewing frontend logs: $LOG_DIR\frontend.log" + Get-Content (Join-Path $LOG_DIR "frontend.log") -Wait -Tail 20 + } + "celery" { + Write-Host "Viewing Celery logs..." + Write-Host "=== Worker ===" + Get-Content (Join-Path $LOG_DIR "celery_worker.log") -Wait -Tail 10 + } + "all" { + Write-Host "Viewing all logs (Ctrl+C to exit)..." + Write-Host "" + Write-Host "=== Backend Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "backend.log")) -and (Get-Content (Join-Path $LOG_DIR "backend.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "backend.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Frontend Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "frontend.log")) -and (Get-Content (Join-Path $LOG_DIR "frontend.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "frontend.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Celery Worker Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "celery_worker.log")) -and (Get-Content (Join-Path $LOG_DIR "celery_worker.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "celery_worker.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "=== Celery Beat Logs ===" + if ((Test-Path (Join-Path $LOG_DIR "celery_beat.log")) -and (Get-Content (Join-Path $LOG_DIR "celery_beat.log") -ErrorAction SilentlyContinue)) { + Get-Content (Join-Path $LOG_DIR "celery_beat.log") -Tail 20 + } else { + Write-Host "(empty)" + } + Write-Host "" + Write-Host "--- Live monitoring all logs (Ctrl+C to exit) ---" + Get-Content $logFiles -Wait + } + default { + Write-Host "Usage: $PSCommandPath [backend|frontend|celery|all]" + exit 1 + } +} + +exit 0 diff --git a/scripts/view-logs.sh b/scripts/view-logs.sh new file mode 100644 index 0000000..86ec940 --- /dev/null +++ b/scripts/view-logs.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# ============================================================ +# View Logs +# 使用方法: ./view-logs.sh [backend|frontend|celery|all] +# ============================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +BACKEND_DIR="$PROJECT_ROOT/backend" +LOG_DIR="$BACKEND_DIR/logs" + +TARGET=${1:-all} + +# 确保日志目录存在 +mkdir -p "$LOG_DIR" + +# 创建空的日志文件(如果不存在) +touch "$LOG_DIR/backend.log" 2>/dev/null || true +touch "$LOG_DIR/frontend.log" 2>/dev/null || true +touch "$LOG_DIR/celery_worker.log" 2>/dev/null || true +touch "$LOG_DIR/celery_beat.log" 2>/dev/null || true + +case "$TARGET" in + backend) + echo "查看后端日志: $LOG_DIR/backend.log" + tail -f "$LOG_DIR/backend.log" + ;; + frontend) + echo "查看前端日志: $LOG_DIR/frontend.log" + tail -f "$LOG_DIR/frontend.log" + ;; + celery) + echo "查看 Celery 日志..." + echo "=== Worker ===" + tail -f "$LOG_DIR/celery_worker.log" & + echo "=== Beat ===" + tail -f "$LOG_DIR/celery_beat.log" + ;; + all) + echo "查看所有日志 (Ctrl+C 退出)..." + echo "" + echo "=== 后端日志 ===" + tail -n 20 "$LOG_DIR/backend.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== 前端日志 ===" + tail -n 20 "$LOG_DIR/frontend.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== Celery Worker 日志 ===" + tail -n 20 "$LOG_DIR/celery_worker.log" 2>/dev/null || echo "(空)" + echo "" + echo "=== Celery Beat 日志 ===" + tail -n 20 "$LOG_DIR/celery_beat.log" 2>/dev/null || echo "(空)" + echo "" + echo "--- 实时监控所有日志 (Ctrl+C 退出) ---" + tail -f "$LOG_DIR/backend.log" "$LOG_DIR/frontend.log" "$LOG_DIR/celery_worker.log" "$LOG_DIR/celery_beat.log" + ;; + *) + echo "用法: $0 [backend|frontend|celery|all]" + exit 1 + ;; +esac