新增run.py统一管理项目启动

This commit is contained in:
jingrow 2025-10-30 19:07:02 +08:00
parent fdb14dfa54
commit 191c25696b
3 changed files with 137 additions and 9 deletions

View File

@ -34,8 +34,17 @@ class Settings(BaseSettings):
# 运行模式
run_mode: str = 'api'
# 本地后端主机配置
backend_host: str = '0.0.0.0'
backend_port: int = 9001
backend_reload: bool = True
# 异步任务队列Dramatiq配置
worker_processes: int = 1
worker_threads: int = 1
class Config:
env_file = str(Path(__file__).parent.parent / '.env')
env_file = str(Path(__file__).resolve().parents[3] / '.env')
env_file_encoding = 'utf-8'
case_sensitive = False

121
apps/jingrow/jingrow/run.py Normal file
View File

@ -0,0 +1,121 @@
"""
统一启动入口`python -m jingrow.run [app|dramatiq|all]`
app - 读取 Config 启动 Uvicorn
dramatiq - 读取 Config exec 方式启动 Dramatiq CLI
all - 默认同时以子进程启动 app dramatiq并转发信号
"""
import argparse
import os
import signal
import subprocess
import sys
from pathlib import Path
import uvicorn
from jingrow.config import Config
def run_app() -> None:
host = getattr(Config, "backend_host", "0.0.0.0")
port = int(getattr(Config, "backend_port", 9001))
reload_enabled = bool(getattr(Config, "backend_reload", True))
apps_dir = Path(__file__).resolve().parents[2]
reload_dirs = [str(apps_dir)] if reload_enabled else None
# 使用导入字符串以启用 uvicorn 的 reload 能力并标准化启动行为
uvicorn.run(
"jingrow.main:app",
host=host,
port=port,
reload=reload_enabled,
reload_dirs=reload_dirs,
)
def run_dramatiq() -> None:
processes = int(getattr(Config, "worker_processes", 1))
threads = int(getattr(Config, "worker_threads", 1))
args = [
"dramatiq",
"jingrow.services.queue",
"--processes",
str(processes),
"--threads",
str(threads),
]
os.execvp("dramatiq", args)
def main() -> None:
parser = argparse.ArgumentParser(prog="jingrow.run", description="Jingrow 统一运行入口")
parser.add_argument("role", nargs="?", choices=["app", "dramatiq", "all"], default="all", help="运行角色app/dramatiq/all默认 all")
args = parser.parse_args()
if args.role == "app":
run_app()
return
if args.role == "dramatiq":
run_dramatiq()
return
# all并行启动 app 与 dramatiq作为子进程便于优雅退出
children: list[subprocess.Popen] = []
def spawn(cmd: list[str]) -> subprocess.Popen:
return subprocess.Popen(cmd)
api_cmd = [sys.executable, "-m", "jingrow.run", "app"]
dramatiq_cmd = [sys.executable, "-m", "jingrow.run", "dramatiq"]
children.append(spawn(api_cmd))
children.append(spawn(dramatiq_cmd))
def handle_signal(signum, frame):
for p in children:
if p.poll() is None:
try:
p.terminate()
except Exception:
pass
# 二次强杀
try:
for p in children:
try:
p.wait(timeout=5)
except Exception:
if p.poll() is None:
p.kill()
finally:
# 以第一个子进程退出码作为整体退出码
code = next((p.returncode for p in children if p.returncode is not None), 0)
os._exit(code)
signal.signal(signal.SIGINT, handle_signal)
signal.signal(signal.SIGTERM, handle_signal)
# 等待任一子进程退出,随后清理另一个
exit_code = 0
try:
while True:
any_running = False
for p in children:
rc = p.poll()
if rc is None:
any_running = True
else:
exit_code = rc
raise SystemExit
if not any_running:
break
signal.pause()
except SystemExit:
handle_signal(signal.SIGTERM, None)
finally:
sys.exit(exit_code)
if __name__ == "__main__":
main()

14
dev.sh
View File

@ -125,10 +125,9 @@ start_all() {
info "启动所有服务..."
# 使用延迟启动让后端先启动3秒
npx concurrently \
--names "BACKEND,WORKER,FRONTEND" \
--prefix-colors "blue,yellow,green" \
"cd apps/jingrow && uvicorn jingrow.main:app --host 0.0.0.0 --port 9001 --reload $RELOAD_DIRS" \
"cd apps/jingrow && dramatiq jingrow.services.queue --processes 1 --threads 1" \
--names "BACKEND,FRONTEND" \
--prefix-colors "blue,green" \
"cd apps/jingrow && python3 -m jingrow.run" \
"sleep 3 && cd apps/jingrow/frontend && npm run dev"
}
@ -155,10 +154,9 @@ start_backend() {
RELOAD_DIRS=$(build_reload_dirs)
npx concurrently \
--names "BACKEND,WORKER" \
--prefix-colors "blue,yellow" \
"cd apps/jingrow && uvicorn jingrow.main:app --host 0.0.0.0 --port 9001 --reload $RELOAD_DIRS" \
"cd apps/jingrow && dramatiq jingrow.services.queue --processes 1 --threads 1"
--names "BACKEND" \
--prefix-colors "blue" \
"cd apps/jingrow && python3 -m jingrow.run"
}
# 停止服务