[功能] 添加支持多个上游地址,优化代理路由配置和负载均衡逻辑

This commit is contained in:
ryan
2026-03-18 22:24:05 +08:00
parent c844f4c784
commit 1fbe156a7c
13 changed files with 287 additions and 39 deletions
@@ -36,14 +36,14 @@ const defaultPerformanceFields = {
OpenRestyWorkerRlimitNofile: '65535',
OpenRestyEventsUse: 'epoll',
OpenRestyEventsMultiAcceptEnabled: true,
OpenRestyKeepaliveTimeout: '65',
OpenRestyKeepaliveTimeout: '20',
OpenRestyKeepaliveRequests: '1000',
OpenRestyClientHeaderTimeout: '15',
OpenRestyClientBodyTimeout: '15',
OpenRestyClientMaxBodySize: '64m',
OpenRestyLargeClientHeaderBuffers: '4 16k',
OpenRestySendTimeout: '30',
OpenRestyProxyConnectTimeout: '5',
OpenRestyProxyConnectTimeout: '3',
OpenRestyProxySendTimeout: '60',
OpenRestyProxyReadTimeout: '60',
OpenRestyWebsocketEnabled: true,
@@ -61,7 +61,7 @@ const defaultPerformanceFields = {
OpenRestyCacheLevels: '1:2',
OpenRestyCacheInactive: '30m',
OpenRestyCacheMaxSize: '1g',
OpenRestyCacheKeyTemplate: '$scheme$proxy_host$request_uri',
OpenRestyCacheKeyTemplate: '$scheme$host$request_uri',
OpenRestyCacheLockEnabled: true,
OpenRestyCacheLockTimeout: '5s',
OpenRestyCacheUseStale:
@@ -211,7 +211,7 @@ export function PerformancePage() {
optionMap.OpenRestyEventsMultiAcceptEnabled,
true,
),
OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '65',
OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '20',
OpenRestyKeepaliveRequests:
optionMap.OpenRestyKeepaliveRequests ?? '1000',
OpenRestyClientHeaderTimeout:
@@ -222,7 +222,7 @@ export function PerformancePage() {
optionMap.OpenRestyLargeClientHeaderBuffers ?? '4 16k',
OpenRestySendTimeout: optionMap.OpenRestySendTimeout ?? '30',
OpenRestyProxyConnectTimeout:
optionMap.OpenRestyProxyConnectTimeout ?? '5',
optionMap.OpenRestyProxyConnectTimeout ?? '3',
OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60',
OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60',
OpenRestyWebsocketEnabled: toBoolean(
@@ -251,7 +251,7 @@ export function PerformancePage() {
OpenRestyCacheInactive: optionMap.OpenRestyCacheInactive ?? '30m',
OpenRestyCacheMaxSize: optionMap.OpenRestyCacheMaxSize ?? '1g',
OpenRestyCacheKeyTemplate:
optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$proxy_host$request_uri',
optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$host$request_uri',
OpenRestyCacheLockEnabled: toBoolean(
optionMap.OpenRestyCacheLockEnabled,
true,
@@ -89,6 +89,7 @@ const proxyRouteSchema = z
})()),
'请输入合法的回源主机名',
),
upstreams_text: z.string(),
enabled: z.boolean(),
enable_https: z.boolean(),
cert_id: z.string(),
@@ -108,6 +109,15 @@ const proxyRouteSchema = z
});
}
const upstreams = parseUpstreamsText(value.origin_url, value.upstreams_text);
if (upstreams.length === 0) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ['origin_url'],
message: '至少需要一个上游地址',
});
}
if (value.cache_enabled) {
const cacheRules = parseCacheRulesText(value.cache_rules_text);
if (value.cache_policy !== 'url' && cacheRules.length === 0) {
@@ -164,6 +174,7 @@ const defaultValues: ProxyRouteFormValues = {
domain: '',
origin_url: '',
origin_host: '',
upstreams_text: '',
enabled: true,
enable_https: false,
cert_id: '',
@@ -234,6 +245,25 @@ function parseCacheRulesText(value: string) {
.filter(Boolean);
}
function parseUpstreams(rawValue: string) {
if (!rawValue) {
return [] as string[];
}
try {
const parsed = JSON.parse(rawValue) as string[];
return Array.isArray(parsed) ? parsed.filter(Boolean) : [];
} catch {
return [];
}
}
function parseUpstreamsText(primary: string, value: string) {
return [primary.trim(), ...value.split(/\r?\n/)]
.map((item) => item.trim())
.filter(Boolean);
}
function buildCachePolicyLabel(policy: string) {
switch (policy) {
case 'suffix':
@@ -271,6 +301,7 @@ function toPayload(values: ProxyRouteFormValues): ProxyRouteMutationPayload {
domain: values.domain.trim(),
origin_url: values.origin_url.trim(),
origin_host: values.origin_host.trim(),
upstreams: parseUpstreamsText(values.origin_url, values.upstreams_text).slice(1),
enabled: values.enabled,
enable_https: values.enable_https,
cert_id:
@@ -291,11 +322,13 @@ function toPayload(values: ProxyRouteFormValues): ProxyRouteMutationPayload {
function toFormValues(route: ProxyRouteItem): ProxyRouteFormValues {
const headers = parseCustomHeaders(route.custom_headers);
const cacheRules = parseCacheRules(route.cache_rules);
const upstreams = parseUpstreams(route.upstreams);
return {
domain: route.domain,
origin_url: route.origin_url,
origin_host: route.origin_host || '',
upstreams_text: upstreams.slice(1).join('\n'),
enabled: route.enabled,
enable_https: route.enable_https,
cert_id: route.cert_id ? String(route.cert_id) : '',
@@ -608,6 +641,7 @@ export function ProxyRoutesPage() {
{routes.map((route) => {
const headers = parseCustomHeaders(route.custom_headers);
const cacheRules = parseCacheRules(route.cache_rules);
const upstreams = parseUpstreams(route.upstreams);
return (
<tr key={route.id} className="align-top">
@@ -620,6 +654,9 @@ export function ProxyRoutesPage() {
<p className="text-xs text-[var(--foreground-muted)]">
回源主机名: {route.origin_host || '$host'}
</p>
<p className="text-xs text-[var(--foreground-muted)]">
上游数量: {Math.max(upstreams.length, 1)}
</p>
</div>
</td>
<td className="px-3 py-4">
@@ -770,6 +807,17 @@ export function ProxyRoutesPage() {
/>
</ResourceField>
<ResourceField
label="附加上游"
hint="可选。每行一个上游地址,用于同一规则下的负载均衡;需与主上游保持相同协议,且不能带路径或查询参数。"
error={form.formState.errors.upstreams_text?.message}
>
<ResourceTextarea
placeholder={'https://origin-b.internal\nhttps://origin-c.internal'}
{...form.register('upstreams_text')}
/>
</ResourceField>
<div className="grid gap-4 lg:grid-cols-2">
<ToggleField
label="启用规则"
@@ -8,6 +8,7 @@ export interface ProxyRouteItem {
domain: string;
origin_url: string;
origin_host: string;
upstreams: string;
enabled: boolean;
enable_https: boolean;
cert_id: number | null;
@@ -23,8 +24,9 @@ export interface ProxyRouteItem {
export interface ProxyRouteMutationPayload {
domain: string;
origin_url: string;
origin_host: string;
origin_url: string;
origin_host: string;
upstreams: string[];
enabled: boolean;
enable_https: boolean;
cert_id: number | null;
@@ -80,12 +80,12 @@ const defaultOperationFields = {
OpenRestyWorkerRlimitNofile: '65535',
OpenRestyEventsUse: 'epoll',
OpenRestyEventsMultiAcceptEnabled: true,
OpenRestyKeepaliveTimeout: '65',
OpenRestyKeepaliveTimeout: '20',
OpenRestyKeepaliveRequests: '1000',
OpenRestyClientHeaderTimeout: '15',
OpenRestyClientBodyTimeout: '15',
OpenRestySendTimeout: '30',
OpenRestyProxyConnectTimeout: '5',
OpenRestyProxyConnectTimeout: '3',
OpenRestyProxySendTimeout: '60',
OpenRestyProxyReadTimeout: '60',
OpenRestyProxyBufferingEnabled: true,
@@ -100,7 +100,7 @@ const defaultOperationFields = {
OpenRestyCacheLevels: '1:2',
OpenRestyCacheInactive: '30m',
OpenRestyCacheMaxSize: '1g',
OpenRestyCacheKeyTemplate: '$scheme$proxy_host$request_uri',
OpenRestyCacheKeyTemplate: '$scheme$host$request_uri',
OpenRestyCacheLockEnabled: true,
OpenRestyCacheLockTimeout: '5s',
OpenRestyCacheUseStale:
@@ -353,7 +353,7 @@ export function SettingsPage() {
optionMap.OpenRestyEventsMultiAcceptEnabled,
true,
),
OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '65',
OpenRestyKeepaliveTimeout: optionMap.OpenRestyKeepaliveTimeout ?? '20',
OpenRestyKeepaliveRequests:
optionMap.OpenRestyKeepaliveRequests ?? '1000',
OpenRestyClientHeaderTimeout:
@@ -361,7 +361,7 @@ export function SettingsPage() {
OpenRestyClientBodyTimeout: optionMap.OpenRestyClientBodyTimeout ?? '15',
OpenRestySendTimeout: optionMap.OpenRestySendTimeout ?? '30',
OpenRestyProxyConnectTimeout:
optionMap.OpenRestyProxyConnectTimeout ?? '5',
optionMap.OpenRestyProxyConnectTimeout ?? '3',
OpenRestyProxySendTimeout: optionMap.OpenRestyProxySendTimeout ?? '60',
OpenRestyProxyReadTimeout: optionMap.OpenRestyProxyReadTimeout ?? '60',
OpenRestyProxyBufferingEnabled: toBoolean(
@@ -381,7 +381,7 @@ export function SettingsPage() {
OpenRestyCacheInactive: optionMap.OpenRestyCacheInactive ?? '30m',
OpenRestyCacheMaxSize: optionMap.OpenRestyCacheMaxSize ?? '1g',
OpenRestyCacheKeyTemplate:
optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$proxy_host$request_uri',
optionMap.OpenRestyCacheKeyTemplate ?? '$scheme$host$request_uri',
OpenRestyCacheLockEnabled: toBoolean(
optionMap.OpenRestyCacheLockEnabled,
true,