diff --git a/NewLife.Remoting/Clients/ClientBase.cs b/NewLife.Remoting/Clients/ClientBase.cs
index b65266e..aaf0ff2 100644
--- a/NewLife.Remoting/Clients/ClientBase.cs
+++ b/NewLife.Remoting/Clients/ClientBase.cs
@@ -5,6 +5,7 @@ using System.Net.NetworkInformation;
using System.Reflection;
using NewLife.Caching;
using NewLife.Log;
+using NewLife.Reflection;
using NewLife.Remoting.Models;
using NewLife.Remoting.Services;
using NewLife.Security;
@@ -36,7 +37,7 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
public Int32 Delay { get; set; }
/// <summary>最大失败数。超过该数时,新的数据将被抛弃,默认120</summary>
- public Int32 MaxFails { get; set; } = 120;
+ public Int32 MaxFails { get; set; } = 1 * 24 * 60;
private readonly ConcurrentDictionary<String, Delegate> _commands = new(StringComparer.OrdinalIgnoreCase);
/// <summary>命令集合</summary>
@@ -118,6 +119,10 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
}
}
+ /// <summary>设置令牌。派生类可重定义逻辑</summary>
+ /// <param name="token"></param>
+ protected virtual void SetToken(String? token) { }
+
/// <summary>获取相对于服务器的当前时间,避免两端时间差</summary>
/// <returns></returns>
public DateTime GetNow() => DateTime.Now.Add(_span);
@@ -134,6 +139,8 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
{
var request = BuildLoginRequest();
+ // 登录前清空令牌,避免服务端使用上一次信息
+ SetToken(null);
Logined = false;
var rs = await LoginAsync(request);
@@ -148,9 +155,11 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
FixTime(rs.Time, rs.Time);
+ // 登录后设置用于用户认证的token
+ SetToken(rs.Token);
Logined = true;
- OnLogined?.Invoke(this, new LoginEventArgs { Request = request, Response = rs });
+ OnLogined?.Invoke(this, new(request, rs));
StartTimer();
@@ -189,10 +198,13 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
/// <returns></returns>
public virtual LoginRequest BuildLoginRequest()
{
+ var asm = AssemblyX.Entry ?? AssemblyX.Create(Assembly.GetExecutingAssembly());
var info = new LoginRequest
{
Code = Code,
Secret = Secret.IsNullOrEmpty() ? null : PasswordProvider.Hash(Secret),
+ ClientId = $"{NetHelper.MyIP()}@{Process.GetCurrentProcess().Id}",
+ Version = asm?.FileVersion,
};
return info;
@@ -212,6 +224,9 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
{
var rs = await LogoutAsync(reason);
+ // 更新令牌
+ SetToken(rs?.Token);
+
StopTimer();
Logined = false;
@@ -248,13 +263,48 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
{
var request = BuildPingRequest();
- var rs = await PingAsync(request);
- if (rs != null)
+ // 如果网络不可用,直接保存到队列
+ if (!NetworkInterface.GetIsNetworkAvailable())
{
- // 由服务器改变采样频率
- if (rs.Period > 0 && _timer != null) _timer.Period = rs.Period * 1000;
+ if (_fails.Count < MaxFails) _fails.Enqueue(request);
+ return null;
+ }
- FixTime(rs.Time, rs.ServerTime);
+ PingResponse? rs = null;
+ try
+ {
+ rs = await PingAsync(request);
+ if (rs != null)
+ {
+ // 由服务器改变采样频率
+ if (rs.Period > 0 && _timer != null) _timer.Period = rs.Period * 1000;
+
+ FixTime(rs.Time, rs.ServerTime);
+
+ // 更新令牌。即将过期时,服务端会返回新令牌
+ if (!rs.Token.IsNullOrEmpty()) SetToken(rs.Token);
+
+ // 推队列
+ if (rs.Commands != null && rs.Commands.Length > 0)
+ {
+ foreach (var model in rs.Commands)
+ {
+ await ReceiveCommand(model);
+ }
+ }
+ }
+ }
+ catch
+ {
+ if (_fails.Count < MaxFails) _fails.Enqueue(request);
+
+ throw;
+ }
+
+ // 上报正常,处理历史,失败则丢弃
+ while (_fails.TryDequeue(out var info))
+ {
+ await PingAsync(info);
}
return rs;
@@ -264,7 +314,7 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
span?.SetError(ex, null);
var ex2 = ex.GetTrue();
- if (ex2 is ApiException aex && (aex.Code == 402 || aex.Code == 403))
+ if (ex2 is ApiException aex && (aex.Code == ApiCode.Unauthorized || aex.Code == ApiCode.Forbidden))
{
WriteLog("重新登录");
await Login();
@@ -311,9 +361,9 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
{
if (_timer == null)
{
- _timer = new TimerX(OnPing, null, 3_000, 60_000, "Device") { Async = true };
- _timerUpgrade = new TimerX(s => Upgrade(), null, 5_000, 600_000, "Device") { Async = true };
- _eventTimer = new TimerX(DoPostEvent, null, 3_000, 60_000, "Device") { Async = true };
+ _timer = new TimerX(OnPing, null, 3_000, 60_000, "Client") { Async = true };
+ _timerUpgrade = new TimerX(s => Upgrade(), null, 5_000, 600_000, "Client") { Async = true };
+ _eventTimer = new TimerX(DoPostEvent, null, 3_000, 60_000, "Client") { Async = true };
}
}
}
@@ -335,6 +385,55 @@ public abstract class ClientBase : DisposeBase, ICommandClient, IEventProvider,
/// <returns></returns>
protected virtual Task OnPing(Object state) => Ping();
+ async Task ReceiveCommand(CommandModel model)
+ {
+ if (model == null) return;
+
+ // 去重,避免命令被重复执行
+ if (!_cache.Add($"cmd:{model.Id}", model, 3600)) return;
+
+ // 埋点,建立调用链
+ using var span = Tracer?.NewSpan("cmd:" + model.Command, model);
+ if (!model.TraceId.IsNullOrEmpty()) span?.Detach(model.TraceId);
+ try
+ {
+ //todo 有效期判断可能有隐患,现在只是假设服务器和客户端在同一个时区,如果不同,可能会出现问题
+ var now = GetNow();
+ XTrace.WriteLine("Got Command: {0}", model.ToJson());
+ if (model.Expire.Year < 2000 || model.Expire > now)
+ {
+ // 延迟执行
+ var ts = model.StartTime - now;
+ if (ts.TotalMilliseconds > 0)
+ {
+ TimerX.Delay(s =>
+ {
+ _ = OnReceiveCommand(model);
+ }, (Int32)ts.TotalMilliseconds);
+
+ var reply = new CommandReplyModel
+ {
+ Id = model.Id,
+ Status = CommandStatus.处理中,
+ Data = $"已安排计划执行 {model.StartTime.ToFullString()}"
+ };
+ await CommandReply(reply);
+ }
+ else
+ await OnReceiveCommand(model);
+ }
+ else
+ {
+ var reply = new CommandReplyModel { Id = model.Id, Status = CommandStatus.取消 };
+ await CommandReply(reply);
+ }
+ }
+ catch (Exception ex)
+ {
+ span?.SetError(ex, null);
+ }
+ }
+
/// <summary>触发收到命令的动作</summary>
/// <param name="model"></param>
protected virtual async Task OnReceiveCommand(CommandModel model)
diff --git a/NewLife.Remoting/Clients/HttpClientBase.cs b/NewLife.Remoting/Clients/HttpClientBase.cs
index 993818e..2a1736c 100644
--- a/NewLife.Remoting/Clients/HttpClientBase.cs
+++ b/NewLife.Remoting/Clients/HttpClientBase.cs
@@ -75,6 +75,15 @@ public class HttpClientBase : ClientBase
protected override Service GetService() => Current = base.GetService();
}
+
+ /// <summary>设置令牌。派生类可重定义逻辑</summary>
+ /// <param name="token"></param>
+ protected override void SetToken(String? token)
+ {
+ base.SetToken(token);
+
+ if (_client != null) _client.Token = token;
+ }
#endregion
#region 登录
diff --git a/NewLife.Remoting/Clients/RpcClientBase.cs b/NewLife.Remoting/Clients/RpcClientBase.cs
index fb17943..a94432f 100644
--- a/NewLife.Remoting/Clients/RpcClientBase.cs
+++ b/NewLife.Remoting/Clients/RpcClientBase.cs
@@ -51,6 +51,15 @@ public class RpcClientBase : ClientBase
protected override async Task<Object?> OnLoginAsync(ISocketClient client, Boolean force) => await InvokeWithClientAsync<Object>(client, Client.Prefix + "/Login", Client.BuildLoginRequest());
}
+
+ /// <summary>设置令牌。派生类可重定义逻辑</summary>
+ /// <param name="token"></param>
+ protected override void SetToken(String? token)
+ {
+ base.SetToken(token);
+
+ if (_client != null) _client.Token = token;
+ }
#endregion
#region 登录
diff --git a/NewLife.Remoting/Models/LoginEventArgs.cs b/NewLife.Remoting/Models/LoginEventArgs.cs
index 38696d5..f6433bf 100644
--- a/NewLife.Remoting/Models/LoginEventArgs.cs
+++ b/NewLife.Remoting/Models/LoginEventArgs.cs
@@ -1,11 +1,11 @@
namespace NewLife.Remoting.Models;
/// <summary>登录事件参数</summary>
-public class LoginEventArgs : EventArgs
+public class LoginEventArgs(LoginRequest? request, LoginResponse? response) : EventArgs
{
/// <summary>请求</summary>
- public LoginRequest? Request { get; set; }
+ public LoginRequest? Request { get; set; } = request;
/// <summary>响应</summary>
- public LoginResponse? Response { get; set; }
+ public LoginResponse? Response { get; set; } = response;
}
diff --git a/NewLife.Remoting/Models/LoginRequest.cs b/NewLife.Remoting/Models/LoginRequest.cs
index 1581200..03b0649 100644
--- a/NewLife.Remoting/Models/LoginRequest.cs
+++ b/NewLife.Remoting/Models/LoginRequest.cs
@@ -9,5 +9,11 @@ public class LoginRequest
/// <summary>密钥</summary>
public String? Secret { get; set; }
+
+ /// <summary>实例。应用可能多实例部署,ip@proccessid</summary>
+ public String? ClientId { get; set; }
+
+ /// <summary>版本</summary>
+ public String? Version { get; set; }
#endregion
}
diff --git a/NewLife.Remoting/NewLife.Remoting.csproj b/NewLife.Remoting/NewLife.Remoting.csproj
index c13e62f..5827e3a 100644
--- a/NewLife.Remoting/NewLife.Remoting.csproj
+++ b/NewLife.Remoting/NewLife.Remoting.csproj
@@ -38,6 +38,7 @@
</PropertyGroup>
<ItemGroup>
+ <Compile Remove="Clients\WsClientBase.cs" />
<Compile Remove="IpHelper.cs" />
</ItemGroup>
@@ -49,7 +50,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
+ <PackageReference Include="NewLife.Core" Version="10.10.2024.501" />
</ItemGroup>
<ItemGroup>
diff --git a/Samples/Zero.RpcServer/Zero.RpcServer.csproj b/Samples/Zero.RpcServer/Zero.RpcServer.csproj
index d1ccd59..b6ff9b9 100644
--- a/Samples/Zero.RpcServer/Zero.RpcServer.csproj
+++ b/Samples/Zero.RpcServer/Zero.RpcServer.csproj
@@ -20,9 +20,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="NewLife.Redis" Version="5.6.2024.101" />
- <PackageReference Include="NewLife.Stardust" Version="2.9.2024.101" />
- <PackageReference Include="NewLife.XCode" Version="11.11.2024.402" />
+ <PackageReference Include="NewLife.Redis" Version="5.6.2024.402" />
+ <PackageReference Include="NewLife.Stardust" Version="2.9.2024.402" />
+ <PackageReference Include="NewLife.XCode" Version="11.12.2024.503" />
</ItemGroup>
<ItemGroup>
diff --git a/Test/Test.csproj b/Test/Test.csproj
index 4f021c1..a018cd7 100644
--- a/Test/Test.csproj
+++ b/Test/Test.csproj
@@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
+ <PackageReference Include="NewLife.Core" Version="10.10.2024.501" />
</ItemGroup>
<ItemGroup>
diff --git a/XUnitTest/XUnitTest.csproj b/XUnitTest/XUnitTest.csproj
index a09bf63..debf0aa 100644
--- a/XUnitTest/XUnitTest.csproj
+++ b/XUnitTest/XUnitTest.csproj
@@ -12,7 +12,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Moq" Version="4.20.70" />
- <PackageReference Include="NewLife.Core" Version="10.10.2024.427-beta2349" />
+ <PackageReference Include="NewLife.Core" Version="10.10.2024.501" />
<PackageReference Include="NewLife.UnitTest" Version="1.0.2023.1204" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">