白名单

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<ActionTransfer>();
serviceCollection.AddSingleton<SignInArgsAction>(); serviceCollection.AddSingleton<SignInArgsAction>();
serviceCollection.AddSingleton<ActionClientMessenger>(); serviceCollection.AddSingleton<ActionClientMessenger>();
serviceCollection.AddSingleton<ActionSync>(); serviceCollection.AddSingleton<ActionSync>();
@@ -28,7 +27,7 @@ namespace linker.messenger.api
apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ActionApiController>() }); apiServer.AddPlugins(new List<IApiController> { serviceProvider.GetService<ActionApiController>() });
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>(); 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>(); IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<ActionClientMessenger>() }); messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<ActionClientMessenger>() });
@@ -52,7 +51,8 @@ namespace linker.messenger.api
public static ServiceProvider UseActionServer(this ServiceProvider serviceProvider) public static ServiceProvider UseActionServer(this ServiceProvider serviceProvider)
{ {
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>(); 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 relayServerValidatorTransfer = serviceProvider.GetService<RelayServerValidatorTransfer>();
relayServerValidatorTransfer.AddValidators(new List<IRelayServerValidator> { serviceProvider.GetService<RelayValidatorAction>() }); 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 string Name => "action";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ActionTransfer actionTransfer; private readonly ActionTransfer actionTransfer;
private readonly IActionClientStore actionStore; private readonly IActionClientStore actionStore;
private readonly IActionServerStore actionServerStore; private readonly IActionServerStore actionServerStore;
@@ -153,7 +156,7 @@ namespace linker.messenger.action
public string Name => "action"; public string Name => "action";
private readonly ActionTransfer actionTransfer; private readonly ActionTransfer actionTransfer;
private readonly IActionServerStore actionServerStore; private readonly IActionServerStore actionServerStore;
public RelayValidatorAction(ActionTransfer actionTransfer, IActionServerStore actionServerStore) public RelayValidatorAction(ActionTransfer actionTransfer, IActionServerStore actionServerStore)
{ {
this.actionTransfer = actionTransfer; this.actionTransfer = actionTransfer;
this.actionServerStore = actionServerStore; this.actionServerStore = actionServerStore;

View File

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

View File

@@ -95,6 +95,11 @@ namespace linker.messenger.relay
return true; return true;
} }
/// <summary>
/// 更新节点
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public async Task<bool> UpdateNode(ApiControllerParamsInfo param) public async Task<bool> UpdateNode(ApiControllerParamsInfo param)
{ {
RelayServerNodeUpdateInfo info = param.Content.DeJson<RelayServerNodeUpdateInfo>(); 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); 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) public async Task<bool> CheckKey(ApiControllerParamsInfo param)
{ {
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
@@ -121,6 +131,71 @@ namespace linker.messenger.relay
}).ConfigureAwait(false); }).ConfigureAwait(false);
return resp.Code == MessageResponeCodes.OK && resp.Data.Span.SequenceEqual(Helper.TrueArray); 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 public sealed class SyncInfo

View File

