新增run.py统一管理项目启动
This commit is contained in:
parent
fdb14dfa54
commit
191c25696b
@ -34,8 +34,17 @@ class Settings(BaseSettings):
|
|||||||
# 运行模式
|
# 运行模式
|
||||||
run_mode: str = 'api'
|
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:
|
class Config:
|
||||||
env_file = str(Path(__file__).parent.parent / '.env')
|
env_file = str(Path(__file__).resolve().parents[3] / '.env')
|
||||||
env_file_encoding = 'utf-8'
|
env_file_encoding = 'utf-8'
|
||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
|
|
||||||
|
|||||||
121
apps/jingrow/jingrow/run.py
Normal file
121
apps/jingrow/jingrow/run.py
Normal 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
14
dev.sh
@ -125,10 +125,9 @@ start_all() {
|
|||||||
info "启动所有服务..."
|
info "启动所有服务..."
|
||||||
# 使用延迟启动,让后端先启动3秒
|
# 使用延迟启动,让后端先启动3秒
|
||||||
npx concurrently \
|
npx concurrently \
|
||||||
--names "BACKEND,WORKER,FRONTEND" \
|
--names "BACKEND,FRONTEND" \
|
||||||
--prefix-colors "blue,yellow,green" \
|
--prefix-colors "blue,green" \
|
||||||
"cd apps/jingrow && uvicorn jingrow.main:app --host 0.0.0.0 --port 9001 --reload $RELOAD_DIRS" \
|
"cd apps/jingrow && python3 -m jingrow.run" \
|
||||||
"cd apps/jingrow && dramatiq jingrow.services.queue --processes 1 --threads 1" \
|
|
||||||
"sleep 3 && cd apps/jingrow/frontend && npm run dev"
|
"sleep 3 && cd apps/jingrow/frontend && npm run dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,10 +154,9 @@ start_backend() {
|
|||||||
RELOAD_DIRS=$(build_reload_dirs)
|
RELOAD_DIRS=$(build_reload_dirs)
|
||||||
|
|
||||||
npx concurrently \
|
npx concurrently \
|
||||||
--names "BACKEND,WORKER" \
|
--names "BACKEND" \
|
||||||
--prefix-colors "blue,yellow" \
|
--prefix-colors "blue" \
|
||||||
"cd apps/jingrow && uvicorn jingrow.main:app --host 0.0.0.0 --port 9001 --reload $RELOAD_DIRS" \
|
"cd apps/jingrow && python3 -m jingrow.run"
|
||||||
"cd apps/jingrow && dramatiq jingrow.services.queue --processes 1 --threads 1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 停止服务
|
# 停止服务
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user