节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
20.32 KiB
Stardust
using System.Collections.Concurrent;
using System.Diagnostics;
using NewLife;
using NewLife.Agent;
using NewLife.Data;
using NewLife.Log;
using NewLife.Model;
using NewLife.Net;
using NewLife.Remoting;
using NewLife.Remoting.Models;
using NewLife.Threading;
using Stardust;
using Stardust.Managers;
using Stardust.Models;

namespace StarAgent;

[Api(null)]
public class StarService : DisposeBase, IApi
{
    #region 属性
    /// <summary>
    /// 网络会话
    /// </summary>
    public IApiSession Session { get; set; }

    ///// <summary>服务对象</summary>
    //public ServiceBase Service { get; set; }

    ///// <summary>服务主机</summary>
    //public IHost Host { get; set; }

    /// <summary>应用服务管理</summary>
    public ServiceManager Manager { get; set; }

    /// <summary>服务提供者</summary>
    public IServiceProvider Provider { get; set; }

    ///// <summary>插件管理</summary>
    //public PluginManager PluginManager { get; set; }

    /// <summary>星尘设置</summary>
    public StarSetting StarSetting { get; set; }

    /// <summary>星尘代理设置</summary>
    public StarAgentSetting AgentSetting { get; set; }

    private AgentInfo _agentInfo;
    private TimerX _timer;
    #endregion

    #region 构造
    public StarService()
    {
        // 获取本地进程名比较慢,平均200ms,有时候超过500ms
        //Task.Run(() =>
        //{
        //    _agentInfo = AgentInfo.GetLocal(true);
        //});
        _timer = new TimerX(DoRefreshLocal, null, 100, 5_000) { Async = true };
    }

    /// <summary>销毁</summary>
    /// <param name="disposing"></param>
    protected override void Dispose(Boolean disposing)
    {
        base.Dispose(disposing);

        _timer.TryDispose();
    }
    #endregion

    #region 业务
    /// <summary>信息</summary>
    /// <returns></returns>
    [Api(nameof(Info))]
    public AgentInfo Info(AgentInfo info)
    {
        //XTrace.WriteLine("Info<={0}", info.ToJson());

        var set = StarSetting;

        var ai = _agentInfo ??= AgentInfo.GetLocal(true);
        ai.Server = set.Server;
        ai.Services = Manager?.Services?.Where(e => e.Enable || !e.Name.EqualIgnoreCase("test", "test2")).Select(e => e.Name).ToArray();
        ai.Code = AgentSetting.Code;
        ai.IP = AgentInfo.GetIps();

        var raw = (ControllerContext.Current?.Request as IPacket)?.ToStr();
        if (Provider?.GetService<StarClient>() is StarClient client)
        {
            client.WriteEvent("info", "LocalProbe", raw);
        }

        // 更新应用服务
        var controller = info == null ? null : Manager?.QueryByProcess(info.ProcessId);
        if (controller != null)
        {
            // 标记为星尘应用,停止Deploy上报进程信息
            controller.IsStarApp = true;

            controller.WriteEvent("LocalProbe", raw!);
        }

        // 返回插件服务器地址
        var core = NewLife.Setting.Current;
        if (!core.PluginServer.IsNullOrEmpty() && !core.PluginServer.Contains("x.newlifex.com"))
        {
            ai.PluginServer = core.PluginServer;
        }

        return ai;
    }

    /// <summary>本地心跳</summary>
    /// <param name="info"></param>
    /// <returns></returns>
    [Api(nameof(Ping))]
    public IPingResponse Ping(LocalPingInfo info)
    {
        if (info != null && info.ProcessId > 0)
        {
            // 喂狗
            if (info.WatchdogTimeout > 0) FeedDog(info.ProcessId, info.WatchdogTimeout);
        }

        return new PingResponse { ServerTime = DateTime.UtcNow.ToLong() };
    }

    /// <summary>设置星尘服务端地址</summary>
    /// <returns></returns>
    [Api(nameof(SetServer))]
    public String SetServer(String server)
    {
        var set = StarSetting;
        if (set.Server.IsNullOrEmpty() && !server.IsNullOrEmpty())
        {
            set.Server = server;
            set.Save();

            XTrace.WriteLine("StarAgent uses the Stardust server address provided by [{0}]: {1}", Session, server);

            if (Provider?.GetService<ServiceBase>() is MyService svc)
            {
                ThreadPool.QueueUserWorkItem(s =>
                {
                    Thread.Sleep(1000);

                    svc.StartFactory();
                    svc.StartClient();
                });
            }
        }

        return set.Server;
    }

