白名单

This commit is contained in:
snltty
2025-06-26 17:46:49 +08:00
parent 1fca19dfbc
commit 4c88a585c9
43 changed files with 1291 additions and 324 deletions

View File

@@ -16,7 +16,6 @@ namespace linker.messenger.api
serviceCollection.AddSingleton<ActionTransfer>();
serviceCollection.AddSingleton<SignInArgsAction>();
serviceCollection.AddSingleton<ActionClientMessenger>();
serviceCollection.AddSingleton<ActionSync>();
@@ -28,7 +27,7 @@ namespace linker.messenger.api
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ActionApiController>() });
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { serviceProvider.GetService<SignInArgsAction>() });
signInArgsTransfer.AddArgs(new List<ISignInArgsClient> { serviceProvider.GetService<SignInArgsAction>() });
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<ActionClientMessenger>() });
@@ -52,7 +51,8 @@ namespace linker.messenger.api
public static ServiceProvider UseActionServer(this ServiceProvider serviceProvider)
{
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { serviceProvider.GetService<SignInArgsAction>() });
signInArgsTransfer.AddArgs(new List<ISignInArgsClient> { serviceProvider.GetService<SignInArgsAction>() });
signInArgsTransfer.AddArgs(new List<ISignInArgsServer> { serviceProvider.GetService<SignInArgsAction>() });
RelayServerValidatorTransfer relayServerValidatorTransfer = serviceProvider.GetService<RelayServerValidatorTransfer>();
relayServerValidatorTransfer.AddValidators(new List<IRelayServerValidator> { serviceProvider.GetService<RelayValidatorAction>() });

View File

@@ -101,9 +101,12 @@ namespace linker.messenger.action
}
}
public sealed class SignInArgsAction : JsonArgReplace, ISignInArgs
public sealed class SignInArgsAction : JsonArgReplace, ISignInArgsClient, ISignInArgsServer
{
public string Name => "action";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ActionTransfer actionTransfer;
private readonly IActionClientStore actionStore;
private readonly IActionServerStore actionServerStore;

View File

@@ -152,6 +152,9 @@ namespace linker.messenger.api
[AccessDisplay("修改所有验证参数")]
ActionOther = 48,
[AccessDisplay("中继白名单")]
User2Node = 49,
}
public sealed class AccessTextInfo

View File

@@ -95,6 +95,11 @@ namespace linker.messenger.relay
return true;
}
/// <summary>
/// 更新节点
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> UpdateNode(ApiControllerParamsInfo param)
{
RelayServerNodeUpdateInfo info = param.Content.DeJson<RelayServerNodeUpdateInfo>();
@@ -111,6 +116,11 @@ namespace linker.messenger.relay
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
/// <summary>
/// 检查密钥
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> CheckKey(ApiControllerParamsInfo param)
{
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
@@ -121,6 +131,71 @@ namespace linker.messenger.relay
}).ConfigureAwait(false);
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
/// <summary>
/// 添加用户到节点
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> AddUser2Node(ApiControllerParamsInfo param)
{
RelayServerUser2NodeInfo info = param.Content.DeJson<RelayServerUser2NodeInfo>();
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.AddUser2Node,
Payload = serializer.Serialize(new RelayServerUser2NodeAddInfo
{
Data = info,
SecretKey = relayClientStore.Server.SecretKey
})
}).ConfigureAwait(false);
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
/// <summary>
/// 删除用户到节点
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> DelUser2Node(ApiControllerParamsInfo param)
{
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.DelUser2Node,
Payload = serializer.Serialize(new RelayServerUser2NodeDelInfo
{
Id = int.Parse(param.Content),
SecretKey = relayClientStore.Server.SecretKey
})
}).ConfigureAwait(false);
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray);
}
/// <summary>
/// 用户到节点分页查询
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<RelayServerUser2NodePageResultInfo> PageUser2Node(ApiControllerParamsInfo param)
{
RelayServerUser2NodePageRequestInfo info = param.Content.DeJson<RelayServerUser2NodePageRequestInfo>();
info.SecretKey = relayClientStore.Server.SecretKey;
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = signInClientState.Connection,
MessengerId = (ushort)RelayMessengerIds.PageUser2Node,
Payload = serializer.Serialize(info)
}).ConfigureAwait(false);
if (resp.Code == MessageResponeCodes.OK)
{
return serializer.Deserialize<RelayServerUser2NodePageResultInfo>(resp.Data.Span);
}
return new RelayServerUser2NodePageResultInfo();
}
}
public sealed class SyncInfo

View File

