using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using NewLife;
using NewLife.Log;
using NewLife.Net;
namespace Rainbow.Services;
/// <summary>在线升级服务。检测线上更新包,下载解压并重启</summary>
/// <remarks>
/// 参考 CrazyCoder 升级机制:使用 NewLife.Net.Upgrade 检查版本 → 下载 → 解压覆盖 → 重启。
/// 升级源为 GitHub Releases 或自定义 HTTP 服务器的 zip 包。
/// 重启由 StarAgent 守护进程负责(崩溃自动拉起)。
/// </remarks>
public class UpgradeService
{
private readonly String _updateDir;
/// <summary>升级服务日志</summary>
public ILog Log { get; set; } = XTrace.Log;
/// <summary>当前版本号(从程序集读取)</summary>
public String CurrentVersion { get; }
/// <summary>更新服务器地址。默认 GitHub Releases</summary>
public String Server { get; set; } = "https://github.com/NewLifeX/Rainbow/releases";
/// <summary>更新通道。默认 Release</summary>
public String Channel { get; set; } = "Release";
/// <summary>最后检查时间</summary>
public DateTime LastCheck { get; set; }
/// <summary>最后发现的远程版本</summary>
public String? LatestVersion { get; set; }
/// <summary>升级状态</summary>
public UpgradeStatus Status { get; set; } = UpgradeStatus.Idle;
/// <summary>创建升级服务</summary>
/// <param name="updateDir">更新文件存放目录,默认 Update/</param>
public UpgradeService(String? updateDir = null)
{
_updateDir = (updateDir ?? "Update").GetFullPath();
// 从程序集读取当前版本
var asm = Assembly.GetExecutingAssembly();
CurrentVersion = asm.GetName().Version?.ToString() ?? "1.0.0";
}
/// <summary>检查是否有新版本可用</summary>
/// <returns>有新版本返回 true</returns>
public async Task<UpgradeCheckResult> CheckAsync()
{
if (Status == UpgradeStatus.Downloading || Status == UpgradeStatus.Installing)
throw new InvalidOperationException($"当前正在 {Status},无法检查更新");
Status = UpgradeStatus.Checking;
LastCheck = DateTime.Now;
try
{
var url = $"{Server}/{Channel}";
Log.Info("UpgradeService: 检查更新 {0}", url);
var up = new Upgrade
{
Log = Log,
Name = "Rainbow",
Server = url,
UpdatePath = _updateDir.CombinePath("Rainbow")
};
up.DeleteBackup(".");
if (up.Check())
{
LatestVersion = up.Link?.Version?.ToString();
Log.Info("UpgradeService: 发现新版本 {0}(当前 {1})", LatestVersion, CurrentVersion);
return new UpgradeCheckResult
{
HasUpdate = true,
CurrentVersion = CurrentVersion,
LatestVersion = LatestVersion ?? "未知",
UpdateTime = up.Link?.Time ?? DateTime.MinValue,
FileName = up.Link?.Name,
FileUrl = up.Link?.Url
};
}
Log.Info("UpgradeService: 已是最新版本 {0}", CurrentVersion);
return new UpgradeCheckResult
{
HasUpdate = false,
CurrentVersion = CurrentVersion
};
}
catch (Exception ex)
{
Log.Error("UpgradeService: 检查更新失败 {0}", ex.Message);
throw;
}
finally
{
if (Status == UpgradeStatus.Checking)
Status = UpgradeStatus.Idle;
}
}
/// <summary>下载并安装更新</summary>
/// <returns>是否成功</returns>
public async Task<UpgradeInstallResult> InstallAsync()
{
if (String.IsNullOrEmpty(LatestVersion))
throw new InvalidOperationException("请先执行 CheckAsync 检查更新");
Status = UpgradeStatus.Downloading;
try
{
var url = $"{Server}/{Channel}";
var up = new Upgrade
{
Log = Log,
Name = "Rainbow",
Server = url,
UpdatePath = _updateDir.CombinePath("Rainbow")
};
// 备份旧文件(.del)
up.DeleteBackup(".");
// 检查并下载
if (!up.Check())
throw new InvalidOperationException("未检测到可用的更新包");
Log.Info("UpgradeService: 开始下载更新包 {0}", up.Link?.Url);
up.Download();
var sourceFile = up.SourceFile;
if (!File.Exists(sourceFile))
throw new FileNotFoundException($"下载的更新包不存在: {sourceFile}");
var fileInfo = new FileInfo(sourceFile);
Log.Info("UpgradeService: 下载完成 {0} ({1:#,##0} 字节)", sourceFile, fileInfo.Length);
// 安装:解压覆盖
Status = UpgradeStatus.Installing;
Log.Info("UpgradeService: 开始安装更新到 {0}", ".".GetFullPath());
var rs = up.Update();
if (!rs)
{
Log.Error("UpgradeService: 更新安装失败");
return new UpgradeInstallResult { Success = false, Message = "更新安装失败" };
}
Log.Info("UpgradeService: 更新安装完成");
// 重启进程(StarAgent 会自动拉起重启)
Log.Info("UpgradeService: 进程即将退出,StarAgent 将自动重启...");
// 延迟重启,先返回响应给前端
_ = Task.Run(async () =>
{
await Task.Delay(2000);
Environment.Exit(0);
});
return new UpgradeInstallResult
{
Success = true,
Message = "更新安装完成,服务即将重启",
NeedRestart = true
};
}
catch (Exception ex)
{
Log.Error("UpgradeService: 安装更新失败 {0}", ex.Message);
Status = UpgradeStatus.Failed;
return new UpgradeInstallResult { Success = false, Message = ex.Message };
}
}
}
/// <summary>升级状态</summary>
public enum UpgradeStatus
{
/// <summary>空闲</summary>
Idle,
/// <summary>检查中</summary>
Checking,
/// <summary>下载中</summary>
Downloading,
/// <summary>安装中</summary>
Installing,
/// <summary>失败</summary>
Failed
}
/// <summary>升级检查结果</summary>
public class UpgradeCheckResult
{
/// <summary>是否有新版本</summary>
public Boolean HasUpdate { get; set; }
/// <summary>当前版本</summary>
public String CurrentVersion { get; set; } = "";
/// <summary>最新版本</summary>
public String? LatestVersion { get; set; }
/// <summary>更新时间</summary>
public DateTime UpdateTime { get; set; }
/// <summary>文件名</summary>
public String? FileName { get; set; }
/// <summary>文件大小(字节)</summary>
public Int64 FileSize { get; set; }
/// <summary>下载地址</summary>
public String? FileUrl { get; set; }
}
/// <summary>升级安装结果</summary>
public class UpgradeInstallResult
{
/// <summary>是否成功</summary>
public Boolean Success { get; set; }
/// <summary>消息</summary>
public String? Message { get; set; }
/// <summary>是否需要重启</summary>
public Boolean NeedRestart { get; set; }
}
|