feat: 扩展 Rust gateway 全功能模块,新增 billing/crypto/wallet crate 及完整数据层

- 新增 aether-billing、aether-crypto、aether-wallet 独立 crate
- aether-data 扩展 repository 层:announcements、auth_modules、billing、
  candidate_selection、gemini_file_mappings、global_models、management_tokens、
  oauth_providers、proxy_nodes、quota、users、wallet 等模块
- aether-gateway 新增 api/auth/billing/control/middleware/scheduler/usage/
  video_tasks/hooks/maintenance/model_fetch/provider_transport 等功能模块
- 重构 executor decision 和 gateway state 为模块目录结构
- 新增 gateway router、frontdoor 路由层及对应测试
- Python 侧 API 路由重构,新增 compat/support 模块
- 前端 Logo 组件更新及 Provider 管理页面调整
This commit is contained in:
fawney19
2026-03-31 19:19:04 +08:00
parent b5a0070023
commit ddf18fed9a
690 changed files with 235085 additions and 16299 deletions
+112 -101
View File
@@ -1,17 +1,32 @@
#!/bin/bash
# 本地开发启动脚本
set -euo pipefail
clear
clear >/dev/null 2>&1 || true
# 加载 .env 文件
set -a
source .env
set +a
dotenv_has_key() {
local key="$1"
rg -q "^[[:space:]]*${key}=" .env
}
# 构建 DATABASE_URL
export DATABASE_URL="postgresql://${DB_USER:-postgres}:${DB_PASSWORD}@${DB_HOST:-localhost}:${DB_PORT:-5432}/${DB_NAME:-aether}"
export REDIS_URL=redis://:${REDIS_PASSWORD}@${REDIS_HOST:-localhost}:${REDIS_PORT:-6379}/0
if ! dotenv_has_key "AETHER_GATEWAY_DATA_POSTGRES_URL"; then
export AETHER_GATEWAY_DATA_POSTGRES_URL="${DATABASE_URL}"
fi
if ! dotenv_has_key "AETHER_GATEWAY_DATA_REDIS_URL"; then
export AETHER_GATEWAY_DATA_REDIS_URL="${REDIS_URL}"
fi
if ! dotenv_has_key "AETHER_GATEWAY_DATA_ENCRYPTION_KEY"; then
export AETHER_GATEWAY_DATA_ENCRYPTION_KEY="${ENCRYPTION_KEY:-}"
fi
# 开发环境连接池低配(节省内存)
export DB_POOL_SIZE=${DB_POOL_SIZE:-5}
export DB_MAX_OVERFLOW=${DB_MAX_OVERFLOW:-5}
@@ -20,6 +35,7 @@ export HTTP_KEEPALIVE_CONNECTIONS=${HTTP_KEEPALIVE_CONNECTIONS:-5}
EXECUTOR_PID=""
GATEWAY_PID=""
STARTUP_WAIT_EARLY_EXIT=false
cleanup() {
if [ -n "${GATEWAY_PID}" ]; then
@@ -36,141 +52,136 @@ cleanup() {
wait "${EXECUTOR_PID}" >/dev/null 2>&1 || true
fi
if [ "${EXECUTOR_TRANSPORT:-}" = "unix_socket" ] && [ -n "${EXECUTOR_SOCKET_PATH:-}" ]; then
rm -f "${EXECUTOR_SOCKET_PATH}" >/dev/null 2>&1 || true
fi
}
trap cleanup EXIT
wait_for_startup() {
local pid="$1"
local timeout_seconds="$2"
local service_name="$3"
shift 3
STARTUP_WAIT_EARLY_EXIT=false
local attempts=$((timeout_seconds * 10))
if [ "${attempts}" -lt 1 ]; then
attempts=1
fi
for ((i = 0; i < attempts; i++)); do
if "$@" >/dev/null 2>&1; then
return 0
fi
if ! kill -0 "${pid}" >/dev/null 2>&1; then
STARTUP_WAIT_EARLY_EXIT=true
echo "=> ${service_name} 启动进程已提前退出,请检查上面的日志。"
return 1
fi
sleep 0.1
done
if "$@" >/dev/null 2>&1; then
return 0
fi
if ! kill -0 "${pid}" >/dev/null 2>&1; then
STARTUP_WAIT_EARLY_EXIT=true
echo "=> ${service_name} 启动进程已提前退出,请检查上面的日志。"
return 1
fi
echo "=> ${service_name} 在 ${timeout_seconds}s 内未通过启动检查。"
echo "=> 如果这是冷编译或存在并发 cargo 构建,可调大 *_STARTUP_TIMEOUT_SECONDS 后重试。"
return 1
}
# 本地开发默认约定:
# - Rust aether-gateway 绑定 APP_PORT,作为唯一公开入口
# - ./dev.sh 不再启动 Python 宿主;本地默认只验证 Rust-owned 路径
APP_PORT=${APP_PORT:-8084}
PYTHON_INTERNAL_HOST=${PYTHON_INTERNAL_HOST:-127.0.0.1}
PYTHON_INTERNAL_PORT=${PYTHON_INTERNAL_PORT:-18084}
RUST_SERVICE_STARTUP_TIMEOUT_SECONDS=${RUST_SERVICE_STARTUP_TIMEOUT_SECONDS:-120}
EXECUTOR_STARTUP_TIMEOUT_SECONDS=${EXECUTOR_STARTUP_TIMEOUT_SECONDS:-${RUST_SERVICE_STARTUP_TIMEOUT_SECONDS}}
GATEWAY_STARTUP_TIMEOUT_SECONDS=${GATEWAY_STARTUP_TIMEOUT_SECONDS:-${RUST_SERVICE_STARTUP_TIMEOUT_SECONDS}}
should_start_executor=false
if [ "${DEV_START_EXECUTOR:-false}" = "true" ] || [ "${EXECUTOR_BACKEND:-rust}" = "rust" ]; then
should_start_executor=true
fi
should_start_gateway=true
if [ "${DEV_START_GATEWAY:-true}" != "true" ]; then
should_start_gateway=false
fi
if [ "${should_start_executor}" = "true" ]; then
if ! command -v cargo >/dev/null 2>&1; then
echo "=> 未找到 cargo,无法启动 aether-executor。请先安装 Rust toolchain。"
exit 1
fi
if [ -z "${EXECUTOR_TRANSPORT:-}" ]; then
if [ "${should_start_gateway}" = "true" ]; then
export EXECUTOR_TRANSPORT=tcp
else
export EXECUTOR_TRANSPORT=unix_socket
fi
if [ -n "${EXECUTOR_TRANSPORT:-}" ] && [ "${EXECUTOR_TRANSPORT}" != "tcp" ]; then
echo "=> 本地 ./dev.sh 只支持 gateway -> executor 的 TCP 模式。"
echo "=> 请移除 EXECUTOR_TRANSPORT=${EXECUTOR_TRANSPORT},或改为 EXECUTOR_TRANSPORT=tcp。"
exit 1
fi
export EXECUTOR_SOCKET_PATH=${EXECUTOR_SOCKET_PATH:-/tmp/aether-executor.sock}
export EXECUTOR_TRANSPORT=tcp
export EXECUTOR_BASE_URL=${EXECUTOR_BASE_URL:-http://127.0.0.1:5219}
export EXECUTOR_BIND=${EXECUTOR_BIND:-127.0.0.1:5219}
if [ "${EXECUTOR_TRANSPORT}" = "unix_socket" ]; then
rm -f "${EXECUTOR_SOCKET_PATH}"
echo "=> 启动 aether-executor (unix socket: ${EXECUTOR_SOCKET_PATH})..."
cargo run -q -p aether-executor -- --transport unix_socket --unix-socket "${EXECUTOR_SOCKET_PATH}" &
EXECUTOR_PID=$!
echo "=> 启动 aether-executor (tcp: ${EXECUTOR_BIND})..."
cargo run -q -p aether-executor -- --transport tcp --bind "${EXECUTOR_BIND}" &
EXECUTOR_PID=$!
for _ in {1..100}; do
if [ -S "${EXECUTOR_SOCKET_PATH}" ]; then
break
fi
sleep 0.1
done
if [ ! -S "${EXECUTOR_SOCKET_PATH}" ]; then
echo "=> aether-executor 未能在预期时间内启动。"
exit 1
if ! wait_for_startup "${EXECUTOR_PID}" "${EXECUTOR_STARTUP_TIMEOUT_SECONDS}" "aether-executor" curl -sf "${EXECUTOR_BASE_URL}/health"; then
if [ "${STARTUP_WAIT_EARLY_EXIT}" = "true" ]; then
EXECUTOR_PID=""
fi
else
echo "=> 启动 aether-executor (tcp: ${EXECUTOR_BIND})..."
cargo run -q -p aether-executor -- --transport tcp --bind "${EXECUTOR_BIND}" &
EXECUTOR_PID=$!
for _ in {1..100}; do
if curl -sf "${EXECUTOR_BASE_URL}/health" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
if ! curl -sf "${EXECUTOR_BASE_URL}/health" >/dev/null 2>&1; then
echo "=> aether-executor 未能在预期时间内启动。"
exit 1
fi
fi
fi
if [ "${should_start_gateway}" = "true" ]; then
if ! command -v cargo >/dev/null 2>&1; then
echo "=> 未找到 cargo,无法启动 aether-gateway。请先安装 Rust toolchain。"
exit 1
fi
export AETHER_GATEWAY_BIND=${AETHER_GATEWAY_BIND:-0.0.0.0:${APP_PORT}}
export AETHER_GATEWAY_UPSTREAM=${AETHER_GATEWAY_UPSTREAM:-http://${PYTHON_INTERNAL_HOST}:${PYTHON_INTERNAL_PORT}}
export AETHER_GATEWAY_CONTROL_URL=${AETHER_GATEWAY_CONTROL_URL:-http://${PYTHON_INTERNAL_HOST}:${PYTHON_INTERNAL_PORT}}
if [ "${should_start_executor}" = "true" ] && [ "${EXECUTOR_TRANSPORT:-}" = "tcp" ]; then
export AETHER_GATEWAY_EXECUTOR_URL=${AETHER_GATEWAY_EXECUTOR_URL:-${EXECUTOR_BASE_URL}}
fi
if [ -n "${AETHER_GATEWAY_EXECUTOR_URL:-}" ]; then
echo "=> 启动 aether-gateway (${AETHER_GATEWAY_BIND} -> ${AETHER_GATEWAY_UPSTREAM}, control=${AETHER_GATEWAY_CONTROL_URL}, executor=${AETHER_GATEWAY_EXECUTOR_URL})..."
cargo run -q -p aether-gateway -- --bind "${AETHER_GATEWAY_BIND}" --upstream "${AETHER_GATEWAY_UPSTREAM}" --control-url "${AETHER_GATEWAY_CONTROL_URL}" --executor-url "${AETHER_GATEWAY_EXECUTOR_URL}" &
else
echo "=> 启动 aether-gateway (${AETHER_GATEWAY_BIND} -> ${AETHER_GATEWAY_UPSTREAM}, control=${AETHER_GATEWAY_CONTROL_URL})..."
cargo run -q -p aether-gateway -- --bind "${AETHER_GATEWAY_BIND}" --upstream "${AETHER_GATEWAY_UPSTREAM}" --control-url "${AETHER_GATEWAY_CONTROL_URL}" &
fi
GATEWAY_PID=$!
for _ in {1..100}; do
if curl -sf "http://127.0.0.1:${APP_PORT}/_gateway/health" >/dev/null 2>&1; then
break
fi
sleep 0.1
done
if ! curl -sf "http://127.0.0.1:${APP_PORT}/_gateway/health" >/dev/null 2>&1; then
echo "=> aether-gateway 未能在预期时间内启动。"
exit 1
fi
fi
PYTHON_BIND_HOST=${HOST:-0.0.0.0}
PYTHON_BIND_PORT=${PORT:-${APP_PORT}}
if [ "${should_start_gateway}" = "true" ]; then
PYTHON_BIND_HOST=${PYTHON_INTERNAL_HOST}
PYTHON_BIND_PORT=${PYTHON_INTERNAL_PORT}
if ! command -v cargo >/dev/null 2>&1; then
echo "=> 未找到 cargo,无法启动 aether-gateway。请先安装 Rust toolchain。"
exit 1
fi
export HOST="${PYTHON_BIND_HOST}"
export PORT="${PYTHON_BIND_PORT}"
export AETHER_GATEWAY_BIND=${AETHER_GATEWAY_BIND:-0.0.0.0:${APP_PORT}}
export AETHER_GATEWAY_UPSTREAM=${AETHER_GATEWAY_UPSTREAM:-http://127.0.0.1:9}
export AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE=${AETHER_GATEWAY_VIDEO_TASK_TRUTH_SOURCE_MODE:-rust-authoritative}
if [ "${should_start_executor}" = "true" ] && [ "${EXECUTOR_TRANSPORT:-}" = "tcp" ]; then
export AETHER_GATEWAY_EXECUTOR_URL=${AETHER_GATEWAY_EXECUTOR_URL:-${EXECUTOR_BASE_URL}}
fi
# 启动 uvicorn(热重载模式,只监视 src 目录)
echo "=> 启动本地开发服务器..."
if [ "${should_start_gateway}" = "true" ]; then
echo "=> 主入口: http://localhost:${APP_PORT} (aether-gateway)"
echo "=> Python内部: http://${PYTHON_BIND_HOST}:${PYTHON_BIND_PORT}"
echo "=> Control API: ${AETHER_GATEWAY_CONTROL_URL}"
if [ -n "${AETHER_GATEWAY_EXECUTOR_URL:-}" ]; then
echo "=> Executor API: ${AETHER_GATEWAY_EXECUTOR_URL}"
fi
GATEWAY_ARGS=(--bind "${AETHER_GATEWAY_BIND}" --upstream "${AETHER_GATEWAY_UPSTREAM}")
if [ -n "${AETHER_GATEWAY_EXECUTOR_URL:-}" ]; then
GATEWAY_ARGS+=(--executor-url "${AETHER_GATEWAY_EXECUTOR_URL}")
fi
if [ -n "${AETHER_GATEWAY_EXECUTOR_URL:-}" ]; then
echo "=> 启动 aether-gateway (Rust frontdoor: ${AETHER_GATEWAY_BIND}, upstream=${AETHER_GATEWAY_UPSTREAM}, executor=${AETHER_GATEWAY_EXECUTOR_URL})..."
else
echo "=> 后端地址: http://localhost:${APP_PORT}"
echo "=> 启动 aether-gateway (Rust frontdoor: ${AETHER_GATEWAY_BIND}, upstream=${AETHER_GATEWAY_UPSTREAM})..."
fi
cargo run -q -p aether-gateway -- "${GATEWAY_ARGS[@]}" &
GATEWAY_PID=$!
if ! wait_for_startup "${GATEWAY_PID}" "${GATEWAY_STARTUP_TIMEOUT_SECONDS}" "aether-gateway" curl -sf "http://127.0.0.1:${APP_PORT}/_gateway/health"; then
if [ "${STARTUP_WAIT_EARLY_EXIT}" = "true" ]; then
GATEWAY_PID=""
fi
exit 1
fi
echo "=> 启动本地开发服务..."
echo "=> Rust公开入口: http://localhost:${APP_PORT}"
echo "=> Frontdoor健康检查: http://localhost:${APP_PORT}/_gateway/health"
echo "=> Legacy upstream: ${AETHER_GATEWAY_UPSTREAM}"
if [ -n "${AETHER_GATEWAY_EXECUTOR_URL:-}" ]; then
echo "=> Executor API: ${AETHER_GATEWAY_EXECUTOR_URL}"
fi
echo "=> 数据库: ${DATABASE_URL}"
if [ "${should_start_executor}" = "true" ]; then
echo "=> Executor 后端: ${EXECUTOR_BACKEND:-rust} (${EXECUTOR_TRANSPORT})"
fi
echo "=> 提示: 未下沉到 Rust 的 legacy 路由会直接失败,除非你手动设置了可用的 upstream/control URL。"
echo ""
uv run uvicorn src.main:app --reload --reload-dir src --host "${PYTHON_BIND_HOST}" --port "${PYTHON_BIND_PORT}"
wait "${GATEWAY_PID}"