@@ -46,20 +46,20 @@ namespace linker.messenger.relay.messenger
private readonly RelayServerMasterTransfer relayServerTransfer;
private readonly RelayServerValidatorTransfer relayValidatorTransfer;
private readonly ISerializer serializer;
private readonly ICdkeyServerStore cdkeyStore;
private readonly IRelayServerStore relayServerStore;
private readonly RelayServerNodeTransfer relayServerNodeTransfer;
private readonly IRelayServerUser2NodeStore relayServerUser2NodeStore;
public RelayServerMessenger(IMessengerSender messengerSender, SignInServerCaching signCaching, ISerializer serializer, RelayServerMasterTransfer relayServerTransfer, RelayServerValidatorTransfer relayValidatorTransfer, ICdkeyServerStore cdkeyStore, IRelayServerStore relayServerStore, RelayServerNodeTransfer relayServerNodeTransfer)
public RelayServerMessenger(IMessengerSender messengerSender, SignInServerCaching signCaching, ISerializer serializer, RelayServerMasterTransfer relayServerTransfer, RelayServerValidatorTransfer relayValidatorTransfer, IRelayServerStore relayServerStore, RelayServerNodeTransfer relayServerNodeTransfer, IRelayServerUser2NodeStore relayServerUser2NodeStore)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
this.relayServerTransfer = relayServerTransfer;
this.relayValidatorTransfer = relayValidatorTransfer;
this.serializer = serializer;
this.cdkeyStore = cdkeyStore;
this.relayServerStore = relayServerStore;
this.relayServerNodeTransfer = relayServerNodeTransfer;
this.relayServerUser2NodeStore = relayServerUser2NodeStore;
}
/// <summary>
@@ -70,39 +70,40 @@ namespace linker.messenger.relay.messenger
public async Task RelayTest(IConnection connection)
{
RelayTestInfo info = serializer.Deserialize<RelayTestInfo>(connection.ReceiveRequestWrap.Payload.Span);
await RelayTest(connection, info, (validated) =>
{
List<RelayServerNodeReportInfo> list = relayServerTransfer.GetNodes(validated).Select(c => (RelayServerNodeReportInfo)c).ToList();
return serializer.Serialize(list);
}).ConfigureAwait(false);
}
[MessengerId((ushort)RelayMessengerIds.RelayTest170)]
public async Task RelayTest170(IConnection connection)
{
RelayTestInfo170 info = serializer.Deserialize<RelayTestInfo170>(connection.ReceiveRequestWrap.Payload.Span);
await RelayTest(connection, info, (validated) =>
{
return serializer.Serialize(relayServerTransfer.GetNodes(validated));
}).ConfigureAwait(false);
}
private async Task RelayTest(IConnection connection, RelayTestInfo info, Func<bool, byte[]> data)
{
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(Helper.FalseArray);
connection.Write(serializer.Serialize(new List<RelayServerNodeReportInfo> { }));
return;
}
string result = await relayValidatorTransfer.Validate(new client.transport.RelayInfo
(List<RelayServerNodeReportInfo170> nodes, bool validated) = await GetNodes(new client.transport.RelayInfo170
{
SecretKey = info.SecretKey,
FromMachineId = info.MachineId,
FromMachineName = cache.MachineName,
TransactionId = "test",
TransportName = "test",
}, cache, null).ConfigureAwait(false);
connection.Write(data(string.IsNullOrWhiteSpace(result)));
}, cache, null);
connection.Write(serializer.Serialize(nodes.Select(c => (RelayServerNodeReportInfo)c).ToList()));
}
[MessengerId((ushort)RelayMessengerIds.RelayTest170)]
public async Task RelayTest170(IConnection connection)
{
RelayTestInfo170 info = serializer.Deserialize<RelayTestInfo170>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(serializer.Serialize(new List<RelayServerNodeReportInfo170> { }));
return;
}
(List<RelayServerNodeReportInfo170> nodes, bool validated) = await GetNodes(new client.transport.RelayInfo170
{
SecretKey = info.SecretKey,
FromMachineId = info.MachineId,
FromMachineName = cache.MachineName,
UserId = info.UserId,
TransactionId = "test",
TransportName = "test",
}, cache, null);
connection.Write(serializer.Serialize(nodes));
}
/// <summary>
@@ -125,15 +126,12 @@ namespace linker.messenger.relay.messenger
info.FromMachineName = from.MachineName;
RelayAskResultInfo result = new RelayAskResultInfo();
string error = await relayValidatorTransfer.Validate(info, from, to).ConfigureAwait(false);
bool validated = string.IsNullOrWhiteSpace(error);
result.Nodes = relayServerTransfer.GetNodes(validated).Select(c => (RelayServerNodeReportInfo)c).ToList();
(List<RelayServerNodeReportInfo170> nodes, bool validated) = await GetNodes(info, from, to);
result.Nodes = nodes.Select(c => (RelayServerNodeReportInfo)c).ToList();
if (result.Nodes.Count > 0)
{
result.FlowingId = relayServerTransfer.AddRelay(from.MachineId, from.MachineName, to.MachineId, to.MachineName, from.GroupId, validated, []);
result.FlowingId = relayServerTransfer.AddRelay(from.MachineId, from.MachineName, to.MachineId, to.MachineName, from.GroupId, string.Empty, validated, false);
}
connection.Write(serializer.Serialize(result));
}
[MessengerId((ushort)RelayMessengerIds.RelayAsk170)]
@@ -152,22 +150,29 @@ namespace linker.messenger.relay.messenger
info.FromMachineName = from.MachineName;
RelayAskResultInfo170 result = new RelayAskResultInfo170();
string error = await relayValidatorTransfer.Validate(info, from, to).ConfigureAwait(false);
bool validated = string.IsNullOrWhiteSpace(error);
result.Nodes = relayServerTransfer.GetNodes(validated);
(result.Nodes, bool validated) = await GetNodes(info, from, to).ConfigureAwait(false);
if (result.Nodes.Count > 0)
{
List<CdkeyInfo> cdkeys = info.UseCdkey
? (await cdkeyStore.GetAvailable(info.UserId, "Relay").ConfigureAwait(false)).Select(c => new CdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList()
: [];
result.FlowingId = relayServerTransfer.AddRelay(from.MachineId, from.MachineName, to.MachineId, to.MachineName, from.GroupId, validated, cdkeys);
result.FlowingId = relayServerTransfer.AddRelay(from.MachineId, from.MachineName, to.MachineId, to.MachineName, from.GroupId, info.UserId, validated, info.UseCdkey);
}
connection.Write(serializer.Serialize(result));
}
private async Task<(List<RelayServerNodeReportInfo170>, bool)> GetNodes(RelayInfo relayInfo, SignCacheInfo from, SignCacheInfo to)
{
string error = await relayValidatorTransfer.Validate(relayInfo, from, to).ConfigureAwait(false);
bool validated = string.IsNullOrWhiteSpace(error);
return (await relayServerTransfer.GetNodes(validated, string.Empty), validated);
}
private async Task<(List<RelayServerNodeReportInfo170>, bool)> GetNodes(RelayInfo170 relayInfo, SignCacheInfo from, SignCacheInfo to)
{
string error = await relayValidatorTransfer.Validate(relayInfo, from, to).ConfigureAwait(false);
bool validated = string.IsNullOrWhiteSpace(error);
return (await relayServerTransfer.GetNodes(validated, relayInfo.UserId), validated);
}
/// <summary>
/// 收到中继请求
/// </summary>
@@ -242,17 +247,31 @@ namespace linker.messenger.relay.messenger
}
}
/// <summary>
/// 获取缓存
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.NodeGetCache)]
public void NodeGetCache(IConnection connection)
public async Task NodeGetCache(IConnection connection)
{
string key = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (relayServerTransfer.TryGetRelayCache(key, out RelayCacheInfo cache))
RelayCacheInfo cache = await relayServerTransfer.TryGetRelayCache(key, string.Empty);
if (cache != null)
{
connection.Write(serializer.Serialize(cache));
}
else
{
connection.Write(Helper.EmptyArray);
}
}
[MessengerId((ushort)RelayMessengerIds.NodeGetCache186)]
public async Task NodeGetCache186(IConnection connection)
{
ValueTuple<string, string> key = serializer.Deserialize<ValueTuple<string, string>>(connection.ReceiveRequestWrap.Payload.Span);
RelayCacheInfo cache = await relayServerTransfer.TryGetRelayCache(key.Item1, key.Item2);
if (cache != null)
{
connection.Write(serializer.Serialize(cache));
}
@@ -262,7 +281,7 @@ namespace linker.messenger.relay.messenger
}
}
/// <summary>
/// 获取缓存
/// 节点报告
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
@@ -326,11 +345,89 @@ namespace linker.messenger.relay.messenger
relayServerNodeTransfer.UpdateLastBytes(info);
}
/// <summary>
/// 检查密钥
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)RelayMessengerIds.CheckKey)]
public void CheckKey(IConnection connection)
{
string key = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
connection.Write(relayServerStore.ValidateSecretKey(key) ? Helper.TrueArray : Helper.FalseArray);
}
/// <summary>
/// 添加CDKEY
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)RelayMessengerIds.AddUser2Node)]
public async Task AddUser2Node(IConnection connection)
{
RelayServerUser2NodeAddInfo info = serializer.Deserialize<RelayServerUser2NodeAddInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(Helper.FalseArray);
return;
}
if (relayServerStore.ValidateSecretKey(info.SecretKey) == false)
{
connection.Write(Helper.FalseArray);
return;
}
await relayServerUser2NodeStore.Add(info.Data).ConfigureAwait(false);
connection.Write(Helper.TrueArray);
}
/// <summary>
/// 删除Cdkey
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.DelUser2Node)]
public async Task DelUser2Node(IConnection connection)
{
RelayServerUser2NodeDelInfo info = serializer.Deserialize<RelayServerUser2NodeDelInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(Helper.FalseArray);
return;
}
if (relayServerStore.ValidateSecretKey(info.SecretKey) == false)
{
connection.Write(Helper.FalseArray);
return;
}
await relayServerUser2NodeStore.Del(info.Id).ConfigureAwait(false);
connection.Write(Helper.TrueArray);
}
/// <summary>
/// 查询CDKEY
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.PageUser2Node)]
public async Task PageUser2Node(IConnection connection)
{
RelayServerUser2NodePageRequestInfo info = serializer.Deserialize<RelayServerUser2NodePageRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{
connection.Write(serializer.Serialize(new RelayServerUser2NodePageResultInfo { }));
return;
}
if (relayServerStore.ValidateSecretKey(info.SecretKey) == false && string.IsNullOrWhiteSpace(info.UserId))
{
connection.Write(serializer.Serialize(new RelayServerUser2NodePageResultInfo { }));
return;
}
var page = await relayServerUser2NodeStore.Page(info).ConfigureAwait(false);
connection.Write(serializer.Serialize(page));
}
}
}

View File

@@ -31,6 +31,12 @@
CheckKey = 2123,
AddUser2Node = 2124,
DelUser2Node = 2125,
PageUser2Node = 2126,
NodeGetCache186 = 2127,
Max = 2199
}
}

View File