    /// <summary>获取所有服务列表</summary>
    /// <returns></returns>
    [Api(nameof(GetServices))]
    public ServicesInfo GetServices()
    {
        CheckLocal();

        var list = Manager.Services;
        var runningList = Manager.RunningServices;

        var result = new ServicesInfo
        {
            Services = list?.ToArray(),
            RunningServices = runningList?.ToArray()
        };

        return result;
    }

    /// <summary>启动服务</summary>
    /// <param name="serviceName">服务名称</param>
    /// <returns></returns>
    [Api("StartService")]
    public ServiceOperationResult StartService(String serviceName)
    {
        CheckLocal();

        if (serviceName.IsNullOrEmpty())
        {
            return new ServiceOperationResult { Success = false, Message = L.T("Service name cannot be empty.") };
        }

        try
        {
            var result = Manager?.Start(serviceName);
            return new ServiceOperationResult
            {
                Success = result ?? false,
                Message = result == true ? L.T("Service started successfully.") : L.T("Service failed to start or does not exist."),
                ServiceName = serviceName
            };
        }
        catch (Exception ex)
        {
            return new ServiceOperationResult
            {
                Success = false,
                Message = L.T("Error while starting service: {0}", ex.Message),
                ServiceName = serviceName
            };
        }
    }

    /// <summary>停止服务</summary>
    /// <param name="serviceName">服务名称</param>
    /// <returns></returns>
    [Api("StopService")]
    public ServiceOperationResult StopService(String serviceName)
    {
        CheckLocal();

        if (serviceName.IsNullOrEmpty())
        {
            return new ServiceOperationResult { Success = false, Message = L.T("Service name cannot be empty.") };
        }

        try
        {
            var result = Manager?.Stop(serviceName, "API call stop");
            return new ServiceOperationResult
            {
                Success = result ?? false,
                Message = result == true ? L.T("Service stopped successfully.") : L.T("Service failed to stop or does not exist."),
                ServiceName = serviceName
            };
        }
        catch (Exception ex)
        {
            return new ServiceOperationResult
            {
                Success = false,
                Message = L.T("Error while stopping service: {0}", ex.Message),
                ServiceName = serviceName
            };
        }
    }

    /// <summary>重启服务</summary>
    /// <param name="serviceName">服务名称</param>
    /// <returns></returns>
    [Api("RestartService")]
    public ServiceOperationResult RestartService(String serviceName)
    {
        CheckLocal();

        if (serviceName.IsNullOrEmpty())
        {
            return new ServiceOperationResult { Success = false, Message = L.T("Service name cannot be empty.") };
        }

        try
        {
            // 使用共同的重启逻辑
            var success = InternalRestartService(Manager, serviceName, "API call restart");

            // 根据结果提供更详细的信息
            var message = success ? L.T("Service restarted successfully.") : GetRestartFailureMessage(Manager, serviceName);

            return new ServiceOperationResult
            {
                Success = success,
                Message = message,
                ServiceName = serviceName
            };
        }
        catch (Exception ex)
        {
            return new ServiceOperationResult
            {
                Success = false,
                Message = L.T("Error while restarting service: {0}", ex.Message),
                ServiceName = serviceName
            };
        }
    }

    /// <summary>获取重启失败的详细原因</summary>
    private static String GetRestartFailureMessage(ServiceManager manager, String serviceName)
    {
        if (manager?.Services?.Any(e => e.Name.EqualIgnoreCase(serviceName)) != true)
        {
            return L.T("Service does not exist.");
        }

        var isRunning = manager.RunningServices?.Any(e => e.Name.EqualIgnoreCase(serviceName)) == true;
        if (isRunning)
        {
            return L.T("Service restart failed: unable to stop service [{0}]", serviceName);
        }
        else
        {
            return L.T("Service restart failed: unable to start service [{0}]", serviceName);
        }
    }

    private void DoRefreshLocal(Object state)
    {
        var ai = AgentInfo.GetLocal(true);
        if (ai != null)
        {
            //XTrace.WriteLine("IP: {0}", ai.IP);
            _agentInfo = ai;

            // 如果未取得本机IP,则在较短时间内重新获取
            if (_timer != null)
                _timer.Period = ai.IP.IsNullOrEmpty() ? 5_000 : 60_000;
        }
    }

    private void CheckLocal()
    {
        if (Session is INetSession ns && !ns.Remote.Address.IsLocal()) throw new InvalidOperationException(Localization.Translate("Operations from remote hosts are not allowed."));
    }

    ///// <summary>杀死并启动进程</summary>
    ///// <param name="processId">进程</param>
    ///// <param name="delay">延迟结束的秒数</param>
    ///// <param name="fileName">文件名</param>
    ///// <param name="arguments">参数</param>
    ///// <param name="workingDirectory">工作目录</param>
    ///// <returns></returns>
    //[Api(nameof(KillAndStart))]
    //public Object KillAndStart(Int32 processId, Int32 delay, String fileName, String arguments, String workingDirectory)
    //{
    //    CheckLocal();

