Add fallback support

This commit is contained in:
DHR60 2025-09-11 15:24:12 +08:00
parent 17a3a516c7
commit 8bb20c0ab8
21 changed files with 130 additions and 8 deletions

View file

@ -3,6 +3,7 @@ namespace ServiceLib.Enums;
public enum EMultipleLoad public enum EMultipleLoad
{ {
LeastPing, LeastPing,
Fallback,
Random, Random,
RoundRobin, RoundRobin,
LeastLoad LeastLoad

View file

@ -1199,16 +1199,22 @@ public static class ConfigHandler
{ {
remark = multipleLoad switch remark = multipleLoad switch
{ {
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerXrayFallback,
EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom, EMultipleLoad.Random => ResUI.menuGenGroupMultipleServerXrayRandom,
EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin, EMultipleLoad.RoundRobin => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerXrayLeastPing,
EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad, EMultipleLoad.LeastLoad => ResUI.menuGenGroupMultipleServerXrayLeastLoad,
_ => ResUI.menuGenGroupMultipleServerXrayRoundRobin, _ => ResUI.menuGenGroupMultipleServerXrayRoundRobin,
}; };
} }
else if (coreType == ECoreType.sing_box) else if (coreType == ECoreType.sing_box)
{ {
remark = ResUI.menuGenGroupMultipleServerSingBoxLeastPing; remark = multipleLoad switch
{
EMultipleLoad.LeastPing => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
EMultipleLoad.Fallback => ResUI.menuGenGroupMultipleServerSingBoxFallback,
_ => ResUI.menuGenGroupMultipleServerSingBoxLeastPing,
};
} }
var profile = new ProfileItem var profile = new ProfileItem
{ {

View file

@ -145,6 +145,7 @@ public class Outbound4Sbox : BaseServer4Sbox
public string? plugin_opts { get; set; } public string? plugin_opts { get; set; }
public List<string>? outbounds { get; set; } public List<string>? outbounds { get; set; }
public bool? interrupt_exist_connections { get; set; } public bool? interrupt_exist_connections { get; set; }
public int? tolerance { get; set; }
} }
public class Endpoints4Sbox : BaseServer4Sbox public class Endpoints4Sbox : BaseServer4Sbox

View file

@ -987,6 +987,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Multi-Configuration Fallback by sing-box 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerSingBoxFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerSingBoxFallback", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。 /// 查找类似 Multi-Configuration LeastPing by sing-box 的本地化字符串。
/// </summary> /// </summary>
@ -996,6 +1005,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Multi-Configuration Fallback by Xray 的本地化字符串。
/// </summary>
public static string menuGenGroupMultipleServerXrayFallback {
get {
return ResourceManager.GetString("menuGenGroupMultipleServerXrayFallback", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。 /// 查找类似 Multi-Configuration LeastLoad by Xray 的本地化字符串。
/// </summary> /// </summary>
@ -2598,6 +2616,15 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Fallback 的本地化字符串。
/// </summary>
public static string TbFallback {
get {
return ResourceManager.GetString("TbFallback", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Fingerprint 的本地化字符串。 /// 查找类似 Fingerprint 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1554,4 +1554,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Server List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
</root> </root>

View file

@ -1554,4 +1554,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Server List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
</root> </root>

View file

@ -1554,4 +1554,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Server List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
</root> </root>

View file

@ -1554,4 +1554,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Server List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
</root> </root>

View file

@ -1551,4 +1551,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>服务器列表</value> <value>服务器列表</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>故障转移</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>多配置文件故障转移 sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>多配置文件故障转移 Xray</value>
</data>
</root> </root>

View file

@ -1551,4 +1551,13 @@
<data name="menuServerList" xml:space="preserve"> <data name="menuServerList" xml:space="preserve">
<value>Server List</value> <value>Server List</value>
</data> </data>
<data name="TbFallback" xml:space="preserve">
<value>Fallback</value>
</data>
<data name="menuGenGroupMultipleServerSingBoxFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by sing-box</value>
</data>
<data name="menuGenGroupMultipleServerXrayFallback" xml:space="preserve">
<value>Multi-Configuration Fallback by Xray</value>
</data>
</root> </root>

View file

@ -36,7 +36,7 @@ public partial class CoreConfigSingboxService(Config config)
switch (node.ConfigType) switch (node.ConfigType)
{ {
case EConfigType.PolicyGroup: case EConfigType.PolicyGroup:
return await GenerateClientMultipleLoadConfig(childProfiles); return await GenerateClientMultipleLoadConfig(childProfiles, profileGroupItem.MultipleLoad);
case EConfigType.ProxyChain: case EConfigType.ProxyChain:
return await GenerateClientChainConfig(childProfiles); return await GenerateClientChainConfig(childProfiles);
} }
@ -371,7 +371,7 @@ public partial class CoreConfigSingboxService(Config config)
} }
} }
public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds) public async Task<RetResult> GenerateClientMultipleLoadConfig(List<ProfileItem> selecteds, EMultipleLoad multipleLoad)
{ {
var ret = new RetResult(); var ret = new RetResult();
try try
@ -446,7 +446,7 @@ public partial class CoreConfigSingboxService(Config config)
ret.Msg = ResUI.FailedGenDefaultConfiguration; ret.Msg = ResUI.FailedGenDefaultConfiguration;
return ret; return ret;
} }
await GenOutboundsList(proxyProfiles, singboxConfig); await GenOutboundsList(proxyProfiles, singboxConfig, multipleLoad);
await GenDns(null, singboxConfig); await GenDns(null, singboxConfig);
await ConvertGeo2Ruleset(singboxConfig); await ConvertGeo2Ruleset(singboxConfig);

View file

@ -410,7 +410,7 @@ public partial class CoreConfigSingboxService
return 0; return 0;
} }
private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig) private async Task<int> GenOutboundsList(List<ProfileItem> nodes, SingboxConfig singboxConfig, EMultipleLoad multipleLoad)
{ {
try try
{ {
@ -513,6 +513,11 @@ public partial class CoreConfigSingboxService
interrupt_exist_connections = false, interrupt_exist_connections = false,
}; };
if (multipleLoad == EMultipleLoad.Fallback)
{
outUrltest.tolerance = 5000;
}
// Add selector outbound (manual selection) // Add selector outbound (manual selection)
var outSelector = new Outbound4Sbox var outSelector = new Outbound4Sbox
{ {

View file

@ -15,7 +15,7 @@ public partial class CoreConfigV2rayService
}; };
v2rayConfig.observatory = observatory; v2rayConfig.observatory = observatory;
} }
else if (multipleLoad == EMultipleLoad.LeastLoad) else if (multipleLoad is EMultipleLoad.LeastLoad or EMultipleLoad.Fallback)
{ {
var burstObservatory = new BurstObservatory4Ray var burstObservatory = new BurstObservatory4Ray
{ {
@ -41,7 +41,14 @@ public partial class CoreConfigV2rayService
var balancer = new BalancersItem4Ray var balancer = new BalancersItem4Ray
{ {
selector = [Global.ProxyTag], selector = [Global.ProxyTag],
strategy = new() { type = strategyType }, strategy = new()
{
type = strategyType,
settings = new()
{
expected = 1,
},
},
tag = $"{Global.ProxyTag}-round", tag = $"{Global.ProxyTag}-round",
}; };
v2rayConfig.routing.balancers = [balancer]; v2rayConfig.routing.balancers = [balancer];

View file

@ -76,6 +76,7 @@ public class AddGroupServerViewModel : MyReactiveObject
PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch PolicyGroupType = (profileGroup?.MultipleLoad ?? EMultipleLoad.LeastPing) switch
{ {
EMultipleLoad.LeastPing => ResUI.TbLeastPing, EMultipleLoad.LeastPing => ResUI.TbLeastPing,
EMultipleLoad.Fallback => ResUI.TbFallback,
EMultipleLoad.Random => ResUI.TbRandom, EMultipleLoad.Random => ResUI.TbRandom,
EMultipleLoad.RoundRobin => ResUI.TbRoundRobin, EMultipleLoad.RoundRobin => ResUI.TbRoundRobin,
EMultipleLoad.LeastLoad => ResUI.TbLeastLoad, EMultipleLoad.LeastLoad => ResUI.TbLeastLoad,
@ -206,6 +207,7 @@ public class AddGroupServerViewModel : MyReactiveObject
profileGroup.MultipleLoad = PolicyGroupType switch profileGroup.MultipleLoad = PolicyGroupType switch
{ {
var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing, var s when s == ResUI.TbLeastPing => EMultipleLoad.LeastPing,
var s when s == ResUI.TbFallback => EMultipleLoad.Fallback,
var s when s == ResUI.TbRandom => EMultipleLoad.Random, var s when s == ResUI.TbRandom => EMultipleLoad.Random,
var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin, var s when s == ResUI.TbRoundRobin => EMultipleLoad.RoundRobin,
var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad, var s when s == ResUI.TbLeastLoad => EMultipleLoad.LeastLoad,

View file

@ -57,7 +57,9 @@ public class ProfilesViewModel : MyReactiveObject
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayRoundRobinCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastPingCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastLoadCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayLeastLoadCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerXrayFallbackCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxLeastPingCmd { get; } public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxLeastPingCmd { get; }
public ReactiveCommand<Unit, Unit> GenGroupMultipleServerSingBoxFallbackCmd { get; }
//servers move //servers move
public ReactiveCommand<Unit, Unit> MoveTopCmd { get; } public ReactiveCommand<Unit, Unit> MoveTopCmd { get; }
@ -155,10 +157,18 @@ public class ProfilesViewModel : MyReactiveObject
{ {
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad); await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.LeastLoad);
}, canEditRemove); }, canEditRemove);
GenGroupMultipleServerXrayFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.Xray, EMultipleLoad.Fallback);
}, canEditRemove);
GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () => GenGroupMultipleServerSingBoxLeastPingCmd = ReactiveCommand.CreateFromTask(async () =>
{ {
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing); await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.LeastPing);
}, canEditRemove); }, canEditRemove);
GenGroupMultipleServerSingBoxFallbackCmd = ReactiveCommand.CreateFromTask(async () =>
{
await GenGroupMultipleServer(ECoreType.sing_box, EMultipleLoad.Fallback);
}, canEditRemove);
//servers move //servers move
MoveTopCmd = ReactiveCommand.CreateFromTask(async () => MoveTopCmd = ReactiveCommand.CreateFromTask(async () =>

View file

@ -29,6 +29,7 @@ public partial class AddGroupServerWindow : WindowBase<AddGroupServerViewModel>
cmbPolicyGroupType.ItemsSource = new List<string> cmbPolicyGroupType.ItemsSource = new List<string>
{ {
ResUI.TbLeastPing, ResUI.TbLeastPing,
ResUI.TbFallback,
ResUI.TbRandom, ResUI.TbRandom,
ResUI.TbRoundRobin, ResUI.TbRoundRobin,
ResUI.TbLeastLoad, ResUI.TbLeastLoad,

View file

@ -106,6 +106,7 @@
<MenuItem x:Name="menuGenGroupMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" /> <MenuItem x:Name="menuGenGroupMultipleServerXrayLeastLoad" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerXrayLeastLoad}" />
<Separator /> <Separator />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" /> <MenuItem x:Name="menuGenGroupMultipleServerSingBoxLeastPing" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem x:Name="menuGenGroupMultipleServerSingBoxFallback" Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem x:Name="menuMixedTestServer" Header="{x:Static resx:ResUI.menuMixedTestServer}" /> <MenuItem x:Name="menuMixedTestServer" Header="{x:Static resx:ResUI.menuMixedTestServer}" />

View file

@ -73,6 +73,7 @@ public partial class ProfilesView : ReactiveUserControl<ProfilesViewModel>
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
//servers move //servers move
//this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); //this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);

View file

@ -24,6 +24,7 @@ public partial class AddGroupServerWindow
cmbPolicyGroupType.ItemsSource = new List<string> cmbPolicyGroupType.ItemsSource = new List<string>
{ {
ResUI.TbLeastPing, ResUI.TbLeastPing,
ResUI.TbFallback,
ResUI.TbRandom, ResUI.TbRandom,
ResUI.TbRoundRobin, ResUI.TbRoundRobin,
ResUI.TbLeastLoad, ResUI.TbLeastLoad,

View file

@ -147,6 +147,10 @@
x:Name="menuGenGroupMultipleServerSingBoxLeastPing" x:Name="menuGenGroupMultipleServerSingBoxLeastPing"
Height="{StaticResource MenuItemHeight}" Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" /> Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxLeastPing}" />
<MenuItem
x:Name="menuGenGroupMultipleServerSingBoxFallback"
Height="{StaticResource MenuItemHeight}"
Header="{x:Static resx:ResUI.menuGenGroupMultipleServerSingBoxFallback}" />
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem <MenuItem

View file

@ -67,6 +67,7 @@ public partial class ProfilesView
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastPingCmd, v => v.menuGenGroupMultipleServerXrayLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerXrayLeastLoadCmd, v => v.menuGenGroupMultipleServerXrayLeastLoad).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables); this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxLeastPingCmd, v => v.menuGenGroupMultipleServerSingBoxLeastPing).DisposeWith(disposables);
this.BindCommand(ViewModel, vm => vm.GenGroupMultipleServerSingBoxFallbackCmd, v => v.menuGenGroupMultipleServerSingBoxFallback).DisposeWith(disposables);
//servers move //servers move
this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables); this.OneWayBind(ViewModel, vm => vm.SubItems, v => v.cmbMoveToGroup.ItemsSource).DisposeWith(disposables);