refactor: 枚举移入Models目录,命名空间更新为Rainbow.Entity.Models
大石头 authored at 2026-07-02 12:54:58
7.57 KiB
RainbowBridge
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; }
}