Multi Profile

This commit is contained in:
DHR60 2025-09-06 20:12:04 +08:00
parent a452bbe140
commit 55de37e5f3
10 changed files with 248 additions and 8 deletions

View file

@ -12,5 +12,9 @@ public enum EConfigType
TUIC = 8, TUIC = 8,
WireGuard = 9, WireGuard = 9,
HTTP = 10, HTTP = 10,
Anytls = 11 Anytls = 11,
Group = 1000,
PolicyGroup = 1001,
ProxyChain = 1002,
} }

View file

@ -2,8 +2,8 @@ namespace ServiceLib.Enums;
public enum EMultipleLoad public enum EMultipleLoad
{ {
LeastPing,
Random, Random,
RoundRobin, RoundRobin,
LeastPing,
LeastLoad LeastLoad
} }

View file

@ -357,6 +357,11 @@ public static class ConfigHandler
{ {
} }
} }
else if (profileItem.ConfigType > EConfigType.Group)
{
var profileGroupItem = await AppManager.Instance.GetProfileGroupItem(it.IndexId);
await AddGroupServerCommon(config, profileItem, profileGroupItem, true);
}
else else
{ {
await AddServerCommon(config, profileItem, true); await AddServerCommon(config, profileItem, true);
@ -1074,6 +1079,35 @@ public static class ConfigHandler
return 0; return 0;
} }
public static async Task<int> AddGroupServerCommon(Config config, ProfileItem profileItem, ProfileGroupItem profileGroupItem, bool toFile = true)
{
var maxSort = -1;
if (profileItem.IndexId.IsNullOrEmpty())
{
profileItem.IndexId = Utils.GetGuid(false);
maxSort = ProfileExManager.Instance.GetMaxSort();
}
if (maxSort > 0)
{
ProfileExManager.Instance.SetSort(profileItem.IndexId, maxSort + 1);
}
if (toFile)
{
await SQLiteHelper.Instance.ReplaceAsync(profileItem);
if (profileGroupItem != null)
{
profileGroupItem.ParentIndexId = profileItem.IndexId;
await ProfileGroupItemManager.Instance.SaveItemAsync(profileGroupItem);
}
else
{
ProfileGroupItemManager.Instance.GetOrCreateAndMarkDirty(profileItem.IndexId);
await ProfileGroupItemManager.Instance.SaveTo();
}
}
return 0;
}
/// <summary> /// <summary>
/// Compare two profile items to determine if they represent the same server /// Compare two profile items to determine if they represent the same server
/// Used for deduplication and server matching /// Used for deduplication and server matching
@ -1207,7 +1241,7 @@ public static class ConfigHandler
public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType) public static async Task<ProfileItem?> GetPreSocksItem(Config config, ProfileItem node, ECoreType coreType)
{ {
ProfileItem? itemSocks = null; ProfileItem? itemSocks = null;
if (node.ConfigType != EConfigType.Custom && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun) if (node.ConfigType != EConfigType.Custom && node.ConfigType < EConfigType.Group && coreType != ECoreType.sing_box && config.TunModeItem.EnableTun)
{ {
itemSocks = new ProfileItem() itemSocks = new ProfileItem()
{ {
@ -1218,7 +1252,7 @@ public static class ConfigHandler
Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks) Port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks)
}; };
} }
else if (node.ConfigType == EConfigType.Custom && node.PreSocksPort > 0) else if (node.ConfigType == EConfigType.Custom && node.ConfigType < EConfigType.Group && node.PreSocksPort > 0)
{ {
var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray; var preCoreType = config.RunningCoreType = config.TunModeItem.EnableTun ? ECoreType.sing_box : ECoreType.Xray;
itemSocks = new ProfileItem() itemSocks = new ProfileItem()

View file

@ -64,6 +64,7 @@ public sealed class AppManager
SQLiteHelper.Instance.CreateTable<ProfileExItem>(); SQLiteHelper.Instance.CreateTable<ProfileExItem>();
SQLiteHelper.Instance.CreateTable<DNSItem>(); SQLiteHelper.Instance.CreateTable<DNSItem>();
SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>(); SQLiteHelper.Instance.CreateTable<FullConfigTemplateItem>();
SQLiteHelper.Instance.CreateTable<ProfileGroupItem>();
return true; return true;
} }
@ -98,6 +99,7 @@ public sealed class AppManager
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
await ProfileGroupItemManager.Instance.SaveTo();
await StatisticsManager.Instance.SaveTo(); await StatisticsManager.Instance.SaveTo();
await CoreManager.Instance.CoreStop(); await CoreManager.Instance.CoreStop();
StatisticsManager.Instance.Close(); StatisticsManager.Instance.Close();
@ -207,6 +209,15 @@ public sealed class AppManager
return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks); return await SQLiteHelper.Instance.TableAsync<ProfileItem>().FirstOrDefaultAsync(it => it.Remarks == remarks);
} }
public async Task<ProfileGroupItem?> GetProfileGroupItem(string parentIndexId)
{
if (parentIndexId.IsNullOrEmpty())
{
return null;
}
return await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().FirstOrDefaultAsync(it => it.ParentIndexId == parentIndexId);
}
public async Task<List<RoutingItem>?> RoutingItems() public async Task<List<RoutingItem>?> RoutingItems()
{ {
return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync(); return await SQLiteHelper.Instance.TableAsync<RoutingItem>().OrderBy(t => t.Sort).ToListAsync();

View file

@ -0,0 +1,156 @@
using System.Collections.Concurrent;
namespace ServiceLib.Manager;
public class ProfileGroupItemManager
{
private static readonly Lazy<ProfileGroupItemManager> _instance = new(() => new());
private ConcurrentDictionary<string, ProfileGroupItem> _items = new();
public static ProfileGroupItemManager Instance => _instance.Value;
private static readonly string _tag = "ProfileGroupItemManager";
private ProfileGroupItemManager()
{
}
public async Task Init()
{
await InitData();
}
public Task<ConcurrentBag<ProfileGroupItem>> GetProfileGroupItemList()
{
var bag = new ConcurrentBag<ProfileGroupItem>(_items.Values);
return Task.FromResult(bag);
}
private async Task InitData()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem where parentIndexId not in ( select indexId from ProfileItem )");
var list = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
_items = new ConcurrentDictionary<string, ProfileGroupItem>(list.Where(t => !string.IsNullOrEmpty(t.ParentIndexId)).ToDictionary(t => t.ParentIndexId!));
}
private ProfileGroupItem AddProfileGroupItem(string indexId)
{
var profileGroupItem = new ProfileGroupItem()
{
ParentIndexId = indexId,
ChildItems = string.Empty,
MultipleLoad = EMultipleLoad.LeastPing
};
_items[indexId] = profileGroupItem;
return profileGroupItem;
}
private ProfileGroupItem GetProfileGroupItem(string indexId)
{
if (string.IsNullOrEmpty(indexId))
{
throw new ArgumentNullException(nameof(indexId));
}
return _items.GetOrAdd(indexId, AddProfileGroupItem);
}
public async Task ClearAll()
{
await SQLiteHelper.Instance.ExecuteAsync($"delete from ProfileGroupItem ");
_items.Clear();
}
public async Task SaveTo()
{
try
{
var lstExists = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().ToListAsync();
var existsMap = lstExists.Where(t => !string.IsNullOrEmpty(t.ParentIndexId)).ToDictionary(t => t.ParentIndexId!);
var lstInserts = new List<ProfileGroupItem>();
var lstUpdates = new List<ProfileGroupItem>();
foreach (var item in _items.Values)
{
if (string.IsNullOrEmpty(item.ParentIndexId))
{
continue;
}
if (existsMap.ContainsKey(item.ParentIndexId))
{
lstUpdates.Add(item);
}
else
{
lstInserts.Add(item);
}
}
try
{
if (lstInserts.Count > 0)
{
await SQLiteHelper.Instance.InsertAllAsync(lstInserts);
}
if (lstUpdates.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(lstUpdates);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
public ProfileGroupItem GetOrCreateAndMarkDirty(string indexId)
{
return GetProfileGroupItem(indexId);
}
public async ValueTask DisposeAsync()
{
await SaveTo();
}
public async Task SaveItemAsync(ProfileGroupItem item)
{
if (item is null)
{
throw new ArgumentNullException(nameof(item));
}
if (string.IsNullOrWhiteSpace(item.ParentIndexId))
{
throw new ArgumentException("ParentIndexId required", nameof(item));
}
_items[item.ParentIndexId] = item;
try
{
var lst = await SQLiteHelper.Instance.TableAsync<ProfileGroupItem>().Where(t => t.ParentIndexId == item.ParentIndexId).ToListAsync();
if (lst != null && lst.Count > 0)
{
await SQLiteHelper.Instance.UpdateAllAsync(new List<ProfileGroupItem> { item });
}
else
{
await SQLiteHelper.Instance.InsertAllAsync(new List<ProfileGroupItem> { item });
}
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
}
}
}

View file

@ -35,6 +35,7 @@ public class TaskManager
await ConfigHandler.SaveConfig(_config); await ConfigHandler.SaveConfig(_config);
await ProfileExManager.Instance.SaveTo(); await ProfileExManager.Instance.SaveTo();
await ProfileGroupItemManager.Instance.SaveTo();
} }
//Execute once 1 hour //Execute once 1 hour

View file

@ -0,0 +1,13 @@
using SQLite;
namespace ServiceLib.Models;
[Serializable]
public class ProfileGroupItem
{
[PrimaryKey]
public string ParentIndexId { get; set; }
public string ChildItems { get; set; }
public EMultipleLoad MultipleLoad { get; set; } = EMultipleLoad.LeastPing;
}

View file

@ -39,11 +39,14 @@ public class ProfileItem : ReactiveObject
> 1 => $"***{arrAddr.Last()}", > 1 => $"***{arrAddr.Last()}",
_ => Address _ => Address
}; };
summary += ConfigType switch if (ConfigType is EConfigType.Custom or > EConfigType.Group)
{ {
EConfigType.Custom => $"[{CoreType.ToString()}]{Remarks}", summary += $"[{CoreType.ToString()}]{Remarks}";
_ => $"{Remarks}({addr}:{Port})" }
}; else
{
summary += $"{Remarks}({addr}:{Port})";
}
return summary; return summary;
} }

View file

@ -0,0 +1,17 @@
using System.Reactive;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace ServiceLib.ViewModels;
public class AddGroupServerViewModel : MyReactiveObject
{
[Reactive]
public ProfileItem SelectedSource { get; set; }
[Reactive]
public IList<ProfileItem> SelectedChildren { get; set; }
//[Reactive]
//public
}

View file

@ -252,6 +252,7 @@ public class MainWindowViewModel : MyReactiveObject
await ConfigHandler.InitBuiltinDNS(_config); await ConfigHandler.InitBuiltinDNS(_config);
await ConfigHandler.InitBuiltinFullConfigTemplate(_config); await ConfigHandler.InitBuiltinFullConfigTemplate(_config);
await ProfileExManager.Instance.Init(); await ProfileExManager.Instance.Init();
await ProfileGroupItemManager.Instance.Init();
await CoreManager.Instance.Init(_config, UpdateHandler); await CoreManager.Instance.Init(_config, UpdateHandler);
TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler); TaskManager.Instance.RegUpdateTask(_config, UpdateTaskHandler);