@@ -46,20 +46,20 @@ namespace linker.messenger.relay.messenger
private readonly RelayServerMasterTransfer relayServerTransfer; private readonly RelayServerMasterTransfer relayServerTransfer;
private readonly RelayServerValidatorTransfer relayValidatorTransfer; private readonly RelayServerValidatorTransfer relayValidatorTransfer;
private readonly ISerializer serializer; private readonly ISerializer serializer;
private readonly ICdkeyServerStore cdkeyStore;
private readonly IRelayServerStore relayServerStore; private readonly IRelayServerStore relayServerStore;
private readonly RelayServerNodeTransfer relayServerNodeTransfer; 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.messengerSender = messengerSender;
this.signCaching = signCaching; this.signCaching = signCaching;
this.relayServerTransfer = relayServerTransfer; this.relayServerTransfer = relayServerTransfer;
this.relayValidatorTransfer = relayValidatorTransfer; this.relayValidatorTransfer = relayValidatorTransfer;
this.serializer = serializer; this.serializer = serializer;
this.cdkeyStore = cdkeyStore;
this.relayServerStore = relayServerStore; this.relayServerStore = relayServerStore;
this.relayServerNodeTransfer = relayServerNodeTransfer; this.relayServerNodeTransfer = relayServerNodeTransfer;
this.relayServerUser2NodeStore = relayServerUser2NodeStore;
} }
/// <summary> /// <summary>
@@ -70,39 +70,40 @@ namespace linker.messenger.relay.messenger
public async Task RelayTest(IConnection connection) public async Task RelayTest(IConnection connection)
{ {
RelayTestInfo info = serializer.Deserialize<RelayTestInfo>(connection.ReceiveRequestWrap.Payload.Span); 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) if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) == false)
{ {
connection.Write(Helper.FalseArray); connection.Write(serializer.Serialize(new List<RelayServerNodeReportInfo> { }));
return; return;
} }
string result = await relayValidatorTransfer.Validate(new client.transport.RelayInfo (List<RelayServerNodeReportInfo170> nodes, bool validated) = await GetNodes(new client.transport.RelayInfo170
{ {
SecretKey = info.SecretKey, SecretKey = info.SecretKey,
FromMachineId = info.MachineId, FromMachineId = info.MachineId,
FromMachineName = cache.MachineName, FromMachineName = cache.MachineName,
TransactionId = "test", TransactionId = "test",
TransportName = "test", TransportName = "test",
}, cache, null).ConfigureAwait(false); }, cache, null);
connection.Write(serializer.Serialize(nodes.Select(c => (RelayServerNodeReportInfo)c).ToList()));
connection.Write(data(string.IsNullOrWhiteSpace(result))); }
[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> /// <summary>
@@ -125,15 +126,12 @@ namespace linker.messenger.relay.messenger
info.FromMachineName = from.MachineName; info.FromMachineName = from.MachineName;
RelayAskResultInfo result = new RelayAskResultInfo(); RelayAskResultInfo result = new RelayAskResultInfo();
string error = await relayValidatorTransfer.Validate(info, from, to).ConfigureAwait(false); (List<RelayServerNodeReportInfo170> nodes, bool validated) = await GetNodes(info, from, to);
bool validated = string.IsNullOrWhiteSpace(error); result.Nodes = nodes.Select(c => (RelayServerNodeReportInfo)c).ToList();
result.Nodes = relayServerTransfer.GetNodes(validated).Select(c => (RelayServerNodeReportInfo)c).ToList();
if (result.Nodes.Count > 0) 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)); connection.Write(serializer.Serialize(result));
} }
[MessengerId((ushort)RelayMessengerIds.RelayAsk170)] [MessengerId((ushort)RelayMessengerIds.RelayAsk170)]
@@ -152,22 +150,29 @@ namespace linker.messenger.relay.messenger
info.FromMachineName = from.MachineName; info.FromMachineName = from.MachineName;
RelayAskResultInfo170 result = new RelayAskResultInfo170(); RelayAskResultInfo170 result = new RelayAskResultInfo170();
string error = await relayValidatorTransfer.Validate(info, from, to).ConfigureAwait(false); (result.Nodes, bool validated) = await GetNodes(info, from, to).ConfigureAwait(false);
bool validated = string.IsNullOrWhiteSpace(error);
result.Nodes = relayServerTransfer.GetNodes(validated);
if (result.Nodes.Count > 0) if (result.Nodes.Count > 0)
{ {
List<CdkeyInfo> cdkeys = info.UseCdkey result.FlowingId = relayServerTransfer.AddRelay(from.MachineId, from.MachineName, to.MachineId, to.MachineName, from.GroupId, info.UserId, validated, 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);
} }
connection.Write(serializer.Serialize(result)); 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>
/// 收到中继请求 /// 收到中继请求
/// </summary> /// </summary>
@@ -242,17 +247,31 @@ namespace linker.messenger.relay.messenger
} }
} }
/// <summary> /// <summary>
/// 获取缓存 /// 获取缓存
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <returns></returns> /// <returns></returns>
[MessengerId((ushort)RelayMessengerIds.NodeGetCache)] [MessengerId((ushort)RelayMessengerIds.NodeGetCache)]
public void NodeGetCache(IConnection connection) public async Task NodeGetCache(IConnection connection)
{ {
string key = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span); 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)); connection.Write(serializer.Serialize(cache));
} }
@@ -262,7 +281,7 @@ namespace linker.messenger.relay.messenger
} }
} }
/// <summary> /// <summary>
/// 获取缓存 /// 节点报告
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <returns></returns> /// <returns></returns>
@@ -326,11 +345,89 @@ namespace linker.messenger.relay.messenger
relayServerNodeTransfer.UpdateLastBytes(info); relayServerNodeTransfer.UpdateLastBytes(info);
} }
/// <summary>
/// 检查密钥
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)RelayMessengerIds.CheckKey)] [MessengerId((ushort)RelayMessengerIds.CheckKey)]
public void CheckKey(IConnection connection) public void CheckKey(IConnection connection)
{ {
string key = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span); string key = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
connection.Write(relayServerStore.ValidateSecretKey(key) ? Helper.TrueArray : Helper.FalseArray); 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, CheckKey = 2123,
AddUser2Node = 2124,
DelUser2Node = 2125,
PageUser2Node = 2126,
NodeGetCache186 = 2127,
Max = 2199 Max = 2199
} }
} }

