From 191c25696bff705eb1758a1b6b8ac3ce33994f4d Mon Sep 17 00:00:00 2001 From: jingrow Date: Thu, 30 Oct 2025 19:07:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Erun.py=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B9=E7=9B=AE=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/jingrow/jingrow/config.py | 11 ++- apps/jingrow/jingrow/run.py | 121 +++++++++++++++++++++++++++++++++ dev.sh | 14 ++-- 3 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 apps/jingrow/jingrow/run.py diff --git a/apps/jingrow/jingrow/config.py b/apps/jingrow/jingrow/config.py index af1fd99..83ff44d 100644 --- a/apps/jingrow/jingrow/config.py +++ b/apps/jingrow/jingrow/config.py @@ -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 diff --git a/apps/jingrow/jingrow/run.py b/apps/jingrow/jingrow/run.py new file mode 100644 index 0000000..ff6b129 --- /dev/null +++ b/apps/jingrow/jingrow/run.py @@ -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() \ No newline at end of file diff --git a/dev.sh b/dev.sh index 9362b9c..40add7e 100755 --- a/dev.sh +++ b/dev.sh @@ -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" } # 停止服务