feat(proxy): 实现代理节点批量升级回滚、隧道重定向跟随及远程配置管理

核心功能:
- 新增代理节点批量升级回滚工作流,支持分批升级、健康探针、跳过/重试/取消等操作
- proxy 隧道流处理器支持 HTTP 重定向跟随(最多 10 跳),区分 307/308 可重播与不可重播请求体
- proxy 协议新增 follow_redirects / http1_only 字段,网关侧同步支持
- 新增代理节点远端配置变更接口(名称、允许端口、调度状态、升级目标等)
- 新增代理节点注册/反注册/心跳的 Admin API,及节点过期清理维护任务
- gateway 隧道 owner-relay 支持流式代理大请求体,新增 5 MiB 默认限制
- 新增 ProxyNodeRegistrationMutation / ProxyNodeRemoteConfigMutation 数据类型
- proxy 配置新增重定向重播预算、心跳间隔等参数,TUI 安装向导同步更新
- 前端 ProxyNodes 页面新增批量升级操作面板及滚动进度展示
This commit is contained in:
fawney19
2026-04-12 16:02:38 +08:00
parent 7c5bb7f383
commit 9703840a36
83 changed files with 11832 additions and 1520 deletions
@@ -69,6 +69,31 @@ WHERE mt.id = $1
LIMIT 1
"#;
const GET_MANAGEMENT_TOKEN_WITH_USER_BY_HASH_SQL: &str = r#"
SELECT
mt.id,
mt.user_id,
mt.name,
mt.description,
mt.token_prefix,
mt.allowed_ips,
EXTRACT(EPOCH FROM mt.expires_at)::bigint AS expires_at_unix_secs,
EXTRACT(EPOCH FROM mt.last_used_at)::bigint AS last_used_at_unix_secs,
mt.last_used_ip,
COALESCE(mt.usage_count, 0) AS usage_count,
mt.is_active,
EXTRACT(EPOCH FROM mt.created_at)::bigint AS created_at_unix_ms,
EXTRACT(EPOCH FROM mt.updated_at)::bigint AS updated_at_unix_secs,
u.id AS user_row_id,
u.email AS user_email,
u.username AS user_username,
u.role::text AS user_role
FROM management_tokens mt
JOIN users u ON u.id = mt.user_id
WHERE mt.token_hash = $1
LIMIT 1
"#;
const DELETE_MANAGEMENT_TOKEN_SQL: &str = r#"
DELETE FROM management_tokens
WHERE id = $1
@@ -196,6 +221,29 @@ RETURNING
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
"#;
const RECORD_MANAGEMENT_TOKEN_USAGE_SQL: &str = r#"
UPDATE management_tokens
SET last_used_at = NOW(),
last_used_ip = $2,
usage_count = COALESCE(usage_count, 0) + 1,
updated_at = NOW()
WHERE id = $1
RETURNING
id,
user_id,
name,
description,
token_prefix,
allowed_ips,
EXTRACT(EPOCH FROM expires_at)::bigint AS expires_at_unix_secs,
EXTRACT(EPOCH FROM last_used_at)::bigint AS last_used_at_unix_secs,
last_used_ip,
COALESCE(usage_count, 0) AS usage_count,
is_active,
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_ms,
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
"#;
#[derive(Debug, Clone)]
pub struct SqlxManagementTokenRepository {
pool: PgPool,
@@ -250,6 +298,18 @@ impl ManagementTokenReadRepository for SqlxManagementTokenRepository {
.map_postgres_err()?;
row.as_ref().map(map_token_with_user_row).transpose()
}
async fn get_management_token_with_user_by_hash(
&self,
token_hash: &str,
) -> Result<Option<StoredManagementTokenWithUser>, DataLayerError> {
let row = sqlx::query(GET_MANAGEMENT_TOKEN_WITH_USER_BY_HASH_SQL)
.bind(token_hash)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
row.as_ref().map(map_token_with_user_row).transpose()
}
}
#[async_trait]
@@ -341,6 +401,20 @@ impl ManagementTokenWriteRepository for SqlxManagementTokenRepository {
.map_postgres_err()?;
row.as_ref().map(map_token_row).transpose()
}
async fn record_management_token_usage(
&self,
token_id: &str,
last_used_ip: Option<&str>,
) -> Result<Option<StoredManagementToken>, DataLayerError> {
let row = sqlx::query(RECORD_MANAGEMENT_TOKEN_USAGE_SQL)
.bind(token_id)
.bind(last_used_ip)
.fetch_optional(&self.pool)
.await
.map_postgres_err()?;
row.as_ref().map(map_token_row).transpose()
}
}
fn optional_unix_secs(value: Option<i64>) -> Option<u64> {