    //    var p = Process.GetProcessById(processId);
    //    if (p == null) throw new InvalidOperationException($"无效进程Id[{processId}]");

    //    var name = p.ProcessName;
    //    var pid = 0;

    //    ThreadPool.QueueUserWorkItem(s =>
    //    {
    //        WriteLog("杀死进程 {0}/{1},等待 {2}秒", processId, p.ProcessName, delay);

    //        if (delay > 0) Thread.Sleep(delay * 1000);

    //        try
    //        {
    //            if (!p.HasExited)
    //            {
    //                p.Kill();
    //                p.WaitForExit(5_000);
    //            }
    //        }
    //        catch (Exception ex)
    //        {
    //            XTrace.WriteException(ex);
    //        }

    //        // 启动进程
    //        if (!fileName.IsNullOrEmpty())
    //        {
    //            WriteLog("启动进程:{0} {1} {2}", fileName, arguments, workingDirectory);

    //            var si = new ProcessStartInfo
    //            {
    //                FileName = fileName,
    //                Arguments = arguments,
    //                WorkingDirectory = workingDirectory,

    //                // false时目前控制台合并到当前控制台,一起退出;
    //                // true时目标控制台独立窗口,不会一起退出;
    //                UseShellExecute = true,
    //            };

    //            var p2 = Process.Start(si);
    //            pid = p2.Id;

    //            WriteLog("应用[{0}]启动成功 PID={1}", p2.ProcessName, p2.Id);
    //        }
    //    });

    //    return new { name, pid };
    //}

    ///// <summary>安装应用服务(星尘代理守护)</summary>
    ///// <param name="service"></param>
    ///// <returns></returns>
    //[Api(nameof(Install))]
    //public ProcessInfo Install(ServiceInfo service)
    //{
    //    XTrace.WriteLine("Install<={0}", service.ToJson());

    //    CheckLocal();

    //    var rs = Manager.Install(service);
    //    if (rs != null)
    //    {
    //        var set = Setting.Current;
    //        set.Services = Manager.Services;
    //        set.Save();
    //    }

    //    return rs;
    //}

    ///// <summary>卸载应用服务</summary>
    ///// <param name="serviceName"></param>
    ///// <returns></returns>
    //[Api(nameof(Uninstall))]
    //public Boolean Uninstall(String serviceName)
    //{
    //    XTrace.WriteLine("Uninstall<={0}", serviceName);

    //    CheckLocal();

    //    var rs = Manager.Uninstall(serviceName, "ServiceUninstall");
    //    if (rs)
    //    {
    //        var set = Setting.Current;
    //        set.Services = Manager.Services;
    //        set.Save();
    //    }

    //    return rs;
    //}
    #endregion

    #region 看门狗
    static ConcurrentDictionary<Int32, DateTime> _dogs = new();
    static TimerX _dogTimer;
    void FeedDog(Int32 pid, Int32 timeout)
    {
        if (pid <= 0 || timeout <= 0) return;

        using var span = DefaultTracer.Instance?.NewSpan(nameof(FeedDog), new { pid, timeout });

        // 更新过期时间,超过该时间未收到心跳,将会重启本应用进程
        _dogs[pid] = DateTime.Now.AddSeconds(timeout);

        _dogTimer ??= new TimerX(CheckDog, Manager, 1000, 15000) { Async = true };
    }