@@ -130,7 +130,7 @@ namespace linker.messenger.relay.server
public long LastTicks { get; set; }
}
public sealed partial class RelayServerNodeReportInfo170 : RelayServerNodeReportInfo
public partial class RelayServerNodeReportInfo170 : RelayServerNodeReportInfo
{
public string Url { get; set; } = "https://linker-doc.snltty.com";
public TunnelProtocolType AllowProtocol { get; set; } = TunnelProtocolType.Tcp;

View File

@@ -0,0 +1,47 @@
namespace linker.messenger.relay.server
{
public interface IRelayServerUser2NodeStore
{
public Task<RelayServerUser2NodePageResultInfo> Page(RelayServerUser2NodePageRequestInfo request);
public Task<bool> Add(RelayServerUser2NodeInfo info);
public Task<bool> Del(int id);
public Task<List<string>> Get(string userid);
}
public sealed partial class RelayServerUser2NodeDelInfo
{
public string SecretKey { get; set; }
public int Id { get; set; }
}
public sealed partial class RelayServerUser2NodeAddInfo
{
public string SecretKey { get; set; }
public RelayServerUser2NodeInfo Data { get; set; }
}
public sealed partial class RelayServerUser2NodePageRequestInfo
{
public int Page { get; set; }
public int Size { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public string Remark { get; set; }
public string SecretKey { get; set; }
}
public sealed partial class RelayServerUser2NodePageResultInfo
{
public int Page { get; set; }
public int Size { get; set; }
public int Count { get; set; }
public List<RelayServerUser2NodeInfo> List { get; set; }
}
public sealed partial class RelayServerUser2NodeInfo
{
public int Id { get; set; }
public string UserId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Remark { get; set; } = string.Empty;
public DateTime AddTime { get; set; } = DateTime.Now;
public string[] Nodes { get; set; } = [];
}
}

View File

@@ -1,5 +1,6 @@
using linker.libs;
using linker.libs.timer;
using linker.libs.winapis;
using linker.messenger.cdkey;
using linker.messenger.relay.messenger;
using linker.messenger.relay.server.caching;
@@ -24,20 +25,23 @@ namespace linker.messenger.relay.server
private readonly IRelayServerMasterStore relayServerMasterStore;
private readonly ICdkeyServerStore relayServerCdkeyStore;
private readonly IMessengerSender messengerSender;
private readonly IRelayServerUser2NodeStore relayServerUser2NodeStore;
private readonly ICdkeyServerStore cdkeyServerStore;
public RelayServerMasterTransfer(IRelayServerCaching relayCaching, ISerializer serializer, IRelayServerMasterStore relayServerMasterStore, ICdkeyServerStore relayServerCdkeyStore, IMessengerSender messengerSender)
public RelayServerMasterTransfer(IRelayServerCaching relayCaching, ISerializer serializer, IRelayServerMasterStore relayServerMasterStore, ICdkeyServerStore relayServerCdkeyStore, IMessengerSender messengerSender, IRelayServerUser2NodeStore relayServerUser2NodeStore, ICdkeyServerStore cdkeyServerStore)
{
this.relayCaching = relayCaching;
this.serializer = serializer;
this.relayServerMasterStore = relayServerMasterStore;
this.relayServerCdkeyStore = relayServerCdkeyStore;
this.messengerSender = messengerSender;
this.relayServerUser2NodeStore = relayServerUser2NodeStore;
this.cdkeyServerStore = cdkeyServerStore;
TrafficTask();
}
public ulong AddRelay(string fromid, string fromName, string toid, string toName, string groupid, bool validated, List<CdkeyInfo> cdkeys)
public ulong AddRelay(string fromid, string fromName, string toid, string toName, string groupid, string userid, bool validated, bool useCdkey)
{
ulong flowingId = Interlocked.Increment(ref relayFlowingId);
@@ -49,8 +53,9 @@ namespace linker.messenger.relay.server
ToId = toid,
ToName = toName,
GroupId = groupid,
UserId = userid,
Validated = validated,
Cdkey = cdkeys
UseCdkey = useCdkey,
};
bool added = relayCaching.TryAdd($"{fromid}->{toid}->{flowingId}", cache, 15000);
if (added == false) return 0;
@@ -58,9 +63,16 @@ namespace linker.messenger.relay.server
return flowingId;
}
public bool TryGetRelayCache(string key, out RelayCacheInfo value)
public async Task<RelayCacheInfo> TryGetRelayCache(string key, string nodeid)
{
return relayCaching.TryGetValue(key, out value);
if (relayCaching.TryGetValue(key, out RelayCacheInfo value))
{
value.Validated = value.Validated || (await relayServerUser2NodeStore.Get(value.UserId)).Contains(nodeid);
value.Cdkey = value.UseCdkey
? (await cdkeyServerStore.GetAvailable(value.UserId, "Relay").ConfigureAwait(false)).Select(c => new CdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList()
: [];
}
return value;
}
/// <summary>
/// 设置节点
@@ -113,12 +125,17 @@ namespace linker.messenger.relay.server
/// </summary>
/// <param name="validated">是否已认证</param>
/// <returns></returns>
public List<RelayServerNodeReportInfo170> GetNodes(bool validated)
public async Task<List<RelayServerNodeReportInfo170>> GetNodes(bool validated, string userid)
{
var nodes = await relayServerUser2NodeStore.Get(userid);
var result = reports.Values
.Where(c => c.Public || validated)
.Where(c => Environment.TickCount64 - c.LastTicks < 15000)
.Where(c => validated || (c.ConnectionRatio < c.MaxConnection && (c.MaxGbTotal == 0 || (c.MaxGbTotal > 0 && c.MaxGbTotalLastBytes > 0))))
.Where(c =>
{
return validated || nodes.Contains(c.Id)
|| (c.Public && (c.ConnectionRatio < c.MaxConnection && (c.MaxGbTotal == 0 || (c.MaxGbTotal > 0 && c.MaxGbTotalLastBytes > 0))));
})
.OrderByDescending(c => c.LastTicks);
return result.OrderByDescending(x => x.MaxConnection == 0 ? int.MaxValue : x.MaxConnection)
@@ -148,7 +165,6 @@ namespace linker.messenger.relay.server
/// <returns></returns>
public void AddTraffic(RelayTrafficUpdateInfo info)
{
if (info.SecretKey != relayServerMasterStore.Master.SecretKey) return;
if (info.Dic.Count == 0) return;
trafficQueue.Enqueue(info.Dic);

View File

@@ -60,17 +60,15 @@ namespace linker.messenger.relay.server
}
public async ValueTask<RelayCacheInfo> TryGetRelayCache(string key)
public async Task<RelayCacheInfo> TryGetRelayCache(string key)
{
try
{
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = connection,
MessengerId = (ushort)RelayMessengerIds.NodeGetCache,
Payload = serializer.Serialize(key)
MessengerId = (ushort)RelayMessengerIds.NodeGetCache186,
Payload = serializer.Serialize(new ValueTuple<string, string>(key, node.Id))
}).ConfigureAwait(false);
if (resp.Code == MessageResponeCodes.OK && resp.Data.Length > 0)
{
@@ -94,7 +92,6 @@ namespace linker.messenger.relay.server
}
}
public bool Validate(TunnelProtocolType tunnelProtocolType)
{
if (tunnelProtocolType == TunnelProtocolType.Udp && node.AllowUdp == false) return false;
@@ -362,7 +359,7 @@ namespace linker.messenger.relay.server
ConnectionRatio = connectionNum,
EndPoint = endPoint,
Url = node.Url,
AllowProtocol = (node.AllowTcp ? TunnelProtocolType.Tcp :TunnelProtocolType.None)
AllowProtocol = (node.AllowTcp ? TunnelProtocolType.Tcp : TunnelProtocolType.None)
| (node.AllowUdp ? TunnelProtocolType.Udp : TunnelProtocolType.None)
};
await messengerSender.SendOnly(new MessageRequestWrap

View File

@@ -1,10 +1,8 @@
using System.Net.Sockets;
using System.Buffers;
using linker.libs.extends;
using System.Collections.Concurrent;
using System.Net;
using linker.libs;
using System;
using System.Text;
using linker.libs.timer;
using linker.messenger.cdkey;
@@ -417,9 +415,11 @@ namespace linker.messenger.relay.server
public string ToId { get; set; }
public string ToName { get; set; }
public string GroupId { get; set; }
public string UserId { get; set; }
public bool Validated { get; set; }
public List<CdkeyInfo> Cdkey { get; set; }
public bool UseCdkey { get; set; }
public List<CdkeyInfo> Cdkey { get; set; } = [];
}
public sealed class RelayTrafficCacheInfo
{

View File

@@ -19,9 +19,7 @@ namespace linker.messenger.relay.server.validator
{
return $"SecretKey validate fail";
}
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
return await Task.FromResult(string.Empty);
}
}
}

View File

@@ -73,6 +73,12 @@ namespace linker.messenger.serializer.memorypack
MemoryPackFormatterProvider.Register(new RelayServerNodeUpdateInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerNodeUpdateWrapInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerUser2NodeInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerUser2NodeAddInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerUser2NodeDelInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerUser2NodePageRequestInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerUser2NodePageResultInfoFormatter());
MemoryPackFormatterProvider.Register(new CdkeyInfoFormatter());
MemoryPackFormatterProvider.Register(new CdkeyStoreInfoFormatter());
MemoryPackFormatterProvider.Register(new CdkeyPageRequestInfoFormatter());

View File

