- gateway/proxy/ 十文件:config(ProxyConfig:元->毫元加载期换算 D-P1、差异化售价、 桶/峰谷/限流/semcache 配置)/ errors(8 类 HTTP 语义异常)/ ledger(四表两索引 DDL, WAL,ReviewQueue 连接纪律)/ auth·pricing·normalizer·semcache·upstream(§6 签名占位) / routes(/proxy/v1/models OpenAI 形状)/ __init__(build_proxy_router 组装点) - settings DEFAULTS 增 proxy 段(enabled 默认 False,D-P7) - api.py 首次 include_router(enabled 门控 + 装配失败不拖垮主应用) - serve.py workers>1 拒绝启动(D-P9:WEB_CONCURRENCY/UVICORN_WORKERS 校验) - 测试 +4(门控 404/DDL 幂等/毫元换算/池模型列表),全量 322 passed
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
"""启动路由网关服务(后台、无窗口)。
|
|
|
|
用法:
|
|
python scripts/serve.py [--port 8000] [--stop]
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
PID_FILE = ROOT / "_gateway.pid"
|
|
|
|
|
|
def start(port: int):
|
|
if PID_FILE.exists():
|
|
old = PID_FILE.read_text().strip()
|
|
if old:
|
|
print(f"已有服务运行 (pid={old}),先执行 --stop 再启动。")
|
|
return
|
|
# D-P9:代理层(singleflight/令牌桶/内存 LRU)为进程内状态,多 worker 会静默失效
|
|
for var in ("WEB_CONCURRENCY", "UVICORN_WORKERS"):
|
|
v = (os.environ.get(var) or "").strip()
|
|
if v.isdigit() and int(v) > 1:
|
|
print(f"拒绝启动:{var}={v}。代理层要求单进程(workers=1),"
|
|
"扩容路径见《实施方案_代理层与缓存层.md》D-P9。")
|
|
return
|
|
out = open(ROOT / "_gateway.out.log", "ab", buffering=0)
|
|
err = open(ROOT / "_gateway.err.log", "ab", buffering=0)
|
|
flags = 0x00000008 | 0x08000000 # DETACHED_PROCESS | CREATE_NO_WINDOW
|
|
proc = subprocess.Popen(
|
|
[sys.executable, "-m", "uvicorn", "gateway.api:app",
|
|
"--host", "127.0.0.1", "--port", str(port)],
|
|
cwd=str(ROOT),
|
|
stdout=out,
|
|
stderr=err,
|
|
creationflags=flags,
|
|
close_fds=True,
|
|
)
|
|
PID_FILE.write_text(str(proc.pid))
|
|
print(f"gateway 已启动 pid={proc.pid} port={port},日志: _gateway.out.log")
|
|
|
|
|
|
def stop():
|
|
if not PID_FILE.exists():
|
|
print("没有运行中的服务。")
|
|
return
|
|
pid = int(PID_FILE.read_text().strip())
|
|
try:
|
|
# Windows 下 SIGTERM 对 detached 进程不可靠,改用 taskkill 强制结束进程树
|
|
subprocess.run(
|
|
["taskkill", "/PID", str(pid), "/T", "/F"],
|
|
capture_output=True, text=True, timeout=15,
|
|
)
|
|
print(f"已终止服务 pid={pid}")
|
|
except subprocess.TimeoutExpired:
|
|
print(f"终止超时 pid={pid},请手动结束进程。")
|
|
except Exception as e:
|
|
print(f"终止失败(进程可能不存在): {e}")
|
|
PID_FILE.unlink(missing_ok=True)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--port", type=int, default=8000)
|
|
parser.add_argument("--stop", action="store_true", help="停止服务")
|
|
args = parser.parse_args()
|
|
if args.stop:
|
|
stop()
|
|
else:
|
|
start(args.port)
|