diff --git a/NewLife.Remoting/Clients/ClientBase.cs b/NewLife.Remoting/Clients/ClientBase.cs
index 8adc72e..74c199c 100644
--- a/NewLife.Remoting/Clients/ClientBase.cs
+++ b/NewLife.Remoting/Clients/ClientBase.cs
@@ -153,6 +153,8 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
[Features.Upgrade] = prefix + "Upgrade",
[Features.PostEvent] = prefix + "PostEvents"
};
+
+ this.RegisterCommand(prefix + "Upgrade", s => _ = CheckUpgrade(null));
}
/// <summary>初始化</summary>
@@ -698,7 +700,7 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
_timer = new TimerX(OnPing, null, 1000, 60_000, "Client") { Async = true };
if (Features.HasFlag(Features.Upgrade))
- _timerUpgrade = new TimerX(s => Upgrade(null), null, 5_000, 600_000, "Client") { Async = true };
+ _timerUpgrade = new TimerX(CheckUpgrade, null, 5_000, 600_000, "Client") { Async = true };
}
}
}
@@ -763,7 +765,7 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
if (model == null) return null;
// 去重,避免命令被重复执行
- if (!_cache.Add($"cmd:{model.Id}", model, 3600)) return null;
+ if (model.Id > 0 && !_cache.Add($"cmd:{model.Id}", model, 3600)) return null;
// 埋点,建立调用链
using var span = Tracer?.NewSpan("cmd:" + model.Command, model);
@@ -935,6 +937,13 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
#endregion
#region 升级更新
+ private async Task CheckUpgrade(Object? data)
+ {
+ if (!NetworkInterface.GetIsNetworkAvailable()) return;
+
+ await Upgrade(null);
+ }
+
private String? _lastVersion;
/// <summary>获取更新信息。如有更新,则下载解压覆盖并重启应用</summary>
/// <returns></returns>
@@ -953,23 +962,45 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
{
// _lastVersion避免频繁更新同一个版本
WriteLog("发现更新:{0}", info.ToJson(true));
+ this.WriteInfoEvent("Upgrade", $"准备从[{_lastVersion}]更新到[{info.Version}],开始下载 {info.Source}");
// 下载文件包
- ug.Url = info.Source;
+ ug.Url = BuildUrl(info.Source!);
await ug.Download(cancellationToken);
// 检查文件完整性
- if (info.FileHash.IsNullOrEmpty() || ug.CheckFileHash(info.FileHash))
+ if (!info.FileHash.IsNullOrEmpty() && !ug.CheckFileHash(info.FileHash))
+ {
+ this.WriteInfoEvent("Upgrade", "下载完成,哈希校验失败");
+ }
+ else
{
- // 执行更新,解压缩覆盖文件
- var rs = ug.Update();
+ this.WriteInfoEvent("Upgrade", "下载完成,准备解压文件");
+ if (!ug.Extract())
+ {
+ this.WriteInfoEvent("Upgrade", "解压失败");
+ }
+ else
+ {
+ if (info is UpgradeInfo info2 && !info2.Preinstall.IsNullOrEmpty())
+ {
+ this.WriteInfoEvent("Upgrade", "执行预安装脚本");
+
+ ug.Run(info2.Preinstall);
+ }
+
+ this.WriteInfoEvent("Upgrade", "解压完成,准备覆盖文件");
- // 执行前置命令
- if (rs && !info.Executor.IsNullOrEmpty()) ug.Run(info.Executor);
- _lastVersion = info.Version;
+ // 执行更新,解压缩覆盖文件
+ var rs = ug.Update();
- // 强制更新时,马上重启
- if (rs && info.Force) Restart(ug);
+ // 执行前置命令
+ if (rs && !info.Executor.IsNullOrEmpty()) ug.Run(info.Executor);
+ _lastVersion = info.Version;
+
+ // 强制更新时,马上重启
+ if (rs && info.Force) Restart(ug);
+ }
}
}
@@ -981,11 +1012,25 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
protected virtual void Restart(Upgrade upgrade)
{
var asm = Assembly.GetEntryAssembly();
+ if (asm == null) return;
+
+ var name = asm.GetName().Name;
+ if (name.IsNullOrEmpty()) return;
// 重新拉起进程
- var rs = upgrade.Run("dotnet", $"{asm!.Location} -upgrade {Environment.CommandLine}");
+ var rs = upgrade.Run(name, $"-upgrade {Environment.CommandLine}");
+
+ if (rs)
+ {
+ var pid = Process.GetCurrentProcess().Id;
+ this.WriteInfoEvent("Upgrade", "强制更新完成,新进程已拉起,准备退出当前进程!PID=" + pid);
- if (rs) upgrade.KillSelf();
+ upgrade.KillSelf();
+ }
+ else
+ {
+ this.WriteInfoEvent("Upgrade", "强制更新完成,但拉起新进程失败");
+ }
}
/// <summary>放弃更新异步请求。由Upgrade内部调用</summary>
@@ -1000,6 +1045,23 @@ public abstract class ClientBase : DisposeBase, IApiClient, ICommandClient, IEve
#endregion
#region 辅助
+ /// <summary>
+ /// 把Url相对路径格式化为绝对路径。常用于文件下载
+ /// </summary>
+ /// <param name="url"></param>
+ /// <returns></returns>
+ public virtual String BuildUrl(String url)
+ {
+ if (Client is ApiHttpClient client && !url.StartsWithIgnoreCase("http://", "https://"))
+ {
+ var svr = client.Services.FirstOrDefault(e => e.Name == client.Source) ?? client.Services.FirstOrDefault();
+ if (svr != null && svr.Address != null)
+ url = new Uri(svr.Address, url) + "";
+ }
+
+ return url;
+ }
+
/// <summary>从服务提供者(对象容器)创建模型对象</summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>