@@ -934,4 +934,309 @@ namespace linker.messenger.serializer.memorypack
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerUser2NodeInfo
{
[MemoryPackIgnore]
public readonly RelayServerUser2NodeInfo info;
[MemoryPackInclude]
int Id => info.Id;
[MemoryPackInclude]
string UserId => info.UserId;
[MemoryPackInclude]
string Name => info.Name;
[MemoryPackInclude]
string Remark => info.Remark;
[MemoryPackInclude]
DateTime AddTime => info.AddTime;
[MemoryPackInclude]
string[] Nodes => info.Nodes;
[MemoryPackConstructor]
SerializableRelayServerUser2NodeInfo(int id, string userid, string name, string remark, DateTime addTime, string[] nodes)
{
var info = new RelayServerUser2NodeInfo
{
Id = id,
UserId = userid,
AddTime = addTime,
Remark = remark,
Nodes = nodes,
Name = name
};
this.info = info;
}
public SerializableRelayServerUser2NodeInfo(RelayServerUser2NodeInfo info)
{
this.info = info;
}
}
public class RelayServerUser2NodeInfoFormatter : MemoryPackFormatter<RelayServerUser2NodeInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerUser2NodeInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerUser2NodeInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerUser2NodeInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerUser2NodeInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerUser2NodeAddInfo
{
[MemoryPackIgnore]
public readonly RelayServerUser2NodeAddInfo info;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackInclude, MemoryPackAllowSerialize]
RelayServerUser2NodeInfo Data => info.Data;
[MemoryPackConstructor]
SerializableRelayServerUser2NodeAddInfo(string secretKey, RelayServerUser2NodeInfo data)
{
var info = new RelayServerUser2NodeAddInfo
{
SecretKey = secretKey,
Data = data
};
this.info = info;
}
public SerializableRelayServerUser2NodeAddInfo(RelayServerUser2NodeAddInfo info)
{
this.info = info;
}
}
public class RelayServerUser2NodeAddInfoFormatter : MemoryPackFormatter<RelayServerUser2NodeAddInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerUser2NodeAddInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerUser2NodeAddInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerUser2NodeAddInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerUser2NodeAddInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerUser2NodeDelInfo
{
[MemoryPackIgnore]
public readonly RelayServerUser2NodeDelInfo info;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackInclude]
int Id => info.Id;
[MemoryPackConstructor]
SerializableRelayServerUser2NodeDelInfo(string secretKey, int id)
{
var info = new RelayServerUser2NodeDelInfo
{
SecretKey = secretKey,
Id = id
};
this.info = info;
}
public SerializableRelayServerUser2NodeDelInfo(RelayServerUser2NodeDelInfo info)
{
this.info = info;
}
}
public class RelayServerUser2NodeDelInfoFormatter : MemoryPackFormatter<RelayServerUser2NodeDelInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerUser2NodeDelInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerUser2NodeDelInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerUser2NodeDelInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerUser2NodeDelInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerUser2NodePageRequestInfo
{
[MemoryPackIgnore]
public readonly RelayServerUser2NodePageRequestInfo info;
[MemoryPackInclude]
int Page => info.Page;
[MemoryPackInclude]
int Size => info.Size;
[MemoryPackInclude]
string UserId => info.UserId;
[MemoryPackInclude]
string Name => info.Name;
[MemoryPackInclude]
string Remark => info.Remark;
[MemoryPackInclude]
string SecretKey => info.SecretKey;
[MemoryPackConstructor]
SerializableRelayServerUser2NodePageRequestInfo(int page, int size, string userid, string name, string remark, string secretKey)
{
var info = new RelayServerUser2NodePageRequestInfo
{
Size = size,
Page = page,
UserId = userid,
Remark = remark,
SecretKey = secretKey,
Name = name,
};
this.info = info;
}
public SerializableRelayServerUser2NodePageRequestInfo(RelayServerUser2NodePageRequestInfo info)
{
this.info = info;
}
}
public class RelayServerUser2NodePageRequestInfoFormatter : MemoryPackFormatter<RelayServerUser2NodePageRequestInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerUser2NodePageRequestInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerUser2NodePageRequestInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerUser2NodePageRequestInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerUser2NodePageRequestInfo>();
value = wrapped.info;
}
}
[MemoryPackable]
public readonly partial struct SerializableRelayServerUser2NodePageResultInfo
{
[MemoryPackIgnore]
public readonly RelayServerUser2NodePageResultInfo info;
[MemoryPackInclude]
int Page => info.Page;
[MemoryPackInclude]
int Size => info.Size;
[MemoryPackInclude]
int Count => info.Count;
[MemoryPackInclude]
List<RelayServerUser2NodeInfo> List => info.List;
[MemoryPackConstructor]
SerializableRelayServerUser2NodePageResultInfo(int page, int size, int count, List<RelayServerUser2NodeInfo> list)
{
var info = new RelayServerUser2NodePageResultInfo
{
Count = count,
List = list,
Size = size,
Page = page
};
this.info = info;
}
public SerializableRelayServerUser2NodePageResultInfo(RelayServerUser2NodePageResultInfo info)
{
this.info = info;
}
}
public class RelayServerUser2NodePageResultInfoFormatter : MemoryPackFormatter<RelayServerUser2NodePageResultInfo>
{
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref RelayServerUser2NodePageResultInfo value)
{
if (value == null)
{
writer.WriteNullObjectHeader();
return;
}
writer.WritePackable(new SerializableRelayServerUser2NodePageResultInfo(value));
}
public override void Deserialize(ref MemoryPackReader reader, scoped ref RelayServerUser2NodePageResultInfo value)
{
if (reader.PeekIsNull())
{
reader.Advance(1); // skip null block
value = null;
return;
}
var wrapped = reader.ReadPackable<SerializableRelayServerUser2NodePageResultInfo>();
value = wrapped.info;
}
}
}

View File

