节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
5.56 KiB
Stardust
using System.Diagnostics;
using NewLife;
using Stardust.Models;

namespace StarAgent.Managers;

/// <summary>托管部署策略</summary>
/// <remarks>
/// 解压到工作目录,由外部宿主托管运行。
/// 适用于IIS托管的Web应用、前端静态站点等场景。
/// 由外部宿主(如IIS、Nginx)负责运行应用。
/// 包含IIS特殊处理:创建app_offline.htm使网站离线,更新完成后删除。
/// </remarks>
public class HostedStrategy : DeployStrategyBase
{
    #region 常量
    /// <summary>IIS 应用离线页面内容</summary>
    private const String AppOfflineHtml = "<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><title>应用维护中</title></head><body><h1>应用正在更新,请稍候...</h1></body></html>";

    /// <summary>IIS web.config 文件名</summary>
    private const String WebConfigFileName = "web.config";

    /// <summary>IIS app_offline.htm 文件名</summary>
    private const String AppOfflineFileName = "app_offline.htm";

    /// <summary>备份文件扩展名</summary>
    private const String BackupExtension = ".bak";

    /// <summary>IIS 释放文件锁等待时间(毫秒)</summary>
    private const Int32 IisFileReleaseDelayMs = 1000;
    #endregion

    /// <summary>部署模式</summary>
    public override DeployMode Mode => DeployMode.Hosted;

    /// <summary>是否需要守护进程</summary>
    public override Boolean NeedGuardian => false;

    /// <summary>是否允许接管已存在的进程</summary>
    public override Boolean AllowTakeOver => false;

    /// <summary>解压部署包</summary>
    /// <param name="context">部署上下文</param>
    /// <returns>是否成功</returns>
    public override Boolean Extract(DeployContext context)
    {
        using var span = Tracer?.NewSpan("Deploy-Extract", new { context.Name, context.ZipFile, context.WorkingDirectory });

        var fi = RetrieveZip(context);
        if (fi == null) return false;

        var workDir = context.WorkingDirectory;
        context.WriteLog("Hosted mode: extract to the working directory: {0}", workDir);
        context.WriteLog("An external host (such as IIS/Nginx) will manage the application");

        // 检测是否为IIS部署
        var isIisDeploy = false;
        var webConfigPath = workDir.CombinePath(WebConfigFileName);
        var webConfigBackupPath = "";
        var appOfflinePath = "";

        if (Runtime.Windows && File.Exists(webConfigPath))
        {
            isIisDeploy = true;
            context.WriteLog("web.config detected; treating this as an IIS deployment");
        }

        // IIS部署策略:创建app_offline.htm确保网站离线
        if (isIisDeploy)
        {
            appOfflinePath = workDir.CombinePath(AppOfflineFileName);
            try
            {
                context.WriteLog("Creating app_offline.htm to take the site offline");
                File.WriteAllText(appOfflinePath, AppOfflineHtml);

                // 备份并删除web.config,进一步确保文件不被占用
                webConfigBackupPath = webConfigPath + BackupExtension;
                context.WriteLog("Backing up web.config to {0}", webConfigBackupPath);
                File.Copy(webConfigPath, webConfigBackupPath, true);
                File.Delete(webConfigPath);

                // 等待IIS释放文件锁
                Thread.Sleep(IisFileReleaseDelayMs);
            }
            catch (Exception ex)
            {
                context.WriteLog("Failed to create app_offline.htm: {0}", ex.Message);
            }
        }

        try
        {
            // 直接解压到工作目录
            ExtractZip(fi.FullName, workDir, context.Deploy, context.Log);
        }
        finally
        {
            // IIS部署后清理
            if (isIisDeploy)
            {
                try
                {
                    // 恢复web.config
                    if (!webConfigBackupPath.IsNullOrEmpty() && File.Exists(webConfigBackupPath))
                    {
                        // 检查zip包是否包含新的web.config
                        if (File.Exists(webConfigPath))
                        {
                            context.WriteLog("A new web.config was found in the zip package; keeping the new version");
                            File.Delete(webConfigBackupPath);
                        }
                        else
                        {
                            context.WriteLog("Restoring web.config from {0}", webConfigBackupPath);
                            File.Copy(webConfigBackupPath, webConfigPath, true);
                            File.Delete(webConfigBackupPath);
                        }
                    }

                    // 删除app_offline.htm使网站上线
                    if (!appOfflinePath.IsNullOrEmpty() && File.Exists(appOfflinePath))
                    {
                        context.WriteLog("Deleting app_offline.htm to bring the site online");
                        File.Delete(appOfflinePath);
                    }
                }
                catch (Exception ex)
                {
                    context.WriteLog("Failed to clean up IIS deployment files: {0}", ex.Message);
                }
            }
        }

        return true;
    }

    /// <summary>执行应用</summary>
    /// <param name="context">部署上下文</param>
    /// <returns>不运行进程,始终返回null</returns>
    public override Process? Execute(DeployContext context)
    {
        // 托管模式不执行进程
        context.WriteLog("Hosted mode: do not start the process");
        return null;
    }
}