View File

@@ -130,7 +130,7 @@ namespace linker.messenger.relay.server
public long LastTicks { get; set; } 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 string Url { get; set; } = "https://linker-doc.snltty.com";
public TunnelProtocolType AllowProtocol { get; set; } = TunnelProtocolType.Tcp; 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;
using linker.libs.timer; using linker.libs.timer;
using linker.libs.winapis;
using linker.messenger.cdkey; using linker.messenger.cdkey;
using linker.messenger.relay.messenger; using linker.messenger.relay.messenger;
using linker.messenger.relay.server.caching; using linker.messenger.relay.server.caching;
@@ -24,20 +25,23 @@ namespace linker.messenger.relay.server
private readonly IRelayServerMasterStore relayServerMasterStore; private readonly IRelayServerMasterStore relayServerMasterStore;
private readonly ICdkeyServerStore relayServerCdkeyStore; private readonly ICdkeyServerStore relayServerCdkeyStore;
private readonly IMessengerSender messengerSender; private readonly IMessengerSender messengerSender;
private readonly IRelayServerUser2NodeStore relayServerUser2NodeStore;
private readonly ICdkeyServerStore cdkeyServerStore;
public RelayServerMasterTransfer(IRelayServerCaching relayCaching, ISerializer serializer, IRelayServerMasterStore relayServerMasterStore, ICdkeyServerStore relayServerCdkeyStore, IMessengerSender messengerSender, IRelayServerUser2NodeStore relayServerUser2NodeStore, ICdkeyServerStore cdkeyServerStore)
public RelayServerMasterTransfer(IRelayServerCaching relayCaching, ISerializer serializer, IRelayServerMasterStore relayServerMasterStore, ICdkeyServerStore relayServerCdkeyStore, IMessengerSender messengerSender)
{ {
this.relayCaching = relayCaching; this.relayCaching = relayCaching;
this.serializer = serializer; this.serializer = serializer;
this.relayServerMasterStore = relayServerMasterStore; this.relayServerMasterStore = relayServerMasterStore;
this.relayServerCdkeyStore = relayServerCdkeyStore; this.relayServerCdkeyStore = relayServerCdkeyStore;
this.messengerSender = messengerSender; this.messengerSender = messengerSender;
this.relayServerUser2NodeStore = relayServerUser2NodeStore;
this.cdkeyServerStore = cdkeyServerStore;
TrafficTask(); 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); ulong flowingId = Interlocked.Increment(ref relayFlowingId);
@@ -49,8 +53,9 @@ namespace linker.messenger.relay.server
ToId = toid, ToId = toid,
ToName = toName, ToName = toName,
GroupId = groupid, GroupId = groupid,
UserId = userid,
Validated = validated, Validated = validated,
Cdkey = cdkeys UseCdkey = useCdkey,
}; };
bool added = relayCaching.TryAdd($"{fromid}->{toid}->{flowingId}", cache, 15000); bool added = relayCaching.TryAdd($"{fromid}->{toid}->{flowingId}", cache, 15000);
if (added == false) return 0; if (added == false) return 0;
@@ -58,9 +63,16 @@ namespace linker.messenger.relay.server
return flowingId; 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> /// <summary>
/// 设置节点 /// 设置节点
@@ -113,12 +125,17 @@ namespace linker.messenger.relay.server
/// </summary> /// </summary>
/// <param name="validated">是否已认证</param> /// <param name="validated">是否已认证</param>
/// <returns></returns> /// <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 var result = reports.Values
.Where(c => c.Public || validated)
.Where(c => Environment.TickCount64 - c.LastTicks < 15000) .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); .OrderByDescending(c => c.LastTicks);
return result.OrderByDescending(x => x.MaxConnection == 0 ? int.MaxValue : x.MaxConnection) return result.OrderByDescending(x => x.MaxConnection == 0 ? int.MaxValue : x.MaxConnection)
@@ -148,7 +165,6 @@ namespace linker.messenger.relay.server
/// <returns></returns> /// <returns></returns>
public void AddTraffic(RelayTrafficUpdateInfo info) public void AddTraffic(RelayTrafficUpdateInfo info)
{ {
if (info.SecretKey != relayServerMasterStore.Master.SecretKey) return;
if (info.Dic.Count == 0) return; if (info.Dic.Count == 0) return;
trafficQueue.Enqueue(info.Dic); trafficQueue.Enqueue(info.Dic);

View File

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

View File

@@ -1,10 +1,8 @@
using System.Net.Sockets; using System.Net.Sockets;
using System.Buffers;
using linker.libs.extends; using linker.libs.extends;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Net; using System.Net;
using linker.libs; using linker.libs;
using System;
using System.Text; using System.Text;
using linker.libs.timer; using linker.libs.timer;
using linker.messenger.cdkey; using linker.messenger.cdkey;
@@ -417,9 +415,11 @@ namespace linker.messenger.relay.server
public string ToId { get; set; } public string ToId { get; set; }
public string ToName { get; set; } public string ToName { get; set; }
public string GroupId { get; set; } public string GroupId { get; set; }
public string UserId { get; set; }
public bool Validated { 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 public sealed class RelayTrafficCacheInfo
{ {

View File

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

View File

@@ -73,6 +73,12 @@ namespace linker.messenger.serializer.memorypack
MemoryPackFormatterProvider.Register(new RelayServerNodeUpdateInfoFormatter()); MemoryPackFormatterProvider.Register(new RelayServerNodeUpdateInfoFormatter());
MemoryPackFormatterProvider.Register(new RelayServerNodeUpdateWrapInfoFormatter()); 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 CdkeyInfoFormatter());
MemoryPackFormatterProvider.Register(new CdkeyStoreInfoFormatter()); MemoryPackFormatterProvider.Register(new CdkeyStoreInfoFormatter());
MemoryPackFormatterProvider.Register(new CdkeyPageRequestInfoFormatter()); 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) public static ServiceProvider UseSignInClient(this ServiceProvider serviceProvider)
{ {
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>(); SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { signInArgsTransfer.AddArgs(new List<ISignInArgsClient> {
serviceProvider.GetService<SignInArgsGroupPasswordClient>(), serviceProvider.GetService<SignInArgsGroupPasswordClient>(),
serviceProvider.GetService<SignInArgsSecretKeyClient>(), serviceProvider.GetService<SignInArgsSecretKeyClient>(),
serviceProvider.GetService<SignInArgsMachineKeyClient>(), serviceProvider.GetService<SignInArgsMachineKeyClient>(),
@@ -44,7 +44,7 @@ namespace linker.messenger.signin
IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>(); IMessengerResolver messengerResolver = serviceProvider.GetService<IMessengerResolver>();
messengerResolver.AddMessenger(new List<IMessenger> { serviceProvider.GetService<SignInClientMessenger>() }); 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>() }); exRouteTransfer.AddExRoutes(new List<IExRoute> { serviceProvider.GetService<SignInExRoute>() });
LoggerHelper.Instance.Info($"start signin"); LoggerHelper.Instance.Info($"start signin");
@@ -76,7 +76,7 @@ namespace linker.messenger.signin
SignInServerCaching signInServerCaching = serviceProvider.GetService<SignInServerCaching>(); SignInServerCaching signInServerCaching = serviceProvider.GetService<SignInServerCaching>();
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>(); SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>();
signInArgsTransfer.AddArgs(new List<ISignInArgs> { signInArgsTransfer.AddArgs(new List<ISignInArgsServer> {
serviceProvider.GetService<SignInArgsGroupPasswordServer>(), serviceProvider.GetService<SignInArgsGroupPasswordServer>(),
serviceProvider.GetService<SignInArgsSecretKeyServer>(), serviceProvider.GetService<SignInArgsSecretKeyServer>(),
serviceProvider.GetService<SignInArgsMachineKeyServer>(), serviceProvider.GetService<SignInArgsMachineKeyServer>(),

View File

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

View File

@@ -3,47 +3,34 @@
/// <summary> /// <summary>
/// 添加分组密码 /// 添加分组密码
/// </summary> /// </summary>
public sealed class SignInArgsGroupPasswordClient : ISignInArgs public sealed class SignInArgsGroupPasswordClient : ISignInArgsClient
{ {
public string Name => "group"; public string Name => "group";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ISignInClientStore signInClientStore; private readonly ISignInClientStore signInClientStore;
public SignInArgsGroupPasswordClient(ISignInClientStore signInClientStore) public SignInArgsGroupPasswordClient(ISignInClientStore signInClientStore)
{ {
this.signInClientStore = signInClientStore; this.signInClientStore = signInClientStore;
} }
public async Task<string> Invoke(string host, Dictionary<string, string> args) public async Task<string> Invoke(string host, Dictionary<string, string> args)
{ {
args.TryAdd("signin-gpwd", signInClientStore.Group.Password); args.TryAdd("signin-gpwd", signInClientStore.Group.Password);
await Task.CompletedTask.ConfigureAwait(false); await Task.CompletedTask.ConfigureAwait(false);
return string.Empty; return string.Empty;
} }
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
} }
/// <summary> /// <summary>
/// 验证分组密码 /// 验证分组密码
/// </summary> /// </summary>
public sealed class SignInArgsGroupPasswordServer : ISignInArgs public sealed class SignInArgsGroupPasswordServer : ISignInArgsServer
{ {
public string Name => "group"; public string Name => "group";
public SignInArgsLevel Level => SignInArgsLevel.Hight;
public SignInArgsGroupPasswordServer() public SignInArgsGroupPasswordServer()
{ {
} }
public async Task<string> Invoke(string host, Dictionary<string, string> args)
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
/// <summary> /// <summary>
/// 验证参数 /// 验证参数
/// </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> /// <summary>
/// 给登录加一个唯一ID的参数 /// 给登录加一个唯一ID的参数
/// </summary> /// </summary>
public sealed class SignInArgsMachineKeyClient : ISignInArgs public sealed class SignInArgsMachineKeyClient : ISignInArgsClient
{ {
public string Name => "machineId"; public string Name => "machineId";
public SignInArgsLevel Level => SignInArgsLevel.Default;
public async Task<string> Invoke(string host, Dictionary<string, string> args) public async Task<string> Invoke(string host, Dictionary<string, string> args)
{ {
string machineKey = SystemIdHelper.GetSystemId(); string machineKey = SystemIdHelper.GetSystemId();
@@ -19,30 +20,17 @@ namespace linker.messenger.signin.args
} }
args.TryAdd("machineKey", machineKey); args.TryAdd("machineKey", machineKey);
return await Task.FromResult(string.Empty);
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>
/// 验证登录唯一参数 /// 验证登录唯一参数
/// </summary> /// </summary>
public sealed class SignInArgsMachineKeyServer : ISignInArgs public sealed class SignInArgsMachineKeyServer : ISignInArgsServer
{ {
public string Name => "machineId"; public string Name => "machineId";
public async Task<string> Invoke(string host, Dictionary<string, string> args) public SignInArgsLevel Level => SignInArgsLevel.Default;
{
await Task.CompletedTask.ConfigureAwait(false);
return string.Empty;
}
/// <summary> /// <summary>
/// 验证参数 /// 验证参数

View File

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

View File

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

View File

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

View File

@@ -70,6 +70,7 @@ namespace linker.messenger.store.file
serviceCollection.AddSingleton<IRelayServerStore, RelayServerStore>(); serviceCollection.AddSingleton<IRelayServerStore, RelayServerStore>();
serviceCollection.AddSingleton<IRelayServerNodeStore, RelayServerNodeStore>(); serviceCollection.AddSingleton<IRelayServerNodeStore, RelayServerNodeStore>();
serviceCollection.AddSingleton<IRelayServerMasterStore, RelayServerMasterStore>(); serviceCollection.AddSingleton<IRelayServerMasterStore, RelayServerMasterStore>();
serviceCollection.AddSingleton<IRelayServerUser2NodeStore, RelayServerUser2NodeStore>();
serviceCollection.AddSingleton<ICdkeyServerStore, CdkeyServerStore>(); serviceCollection.AddSingleton<ICdkeyServerStore, CdkeyServerStore>();
serviceCollection.AddSingleton<ICdkeyClientStore, CdkeyClientStore>(); 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) 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); 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) 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) public static ServiceProvider UseTunnelClient(this ServiceProvider serviceProvider)
{ {
SignInArgsTransfer signInArgsTransfer = serviceProvider.GetService<SignInArgsTransfer>(); 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>(); TunnelNetworkTransfer tunnelNetworkTransfer = serviceProvider.GetService<TunnelNetworkTransfer>();

View File

@@ -1,12 +1,13 @@
using linker.messenger.signin.args; using linker.messenger.signin.args;
using linker.messenger.signin;
using linker.libs.extends; using linker.libs.extends;
namespace linker.messenger.tunnel namespace linker.messenger.tunnel
{ {
public sealed class SignInArgsNet : ISignInArgs public sealed class SignInArgsNet : ISignInArgsClient
{ {
public string Name => "tunnelNet"; public string Name => "tunnelNet";
public SignInArgsLevel Level => SignInArgsLevel.Default;
private readonly ITunnelClientStore tunnelClientStore; private readonly ITunnelClientStore tunnelClientStore;
public SignInArgsNet(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) 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()); args.TryAdd("tunnelNet", new SignInArgsNetInfo { Lat = tunnelClientStore.Network.Net.Lat, Lon = tunnelClientStore.Network.Net.Lon, City = tunnelClientStore.Network.Net.City }.ToJson());
return await Task.FromResult(string.Empty);
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;
} }
} }
public sealed class SignInArgsNetInfo public sealed class SignInArgsNetInfo

View File

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

View File

@@ -23,4 +23,14 @@ export const relayUpdateNode = (data) => {
} }
export const checkRelayKey = (key) => { export const checkRelayKey = (key) => {
return sendWebsocketMsg('relay/checkkey',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.relayDefault': '默认',
'server.relaySetDefault': '设置默认中继节点', 'server.relaySetDefault': '设置默认中继节点',
'server.relaySetDefaultText': '不选择则所有客户端', '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.cdkeySecretKey': 'Cdkey密钥',
'server.cdkeyText': '密钥正确时可管理cdkey', 'server.cdkeyText': '密钥正确时可管理cdkey',

View File

@@ -1,7 +1,7 @@
<template> <template>
<el-form-item :label="$t('server.cdkeySecretKey')"> <el-form-item :label="$t('server.cdkeySecretKey')">
<div class="flex"> <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> <Sync class="mgl-1" name="CdkeySecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.cdkeyText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.cdkeyText')}}</span>
</div> </div>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.relaySecretKey')"> <el-form-item :label="$t('server.relaySecretKey')">
<div > <div >
<div class="flex"> <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> <Sync class="mgl-1" name="RelaySecretKey"></Sync>
</div> </div>
<div class="flex"> <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 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" /> <el-checkbox v-model="state.list.Disabled" :label="$t('server.relayDisable')" @change="handleSave" />
</div> </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}} {{$t('server.relayNodes')}} : {{state.nodes.length}}
</a> </a>
<User2Node :keyState="state.keyState"></User2Node>
<div class="mgr-1" :title="$t('server.relayUseCdkeyTitle')"> <div class="mgr-1" :title="$t('server.relayUseCdkeyTitle')">
<el-checkbox v-model="state.list.UseCdkey" :label="$t('server.relayUseCdkey')" @change="handleSave" /> <el-checkbox v-model="state.list.UseCdkey" :label="$t('server.relayUseCdkey')" @change="handleSave" />
</div> </div>
<Cdkey type="Relay"></Cdkey> <Cdkey type="Relay"></Cdkey>
<Nodes v-if="state.showModes" v-model="state.showModes" :data="state.nodes" :keyState="state.keyState"></Nodes>
</div> </div>
</div> </div>
</el-form-item>
</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> </template>
<script> <script>
import { checkRelayKey, getDefault, setRelayServers, setRelaySubscribe, syncDefault } from '@/apis/relay'; import { checkRelayKey, setRelayServers, setRelaySubscribe } from '@/apis/relay';
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus'; 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 { useI18n } from 'vue-i18n';
import Sync from '../sync/Index.vue' import Sync from '../sync/Index.vue'
import Ids from '../sync/Ids.vue';
import Cdkey from './cdkey/Index.vue' import Cdkey from './cdkey/Index.vue'
import EditNode from './EditNode.vue'; import Nodes from './relay/Nodes.vue';
import { Edit,ArrowDown } from '@element-plus/icons-vue'; import User2Node from './user2node/Index.vue';
export default { export default {
components:{Sync,Ids,Cdkey,EditNode,Edit,ArrowDown}, components:{Sync,Cdkey,Nodes,User2Node},
setup(props) { setup(props) {
const {t} = useI18n(); const {t} = useI18n();
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const state = reactive({ const state = reactive({
list:globalData.value.config.Client.Relay.Server, list:globalData.value.config.Client.Relay.Server,
show:false, showModes:false,
nodes:[], nodes:[],
timer:0, timer:0,
showEdit:false,
current:{},
keyState:false, 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 = ()=>{ const handleSave = ()=>{
setRelayServers(state.list).then(()=>{ setRelayServers(state.list).then(()=>{
ElMessage.success(t('common.oper')); ElMessage.success(t('common.oper'));
@@ -155,10 +55,14 @@ export default {
}); });
handleCheckKey(); handleCheckKey();
} }
const nodes = ref([]);
provide('nodes',nodes);
const _setRelaySubscribe = ()=>{ const _setRelaySubscribe = ()=>{
clearTimeout(state.timer); clearTimeout(state.timer);
setRelaySubscribe().then((res)=>{ setRelaySubscribe().then((res)=>{
state.nodes = res; state.nodes = res;
nodes.value = res;
state.timer = setTimeout(_setRelaySubscribe,1000); state.timer = setTimeout(_setRelaySubscribe,1000);
}).catch(()=>{ }).catch(()=>{
state.timer = setTimeout(_setRelaySubscribe,1000); state.timer = setTimeout(_setRelaySubscribe,1000);
@@ -170,37 +74,15 @@ export default {
}).catch(()=>{}); }).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(()=>{ onMounted(()=>{
_setRelaySubscribe(); _setRelaySubscribe();
handleCheckKey(); handleCheckKey();
getDefault().then((res)=>{
state.syncData.Key = res.Key || '';
state.syncData.Value = res.Value || 0;
});
}); });
onUnmounted(()=>{ onUnmounted(()=>{
clearTimeout(state.timer); clearTimeout(state.timer);
}); });
return {globalData,state,handleSave,handleEdit,domIds,handleShowSync,handleSync} return {globalData,state,handleSave}
} }
} }
</script> </script>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.sforwardSecretKey')"> <el-form-item :label="$t('server.sforwardSecretKey')">
<div> <div>
<div class="flex"> <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> <Sync class="mgl-1" name="SForwardSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.sforwardText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.sforwardText')}}</span>
</div> </div>

View File

@@ -6,26 +6,26 @@
<el-form label-width="auto" :label-position="state.position"> <el-form label-width="auto" :label-position="state.position">
<el-form-item :label="$t('server.messengerAddr')"> <el-form-item :label="$t('server.messengerAddr')">
<div class="flex"> <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> <Sync class="mgl-1" name="SignInServer"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="`${$t('server.messengerAddr')}1`"> <el-form-item :label="`${$t('server.messengerAddr')}1`">
<div class="flex"> <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> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('server.messengerSecretKey')"> <el-form-item :label="$t('server.messengerSecretKey')">
<div class="flex"> <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> <Sync class="mgl-1" name="SignInSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerSecretKeyText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerSecretKeyText')}}</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item :label="$t('server.messengerUserId')"> <el-form-item :label="$t('server.messengerUserId')">
<div class="flex"> <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> <Sync class="mgl-1" name="SignInUserId"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerUserIdText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.messengerUserIdText')}}</span>
</div> </div>

View File

@@ -2,7 +2,7 @@
<el-form-item :label="$t('server.updaterSecretKey')"> <el-form-item :label="$t('server.updaterSecretKey')">
<div > <div >
<div class="flex"> <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> <Sync class="mgl-1" name="UpdaterSecretKey"></Sync>
<span class="mgl-1" v-if="globalData.isPc">{{$t('server.updaterText')}}</span> <span class="mgl-1" v-if="globalData.isPc">{{$t('server.updaterText')}}</span>
</div> </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 v1.8.5
2025-06-25 16:12:07 2025-06-26 17:46:49
1. 一些累计更新 1. 一些累计更新
2. 备用信标服务器 2. 备用信标服务器
3. 设置默认中继节点 3. 设置默认中继节点