@@ -29,7 +29,7 @@ namespace linker.messenger.signin
public static ServiceProvider UseSignInClient(this ServiceProvider serviceProvider)
{
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> {
signInArgsTransfer.AddArgs(new List<ISignInArgsClient> {
serviceProvider.GetService<SignInArgsGroupPasswordClient>(),
serviceProvider.GetService<SignInArgsSecretKeyClient>(),
serviceProvider.GetService<SignInArgsMachineKeyClient>(),
@@ -44,7 +44,7 @@ namespace linker.messenger.signin
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<SignInClientMessenger>() });
ExRouteTransfer exRouteTransfer= serviceProvider.GetService<ExRouteTransfer>();
ExRouteTransfer exRouteTransfer = serviceProvider.GetService<ExRouteTransfer>();
exRouteTransfer.AddExRoutes(new List<IExRoute> { serviceProvider.GetService<SignInExRoute>() });
LoggerHelper.Instance.Info($"start signin");
@@ -76,7 +76,7 @@ namespace linker.messenger.signin
SignInServerCaching signInServerCaching = serviceProvider.GetService<SignInServerCaching>();
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> {
signInArgsTransfer.AddArgs(new List<ISignInArgsServer> {
serviceProvider.GetService<SignInArgsGroupPasswordServer>(),
serviceProvider.GetService<SignInArgsSecretKeyServer>(),
serviceProvider.GetService<SignInArgsMachineKeyServer>(),

View File

@@ -1,11 +1,19 @@
namespace linker.messenger.signin.args
{
public enum SignInArgsLevel
{
Low = -99,
Default = 0,
Hight = 99
}
/// <summary>
/// 登录参数处理
/// </summary>
public interface ISignInArgs
public interface ISignInArgsClient
{
public string Name { get; }
public SignInArgsLevel Level { get; }
/// <summary>
/// 添加参数,客户端调用
/// </summary>
@@ -13,6 +21,11 @@
/// <param name="args"></param>
/// <returns></returns>
public Task<string> Invoke(string host, Dictionary<string, string> args);
}
public interface ISignInArgsServer
{
public string Name { get; }
public SignInArgsLevel Level { get; }
/// <summary>
/// 验证参数,服务端调用
/// </summary>
@@ -21,5 +34,4 @@
/// <returns></returns>
public Task<string> Validate(SignInfo signInfo, SignCacheInfo cache);
}
}

View File

@@ -3,47 +3,34 @@
/// <summary>
/// 添加分组密码
/// </summary>
public sealed class SignInArgsGroupPasswordClient : ISignInArgs
public sealed class SignInArgsGroupPasswordClient : ISignInArgsClient
{
public string Name => "group";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ISignInClientStore signInClientStore;
public SignInArgsGroupPasswordClient(ISignInClientStore signInClientStore)
{
this.signInClientStore = signInClientStore;
}
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
args.TryAdd("signin-gpwd", signInClientStore.Group.Password);
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
}
/// <summary>
/// 验证分组密码
/// </summary>
public sealed class SignInArgsGroupPasswordServer : ISignInArgs
public sealed class SignInArgsGroupPasswordServer : ISignInArgsServer
{
public string Name => "group";
public SignInArgsLevel Level => SignInArgsLevel.Hight;
public SignInArgsGroupPasswordServer()
{
}
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
/// <summary>
/// 验证参数
/// </summary>

View File

@@ -0,0 +1,12 @@
namespace linker.messenger.signin.args
{
public sealed class SignInArgsLimitServer : ISignInArgsServer
{
public string Name => "limit";
public SignInArgsLevel Level => SignInArgsLevel.Low;
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
return await Task.FromResult(string.Empty);
}
}
}

View File

@@ -5,9 +5,10 @@ namespace linker.messenger.signin.args
/// <summary>
/// 给登录加一个唯一ID的参数
/// </summary>
public sealed class SignInArgsMachineKeyClient : ISignInArgs
public sealed class SignInArgsMachineKeyClient : ISignInArgsClient
{
public string Name => "machineId";
public SignInArgsLevel Level => SignInArgsLevel.Default;
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
string machineKey = SystemIdHelper.GetSystemId();
@@ -19,30 +20,17 @@ namespace linker.messenger.signin.args
}
args.TryAdd("machineKey", machineKey);
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
return await Task.FromResult(string.Empty);
}
}
/// <summary>
/// 验证登录唯一参数
/// </summary>
public sealed class SignInArgsMachineKeyServer : ISignInArgs
public sealed class SignInArgsMachineKeyServer : ISignInArgsServer
{
public string Name => "machineId";
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public SignInArgsLevel Level => SignInArgsLevel.Default;
/// <summary>
/// 验证参数

View File

@@ -3,10 +3,12 @@
/// <summary>
/// 给登录加一个唯一ID的参数
/// </summary>
public sealed class SignInArgsSecretKeyClient : ISignInArgs
public sealed class SignInArgsSecretKeyClient : ISignInArgsClient
{
public string Name => "secretKey";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ISignInClientStore signInClientStore;
public SignInArgsSecretKeyClient(ISignInClientStore signInClientStore)
{
@@ -18,31 +20,21 @@
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
}
/// <summary>
/// 验证登录唯一参数
/// </summary>
public sealed class SignInArgsSecretKeyServer : ISignInArgs
public sealed class SignInArgsSecretKeyServer : ISignInArgsServer
{
public string Name => "secretKey";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ISignInServerStore signInServerStore;
public SignInArgsSecretKeyServer(ISignInServerStore signInServerStore)
{
this.signInServerStore = signInServerStore;
}
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
/// <summary>
/// 验证参数
/// </summary>

View File

@@ -5,7 +5,8 @@
/// </summary>
public sealed partial class SignInArgsTransfer
{
private List<ISignInArgs> startups = new List<ISignInArgs>();
private List<ISignInArgsClient> clients = new List<ISignInArgsClient>();
private List<ISignInArgsServer> servers = new List<ISignInArgsServer>();
public SignInArgsTransfer()
{
@@ -15,23 +16,18 @@
/// 加载所有登录参数实现类
/// </summary>
/// <param name="list"></param>
public void AddArgs(List<ISignInArgs> list)
public void AddArgs(List<ISignInArgsClient> list)
{
startups = startups.Concat(list).Distinct().ToList();
clients = clients.Concat(list).Distinct().OrderByDescending(c=>c.Level).ToList();
}
/// <summary>
/// 删除实现类
/// 加载所有登录参数实现类
/// </summary>
/// <param name="names"></param>
public void RemoveArgs(List<string> names)
/// <param name="list"></param>
public void AddArgs(List<ISignInArgsServer> list)
{
foreach (string name in names)
{
ISignInArgs item = startups.FirstOrDefault(c => c.Name == name);
if (item != null)
startups.Remove(item);
}
servers = servers.Concat(list).Distinct().OrderByDescending(c => c.Level).ToList();
}
/// <summary>
@@ -42,7 +38,7 @@
/// <returns></returns>
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
foreach (var item in startups)
foreach (var item in clients)
{
string result = await item.Invoke(host, args).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(result) == false)
@@ -60,7 +56,7 @@
/// <returns></returns>
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
foreach (var item in startups)
foreach (var item in servers)
{
string result = await item.Validate(signInfo, cache).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(result) == false)

View File

@@ -5,42 +5,25 @@ namespace linker.messenger.signin.args
/// <summary>
/// 版本限制
/// </summary>
public sealed class SignInArgsVersionClient : ISignInArgs
public sealed class SignInArgsVersionClient : ISignInArgsClient
{
public string Name => "version";
public SignInArgsLevel Level => SignInArgsLevel.Default;
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
args.TryAdd("version", VersionHelper.Version);
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
return await Task.FromResult(string.Empty);
}
}
/// <summary>
/// 版本限制
/// </summary>
public sealed class SignInArgsVersionServer : ISignInArgs
public sealed class SignInArgsVersionServer : ISignInArgsServer
{
public string Name => "version";
/// <summary>
/// 客户端调用
/// </summary>
/// <param name="host"></param>
/// <param name="args"></param>
/// <returns></returns>
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public SignInArgsLevel Level => SignInArgsLevel.Default;
/// <summary>
/// 服务端调用
@@ -54,11 +37,7 @@ namespace linker.messenger.signin.args
{
return "need v1.5.0+";
}
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
return await Task.FromResult(string.Empty);
}
}
}

View File

@@ -70,6 +70,7 @@ namespace linker.messenger.store.file
serviceCollection.AddSingleton<IRelayServerStore, RelayServerStore>();
serviceCollection.AddSingleton<IRelayServerNodeStore, RelayServerNodeStore>();
serviceCollection.AddSingleton<IRelayServerMasterStore, RelayServerMasterStore>();
serviceCollection.AddSingleton<IRelayServerUser2NodeStore, RelayServerUser2NodeStore>();
serviceCollection.AddSingleton<ICdkeyServerStore, CdkeyServerStore>();
serviceCollection.AddSingleton<ICdkeyClientStore, CdkeyClientStore>();

View File

@@ -189,6 +189,8 @@ namespace linker.messenger.store.file.relay
public async Task<List<CdkeyStoreInfo>> GetAvailable(string userid, string type)
{
if (string.IsNullOrWhiteSpace(userid) || string.IsNullOrWhiteSpace(type)) return [];
return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.Type == type && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime >= DateTime.Now && x.Deleted == false).ToList()).ConfigureAwait(false);
}
public async Task<List<CdkeyStoreInfo>> Get(List<int> ids)

View File

@@ -0,0 +1,72 @@
using linker.messenger.relay.server;
using LiteDB;
namespace linker.messenger.store.file.relay
{
public sealed class RelayServerUser2NodeStore : IRelayServerUser2NodeStore
{
private readonly ILiteCollection<RelayServerUser2NodeInfo> liteCollection;
public RelayServerUser2NodeStore(Storefactory dBfactory)
{
liteCollection = dBfactory.GetCollection<RelayServerUser2NodeInfo>("relayUser2Node");
}
public async Task<bool> Add(RelayServerUser2NodeInfo info)
{
if (info.Id == 0)
{
info.AddTime = DateTime.Now;
liteCollection.Insert(info);
}
else
{
liteCollection.UpdateMany(p => new RelayServerUser2NodeInfo
{
Name = info.Name,
Remark = info.Remark,
Nodes = info.Nodes,
}, c => c.Id == info.Id);
}
return await Task.FromResult(true).ConfigureAwait(false);
}
public async Task<bool> Del(int id)
{
return await Task.FromResult(liteCollection.Delete(id)).ConfigureAwait(false);
}
public async Task<List<string>> Get(string userid)
{
if (string.IsNullOrWhiteSpace(userid)) return [];
return await Task.FromResult(liteCollection.Find(c => c.UserId == userid).SelectMany(c => c.Nodes).ToList()).ConfigureAwait(false);
}
public async Task<RelayServerUser2NodePageResultInfo> Page(RelayServerUser2NodePageRequestInfo info)
{
ILiteQueryable<RelayServerUser2NodeInfo> query = liteCollection.Query();
if (string.IsNullOrWhiteSpace(info.UserId) == false)
{
query = query.Where(x => x.UserId == info.UserId);
}
if (string.IsNullOrWhiteSpace(info.Name) == false)
{
query = query.Where(x => x.Name.Contains(info.Name));
}
if (string.IsNullOrWhiteSpace(info.Remark) == false)
{
query = query.Where(x => x.Remark.Contains(info.Remark));
}
query = query.OrderBy(c => c.Id, Query.Descending);
return await Task.FromResult(new RelayServerUser2NodePageResultInfo
{
Page = info.Page,
Size = info.Size,
Count = query.Count(),
List = query.Skip((info.Page - 1) * info.Size).Limit(info.Size).ToList()
}).ConfigureAwait(false);
}
}
}

View File

@@ -42,7 +42,7 @@ namespace linker.messenger.tunnel
public static ServiceProvider UseTunnelClient(this ServiceProvider serviceProvider)
{
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { serviceProvider.GetService<SignInArgsNet>() });
signInArgsTransfer.AddArgs(new List<ISignInArgsClient> { serviceProvider.GetService<SignInArgsNet>() });
TunnelNetworkTransfer tunnelNetworkTransfer = serviceProvider.GetService<TunnelNetworkTransfer>();

View File

@@ -1,12 +1,13 @@
using linker.messenger.signin.args;
using linker.messenger.signin;
using linker.libs.extends;
namespace linker.messenger.tunnel
{
public sealed class SignInArgsNet : ISignInArgs
public sealed class SignInArgsNet : ISignInArgsClient
{
public string Name => "tunnelNet";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ITunnelClientStore tunnelClientStore;
public SignInArgsNet(ITunnelClientStore tunnelClientStore)
{
@@ -15,16 +16,7 @@ namespace linker.messenger.tunnel
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
args.TryAdd("tunnelNet", new SignInArgsNetInfo { Lat = tunnelClientStore.Network.Net.Lat, Lon = tunnelClientStore.Network.Net.Lon, City = tunnelClientStore.Network.Net.City }.ToJson());
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
return await Task.FromResult(string.Empty);
}
}
public sealed class SignInArgsNetInfo

View File

@@ -54,6 +54,8 @@ namespace linker.messenger.tuntap.cidr
{
try
{
if (File.Exists(Path.Join(path, file)) == false) return;
maps = File.ReadAllText(Path.Join(path, file)).Split(Environment.NewLine)
.Where(c => string.IsNullOrWhiteSpace(c) == false)
.Select(c => c.Split(','))

View File

@@ -24,3 +24,13 @@ export const relayUpdateNode = (data) => {
export const checkRelayKey = (key) => {
return sendWebsocketMsg('relay/checkkey',key);
}
export const user2NodePage = (data) => {
return sendWebsocketMsg('relay/PageUser2Node', data);
}
export const user2NodeAdd = (data) => {
return sendWebsocketMsg('relay/AddUser2Node', data);
}
export const user2NodeDel = (data) => {
return sendWebsocketMsg('relay/DelUser2Node', data);
}

View File

@@ -254,6 +254,17 @@ export default {
'server.relayDefault': '默认',
'server.relaySetDefault': '设置默认中继节点',
'server.relaySetDefaultText': '不选择则所有客户端',
'server.relayUser2Node': '中继白名单',
'server.relayUser2NodeUserId': '用户编号',
'server.relayUser2NodeName': '名称',
'server.relayUser2NodeRemark': '备注',
'server.relayUser2NodeAddTime': '添加时间',
'server.relayUser2NodeNodes': '节点',
'server.relayUser2NodeOper': '操作',
'server.relayUser2NodeDelConfirm': '确认删除吗?',
'server.relayUser2NodeAdd': '添加白名单',
'server.relayUser2NodeUnselect': '未选择',
'server.relayUser2NodeSelected': '已选择',
'server.cdkeySecretKey': 'Cdkey密钥',
'server.cdkeyText': '密钥正确时可管理cdkey',

View File

@@ -1,7 +1,7 @@
<template>
<el-form-item :label="$t('server.cdkeySecretKey')">
<div class="flex">
<el-input :class="{success:state.keyState,error:state.keyState==false}" class="flex-1" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/>
<el-input :class="{success:state.keyState,error:state.keyState==false}" style="width:20rem;" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/>
<Sync class="mgl-1" name="CdkeySecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.cdkeyText')}}</span>
</div>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.relaySecretKey')">
<div >
<div class="flex">
<el-input :class="{success:state.keyState,error:state.keyState==false}" class="flex-1" type="password" show-password v-model="state.list.SecretKey" maxlength="36" @change="handleSave" />
<el-input :class="{success:state.keyState,error:state.keyState==false}" style="width:20rem;" type="password" show-password v-model="state.list.SecretKey" maxlength="36" @blur="handleSave" />
<Sync class="mgl-1" name="RelaySecretKey"></Sync>
</div>
<div class="flex">
@@ -10,142 +10,42 @@
<el-checkbox class="mgr-1" v-model="state.list.SSL" :label="$t('server.relaySSL')" @change="handleSave" />
<el-checkbox v-model="state.list.Disabled" :label="$t('server.relayDisable')" @change="handleSave" />
</div>
<a href="javascript:;" @click="state.show=true" class="mgr-1 delay a-line" :class="{red:state.nodes.length==0,green:state.nodes.length>0}">
<a href="javascript:;" @click="state.showModes=true" class="mgr-1 delay a-line" :class="{red:state.nodes.length==0,green:state.nodes.length>0}">
{{$t('server.relayNodes')}} : {{state.nodes.length}}
</a>
<User2Node :keyState="state.keyState"></User2Node>
<div class="mgr-1" :title="$t('server.relayUseCdkeyTitle')">
<el-checkbox v-model="state.list.UseCdkey" :label="$t('server.relayUseCdkey')" @change="handleSave" />
</div>
<Cdkey type="Relay"></Cdkey>
<Nodes v-if="state.showModes" v-model="state.showModes" :data="state.nodes" :keyState="state.keyState"></Nodes>
</div>
</div>
</el-form-item>
<el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="98%" top="2vh">
<div>
<el-table :data="state.nodes" size="small" border height="500">
<el-table-column property="Name" :label="$t('server.relayName')">
<template #default="scope">
<div>
<a :href="scope.row.Url" class="a-line" :class="{green:scope.row.Public}" target="_blank" :title="scope.row.Public?$t('server.public'):''">{{ scope.row.Name }}</a>
<a v-if="state.keyState" href="javascript:;" class="a-line a-edit" @click="handleEdit(scope.row)">
<span><el-icon><Edit /></el-icon></span>
<span :class="{green:state.syncData.Value == 1 && scope.row.Id==state.syncData.Key}"
:title="state.syncData.Value == 1 && scope.row.Id==state.syncData.Key ? `${$t('server.relayDefault')}TCP`:''"
v-if="(scope.row.AllowProtocol & 1) == 1">,tcp</span>
<span
:class="{green:state.syncData.Value == 2 && scope.row.Id==state.syncData.Key}"
:title="state.syncData.Value == 2 && scope.row.Id==state.syncData.Key ? `${$t('server.relayDefault')}UDP`:''"
v-if="(scope.row.AllowProtocol & 2) == 2">,udp</span>
</a>
</div>
</template>
</el-table-column>
<el-table-column property="MaxGbTotal" :label="$t('server.relayFlow')" width="160">
<template #default="scope">
<span v-if="scope.row.MaxGbTotal == 0">--</span>
<span v-else>{{ (scope.row.MaxGbTotalLastBytes/1024/1024/1024).toFixed(2) }}GB / {{ scope.row.MaxGbTotal }}GB</span>
</template>
</el-table-column>
<el-table-column property="MaxBandwidth" :label="$t('server.relaySpeed')" width="80">
<template #default="scope">
<span v-if="scope.row.MaxBandwidth == 0">--</span>
<span v-else>{{ scope.row.MaxBandwidth }}Mbps</span>
</template>
</el-table-column>
<el-table-column property="MaxBandwidthTotal" :label="`${$t('server.relaySpeed2')}/${$t('server.relaySpeed1')}`" width="120">
<template #default="scope">
<span>
<span>{{scope.row.BandwidthRatio}}</span>
<span>/</span>
<span v-if="scope.row.MaxBandwidthTotal == 0">--</span>
<span v-else>{{ scope.row.MaxBandwidthTotal }}Mbps</span>
</span>
</template>
</el-table-column>
<el-table-column property="ConnectionRatio" :label="$t('server.relayConnection')" width="100">
<template #default="scope">
<span><strong>{{scope.row.ConnectionRatio}}</strong>/{{scope.row.MaxConnection}}</span>
</template>
</el-table-column>
<el-table-column property="Delay" :label="$t('server.relayDelay')" width="60">
<template #default="scope">
<span>{{ scope.row.Delay }}ms</span>
</template>
</el-table-column>
<el-table-column property="Public" :label="$t('server.relayDefault')" width="60">
<template #default="scope">
<el-dropdown size="small">
<div class="dropdown">
<span style="font-size: 1.2rem;">{{ $t('server.relayDefault') }}</span>
<el-icon class="el-icon--right">
<ArrowDown />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="(scope.row.AllowProtocol & 1) == 1" @click="handleShowSync(scope.row.Id, 1)">{{$t('common.relay')}}TCP</el-dropdown-item>
<el-dropdown-item v-if="(scope.row.AllowProtocol & 2) == 2" @click="handleShowSync(scope.row.Id, 2)">{{$t('common.relay')}}UDP</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
<EditNode v-if="state.showEdit" v-model="state.showEdit" :data="state.current"></EditNode>
<el-dialog class="options-center" :title="$t('server.relaySetDefault')" destroy-on-close v-model="state.showSync" width="54rem" top="2vh">
<div>
<div class="t-c">{{ $t('server.relaySetDefaultText') }}</div>
<Ids ref="domIds"></Ids>
<div class="t-c w-100 mgt-1">
<el-button @click="state.showSync = false">{{$t('common.cancel')}}</el-button>
<el-button type="primary" @click="handleSync">{{$t('common.confirm')}}</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import { checkRelayKey, getDefault, setRelayServers, setRelaySubscribe, syncDefault } from '@/apis/relay';
import { checkRelayKey, setRelayServers, setRelaySubscribe } from '@/apis/relay';
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { onMounted, onUnmounted, provide, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue'
import Ids from '../sync/Ids.vue';
import Cdkey from './cdkey/Index.vue'
import EditNode from './EditNode.vue';
import { Edit,ArrowDown } from '@element-plus/icons-vue';
import Nodes from './relay/Nodes.vue';
import User2Node from './user2node/Index.vue';
export default {
components:{Sync,Ids,Cdkey,EditNode,Edit,ArrowDown},
components:{Sync,Cdkey,Nodes,User2Node},
setup(props) {
const {t} = useI18n();
const globalData = injectGlobalData();
const state = reactive({
list:globalData.value.config.Client.Relay.Server,
show:false,
showModes:false,
nodes:[],
timer:0,
showEdit:false,
current:{},
keyState:false,
showSync:false,
syncData:{
Key:'',
Value:0
}
});
watch(()=>globalData.value.config.Client.Relay.Server,()=>{
state.list.Delay = globalData.value.config.Client.Relay.Server.Delay;
});
const handleEdit = (row)=>{
state.current = row;
state.showEdit = true;
}
const handleSave = ()=>{
setRelayServers(state.list).then(()=>{
ElMessage.success(t('common.oper'));
@@ -155,10 +55,14 @@ export default {
});
handleCheckKey();
}
const nodes = ref([]);
provide('nodes',nodes);
const _setRelaySubscribe = ()=>{
clearTimeout(state.timer);
setRelaySubscribe().then((res)=>{
state.nodes = res;
nodes.value = res;
state.timer = setTimeout(_setRelaySubscribe,1000);
}).catch(()=>{
state.timer = setTimeout(_setRelaySubscribe,1000);
@@ -170,37 +74,15 @@ export default {
}).catch(()=>{});
}
const domIds = ref(null);
const handleShowSync = (id,proto)=>{
state.syncData.Key = id;
state.syncData.Value = proto;
state.showSync = true;
}
const handleSync = ()=>{
syncDefault({
Ids:domIds.value.getIds(),
Data:state.syncData
}).then((res)=>{
state.showSync = false;
ElMessage.success(t('common.oper'));
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
}
onMounted(()=>{
_setRelaySubscribe();
handleCheckKey();
getDefault().then((res)=>{
state.syncData.Key = res.Key || '';
state.syncData.Value = res.Value || 0;
});
});
onUnmounted(()=>{
clearTimeout(state.timer);
});
return {globalData,state,handleSave,handleEdit,domIds,handleShowSync,handleSync}
return {globalData,state,handleSave}
}
}
</script>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.sforwardSecretKey')">
<div>
<div class="flex">
<el-input :class="{success:state.keyState,error:state.keyState==false}" class="flex-1" type="password" show-password v-model="state.SForwardSecretKey" maxlength="36" @blur="handleChange" />
<el-input :class="{success:state.keyState,error:state.keyState==false}" style="width:20rem;" type="password" show-password v-model="state.SForwardSecretKey" maxlength="36" @blur="handleChange" />
<Sync class="mgl-1" name="SForwardSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.sforwardText')}}</span>
</div>

View File

@@ -6,26 +6,26 @@
<el-form label-width="auto" :label-position="state.position">
<el-form-item :label="$t('server.messengerAddr')">
<div class="flex">
<el-input class="flex-1" v-model="state.list.Host" @change="handleSave" />
<el-input style="width:20rem;" v-model="state.list.Host" @blur="handleSave" />
<Sync class="mgl-1" name="SignInServer"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerText')}}</span>
</div>
</el-form-item>
<el-form-item :label="`${$t('server.messengerAddr')}1`">
<div class="flex">
<el-input class="flex-1" v-model="state.list.Host1" @change="handleSave" />
<el-input style="width:20rem;" v-model="state.list.Host1" @blur="handleSave" />
</div>
</el-form-item>
<el-form-item :label="$t('server.messengerSecretKey')">
<div class="flex">
<el-input :class="{success:state.keyState,error:state.keyState==false}" class="flex-1" type="password" show-password maxlength="36" v-model="state.list.SecretKey" @change="handleSave" />
<el-input :class="{success:state.keyState,error:state.keyState==false}" style="width:20rem;" type="password" show-password maxlength="36" v-model="state.list.SecretKey" @blur="handleSave" />
<Sync class="mgl-1" name="SignInSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerSecretKeyText')}}</span>
</div>
</el-form-item>
<el-form-item :label="$t('server.messengerUserId')">
<div class="flex">
<el-input class="flex-1" type="password" show-password maxlength="36" v-model="state.list.UserId" @change="handleSave" />
<el-input style="width:20rem;" type="password" show-password maxlength="36" v-model="state.list.UserId" @blur="handleSave" />
<Sync class="mgl-1" name="SignInUserId"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerUserIdText')}}</span>
</div>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.updaterSecretKey')">
<div >
<div class="flex">
<el-input :class="{success:state.keyState,error:state.keyState==false}" class="flex-1" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/>
<el-input :class="{success:state.keyState,error:state.keyState==false}" style="width:20rem;" type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange"/>
<Sync class="mgl-1" name="UpdaterSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.updaterText')}}</span>
</div>

View File

@@ -0,0 +1,173 @@
<template>
<div>
<el-dialog v-model="state.show" :title="$t('server.relayTitle')" width="98%" top="2vh">
<div>
<el-table :data="state.nodes" size="small" border height="500">
<el-table-column property="Name" :label="$t('server.relayName')">
<template #default="scope">
<div>
<a :href="scope.row.Url" class="a-line" :class="{green:scope.row.Public}" target="_blank" :title="scope.row.Public?$t('server.public'):''">{{ scope.row.Name }}</a>
<a v-if="state.keyState" href="javascript:;" class="a-line a-edit" @click="handleEdit(scope.row)">
<span><el-icon><Edit /></el-icon></span>
<span :class="{green:state.syncData.Value == 1 && scope.row.Id==state.syncData.Key}"
:title="state.syncData.Value == 1 && scope.row.Id==state.syncData.Key ? `${$t('server.relayDefault')}TCP`:''"
v-if="(scope.row.AllowProtocol & 1) == 1">,tcp</span>
<span
:class="{green:state.syncData.Value == 2 && scope.row.Id==state.syncData.Key}"
:title="state.syncData.Value == 2 && scope.row.Id==state.syncData.Key ? `${$t('server.relayDefault')}UDP`:''"
v-if="(scope.row.AllowProtocol & 2) == 2">,udp</span>
</a>
</div>
</template>
</el-table-column>
<el-table-column property="MaxGbTotal" :label="$t('server.relayFlow')" width="160">
<template #default="scope">
<span v-if="scope.row.MaxGbTotal == 0">--</span>
<span v-else>{{ (scope.row.MaxGbTotalLastBytes/1024/1024/1024).toFixed(2) }}GB / {{ scope.row.MaxGbTotal }}GB</span>
</template>
</el-table-column>
<el-table-column property="MaxBandwidth" :label="$t('server.relaySpeed')" width="80">
<template #default="scope">
<span v-if="scope.row.MaxBandwidth == 0">--</span>
<span v-else>{{ scope.row.MaxBandwidth }}Mbps</span>
</template>
</el-table-column>
<el-table-column property="MaxBandwidthTotal" :label="`${$t('server.relaySpeed2')}/${$t('server.relaySpeed1')}`" width="120">
<template #default="scope">
<span>
<span>{{scope.row.BandwidthRatio}}</span>
<span>/</span>
<span v-if="scope.row.MaxBandwidthTotal == 0">--</span>
<span v-else>{{ scope.row.MaxBandwidthTotal }}Mbps</span>
</span>
</template>
</el-table-column>
<el-table-column property="ConnectionRatio" :label="$t('server.relayConnection')" width="100">
<template #default="scope">
<span><strong>{{scope.row.ConnectionRatio}}</strong>/{{scope.row.MaxConnection}}</span>
</template>
</el-table-column>
<el-table-column property="Delay" :label="$t('server.relayDelay')" width="60">
<template #default="scope">
<span>{{ scope.row.Delay }}ms</span>
</template>
</el-table-column>
<el-table-column property="Public" :label="$t('server.relayDefault')" width="60">
<template #default="scope">
<el-dropdown size="small">
<div class="dropdown">
<span style="font-size: 1.2rem;">{{ $t('server.relayDefault') }}</span>
<el-icon class="el-icon--right">
<ArrowDown />
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="(scope.row.AllowProtocol & 1) == 1" @click="handleShowSync(scope.row.Id, 1)">{{$t('common.relay')}}TCP</el-dropdown-item>
<el-dropdown-item v-if="(scope.row.AllowProtocol & 2) == 2" @click="handleShowSync(scope.row.Id, 2)">{{$t('common.relay')}}UDP</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
<EditNode v-if="state.showEdit" v-model="state.showEdit" :data="state.current"></EditNode>
<el-dialog class="options-center" :title="$t('server.relaySetDefault')" destroy-on-close v-model="state.showSync" width="54rem" top="2vh">
<div>
<div class="t-c">{{ $t('server.relaySetDefaultText') }}</div>
<Ids ref="domIds"></Ids>
<div class="t-c w-100 mgt-1">
<el-button @click="state.showSync = false">{{$t('common.cancel')}}</el-button>
<el-button type="primary" @click="handleSync">{{$t('common.confirm')}}</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { getDefault,syncDefault } from '@/apis/relay';
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n';
import Ids from '../../sync/Ids.vue';
import EditNode from './EditNode.vue';
import { Edit,ArrowDown } from '@element-plus/icons-vue';
export default {
props: ['modelValue','data','keyState'],
emits: ['update:modelValue','success'],
components:{Ids,EditNode,Edit,ArrowDown},
setup(props,{emit}) {
const {t} = useI18n();
const globalData = injectGlobalData();
const state = reactive({
show:true,
nodes:props.data,
showEdit:false,
current:{},
keyState:props.keyState,
showSync:false,
syncData:{
Key:'',
Value:0
}
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleEdit = (row)=>{
state.current = row;
state.showEdit = true;
}
const domIds = ref(null);
const handleShowSync = (id,proto)=>{
state.syncData.Key = id;
state.syncData.Value = proto;
state.showSync = true;
}
const handleSync = ()=>{
syncDefault({
Ids:domIds.value.getIds(),
Data:state.syncData
}).then((res)=>{
state.showSync = false;
ElMessage.success(t('common.oper'));
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
}
onMounted(()=>{
getDefault().then((res)=>{
state.syncData.Key = res.Key || '';
state.syncData.Value = res.Value || 0;
});
});
onUnmounted(()=>{
clearTimeout(state.timer);
});
return {globalData,state,handleEdit,domIds,handleShowSync,handleSync}
}
}
</script>
<style lang="stylus" scoped>
.blue {
color: #409EFF;
}
a.a-edit{
margin-left:1rem;
.el-icon {
vertical-align middle
}
}
</style>

View File

@@ -0,0 +1,124 @@
<template>
<el-dialog class="options-center" :title="$t('server.relayUser2Node')" destroy-on-close v-model="state.show" width="36rem" top="2vh">
<div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="auto">
<el-form-item :label="$t('server.relayUser2NodeUserId')" prop="UserId">
<el-input maxlength="36" show-word-limit v-model="state.ruleForm.UserId" />
</el-form-item>
<el-form-item :label="$t('server.relayUser2NodeName')" prop="Name">
<el-input v-model="state.ruleForm.Name" />
</el-form-item>
<el-form-item :label="$t('server.relayUser2NodeNodes')" prop="Nodes">
<el-input type="textarea" :value="state.nodes" @click="handleShowNodes" readonly resize="none" rows="4"></el-input>
</el-form-item>
<el-form-item :label="$t('server.relayUser2NodeRemark')" prop="Remark">
<el-input v-model="state.ruleForm.Remark" />
</el-form-item>
<el-form-item></el-form-item>
<el-form-item label="" prop="Btns">
<div class="t-c w-100">
<el-button @click="state.show = false">{{$t('common.cancel')}}</el-button>
<el-button type="primary" @click="handleSave">{{$t('common.confirm')}}</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
<el-dialog class="options-center" :title="$t('server.relayUser2NodeNodes')" destroy-on-close v-model="state.showNodes" width="54rem" top="2vh">
<div>
<el-transfer class="src-tranfer"
v-model="state.nodeIds"
filterable
:filter-method="srcFilterMethod"
:data="nodes"
:titles="[$t('server.relayUser2NodeUnselect'), $t('server.relayUser2NodeSelected')]"
:props="{
key: 'Id',
label: 'Name',
}"
/>
<div class="t-c w-100 mgt-1">
<el-button @click="state.showNodes = false">{{$t('common.cancel')}}</el-button>
<el-button type="primary" @click="handleNodes">{{$t('common.confirm')}}</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import { ElMessage } from 'element-plus';
import { computed, inject, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n';
import { user2NodeAdd } from '@/apis/relay';
export default {
props: ['modelValue'],
emits: ['update:modelValue','success'],
setup(props,{emit}) {
const {t} = useI18n();
const nodes = inject('nodes');
const editState = inject('edit');
const state = reactive({
show:true,
ruleForm:{
Id:editState.value.Id || 0,
UserId:editState.value.UserId || '',
Name:editState.value.Name || '',
Remark:editState.value.Remark || '',
Nodes:editState.value.Nodes || [],
},
nodes:computed(()=>{
const json = nodes.value.reduce((json,item,index)=>{ json[item.Id] = item.Name; return json; },{});
return state.ruleForm.Nodes.map(c=>json[c]).join(',');
}),
rules:{
UserId: [{ required: true, message: "required", trigger: "blur" }],
Name: [{ required: true, message: "required", trigger: "blur" }],
Nodes: [{ required: true, message: "required", trigger: "blur" }],
},
showNodes:false,
nodeIds: []
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handleShowNodes = ()=>{
state.nodeIds = state.ruleForm.Nodes;
state.showNodes = true;
}
const srcFilterMethod = (query, item) => {
return item.Name.toLowerCase().includes(query.toLowerCase())
}
const handleNodes = ()=>{
state.ruleForm.Nodes = state.nodeIds;
state.showNodes = false;
}
const ruleFormRef = ref(null);
const handleSave = ()=>{
ruleFormRef.value.validate((valid) => {
if (!valid) return;
const json = JSON.parse(JSON.stringify(state.ruleForm));
user2NodeAdd(json).then(()=>{
ElMessage.success(t('common.oper'));
state.show = false;
emit('success');
}).catch(()=>{
ElMessage.error(t('common.operFail'));
});
});
}
return {state,nodes,handleShowNodes,srcFilterMethod,handleNodes,ruleFormRef,handleSave}
}
}
</script>
<style lang="stylus" scoped>
.el-form-item{margin-bottom:1rem}
.el-input-number--small{width:10rem !important}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<a v-if="state.hasUser2Node && hasUser2Node" @click="state.showManager = true" href="javascript:;" class="mgr-1 a-line">{{$t('server.relayUser2Node')}}</a>
<Manager v-if="state.showManager" v-model="state.showManager" />
</template>
<script>
import { injectGlobalData } from '@/provide';
import { computed, reactive } from 'vue';
import Manager from './Manager.vue'
export default {
props:['keyState'],
components:{Manager},
setup (props) {
const globalData = injectGlobalData();
const hasUser2Node = computed(()=>globalData.value.hasAccess('User2Node'));
const state = reactive({
hasUser2Node:computed(()=>props.keyState),
showManager:false
});
return {state,hasUser2Node}
}
}
</script>
<style lang="stylus" scoped>
</style>

View File

@@ -0,0 +1,150 @@
<template>
<el-dialog class="options-center" :title="$t('server.relayUser2Node')" destroy-on-close v-model="state.show" width="77rem" top="2vh">
<div class="group-wrap">
<div class="head">
<div class="search flex">
<div><span>{{$t('server.relayUser2NodeUserId')}}</span> <el-input v-model="state.page.UserId" style="width:8rem" size="small" clearable @change="handleSearch" /></div>
<div><span>{{$t('server.relayUser2NodeName')}}</span> <el-input v-model="state.page.Name" style="width:8rem" size="small" clearable @change="handleSearch" /></div>
<div><span>{{$t('server.relayUser2NodeRemark')}}</span> <el-input v-model="state.page.Remark" style="width:8rem" size="small" clearable @change="handleSearch" /></div>
<div>
<el-button size="small" @click="handleSearch()">
<el-icon><Search /></el-icon>
</el-button>
</div>
<div>
<el-button size="small" type="success" @click="handleAdd">
<el-icon><Plus /></el-icon>
</el-button>
</div>
</div>
</div>
<el-table stripe :data="state.list.List" border size="small" width="100%">
<el-table-column prop="Name" :label="$t('server.relayUser2NodeName')"></el-table-column>
<el-table-column prop="Nodes" :label="$t('server.relayUser2NodeNodes')">
<template #default="scope">
<span>{{ scope.row.Nodes.map(c=>state.nodes[c]).join(',') }}</span>
</template>
</el-table-column>
<el-table-column prop="Remark" :label="$t('server.relayUser2NodeRemark')"></el-table-column>
<el-table-column prop="AddTime" :label="`${$t('server.relayUser2NodeAddTime')}`" width="140" sortable="custom">
</el-table-column>
<el-table-column fixed="right" prop="Oper" :label="$t('server.relayUser2NodeOper')" width="110">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">
<el-icon><EditPen /></el-icon>
</el-button>
<el-popconfirm :title="$t('server.relayUser2NodeDelConfirm')" @confirm="handleDel(scope.row)">
<template #reference>
<el-button type="danger" size="small">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div class="t-c">
<div class="page">
<el-pagination small background layout="prev, pager, next"
:page-size="state.page.Size"
:total="state.list.Count"
:pager-count="5"
:current-page="state.page.Page" @current-change="handlePageChange" />
</div>
</div>
</div>
</el-dialog>
<Add v-if="state.showAdd" v-model="state.showAdd" @success="handleSearch"></Add>
</template>
<script>
import { computed, inject, onMounted, provide, reactive, ref, watch } from 'vue'
import { Delete,Plus,Search,Warning,EditPen } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n';
import Add from './Add.vue';
import { user2NodeDel, user2NodePage } from '@/apis/relay';
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
components:{Delete,Plus,Search ,EditPen,Add,Warning},
setup(props,{emit}) {
const {t} = useI18n();
const nodes = inject('nodes');
const state = reactive({
nodes:computed(()=>nodes.value.reduce((json,item,index)=>{ json[item.Id] = item.Name; return json; },{})),
page:{
Page:1,
Size:10,
UserId:'',
Name:'',
Remark:''
},
list:{
Page:1,
Size:15,
Count:0,
List:[]
},
show:true,
showAdd:false
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const editState = ref({
});
provide('edit',editState);
const handleAdd = ()=>{
editState.value = {Id:0,Name:'',Nodes:[],Remark:'',UserId:''};
state.showAdd = true;
}
const handleEdit = (row)=>{
editState.value = row
state.showAdd = true;
}
const handleSearch = ()=>{
user2NodePage(state.page).then((res)=>{
state.list = res;
}).catch(()=>{})
}
const handlePageChange = (p)=>{
state.page.Page = p;
handleSearch();
}
const handleDel = (row)=>{
user2NodeDel(row.Id).then((res)=>{
handleSearch();
}).catch(()=>{})
}
onMounted(()=>{
handleSearch();
})
return {state,handleSearch,handlePageChange,handleDel,handleAdd,handleEdit}
}
}
</script>
<style lang="stylus" scoped>
.head{
.search{
&>div{
margin-right:1rem;
}
}
}
.page{
padding:2rem 0;
display:inline-block;
}
.el-form-item{margin-bottom:1rem}
.el-input-number--small{width:10rem !important}
</style>

View File

@@ -1,5 +1,5 @@
v1.8.5
2025-06-25 16:12:07
2025-06-26 17:46:49
1. 一些累计更新
2. 备用信标服务器
3. 设置默认中继节点