    static void CheckDog(Object state)
    {
        var ks = new List<Int32>();
        var ds = new List<Int32>();
        foreach (var item in _dogs)
        {
            if (item.Value < DateTime.Now)
                ks.Add(item.Key);
            else
            {
                var p = GetProcessById(item.Key);
                if (p == null || p.GetHasExited())
                    ds.Add(item.Key);
            }
        }

        if (ks.Count == 0 && ds.Count == 0) return;

        using var span = DefaultTracer.Instance?.NewSpan(nameof(CheckDog));

        // 重启超时的进程
        foreach (var item in ks)
        {
            try
            {
                var p = GetProcessById(item);
                if (p == null || p.GetHasExited())
                    _dogs.Remove(item);
                else
                {
                    XTrace.WriteLine("Process [{0}/{1}] has not sent a heartbeat for a while and may be hung; preparing to restart.", p.ProcessName, p.Id);

                    var restartSuccess = false;

                    // 优先尝试通过服务重启
                    if (state is ServiceManager manager)
                    {
                        try
                        {
                            // 通过进程ID查找对应的服务控制器
                            var controller = manager.QueryByProcess(item);
                            if (controller != null && !controller.Name.IsNullOrEmpty())
                            {
                                XTrace.WriteLine("Attempting to restart via the service: {0}", controller.Name);
                                span?.AppendTag($"RestartService {controller.Name}");

                                // 使用共同的重启逻辑
                                restartSuccess = InternalRestartService(manager, controller.Name, "Watchdog restart");

                                if (restartSuccess)
                                {
                                    XTrace.WriteLine("Service [{0}] restarted successfully", controller.Name);
                                }
                                else
                                {
                                    XTrace.WriteLine("Failed to restart service [{0}]", controller.Name);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            XTrace.WriteException(ex);
                        }
                    }

                    // 如果服务重启失败,则使用原有的SafetyKill方法
                    if (!restartSuccess)
                    {
                        XTrace.WriteLine("Service restart failed; using SafetyKill to force-terminate process [{0}/{1}]", p.ProcessName, p.Id);
                        span?.AppendTag($"SafetyKill {p.ProcessName}/{p.Id}");
                        p.SafetyKill();
                    }

                    //todo 启动应用。暂时不需要,因为StarAgent会自动启动
                    if (state is ServiceManager manager2) manager2.CheckNow();
                }
            }
            catch (Exception ex)
            {
                _dogs.Remove(item);
                XTrace.WriteException(ex);
            }
        }

        // 删除不存在的进程
        foreach (var item in ds)
        {
            _dogs.Remove(item);
        }
    }

    static Process? GetProcessById(Int32 processId)
    {
        try
        {
            return Process.GetProcessById(processId);
        }
        catch { }

        return null;
    }
    #endregion

    #region 日志
    /// <summary>链路追踪</summary>
    public ITracer Tracer { get; set; }

    /// <summary>日志</summary>
    public ILog Log { get; set; }

    /// <summary>写日志</summary>
    /// <param name="format"></param>
    /// <param name="args"></param>
    public void WriteLog(String format, params Object[] args) => Log?.Info(format, args);
    #endregion

    #region 内部方法
    /// <summary>内部服务重启方法</summary>
    /// <param name="manager">服务管理器</param>
    /// <param name="serviceName">服务名称</param>
    /// <param name="reason">重启原因</param>
    /// <returns>是否成功</returns>
    private static Boolean InternalRestartService(ServiceManager manager, String serviceName, String reason = "Restart")
    {
        if (manager == null || serviceName.IsNullOrEmpty()) return false;

        using var span = DefaultTracer.Instance?.NewSpan(nameof(InternalRestartService), new { serviceName, reason });

        try
        {
            // 检查服务是否存在
            var service = manager.Services?.FirstOrDefault(e => e.Name.EqualIgnoreCase(serviceName));
            if (service == null)
            {
                XTrace.WriteLine("Service restart failed: service [{0}] does not exist", serviceName);
                span?.AppendTag("ServiceNotFound");
                return false;
            }

            // 检查服务是否正在运行
            var runningServices = manager.RunningServices;
            var isRunning = runningServices?.Any(e => e.Name.EqualIgnoreCase(serviceName)) == true;

            XTrace.WriteLine("Restarting service [{0}], current state: {1}, reason: {2}", serviceName, isRunning ? L.TLog("Running") : L.TLog("Stopped"), reason);

            if (isRunning)
            {
                // 服务正在运行,先停止服务
                XTrace.WriteLine("Stopping service [{0}]", serviceName);
                span?.AppendTag("StopService");

                var stopResult = manager.Stop(serviceName, reason);
                if (stopResult != true)
                {
                    XTrace.WriteLine("Service restart failed: unable to stop service [{0}]", serviceName);
                    span?.AppendTag("StopFailed");
                    return false;
                }

                XTrace.WriteLine("Service [{0}] stopped successfully; starting it after 1 second", serviceName);
                // 等待服务完全停止
                Thread.Sleep(1000);
            }
            else
            {
                XTrace.WriteLine("Service [{0}] is not running; starting it directly", serviceName);
                span?.AppendTag("DirectStart");
            }

            // 启动服务(无论之前是否运行)
            XTrace.WriteLine("Starting service [{0}]", serviceName);
            span?.AppendTag("StartService");

            var startResult = manager.Start(serviceName);
            if (startResult == true)
            {
                XTrace.WriteLine("Service [{0}] restarted successfully", serviceName);
                span?.AppendTag("Success");
                return true;
            }
            else
            {
                XTrace.WriteLine("Service restart failed: unable to start service [{0}]", serviceName);
                span?.AppendTag("StartFailed");
                return false;
            }
        }
        catch (Exception ex)
        {
            XTrace.WriteLine("Error while restarting service [{0}]: {1}", serviceName, ex.Message);
            span?.SetError(ex, null);
            return false;
        }
    }
    #endregion
}